Skip to content

Commit e121d9a

Browse files
committed
Synchronization fixes
1 parent c741a41 commit e121d9a

9 files changed

Lines changed: 122 additions & 28 deletions

File tree

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525
testImplementation project(":core")
2626
testImplementation project(":shared")
2727
testImplementation 'com.mysql:mysql-connector-j:8.0.32'
28+
testImplementation 'org.xerial:sqlite-jdbc:3.41.2.2'
2829
testImplementation 'org.apache.logging.log4j:log4j-core:2.19.0'
2930
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
3031
testRuntimeOnly 'com.google.guava:guava:31.0-jre'

core/src/main/java/me/zort/sqllib/SQLiteDatabaseConnection.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import me.zort.sqllib.internal.query.*;
1010
import me.zort.sqllib.internal.query.part.SetStatement;
1111
import me.zort.sqllib.model.column.SQLiteColumnQueryBuilder;
12+
import me.zort.sqllib.model.column.SQLiteColumnTypeAdjuster;
1213
import me.zort.sqllib.model.schema.SQLSchemaSynchronizer;
1314
import me.zort.sqllib.util.PrimaryKey;
1415
import org.jetbrains.annotations.NotNull;
@@ -30,17 +31,20 @@ public class SQLiteDatabaseConnection extends SQLDatabaseConnectionImpl {
3031
@SuppressWarnings("unused")
3132
public SQLiteDatabaseConnection(final @NotNull SQLConnectionFactory connectionFactory) {
3233
super(connectionFactory);
33-
changeSQLiteColumnQueryBuilder();
34+
setup();
3435
}
3536

3637
public SQLiteDatabaseConnection(final @NotNull SQLConnectionFactory connectionFactory, @Nullable ISQLDatabaseOptions options) {
3738
super(connectionFactory, options);
38-
changeSQLiteColumnQueryBuilder();
39+
setup();
3940
}
4041

41-
private void changeSQLiteColumnQueryBuilder() {
42+
private void setup() {
4243
if (getSchemaSynchronizer() instanceof SQLSchemaSynchronizer) {
43-
((SQLSchemaSynchronizer) getSchemaSynchronizer()).setColumnQueryBuilder(new SQLiteColumnQueryBuilder(this));
44+
SQLSchemaSynchronizer schemaSynchronizer = (SQLSchemaSynchronizer) getSchemaSynchronizer();
45+
schemaSynchronizer.setColumnQueryBuilder(new SQLiteColumnQueryBuilder(this));
46+
schemaSynchronizer.setColumnTypeAdjuster(new SQLiteColumnTypeAdjuster());
47+
schemaSynchronizer.setSeparateQueries(true);
4448
}
4549
}
4650

core/src/main/java/me/zort/sqllib/model/column/InnoColumnQueryBuilder.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22

33
import me.zort.sqllib.api.model.ColumnDefinition;
44

5+
import java.util.ArrayList;
6+
import java.util.List;
7+
58
public class InnoColumnQueryBuilder implements SQLColumnQueryBuilder {
69
@Override
7-
public String buildActionQuery(SQLColumnQueryBuilder.ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
10+
public List<String> buildActionQuery(SQLColumnQueryBuilder.ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
11+
List<String> queries = new ArrayList<>();
812
if (action == SQLColumnQueryBuilder.ColumnAction.ADD) {
9-
return "ALTER TABLE " + table + " ADD COLUMN " + from + ";";
13+
queries.add("ALTER TABLE " + table + " ADD COLUMN " + from + ";");
1014
} else if (action == SQLColumnQueryBuilder.ColumnAction.DROP) {
11-
return "ALTER TABLE " + table + " DROP COLUMN " + to.getName() + ";";
15+
queries.add("ALTER TABLE " + table + " DROP COLUMN " + to.getName() + ";");
1216
} else if (action == SQLColumnQueryBuilder.ColumnAction.RENAME) {
13-
return "ALTER TABLE " + table + " RENAME COLUMN " + to.getName() + " TO " + from.getName() + ";";
17+
queries.add("ALTER TABLE " + table + " RENAME COLUMN " + to.getName() + " TO " + from.getName() + ";");
1418
} else if (action == SQLColumnQueryBuilder.ColumnAction.MODIFY) {
15-
return "ALTER TABLE " + table + " MODIFY COLUMN " + from.getName() + " " + from.getType() + ";";
19+
queries.add("ALTER TABLE " + table + " MODIFY COLUMN " + from.getName() + " " + from.getType() + ";");
1620
}
21+
if (queries.size() > 0) return queries;
1722
throw new RuntimeException("Unknown action: " + action);
1823
}
1924
}

core/src/main/java/me/zort/sqllib/model/column/SQLColumnQueryBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import me.zort.sqllib.api.model.ColumnDefinition;
44

5+
import java.util.List;
6+
57
public interface SQLColumnQueryBuilder {
68

7-
String buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to);
9+
List<String> buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to);
810

911
enum ColumnAction {
1012
ADD, DROP, MODIFY, RENAME
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package me.zort.sqllib.model.column;
2+
3+
public interface SQLColumnTypeAdjuster {
4+
5+
String adjust(String type);
6+
7+
}

core/src/main/java/me/zort/sqllib/model/column/SQLiteColumnQueryBuilder.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import me.zort.sqllib.api.model.ColumnDefinition;
66
import me.zort.sqllib.api.model.TableSchema;
77

8+
import java.util.ArrayList;
9+
import java.util.List;
10+
811
@AllArgsConstructor
912
public class SQLiteColumnQueryBuilder extends InnoColumnQueryBuilder {
1013
private final SQLiteDatabaseConnection connection;
1114
@Override
12-
public String buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
15+
public List<String> buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
1316
if (action.equals(ColumnAction.MODIFY)) {
1417
TableSchema schema = connection.getSchemaBuilder(table).buildTableSchema();
1518
String[] newDefinitions = new String[schema.size()];
@@ -20,10 +23,11 @@ public String buildActionQuery(ColumnAction action, String table, ColumnDefiniti
2023
newDefinitions[i] = schema.getDefinition(i);
2124
}
2225
}
23-
String queries = "ALTER TABLE " + table + " RENAME TO " + table + "_old;";
24-
queries += "CREATE TABLE " + table + "(" + String.join(", ", newDefinitions) + ");";
25-
queries += "INSERT INTO " + table + "(" + String.join(", ", schema.getDefinitionNames()) + ") SELECT " + String.join(", ", schema.getDefinitionNames()) + " FROM " + table + "_old;";
26-
queries += "DROP TABLE " + table + "_old;";
26+
List<String> queries = new ArrayList<>();
27+
queries.add("ALTER TABLE " + table + " RENAME TO " + table + "_old;");
28+
queries.add("CREATE TABLE " + table + "(" + String.join(", ", newDefinitions) + ");");
29+
queries.add("INSERT INTO " + table + "(" + String.join(", ", schema.getDefinitionNames()) + ") SELECT " + String.join(", ", schema.getDefinitionNames()) + " FROM " + table + "_old;");
30+
queries.add("DROP TABLE " + table + "_old;");
2731
return queries;
2832
}
2933
return super.buildActionQuery(action, table, from, to);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package me.zort.sqllib.model.column;
2+
3+
import java.util.Arrays;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
7+
public class SQLiteColumnTypeAdjuster implements SQLColumnTypeAdjuster {
8+
private static final Pattern sizePattern = Pattern.compile("(.+)\\(\\d+\\)");
9+
@Override
10+
public String adjust(final String type) {
11+
final String subject = type.split(" ")[0];
12+
final String suffix = subject.equals(type)
13+
? ""
14+
: " " + String.join(" ", subarray(type.split(" ")));
15+
final Matcher matcher = sizePattern.matcher(subject);
16+
return (matcher.matches() ? matcher.group(1) : subject) + suffix;
17+
}
18+
19+
private static String[] subarray(String[] array) {
20+
return Arrays.copyOfRange(array, 1, array.length);
21+
}
22+
}
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,58 @@
11
package me.zort.sqllib.model.schema;
22

3+
import lombok.Getter;
34
import lombok.Setter;
45
import me.zort.sqllib.SQLDatabaseConnection;
56
import me.zort.sqllib.api.data.QueryResult;
67
import me.zort.sqllib.api.model.ColumnDefinition;
78
import me.zort.sqllib.api.model.SchemaSynchronizer;
89
import me.zort.sqllib.api.model.TableSchema;
10+
import me.zort.sqllib.internal.impl.QueryResultImpl;
911
import me.zort.sqllib.model.column.InnoColumnQueryBuilder;
1012
import me.zort.sqllib.model.column.SQLColumnQueryBuilder;
13+
import me.zort.sqllib.model.column.SQLColumnTypeAdjuster;
1114

15+
import java.util.ArrayList;
16+
import java.util.List;
17+
18+
@Setter
19+
@Getter
1220
public class SQLSchemaSynchronizer implements SchemaSynchronizer<SQLDatabaseConnection> {
1321

14-
@Setter
1522
private SQLColumnQueryBuilder columnQueryBuilder = new InnoColumnQueryBuilder();
23+
private SQLColumnTypeAdjuster columnTypeAdjuster = type -> type;
24+
private boolean separateQueries = false;
1625

1726
@Override
1827
public QueryResult synchronize(SQLDatabaseConnection source, TableSchema from, TableSchema to) {
19-
StringBuilder query = new StringBuilder();
28+
List<String> columnQueries = new ArrayList<>();
2029
for (int i = 0; i < Math.max(from.size(), to.size()); i++) {
2130
ColumnDefinition fromDefinition = from.size() > i ? from.getDefinitionDetails(i) : null;
2231
ColumnDefinition toDefinition = to.size() > i ? to.getDefinitionDetails(i) : null;
2332

2433
if (fromDefinition == null && toDefinition != null) {
25-
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.DROP, from.getTable(), fromDefinition, toDefinition));
34+
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.DROP, from.getTable(), fromDefinition, toDefinition));
2635
} else if (fromDefinition != null && toDefinition == null) {
27-
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.ADD, from.getTable(), fromDefinition, toDefinition));
36+
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.ADD, from.getTable(), fromDefinition, toDefinition));
2837
} else {
2938
assert fromDefinition != null;
3039
if (!fromDefinition.getName().equals(toDefinition.getName())) {
31-
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.RENAME, from.getTable(), fromDefinition, toDefinition));
32-
} else if(!fromDefinition.getType().equals(toDefinition.getType())) {
33-
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.MODIFY, from.getTable(), fromDefinition, toDefinition));
40+
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.RENAME, from.getTable(), fromDefinition, toDefinition));
41+
} else if(!columnTypeAdjuster.adjust(fromDefinition.getType()).equals(columnTypeAdjuster.adjust(toDefinition.getType()))) {
42+
System.out.println("Modifying column " + fromDefinition.getName() + " in table " + from.getTable() + " from " + columnTypeAdjuster.adjust(toDefinition.getType()) + " to " + columnTypeAdjuster.adjust(fromDefinition.getType()));
43+
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.MODIFY, from.getTable(), fromDefinition, toDefinition));
3444
}
3545
}
3646
}
37-
return query.length() == 0 ? QueryResult.noChangesResult : source.exec(query.toString());
47+
if (columnQueries.size() == 0) return QueryResult.noChangesResult;
48+
List<QueryResult> results = new ArrayList<>();
49+
if (separateQueries) {
50+
for (String query : columnQueries) {
51+
results.add(source.exec(query));
52+
}
53+
} else {
54+
results.add(source.exec(String.join("", columnQueries)));
55+
}
56+
return results.stream().allMatch(QueryResult::isSuccessful) ? QueryResult.successful() : new QueryResultImpl(false);
3857
}
3958
}

src/test/java/me/zort/sqllib/test/TestCase2.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
import me.zort.sqllib.internal.annotation.PrimaryKey;
1515
import me.zort.sqllib.mapping.annotation.*;
1616
import me.zort.sqllib.model.schema.EntitySchemaBuilder;
17+
import me.zort.sqllib.model.schema.SQLSchemaSynchronizer;
1718
import org.junit.jupiter.api.*;
1819
import org.junit.jupiter.api.condition.EnabledOnOs;
1920
import org.junit.jupiter.api.condition.OS;
2021

22+
import java.io.File;
23+
import java.io.IOException;
2124
import java.util.List;
2225
import java.util.Optional;
2326

@@ -30,6 +33,7 @@
3033
public class TestCase2 { // Experimental features
3134

3235
private SQLDatabaseConnection connection;
36+
private SQLDatabaseConnection sqliteConnection;
3337

3438
@BeforeAll
3539
public void prepare() {
@@ -45,12 +49,28 @@ public void prepare() {
4549
.withDriver("com.mysql.cj.jdbc.Driver")
4650
.build(options);
4751

52+
doPrepareConnection(connection);
53+
}
54+
55+
@BeforeAll
56+
public void prepareSqlite() throws IOException {
57+
File file = new File(System.getProperty("user.dir") + "/test.db");
58+
file.delete();
59+
file.getParentFile().mkdirs();
60+
file.createNewFile();
61+
SQLDatabaseOptions options = new SQLDatabaseOptions();
62+
options.setDebug(true);
63+
sqliteConnection = SQLConnectionBuilder.ofSQLite(file.getAbsolutePath())
64+
.build(options);
65+
doPrepareConnection(sqliteConnection);
66+
}
67+
68+
private static void doPrepareConnection(SQLDatabaseConnection connection) {
4869
assertTrue(connection.connect());
4970
assertTrue(connection.isConnected());
5071

5172
assertNull(connection.exec(() -> "DROP TABLE IF EXISTS users;").getRejectMessage());
5273
assertTrue(connection.buildEntitySchema("users", User.class));
53-
assertNull(connection.exec(() -> "TRUNCATE TABLE users;").getRejectMessage());
5474
}
5575

5676
@Timeout(10)
@@ -75,24 +95,34 @@ public void test1_Mapping() {
7595
@Timeout(5)
7696
@Test
7797
public void test2_Synchronization() {
98+
doTestSynchronization(connection);
99+
doTestSynchronization(sqliteConnection);
100+
}
101+
102+
private static void doTestSynchronization(SQLDatabaseConnection connection) {
103+
System.out.println(connection.getClass().getSimpleName() + ":");
78104
TableSchema schema = new EntitySchemaBuilder("users", User.class, ((SQLDatabaseConnectionImpl) connection).getOptions().getNamingStrategy(), false).buildTableSchema();
79105
assertEquals(2, schema.getDefinitions().length);
80106
assertEquals("nickname VARCHAR(255) PRIMARY KEY", schema.getDefinitions()[0]);
81107
assertEquals("points INTEGER", schema.getDefinitions()[1]);
82108

83109
TableSchema dbSchema = connection.getSchemaBuilder("users").buildTableSchema();
84110
assertEquals(2, dbSchema.getDefinitions().length);
85-
assertEquals("nickname VARCHAR(255) PRIMARY KEY", dbSchema.getDefinitions()[0]);
86-
assertEquals("points INTEGER", dbSchema.getDefinitions()[1]);
111+
assertEquals("nickname " + adjustColumnType(connection, "VARCHAR(255) PRIMARY KEY"), dbSchema.getDefinitions()[0]);
112+
assertEquals("points " + adjustColumnType(connection, "INTEGER"), dbSchema.getDefinitions()[1]);
87113
assertFalse(connection.synchronizeModel(schema, "users"));
88114
assertFalse(connection.synchronizeModel());
89115

90116
assertTrue(connection.synchronizeModel(UserCopy.class, "users"));
91117

92118
TableSchema copySchema = connection.getSchemaBuilder("users").buildTableSchema();
93119
assertEquals(2, copySchema.getDefinitions().length);
94-
assertEquals("nickname VARCHAR(255) PRIMARY KEY", copySchema.getDefinitions()[0]);
95-
assertEquals("points INTEGER DEFAULT 0", copySchema.getDefinitions()[1]);
120+
assertEquals("nickname " + adjustColumnType(connection, "VARCHAR(255) PRIMARY KEY"), copySchema.getDefinitions()[0]);
121+
assertEquals("points " + adjustColumnType(connection, "INTEGER DEFAULT 0"), copySchema.getDefinitions()[1]);
122+
}
123+
124+
private static String adjustColumnType(SQLDatabaseConnection connection, String type) {
125+
return ((SQLSchemaSynchronizer) connection.getSchemaSynchronizer()).getColumnTypeAdjuster().adjust(type);
96126
}
97127

98128
@Timeout(5)

0 commit comments

Comments
 (0)