Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/src/main/java/me/zort/sqllib/api/model/TableSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public String[] getDefinitions() {
return definitions;
}

public String[] getDefinitionNames() {
String[] definitions = new String[this.definitions.length];
for (int i = 0; i < definitions.length; i++) {
definitions[i] = getDefinitionName(i);
}
return definitions;
}

public String getTable() {
return table;
}
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
testImplementation project(":core")
testImplementation project(":shared")
testImplementation 'com.mysql:mysql-connector-j:8.0.32'
testImplementation 'org.xerial:sqlite-jdbc:3.41.2.2'
testImplementation 'org.apache.logging.log4j:log4j-core:2.19.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testRuntimeOnly 'com.google.guava:guava:31.0-jre'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public SQLConnectionBuilder(@Nullable SQLEndpoint endpoint) {
if (driver == null) driver = SQLDatabaseConnectionImpl.DEFAULT_DRIVER;
SQLConnectionFactory connectionFactory = new LocalConnectionFactory(driver);
return jdbc.contains("jdbc:sqlite")
? new SQLiteDatabaseConnectionImpl(connectionFactory, options)
? new SQLiteDatabaseConnection(connectionFactory, options)
: new SQLDatabaseConnectionImpl(connectionFactory, options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public boolean synchronizeModel() {
return mappingRegistry.getProxyInstances()
.stream().flatMap(i -> i.getTableSchemas(
getOptions().getNamingStrategy(),
this instanceof SQLiteDatabaseConnectionImpl).stream())
this instanceof SQLiteDatabaseConnection).stream())
.anyMatch(schema -> synchronizeModel(schema, schema.getTable()));
}

Expand Down Expand Up @@ -227,7 +227,7 @@ public boolean synchronizeModel(TableSchema entitySchema, String table) {
public boolean synchronizeModel(Class<?> entity, String table) {
return synchronizeModel(new EntitySchemaBuilder(table, entity,
getOptions().getNamingStrategy(),
this instanceof SQLiteDatabaseConnectionImpl).buildTableSchema(), table);
this instanceof SQLiteDatabaseConnection).buildTableSchema(), table);
}

/**
Expand Down Expand Up @@ -332,7 +332,7 @@ public final <T> T createProxy(final @NotNull Class<T> mappingInterface, final @
public final boolean buildEntitySchema(final @NotNull String tableName, final @NotNull Class<?> entityClass) {
Objects.requireNonNull(entityClass, "Entity class cannot be null!");

EntitySchemaBuilder converter = new EntitySchemaBuilder(tableName, entityClass, options.getNamingStrategy(), this instanceof SQLiteDatabaseConnectionImpl);
EntitySchemaBuilder converter = new EntitySchemaBuilder(tableName, entityClass, options.getNamingStrategy(), this instanceof SQLiteDatabaseConnection);
String query = converter.buildTableQuery();

return exec(() -> query).isSuccessful();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import me.zort.sqllib.internal.factory.SQLConnectionFactory;
import me.zort.sqllib.internal.query.*;
import me.zort.sqllib.internal.query.part.SetStatement;
import me.zort.sqllib.model.column.SQLiteColumnQueryBuilder;
import me.zort.sqllib.model.column.SQLiteColumnTypeAdjuster;
import me.zort.sqllib.model.schema.SQLSchemaSynchronizer;
import me.zort.sqllib.util.PrimaryKey;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -22,16 +25,27 @@
*
* @author ZorTik
*/
public class SQLiteDatabaseConnectionImpl extends SQLDatabaseConnectionImpl {
private final SQLiteDatabaseConnectionImpl identity = this;
public class SQLiteDatabaseConnection extends SQLDatabaseConnectionImpl {
private final SQLiteDatabaseConnection identity = this;

@SuppressWarnings("unused")
public SQLiteDatabaseConnectionImpl(final @NotNull SQLConnectionFactory connectionFactory) {
public SQLiteDatabaseConnection(final @NotNull SQLConnectionFactory connectionFactory) {
super(connectionFactory);
setup();
}

public SQLiteDatabaseConnectionImpl(final @NotNull SQLConnectionFactory connectionFactory, @Nullable ISQLDatabaseOptions options) {
public SQLiteDatabaseConnection(final @NotNull SQLConnectionFactory connectionFactory, @Nullable ISQLDatabaseOptions options) {
super(connectionFactory, options);
setup();
}

private void setup() {
if (getSchemaSynchronizer() instanceof SQLSchemaSynchronizer) {
SQLSchemaSynchronizer schemaSynchronizer = (SQLSchemaSynchronizer) getSchemaSynchronizer();
schemaSynchronizer.setColumnQueryBuilder(new SQLiteColumnQueryBuilder(this));
schemaSynchronizer.setColumnTypeAdjuster(new SQLiteColumnTypeAdjuster());
schemaSynchronizer.setSeparateQueries(true);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public void accept(int value) {
}

private static void set(PreparedStatement statement, int index, Object value) throws SQLException {
switch(value.getClass().getSimpleName().toLowerCase()) {
String type = value != null ? value.getClass().getSimpleName().toLowerCase() : "null";
switch(type) {
case "string":
statement.setString(index, (String) value);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl

@Override
public List<TableSchema> getTableSchemas(NamingStrategy namingStrategy, boolean sqLite) {
List<Class<?>> builtTypes = new ArrayList<>();
List<TableSchema> schemaList = new ArrayList<>();
for (Method method : getTypeClass().getDeclaredMethods()) {
Class<?> resultType = mappingResultAdapter.retrieveResultType(method);

if (!QueryResult.class.isAssignableFrom(resultType) && statementMapping.isMappingMethod(method)) {
if (!QueryResult.class.isAssignableFrom(resultType) && statementMapping.isMappingMethod(method) && !builtTypes.contains(resultType)) {
String table = options.getTable() != null ? options.getTable() : Table.Util.getFromContext(method, null);
schemaList.add(new EntitySchemaBuilder(table, resultType, namingStrategy, sqLite).buildTableSchema());
builtTypes.add(resultType);
}
}
return schemaList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@

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

import java.util.ArrayList;
import java.util.List;

public class InnoColumnQueryBuilder implements SQLColumnQueryBuilder {
@Override
public String buildActionQuery(SQLColumnQueryBuilder.ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
public List<String> buildActionQuery(SQLColumnQueryBuilder.ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
List<String> queries = new ArrayList<>();
if (action == SQLColumnQueryBuilder.ColumnAction.ADD) {
return "ALTER TABLE " + table + " ADD COLUMN " + from + ";";
queries.add("ALTER TABLE " + table + " ADD COLUMN " + from + ";");
} else if (action == SQLColumnQueryBuilder.ColumnAction.DROP) {
return "ALTER TABLE " + table + " DROP COLUMN " + to.getName() + ";";
queries.add("ALTER TABLE " + table + " DROP COLUMN " + to.getName() + ";");
} else if (action == SQLColumnQueryBuilder.ColumnAction.RENAME) {
return "ALTER TABLE " + table + " RENAME COLUMN " + to.getName() + " TO " + from.getName() + ";";
queries.add("ALTER TABLE " + table + " RENAME COLUMN " + to.getName() + " TO " + from.getName() + ";");
} else if (action == SQLColumnQueryBuilder.ColumnAction.MODIFY) {
return "ALTER TABLE " + table + " MODIFY COLUMN " + from.getName() + " " + from.getType() + ";";
queries.add("ALTER TABLE " + table + " MODIFY COLUMN " + from.getName() + " " + from.getType() + ";");
}
if (queries.size() > 0) return queries;
throw new RuntimeException("Unknown action: " + action);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

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

import java.util.List;

public interface SQLColumnQueryBuilder {

String buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to);
List<String> buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to);

enum ColumnAction {
ADD, DROP, MODIFY, RENAME
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.zort.sqllib.model.column;

public interface SQLColumnTypeAdjuster {

String adjust(String type);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package me.zort.sqllib.model.column;

import lombok.AllArgsConstructor;
import me.zort.sqllib.SQLiteDatabaseConnection;
import me.zort.sqllib.api.model.ColumnDefinition;
import me.zort.sqllib.api.model.TableSchema;

import java.util.ArrayList;
import java.util.List;

@AllArgsConstructor
public class SQLiteColumnQueryBuilder extends InnoColumnQueryBuilder {
private final SQLiteDatabaseConnection connection;
@Override
public List<String> buildActionQuery(ColumnAction action, String table, ColumnDefinition from, ColumnDefinition to) {
if (action.equals(ColumnAction.MODIFY)) {
TableSchema schema = connection.getSchemaBuilder(table).buildTableSchema();
String[] newDefinitions = new String[schema.size()];
for (int i = 0; i < schema.size(); i++) {
if (schema.getDefinitionName(i).equals(from.getName())) {
newDefinitions[i] = from.getName() + " " + from.getType();
} else {
newDefinitions[i] = schema.getDefinition(i);
}
}
List<String> queries = new ArrayList<>();
queries.add("ALTER TABLE " + table + " RENAME TO " + table + "_old;");
queries.add("CREATE TABLE " + table + "(" + String.join(", ", newDefinitions) + ");");
queries.add("INSERT INTO " + table + "(" + String.join(", ", schema.getDefinitionNames()) + ") SELECT " + String.join(", ", schema.getDefinitionNames()) + " FROM " + table + "_old;");
queries.add("DROP TABLE " + table + "_old;");
return queries;
}
return super.buildActionQuery(action, table, from, to);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package me.zort.sqllib.model.column;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SQLiteColumnTypeAdjuster implements SQLColumnTypeAdjuster {
private static final Pattern sizePattern = Pattern.compile("(.+)\\(\\d+\\)");
@Override
public String adjust(final String type) {
final String subject = type.split(" ")[0];
final String suffix = subject.equals(type)
? ""
: " " + String.join(" ", subarray(type.split(" ")));
final Matcher matcher = sizePattern.matcher(subject);
return (matcher.matches() ? matcher.group(1) : subject) + suffix;
}

private static String[] subarray(String[] array) {
return Arrays.copyOfRange(array, 1, array.length);
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,58 @@
package me.zort.sqllib.model.schema;

import lombok.Getter;
import lombok.Setter;
import me.zort.sqllib.SQLDatabaseConnection;
import me.zort.sqllib.api.data.QueryResult;
import me.zort.sqllib.api.model.ColumnDefinition;
import me.zort.sqllib.api.model.SchemaSynchronizer;
import me.zort.sqllib.api.model.TableSchema;
import me.zort.sqllib.internal.impl.QueryResultImpl;
import me.zort.sqllib.model.column.InnoColumnQueryBuilder;
import me.zort.sqllib.model.column.SQLColumnQueryBuilder;
import me.zort.sqllib.model.column.SQLColumnTypeAdjuster;

import java.util.ArrayList;
import java.util.List;

@Setter
@Getter
public class SQLSchemaSynchronizer implements SchemaSynchronizer<SQLDatabaseConnection> {

@Setter
private SQLColumnQueryBuilder columnQueryBuilder = new InnoColumnQueryBuilder();
private SQLColumnTypeAdjuster columnTypeAdjuster = type -> type;
private boolean separateQueries = false;

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

if (fromDefinition == null && toDefinition != null) {
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.DROP, from.getTable(), fromDefinition, toDefinition));
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.DROP, from.getTable(), fromDefinition, toDefinition));
} else if (fromDefinition != null && toDefinition == null) {
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.ADD, from.getTable(), fromDefinition, toDefinition));
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.ADD, from.getTable(), fromDefinition, toDefinition));
} else {
assert fromDefinition != null;
if (!fromDefinition.getName().equals(toDefinition.getName())) {
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.RENAME, from.getTable(), fromDefinition, toDefinition));
} else if(!fromDefinition.getType().equals(toDefinition.getType())) {
query.append(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.MODIFY, from.getTable(), fromDefinition, toDefinition));
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.RENAME, from.getTable(), fromDefinition, toDefinition));
} else if(!columnTypeAdjuster.adjust(fromDefinition.getType()).equals(columnTypeAdjuster.adjust(toDefinition.getType()))) {
System.out.println("Modifying column " + fromDefinition.getName() + " in table " + from.getTable() + " from " + columnTypeAdjuster.adjust(toDefinition.getType()) + " to " + columnTypeAdjuster.adjust(fromDefinition.getType()));
columnQueries.addAll(columnQueryBuilder.buildActionQuery(SQLColumnQueryBuilder.ColumnAction.MODIFY, from.getTable(), fromDefinition, toDefinition));
}
}
}
return query.length() == 0 ? QueryResult.noChangesResult : source.exec(query.toString());
if (columnQueries.size() == 0) return QueryResult.noChangesResult;
List<QueryResult> results = new ArrayList<>();
if (separateQueries) {
for (String query : columnQueries) {
results.add(source.exec(query));
}
} else {
results.add(source.exec(String.join("", columnQueries)));
}
return results.stream().allMatch(QueryResult::isSuccessful) ? QueryResult.successful() : new QueryResultImpl(false);
}
}
40 changes: 35 additions & 5 deletions src/test/java/me/zort/sqllib/test/TestCase2.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
import me.zort.sqllib.internal.annotation.PrimaryKey;
import me.zort.sqllib.mapping.annotation.*;
import me.zort.sqllib.model.schema.EntitySchemaBuilder;
import me.zort.sqllib.model.schema.SQLSchemaSynchronizer;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

Expand All @@ -30,6 +33,7 @@
public class TestCase2 { // Experimental features

private SQLDatabaseConnection connection;
private SQLDatabaseConnection sqliteConnection;

@BeforeAll
public void prepare() {
Expand All @@ -45,12 +49,28 @@ public void prepare() {
.withDriver("com.mysql.cj.jdbc.Driver")
.build(options);

doPrepareConnection(connection);
}

@BeforeAll
public void prepareSqlite() throws IOException {
File file = new File(System.getProperty("user.dir") + "/test.db");
file.delete();
file.getParentFile().mkdirs();
file.createNewFile();
SQLDatabaseOptions options = new SQLDatabaseOptions();
options.setDebug(true);
sqliteConnection = SQLConnectionBuilder.ofSQLite(file.getAbsolutePath())
.build(options);
doPrepareConnection(sqliteConnection);
}

private static void doPrepareConnection(SQLDatabaseConnection connection) {
assertTrue(connection.connect());
assertTrue(connection.isConnected());

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

@Timeout(10)
Expand All @@ -75,24 +95,34 @@ public void test1_Mapping() {
@Timeout(5)
@Test
public void test2_Synchronization() {
doTestSynchronization(connection);
doTestSynchronization(sqliteConnection);
}

private static void doTestSynchronization(SQLDatabaseConnection connection) {
System.out.println(connection.getClass().getSimpleName() + ":");
TableSchema schema = new EntitySchemaBuilder("users", User.class, ((SQLDatabaseConnectionImpl) connection).getOptions().getNamingStrategy(), false).buildTableSchema();
assertEquals(2, schema.getDefinitions().length);
assertEquals("nickname VARCHAR(255) PRIMARY KEY", schema.getDefinitions()[0]);
assertEquals("points INTEGER", schema.getDefinitions()[1]);

TableSchema dbSchema = connection.getSchemaBuilder("users").buildTableSchema();
assertEquals(2, dbSchema.getDefinitions().length);
assertEquals("nickname VARCHAR(255) PRIMARY KEY", dbSchema.getDefinitions()[0]);
assertEquals("points INTEGER", dbSchema.getDefinitions()[1]);
assertEquals("nickname " + adjustColumnType(connection, "VARCHAR(255) PRIMARY KEY"), dbSchema.getDefinitions()[0]);
assertEquals("points " + adjustColumnType(connection, "INTEGER"), dbSchema.getDefinitions()[1]);
assertFalse(connection.synchronizeModel(schema, "users"));
assertFalse(connection.synchronizeModel());

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

TableSchema copySchema = connection.getSchemaBuilder("users").buildTableSchema();
assertEquals(2, copySchema.getDefinitions().length);
assertEquals("nickname VARCHAR(255) PRIMARY KEY", copySchema.getDefinitions()[0]);
assertEquals("points INTEGER DEFAULT 0", copySchema.getDefinitions()[1]);
assertEquals("nickname " + adjustColumnType(connection, "VARCHAR(255) PRIMARY KEY"), copySchema.getDefinitions()[0]);
assertEquals("points " + adjustColumnType(connection, "INTEGER DEFAULT 0"), copySchema.getDefinitions()[1]);
}

private static String adjustColumnType(SQLDatabaseConnection connection, String type) {
return ((SQLSchemaSynchronizer) connection.getSchemaSynchronizer()).getColumnTypeAdjuster().adjust(type);
}

@Timeout(5)
Expand Down