diff --git a/flyway-database-ydb/pom.xml b/flyway-database-ydb/pom.xml
new file mode 100644
index 0000000..88a181b
--- /dev/null
+++ b/flyway-database-ydb/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+
+ org.flywaydb
+ flyway-community-db-support
+ 10.11.0
+
+
+ flyway-database-ydb
+ Support Flyway YDB Dialect
+
+
+
+ ${project.groupId}
+ flyway-core
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ maven-resources-plugin
+
+
+ maven-jar-plugin
+
+
+
+
\ No newline at end of file
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbConnection.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbConnection.java
new file mode 100644
index 0000000..9e897ba
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbConnection.java
@@ -0,0 +1,27 @@
+package org.flywaydb.community.database.ydb;
+
+import org.flywaydb.core.internal.database.base.Connection;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbConnection extends Connection {
+
+ private static final String YDB_SCHEMA_NAME = "";
+
+ protected YdbConnection(YdbDatabase database, java.sql.Connection connection) {
+ super(database, connection);
+
+ this.jdbcTemplate = new YdbJdbcTemplate(connection, database.getDatabaseType());
+ }
+
+ @Override
+ protected String getCurrentSchemaNameOrSearchPath() {
+ return YDB_SCHEMA_NAME; // schema isn't supported
+ }
+
+ @Override
+ public YdbSchema getSchema(String name) {
+ return new YdbSchema(jdbcTemplate, database, YDB_SCHEMA_NAME);
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabase.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabase.java
new file mode 100644
index 0000000..2040afd
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabase.java
@@ -0,0 +1,118 @@
+package org.flywaydb.community.database.ydb;
+
+import java.sql.Connection;
+import org.flywaydb.core.api.configuration.Configuration;
+import org.flywaydb.core.internal.database.base.Database;
+import org.flywaydb.core.internal.database.base.Table;
+import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory;
+import org.flywaydb.core.internal.jdbc.StatementInterceptor;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbDatabase extends Database {
+
+ public YdbDatabase(
+ Configuration configuration,
+ JdbcConnectionFactory jdbcConnectionFactory,
+ StatementInterceptor statementInterceptor
+ ) {
+ super(configuration, jdbcConnectionFactory, statementInterceptor);
+ }
+
+ @Override
+ protected YdbConnection doGetConnection(Connection connection) {
+ return new YdbConnection(this, connection);
+ }
+
+ @Override
+ public void ensureSupported(Configuration configuration) {
+ }
+
+ @Override
+ public boolean supportsDdlTransactions() {
+ return false;
+ }
+
+ @Override
+ public String getBooleanTrue() {
+ return "TRUE";
+ }
+
+ @Override
+ public String getBooleanFalse() {
+ return "FALSE";
+ }
+
+ @Override
+ public boolean catalogIsSchema() {
+ return false;
+ }
+
+ @Override
+ public String getRawCreateScript(Table table, boolean baseline) {
+ return "CREATE TABLE " + doQuote(table.getName()) + " (\n" +
+ " installed_rank INT32 NOT NULL,\n" +
+ " version TEXT,\n" +
+ " description TEXT,\n" +
+ " type TEXT,\n" +
+ " script TEXT,\n" +
+ " checksum INT32,\n" +
+ " installed_by TEXT,\n" +
+ " installed_on DATETIME,\n" +
+ " execution_time INT32,\n" +
+ " success BOOL,\n" +
+ " PRIMARY KEY (installed_rank)" +
+ ");\n" +
+ (baseline ? getBaselineStatement(table) : "");
+ }
+
+ @Override
+ public String getSelectStatement(Table table) {
+ return "SELECT " + quote("installed_rank")
+ + "," + quote("version")
+ + "," + quote("description")
+ + "," + quote("type")
+ + "," + quote("script")
+ + "," + quote("checksum")
+ + "," + quote("installed_on")
+ + "," + quote("installed_by")
+ + "," + quote("execution_time")
+ + "," + quote("success")
+ + " FROM " + quote(table.getName())
+ + " WHERE " + quote("installed_rank") + " > ?"
+ + " ORDER BY " + quote("installed_rank");
+ }
+
+ @Override
+ public String getInsertStatement(Table table) {
+ return "INSERT INTO " + quote(table.getName())
+ + " (" + quote("installed_rank")
+ + ", " + quote("version")
+ + ", " + quote("description")
+ + ", " + quote("type")
+ + ", " + quote("script")
+ + ", " + quote("checksum")
+ + ", " + quote("installed_by")
+ + ", " + quote("execution_time")
+ + ", " + quote("success")
+ + ", " + quote("installed_on")
+ + ")"
+ + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CurrentUtcDatetime())";
+ }
+
+ @Override
+ protected String getOpenQuote() {
+ return "`";
+ }
+
+ @Override
+ protected String getCloseQuote() {
+ return "`";
+ }
+
+ @Override
+ protected String getEscapedQuote() {
+ return "\\";
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabaseType.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabaseType.java
new file mode 100644
index 0000000..b4abefb
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbDatabaseType.java
@@ -0,0 +1,68 @@
+package org.flywaydb.community.database.ydb;
+
+import java.sql.Connection;
+import java.sql.Types;
+import org.flywaydb.core.api.ResourceProvider;
+import org.flywaydb.core.api.configuration.Configuration;
+import org.flywaydb.core.internal.database.base.BaseDatabaseType;
+import org.flywaydb.core.internal.database.base.Database;
+import org.flywaydb.core.internal.jdbc.JdbcConnectionFactory;
+import org.flywaydb.core.internal.jdbc.StatementInterceptor;
+import org.flywaydb.core.internal.parser.Parser;
+import org.flywaydb.core.internal.parser.ParsingContext;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbDatabaseType extends BaseDatabaseType {
+
+ private static final String YDB_NAME = "YDB";
+ private final static String DRIVER_NAME = "tech.ydb.jdbc.YdbDriver";
+
+ @Override
+ public String getName() {
+ return YDB_NAME;
+ }
+
+ @Override
+ public int getNullType() {
+ return Types.NULL;
+ }
+
+ @Override
+ public boolean handlesJDBCUrl(String url) {
+ return url.startsWith("jdbc:ydb:");
+ }
+
+ @Override
+ public String getDriverClass(String url, ClassLoader classLoader) {
+ return DRIVER_NAME;
+ }
+
+ @Override
+ public boolean handlesDatabaseProductNameAndVersion(
+ String databaseProductName,
+ String databaseProductVersion,
+ Connection connection
+ ) {
+ return databaseProductName.startsWith(YDB_NAME);
+ }
+
+ @Override
+ public Database createDatabase(
+ Configuration configuration,
+ JdbcConnectionFactory jdbcConnectionFactory,
+ StatementInterceptor statementInterceptor
+ ) {
+ return new YdbDatabase(configuration, jdbcConnectionFactory, statementInterceptor);
+ }
+
+ @Override
+ public Parser createParser(
+ Configuration configuration,
+ ResourceProvider resourceProvider,
+ ParsingContext parsingContext
+ ) {
+ return new YdbParser(configuration, parsingContext);
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbJdbcTemplate.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbJdbcTemplate.java
new file mode 100644
index 0000000..73070b5
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbJdbcTemplate.java
@@ -0,0 +1,39 @@
+package org.flywaydb.community.database.ydb;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import org.flywaydb.core.internal.database.DatabaseType;
+import static org.flywaydb.core.internal.jdbc.JdbcNullTypes.BooleanNull;
+import static org.flywaydb.core.internal.jdbc.JdbcNullTypes.IntegerNull;
+import static org.flywaydb.core.internal.jdbc.JdbcNullTypes.StringNull;
+import org.flywaydb.core.internal.jdbc.JdbcTemplate;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbJdbcTemplate extends JdbcTemplate {
+
+ public YdbJdbcTemplate(Connection connection, DatabaseType databaseType) {
+ super(connection, databaseType);
+ }
+
+ @Override
+ protected PreparedStatement prepareStatement(String sql, Object[] params) throws SQLException {
+ PreparedStatement statement = connection.prepareStatement(sql);
+ for (int i = 0; i < params.length; i++) {
+ if (params[i] == StringNull) {
+ statement.setNull(i + 1, Types.VARCHAR);
+ } else if (params[i] == IntegerNull) {
+ statement.setNull(i + 1, Types.INTEGER);
+ } else if (params[i] == BooleanNull) {
+ statement.setNull(i + 1, Types.BOOLEAN);
+ } else {
+ statement.setObject(i + 1, params[i]);
+ }
+ }
+
+ return statement;
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbParser.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbParser.java
new file mode 100644
index 0000000..b467933
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbParser.java
@@ -0,0 +1,15 @@
+package org.flywaydb.community.database.ydb;
+
+import org.flywaydb.core.api.configuration.Configuration;
+import org.flywaydb.core.internal.parser.Parser;
+import org.flywaydb.core.internal.parser.ParsingContext;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbParser extends Parser {
+
+ protected YdbParser(Configuration configuration, ParsingContext parsingContext) {
+ super(configuration, parsingContext, 3);
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbSchema.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbSchema.java
new file mode 100644
index 0000000..427d25f
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbSchema.java
@@ -0,0 +1,89 @@
+package org.flywaydb.community.database.ydb;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.flywaydb.core.internal.database.base.Schema;
+import org.flywaydb.core.internal.jdbc.JdbcTemplate;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbSchema extends Schema {
+
+ /**
+ * @param jdbcTemplate The Jdbc Template for communicating with the DB.
+ * @param database The database-specific support.
+ * @param name The name of the schema.
+ */
+ public YdbSchema(JdbcTemplate jdbcTemplate, YdbDatabase database, String name) {
+ super(jdbcTemplate, database, name);
+ }
+
+ @Override
+ protected boolean doExists() {
+ return true; // Dummy schema exists
+ }
+
+ @Override
+ protected boolean doEmpty() throws SQLException {
+ return doAllTables().length == 0;
+ }
+
+ @Override
+ protected void doCreate() {
+ // Do nothing, YDB doesn't support schemas
+ }
+
+ @Override
+ protected void doDrop() {
+ // Do nothing, YDB doesn't support schemas
+ }
+
+ @Override
+ protected void doClean() throws SQLException {
+ List schemaTables = schemaTables();
+
+ if (schemaTables.isEmpty()) {
+ return;
+ }
+
+ jdbcTemplate.executeStatement(
+ schemaTables.stream()
+ .map(table -> "DROP TABLE " + table)
+ .collect(Collectors.joining("; "))
+ );
+ }
+
+ @Override
+ protected YdbTable[] doAllTables() throws SQLException {
+ return schemaTables().stream()
+ .map(table -> new YdbTable(jdbcTemplate, database, this, name))
+ .toArray(YdbTable[]::new);
+ }
+
+ @Override
+ public YdbTable getTable(String tableName) {
+ return new YdbTable(jdbcTemplate, database, this, tableName);
+ }
+
+ @Override
+ public String toString() {
+ return "ydb_schema";
+ }
+
+ private List schemaTables() throws SQLException {
+ ResultSet rs = jdbcTemplate.getConnection().getMetaData()
+ .getTables(null, name, null, new String[]{"TABLE"});
+
+ List tables = new ArrayList<>();
+
+ while (rs.next()) {
+ tables.add(database.quote(rs.getString("TABLE_NAME")));
+ }
+
+ return tables;
+ }
+}
diff --git a/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbTable.java b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbTable.java
new file mode 100644
index 0000000..294c109
--- /dev/null
+++ b/flyway-database-ydb/src/main/java/org/flywaydb/community/database/ydb/YdbTable.java
@@ -0,0 +1,118 @@
+package org.flywaydb.community.database.ydb;
+
+import java.sql.SQLException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Random;
+import java.util.UUID;
+import org.flywaydb.core.api.FlywayException;
+import org.flywaydb.core.internal.database.base.Table;
+import org.flywaydb.core.internal.jdbc.JdbcTemplate;
+import org.flywaydb.core.internal.jdbc.Results;
+
+/**
+ * @author Kirill Kurdyukov
+ */
+public class YdbTable extends Table {
+
+ private static final Duration WAIT_LOCK_TIMEOUT = Duration.ofMinutes(2);
+ private static final int RELEASE_MAX_ATTEMPT = 10;
+
+ private final String tableLockId = UUID.randomUUID() + "-flyway-lock-id";
+ private final Random random = new Random();
+
+ /**
+ * @param jdbcTemplate The JDBC template for communicating with the YDB.
+ * @param database The database-specific support.
+ * @param schema The schema this table lives in.
+ * @param name The name of the table.
+ */
+ public YdbTable(JdbcTemplate jdbcTemplate, YdbDatabase database, YdbSchema schema, String name) {
+ super(jdbcTemplate, database, schema, name);
+ }
+
+ @Override
+ protected boolean doExists() throws SQLException {
+ return exists(null, schema, name);
+ }
+
+ @Override
+ protected void doLock() {
+ if (lockDepth > 0) {
+ // Lock has already been taken - so the relevant row in the table already exists
+ return;
+ }
+
+ Instant startLock = Instant.now();
+
+ do {
+ if (insertLockingRow()) {
+ return;
+ }
+
+ try {
+ Thread.sleep(100 + random.nextInt(1000)); // pause 0.1s .. 1.1s
+ } catch (InterruptedException ignored) {
+ }
+ } while (startLock.isAfter(Instant.now().minus(WAIT_LOCK_TIMEOUT)));
+
+ throw new FlywayException("Unable to obtain table lock - another Flyway instance may be running");
+ }
+
+ @Override
+ protected void doUnlock() {
+ if (lockDepth > 1) {
+ // Leave the locking row alone until we get to the final level of unlocking
+
+ return;
+ }
+
+ for (int attempt = 0; attempt < RELEASE_MAX_ATTEMPT; attempt++) {
+ SQLException sqlException = jdbcTemplate
+ .executeStatement("DELETE FROM " + this +
+ " WHERE installed_rank = -100 AND version = '" + tableLockId + "'").getException();
+
+ if (sqlException == null) {
+ return;
+ }
+
+ if (attempt == RELEASE_MAX_ATTEMPT - 1) {
+ throw new FlywayException(sqlException);
+ }
+ }
+ }
+
+ @Override
+ protected void doDrop() throws SQLException {
+ jdbcTemplate.execute("DROP TABLE " + database.doQuote(name));
+ }
+
+ /**
+ * Insert the locking row - the primary keys of installed_rank will prevent us having two.
+ *
+ * @return true if no errors.
+ */
+ private boolean insertLockingRow() {
+ Results results = jdbcTemplate.executeStatement(
+ "INSERT INTO " + this +
+ "(installed_rank, version, description, type, script, " +
+ "checksum, installed_by, execution_time, success, installed_on) " +
+ "VALUES (-100, '" + tableLockId + "', 'flyway-lock', " +
+ "'', '', 0, '', 0, TRUE, CurrentUtcDatetime())"
+ );
+
+ try {
+ jdbcTemplate.getConnection().commit();
+
+ // Succeeded if no errors.
+ return results.getException() == null;
+ } catch (SQLException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return database.doQuote(name);
+ }
+}
diff --git a/flyway-database-ydb/src/main/resources/services/org.flywaydb.core.extensibility.Plugin b/flyway-database-ydb/src/main/resources/services/org.flywaydb.core.extensibility.Plugin
new file mode 100644
index 0000000..e813b7f
--- /dev/null
+++ b/flyway-database-ydb/src/main/resources/services/org.flywaydb.core.extensibility.Plugin
@@ -0,0 +1 @@
+tech.ydb.flywaydb.database.YdbDatabaseType
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 097c008..f0e8f4f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@
flyway-database-yugabytedb
flyway-database-clickhouse
flyway-database-oceanbase
+ flyway-database-ydb
flyway-database-databricks
flyway-database-db2zos
flyway-community-db-support-archetype