diff --git a/.gitignore b/.gitignore
index d7f0d54..9738b48 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/.idea/
**/target/
-**.iml
\ No newline at end of file
+**.iml
+demo/logs
\ No newline at end of file
diff --git a/api/src/main/java/cc/carm/lib/easysql/api/SQLManager.java b/api/src/main/java/cc/carm/lib/easysql/api/SQLManager.java
index 10661ee..437c42c 100644
--- a/api/src/main/java/cc/carm/lib/easysql/api/SQLManager.java
+++ b/api/src/main/java/cc/carm/lib/easysql/api/SQLManager.java
@@ -9,6 +9,7 @@ import cc.carm.lib.easysql.api.function.SQLBiFunction;
import cc.carm.lib.easysql.api.function.SQLDebugHandler;
import cc.carm.lib.easysql.api.function.SQLExceptionHandler;
import cc.carm.lib.easysql.api.function.SQLFunction;
+import cc.carm.lib.easysql.api.migrate.SQLMigrate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -265,6 +266,13 @@ public interface SQLManager {
*/
QueryBuilder createQuery();
+ /**
+ * 新建一个迁移操作。
+ *
+ * @return {@link SQLMigrate}
+ */
+ SQLMigrate createMigrate();
+
/**
* 创建一条插入操作。
*
diff --git a/api/src/main/java/cc/carm/lib/easysql/api/enums/MigrateResult.java b/api/src/main/java/cc/carm/lib/easysql/api/enums/MigrateResult.java
new file mode 100644
index 0000000..677bdaf
--- /dev/null
+++ b/api/src/main/java/cc/carm/lib/easysql/api/enums/MigrateResult.java
@@ -0,0 +1,58 @@
+package cc.carm.lib.easysql.api.enums;
+
+/**
+ * 2022/11/28
+ * EasySQL
+ *
+ * @author huanmeng_qwq
+ */
+public class MigrateResult {
+ /**
+ * 旧表不存在
+ */
+ public static final MigrateResult OLD_TABLE_NOT_EXIST = new MigrateResult(true);
+
+ /**
+ * 新表已存在
+ */
+ public static final MigrateResult NEW_TABLE_EXIST = new MigrateResult(false);
+
+ /**
+ * 新表字段为空
+ */
+ public static final MigrateResult NEW_COLUMN_EMPTY = new MigrateResult(false);
+
+ /**
+ * 旧表字段为在新表这设置对应的字段名
+ */
+ public static final MigrateResult COLUMN_NOT_SET = new MigrateResult(false);
+
+ /**
+ * 迁移成功
+ */
+ public static final MigrateResult SUCCESS = new MigrateResult(true);
+
+ private final boolean success;
+ private Throwable error;
+
+ MigrateResult(boolean success) {
+ this.success = success;
+ }
+
+ public MigrateResult(boolean success, Throwable error) {
+ this.success = success;
+ this.error = error;
+ }
+
+ public static MigrateResult from(MigrateResult migrateResult, Throwable error) {
+ return new MigrateResult(migrateResult.success, error);
+ }
+
+ public boolean success() {
+ return success;
+ }
+
+ public Throwable error() {
+ return error;
+ }
+}
diff --git a/api/src/main/java/cc/carm/lib/easysql/api/migrate/SQLMigrate.java b/api/src/main/java/cc/carm/lib/easysql/api/migrate/SQLMigrate.java
new file mode 100644
index 0000000..57743f9
--- /dev/null
+++ b/api/src/main/java/cc/carm/lib/easysql/api/migrate/SQLMigrate.java
@@ -0,0 +1,70 @@
+package cc.carm.lib.easysql.api.migrate;
+
+import cc.carm.lib.easysql.api.enums.IndexType;
+import cc.carm.lib.easysql.api.enums.MigrateResult;
+import org.jetbrains.annotations.Contract;
+
+import java.sql.SQLException;
+
+/**
+ * 将现有的表结构与数据迁移之新的表中
+ *
+ * @author huanmeng_qwq
+ * @since 0.4.7
+ */
+public interface SQLMigrate {
+
+ /**
+ * 为迁移工作定义前后的表名信息
+ *
+ * @param oldTableName 旧表名
+ * @param newTableName 新表名
+ * @param newTableSettings 新表的设置
+ * @return {@link SQLMigrate}
+ */
+ @Contract("null,_,_ -> fail;!null,_,_ -> this")
+ SQLMigrate tableName(String oldTableName, String newTableName, String newTableSettings);
+
+ /**
+ * 为迁移工作定义前后的列名信息
+ *
+ * @param oldColumnName 旧列名
+ * @param newColumnName 新列名
+ * @param newColumnSettings 新列的设置
+ * @return {@link SQLMigrate}
+ */
+ @Contract("null,_,_ -> fail;!null,_,_ -> this")
+ default SQLMigrate column(String oldColumnName, String newColumnName, String newColumnSettings) {
+ return column(oldColumnName, newColumnName, newColumnSettings, null);
+ }
+
+ /**
+ * 为迁移工作定义前后的列名信息
+ *
+ * @param oldColumnName 旧列名
+ * @param newColumnName 新列名
+ * @param newColumnSettings 新列的设置
+ * @param indexType 索引类型
+ * @return {@link SQLMigrate}
+ */
+ @Contract("null,_,_,_ -> fail;!null,_,_,_ -> this")
+ SQLMigrate column(String oldColumnName, String newColumnName, String newColumnSettings, IndexType indexType);
+
+ /**
+ * 为迁移工作定义前后的自增列名信息
+ *
+ * @param oldColumnName 旧列名
+ * @param newColumnName 新列名
+ * @return {@link SQLMigrate}
+ */
+ @Contract("null,_->fail;!null,_ -> this")
+ SQLMigrate autoIncrementColumn(String oldColumnName, String newColumnName);
+
+ /**
+ * 迁移数据
+ *
+ * @return {@link MigrateResult}
+ */
+ MigrateResult migrate() throws SQLException;
+
+}
diff --git a/demo/src/test/java/cc/carm/lib/easysql/EasySQLTest.java b/demo/src/test/java/cc/carm/lib/easysql/EasySQLTest.java
index 3925749..4d92537 100644
--- a/demo/src/test/java/cc/carm/lib/easysql/EasySQLTest.java
+++ b/demo/src/test/java/cc/carm/lib/easysql/EasySQLTest.java
@@ -56,6 +56,8 @@ public class EasySQLTest {
tests.add(new TableMetadataTest());
// tests.add(new DeleteTest());
+ tests.add(new TableMigrateTest());
+
print("准备进行测试...");
int index = 1;
diff --git a/demo/src/test/java/cc/carm/lib/easysql/tests/TableMigrateTest.java b/demo/src/test/java/cc/carm/lib/easysql/tests/TableMigrateTest.java
new file mode 100644
index 0000000..4abb718
--- /dev/null
+++ b/demo/src/test/java/cc/carm/lib/easysql/tests/TableMigrateTest.java
@@ -0,0 +1,70 @@
+package cc.carm.lib.easysql.tests;
+
+import cc.carm.lib.easysql.TestHandler;
+import cc.carm.lib.easysql.api.SQLManager;
+import cc.carm.lib.easysql.api.SQLQuery;
+import cc.carm.lib.easysql.api.enums.IndexType;
+import cc.carm.lib.easysql.api.enums.MigrateResult;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 2022/11/28
+ * EasySQL
+ *
+ * @author huanmeng_qwq
+ */
+public class TableMigrateTest extends TestHandler {
+ @Override
+ public void onTest(SQLManager sqlManager) throws Exception {
+ print(" 创建 test_migrate_table");
+ sqlManager.createTable("test_migrate_table")
+ .addAutoIncrementColumn("id")
+ .addColumn("uuid", "VARCHAR(36) NOT NULL", "用户UUID")
+ .addColumn("username", "VARCHAR(16) NOT NULL", "用户名")
+ .addColumn("age", "TINYINT DEFAULT 0 NOT NULL", "年龄")
+
+ .setIndex("uuid", IndexType.UNIQUE_KEY)
+ .build().execute();
+
+ print(" 向 test_migrate_table 写入数据");
+ sqlManager.createInsert("test_migrate_table")
+ .setColumnNames("uuid", "username", "age")
+ .setParams("1234567890", "qwq", 18)
+ .execute();
+
+ print(" 迁移 test_migrate_table 至 test_transfer_table");
+ MigrateResult migrate = sqlManager.createMigrate()
+ .tableName("test_migrate_table", "test_transfer_table", null)
+ .autoIncrementColumn("id", "dbId")
+ .column("uuid", "uniqueId", "VARCHAR(40) NOT NULL")
+ .column("username", "name", "VARCHAR(20) NOT NULL")
+ .column("age", "age", "TINYINT DEFAULT 0 NOT NULL")
+ .migrate();
+
+ if (migrate.error() != null) {
+ migrate.error().printStackTrace();
+ }
+
+ Set set = sqlManager.fetchTableMetadata("test_transfer_table").listColumns().join()
+ .stream()
+ .map(String::toLowerCase)
+ .collect(Collectors.toSet());
+ print(" 迁移后的表结构: " + set);
+ boolean containsAll = set.containsAll(Stream.of
+ ("dbId", "uniqueId", "name", "age").map(String::toLowerCase).collect(Collectors.toSet()));
+ print(" 迁移结果: " + (migrate.success() ? "true" : migrate.error()) + " , 是否包含所有字段: " + containsAll);
+ try (SQLQuery query = sqlManager.createQuery().inTable("test_transfer_table").build().execute()) {
+ while (query.getResultSet().next()) {
+ print(" 迁移后的数据: " +
+ query.getResultSet().getInt("dbId") + " , " +
+ query.getResultSet().getString("uniqueId") + " , " +
+ query.getResultSet().getString("name") + " , " +
+ query.getResultSet().getInt("age")
+ );
+ }
+ }
+ }
+}
diff --git a/impl/src/main/java/cc/carm/lib/easysql/manager/SQLManagerImpl.java b/impl/src/main/java/cc/carm/lib/easysql/manager/SQLManagerImpl.java
index 4178b42..92a2f9e 100644
--- a/impl/src/main/java/cc/carm/lib/easysql/manager/SQLManagerImpl.java
+++ b/impl/src/main/java/cc/carm/lib/easysql/manager/SQLManagerImpl.java
@@ -14,7 +14,9 @@ import cc.carm.lib.easysql.api.function.SQLBiFunction;
import cc.carm.lib.easysql.api.function.SQLDebugHandler;
import cc.carm.lib.easysql.api.function.SQLExceptionHandler;
import cc.carm.lib.easysql.api.function.SQLFunction;
+import cc.carm.lib.easysql.api.migrate.SQLMigrate;
import cc.carm.lib.easysql.builder.impl.*;
+import cc.carm.lib.easysql.migrate.SQLMigrateImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -199,6 +201,11 @@ public class SQLManagerImpl implements SQLManager {
return new QueryBuilderImpl(this);
}
+ @Override
+ public SQLMigrate createMigrate() {
+ return new SQLMigrateImpl(this);
+ }
+
@Override
public InsertBuilder> createInsertBatch(@NotNull String tableName) {
return new InsertBuilderImpl>(this, tableName) {
diff --git a/impl/src/main/java/cc/carm/lib/easysql/migrate/AutoIncrementMigrateData.java b/impl/src/main/java/cc/carm/lib/easysql/migrate/AutoIncrementMigrateData.java
new file mode 100644
index 0000000..d407288
--- /dev/null
+++ b/impl/src/main/java/cc/carm/lib/easysql/migrate/AutoIncrementMigrateData.java
@@ -0,0 +1,13 @@
+package cc.carm.lib.easysql.migrate;
+
+/**
+ * 2022/11/28
+ * EasySQL
+ *
+ * @author huanmeng_qwq
+ */
+public class AutoIncrementMigrateData extends MigrateData {
+ public AutoIncrementMigrateData(String name) {
+ super(name, null, null);
+ }
+}
diff --git a/impl/src/main/java/cc/carm/lib/easysql/migrate/MigrateData.java b/impl/src/main/java/cc/carm/lib/easysql/migrate/MigrateData.java
new file mode 100644
index 0000000..de6a27e
--- /dev/null
+++ b/impl/src/main/java/cc/carm/lib/easysql/migrate/MigrateData.java
@@ -0,0 +1,33 @@
+package cc.carm.lib.easysql.migrate;
+
+import cc.carm.lib.easysql.api.enums.IndexType;
+
+/**
+ * 2022/11/28
+ * EasySQL
+ *
+ * @author huanmeng_qwq
+ */
+public class MigrateData {
+ private final String name;
+ private final String settings;
+ private final IndexType indexType;
+
+ public MigrateData(String name, String settings, IndexType indexType) {
+ this.name = name;
+ this.settings = settings;
+ this.indexType = indexType;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public String settings() {
+ return settings;
+ }
+
+ public IndexType indexType() {
+ return indexType;
+ }
+}
diff --git a/impl/src/main/java/cc/carm/lib/easysql/migrate/SQLMigrateImpl.java b/impl/src/main/java/cc/carm/lib/easysql/migrate/SQLMigrateImpl.java
new file mode 100644
index 0000000..8e20319
--- /dev/null
+++ b/impl/src/main/java/cc/carm/lib/easysql/migrate/SQLMigrateImpl.java
@@ -0,0 +1,148 @@
+package cc.carm.lib.easysql.migrate;
+
+import cc.carm.lib.easysql.api.SQLManager;
+import cc.carm.lib.easysql.api.SQLQuery;
+import cc.carm.lib.easysql.api.action.PreparedSQLUpdateAction;
+import cc.carm.lib.easysql.api.builder.TableCreateBuilder;
+import cc.carm.lib.easysql.api.enums.IndexType;
+import cc.carm.lib.easysql.api.enums.MigrateResult;
+import cc.carm.lib.easysql.api.migrate.SQLMigrate;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 2022/11/28
+ * EasySQL
+ *
+ * @author huanmeng_qwq
+ */
+public class SQLMigrateImpl implements SQLMigrate {
+ private final SQLManager sqlManager;
+
+ private final Map columnMap;
+
+ private String oldTableName;
+ private MigrateData newTableData;
+
+ public SQLMigrateImpl(SQLManager sqlManager) {
+ this.sqlManager = sqlManager;
+ this.columnMap = new LinkedHashMap<>();
+ }
+
+ @Override
+ public SQLMigrate tableName(String oldTableName, String newTableName, String newTableSettings) {
+ if (oldTableName == null) {
+ throw new IllegalArgumentException("oldTableName can not be null");
+ }
+ this.oldTableName = oldTableName;
+ this.newTableData = new MigrateData(newTableName, newTableSettings, null);
+ return this;
+ }
+
+ @Override
+ public SQLMigrate column(String oldColumnName, String newColumnName, String newColumnSettings, IndexType indexType) {
+ if (oldColumnName == null) {
+ throw new IllegalArgumentException("oldColumnName can not be null");
+ }
+ columnMap.put(oldColumnName, new MigrateData(newColumnName, newColumnSettings, indexType));
+ return this;
+ }
+
+ @Override
+ public SQLMigrate autoIncrementColumn(String oldColumnName, String newColumnName) {
+ if (oldColumnName == null) {
+ throw new IllegalArgumentException("oldColumnName can not be null");
+ }
+ columnMap.put(oldColumnName, new AutoIncrementMigrateData(newColumnName));
+ return this;
+ }
+
+ @Override
+ public MigrateResult migrate() throws SQLException {
+ if (oldTableName == null) {
+ return MigrateResult.from(MigrateResult.OLD_TABLE_NOT_EXIST, new IllegalArgumentException("oldTableName can not be null"));
+ }
+ if (newTableData == null) {
+ return MigrateResult.from(MigrateResult.NEW_TABLE_EXIST, new IllegalArgumentException("new table name can not be null"));
+ }
+ if (columnMap.isEmpty()) {
+ return MigrateResult.from(MigrateResult.NEW_COLUMN_EMPTY, new IllegalArgumentException("new column can not be empty"));
+ }
+ try {
+ // check table
+ Set columns = sqlManager.fetchTableMetadata(oldTableName).listColumns().join();
+ for (String column : columns) {
+ if (columnMap.keySet().stream().noneMatch(k -> k.toLowerCase(Locale.ROOT).equals(column.toLowerCase(Locale.ROOT)))) {
+ return MigrateResult.from(MigrateResult.COLUMN_NOT_SET, new IllegalArgumentException("column " + column + " not set"));
+ }
+ }
+ // create new table
+ TableCreateBuilder table = sqlManager.createTable(newTableData.name());
+ if (newTableData.settings() != null) {
+ table.setTableSettings(newTableData.settings());
+ }
+ // add columns
+ for (Map.Entry entry : columnMap.entrySet()) {
+ MigrateData migrateData = entry.getValue();
+ if (migrateData.name() == null) {
+ // ignore
+ continue;
+ }
+ if (migrateData instanceof AutoIncrementMigrateData) {
+ table.addAutoIncrementColumn(migrateData.name());
+ continue;
+ }
+ table.addColumn(migrateData.name(), migrateData.settings());
+ if (migrateData.indexType() != null) {
+ table.setIndex(migrateData.name(), migrateData.indexType());
+ }
+ }
+ table.build().execute();
+
+ // insert data
+ try (SQLQuery query = sqlManager.createQuery().inTable(oldTableName).build().execute()) {
+ insert(query.getResultSet());
+ }
+ } catch (SQLException e) {
+ return new MigrateResult(false, e);
+ }
+ return MigrateResult.SUCCESS;
+ }
+
+ private void insert(ResultSet resultSet) throws SQLException {
+ while (resultSet.next()) {
+ Map columnNames = tableColumnNames();
+ PreparedSQLUpdateAction updateAction = sqlManager.createInsert(newTableData.name()).setColumnNames(columnNames.values().toArray(new String[0]));
+ Object[] values = new Object[columnNames.size()];
+ int i = 0;
+ for (Map.Entry entry : columnNames.entrySet()) {
+ values[i] = resultSet.getObject(entry.getKey());
+ ++i;
+ }
+ updateAction.setParams(values);
+ updateAction.execute();
+ }
+ }
+
+ private Map tableColumnNames() {
+ Map map = new ConcurrentHashMap<>();
+ for (Map.Entry entry : columnMap.entrySet()) {
+ MigrateData migrateData = entry.getValue();
+ if (migrateData.name() == null) {
+ // ignore
+ continue;
+ }
+ if (migrateData instanceof AutoIncrementMigrateData) {
+ continue;
+ }
+ map.put(entry.getKey(), migrateData.name());
+ }
+ return map;
+ }
+}