Skip to content

Commit 58ba9f7

Browse files
committed
support enums in mariadb
1 parent 483182f commit 58ba9f7

File tree

10 files changed

+230
-59
lines changed

10 files changed

+230
-59
lines changed

core/src/main/java/ch/ergon/adam/core/db/DefaultMigrationStrategy.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class DefaultMigrationStrategy implements MigrationStrategy {
3030
Set<View> viewsToDrop = new LinkedHashSet<>();
3131
Set<DbEnum> enumsToCreate = new LinkedHashSet<>();
3232
Set<DbEnum> enumsToUpdate = new LinkedHashSet<>();
33-
Set<Field> tableFieldsToChangeTypeForEnumMigration = new LinkedHashSet<>();
33+
Set<Pair<Field, Field>> tableFieldsToChangeTypeForEnumMigration = new LinkedHashSet<>();
3434
Set<DbEnum> enumsToDrop = new LinkedHashSet<>();
3535
Set<Constraint> constraintsToDrop = new LinkedHashSet<>();
3636
Set<Constraint> constraintsToCreate = new LinkedHashSet<>();
@@ -215,9 +215,15 @@ public void enumRemoved(DbEnum oldEnum) {
215215
public void enumUpdated(DbEnum oldEnum, DbEnum newEnum) {
216216
enumsToUpdate.add(oldEnum);
217217
enumsToCreate.add(newEnum);
218-
List<Field> referencingTableFields = oldEnum.getReferencingFields().stream()
218+
List<Pair<Field, Field>> referencingTableFields = oldEnum.getReferencingFields().stream()
219219
.filter(f -> f.getContainer() instanceof Table)
220-
.collect(toList());
220+
.map(f -> {
221+
Field newField = newEnum.getReferencingFields().stream()
222+
.filter(nF -> nF.getTable().getName().equals(f.getTable().getName()) && nF.getName().equals(f.getName()))
223+
.findFirst()
224+
.orElse(f);
225+
return new Pair<>(f, newField);
226+
}).toList();
221227
tableFieldsToChangeTypeForEnumMigration.addAll(referencingTableFields);
222228
}
223229

@@ -279,18 +285,18 @@ public void apply(SchemaSink sink) {
279285

280286
sequencesToDrop.forEach(sink::dropSequence);
281287

282-
tableFieldsToChangeTypeForEnumMigration.forEach(field -> {
283-
sink.dropDefault(field);
284-
sink.changeFieldType(field, field, DataType.CLOB);
288+
tableFieldsToChangeTypeForEnumMigration.forEach(p -> {
289+
sink.dropDefault(p.getFirst());
290+
sink.changeFieldType(p.getFirst(), p.getSecond(), DataType.CLOB);
285291
});
286292

287293
enumsToUpdate.forEach(sink::dropEnum);
288294

289295
enumsToCreate.forEach(sink::createEnum);
290296

291-
tableFieldsToChangeTypeForEnumMigration.forEach(field -> {
292-
sink.changeFieldType(field, field, field.getDataType());
293-
sink.setDefault(field);
297+
tableFieldsToChangeTypeForEnumMigration.forEach(p -> {
298+
sink.changeFieldType(p.getFirst(), p.getSecond(), p.getSecond().getDataType());
299+
sink.setDefault(p.getSecond());
294300
});
295301

296302
tablesToRename.forEach(pair -> sink.renameTable(pair.getFirst(), pair.getSecond().getName()));

core/src/main/java/ch/ergon/adam/core/db/schema/Field.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ public void setArray(boolean array) {
105105
isArray = array;
106106
}
107107

108+
public boolean isEnum() {
109+
return dataType == DataType.ENUM;
110+
}
111+
108112
public boolean isSequence() {
109113
return sequence;
110114
}

integration-test-db/src/main/resources/adam/mariadb/schema/test.table.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ fields:
2828
- name: "col7"
2929
dataType: ENUM
3030
enumName: test_status
31-
array: true
3231
nullable: true
3332
foreignKeys: []
3433
indexes:
Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,57 @@
11
package ch.ergon.adam.integrationtest.mariadb;
22

3-
import ch.ergon.adam.integrationtest.testcases.AddSqlForNewTest;
3+
import ch.ergon.adam.core.db.schema.DbEnum;
4+
import ch.ergon.adam.core.db.schema.Field;
5+
import ch.ergon.adam.core.db.schema.Schema;
6+
import ch.ergon.adam.core.db.schema.Table;
7+
import ch.ergon.adam.integrationtest.DummySink;
8+
import org.junit.jupiter.api.Test;
49

5-
import static org.jooq.SQLDialect.MARIADB;
10+
import java.sql.ResultSet;
11+
import java.util.ArrayList;
12+
import java.util.List;
613

7-
public class MariaDbAddSqlForNewTest extends AddSqlForNewTest {
14+
import static ch.ergon.adam.core.db.schema.DataType.ENUM;
15+
import static org.hamcrest.CoreMatchers.is;
16+
import static org.hamcrest.MatcherAssert.assertThat;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
18+
19+
public class MariaDbAddSqlForNewTest extends AbstractMariaDbTestBase {
820
public MariaDbAddSqlForNewTest() {
9-
super(new MariaDbTestDbUrlProvider(), MARIADB);
21+
super();
1022
}
1123

12-
@Override
13-
protected String getCreateEnumSql() {
14-
// MariaDB does not support enum type
15-
return "SELECT true FROM DUAL";
24+
private static final String CREATE_TABLE_SQL =
25+
"create table \"test_table\" (" +
26+
"col1 int" +
27+
")";
28+
29+
private static final String INSERT_DATA_SQL =
30+
"insert into \"test_table\" values (1)";
31+
32+
@Test
33+
public void testAddSqlForNewMigration() throws Exception {
34+
// Setup db
35+
executeOnTargetDb(CREATE_TABLE_SQL);
36+
executeOnTargetDb(INSERT_DATA_SQL);
37+
DummySink dummySink = targetToDummy();
38+
Schema schema = dummySink.getTargetSchema();
39+
40+
// Add field
41+
Table table = schema.getTable("test_table");
42+
List<Field> fields = new ArrayList<>(table.getFields());
43+
Field newField = new Field("custom_type");
44+
newField.setDataType(ENUM);
45+
newField.setDbEnum(new DbEnum("custom_enum"));
46+
newField.setDefaultValue("'val2'");
47+
newField.setSqlForNew("'val1'");
48+
fields.add(newField);
49+
table.setFields(fields);
50+
migrateTargetWithSchema(schema);
51+
52+
// Verify
53+
ResultSet result = executeQueryOnTargetDb("select * from \"test_table\"");
54+
assertTrue(result.next());
55+
assertThat(result.getString(2), is("val1"));
1656
}
1757
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package ch.ergon.adam.integrationtest.mariadb;
2+
3+
import ch.ergon.adam.core.db.schema.DbEnum;
4+
import ch.ergon.adam.core.db.schema.Schema;
5+
import ch.ergon.adam.integrationtest.DummySink;
6+
import org.junit.jupiter.api.Test;
7+
8+
import static org.hamcrest.CoreMatchers.is;
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
13+
public class MariaDbEnumTests extends AbstractMariaDbTestBase {
14+
15+
private static final String CREATE_TABLE_SQL =
16+
"create table test_table (" +
17+
"id enum('val1', 'val2'), " +
18+
"not_null_enum enum('val1', 'val2') not null," +
19+
"enum_with_default enum('val1', 'val2') default 'val1'" +
20+
")";
21+
22+
@Test
23+
public void testCreateEnum() throws Exception {
24+
25+
// Setup db
26+
getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL);
27+
sourceToTarget();
28+
DummySink dummySink = targetToDummy();
29+
Schema schema = dummySink.getTargetSchema();
30+
31+
// Verify
32+
assertThat(schema.getTable("test_table").getField("id").getDbEnum().getName(), is("test_table_id"));
33+
assertThat(schema.getEnums().size(), is(3));
34+
assertFalse(schema.getTable("test_table").getField("not_null_enum").isNullable());
35+
assertNotNull(schema.getTable("test_table").getField("enum_with_default").getDefaultValue());
36+
}
37+
38+
@Test
39+
public void testRecreateTableAfterEnumChange() throws Exception {
40+
41+
// Setup db
42+
getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL);
43+
sourceToTarget();
44+
DummySink dummySink = targetToDummy();
45+
Schema schema = dummySink.getTargetSchema();
46+
47+
// Apply change
48+
schema.getEnum("test_table_id").setValues(new String[] {"val1", "val2", "val3"});
49+
migrateTargetWithSchema(schema);
50+
dummySink = targetToDummy();
51+
schema = dummySink.getTargetSchema();
52+
53+
// Verify
54+
DbEnum dbEnum = schema.getTable("test_table").getField("id").getDbEnum();
55+
assertThat(dbEnum.getName(), is("test_table_id"));
56+
assertThat(dbEnum.getValues().length, is(3));
57+
assertThat(schema.getTable("test_table").getField("not_null_enum").getDbEnum().getValues().length, is(2));
58+
assertThat(schema.getEnums().size(), is(3));
59+
}
60+
}

jooq/src/main/java/ch/ergon/adam/jooq/JooqSource.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import java.sql.DriverManager;
1919
import java.sql.SQLException;
2020
import java.util.*;
21-
import java.util.stream.Collectors;
22-
import java.util.stream.Stream;
2321

2422
import static ch.ergon.adam.core.helper.CollectorsHelper.toLinkedMap;
2523
import static java.util.Arrays.stream;
@@ -176,7 +174,7 @@ protected boolean isGeneratedName(String name) {
176174

177175
private Table mapTableFromJooq(org.jooq.Table<?> jooqTable) {
178176
Table table = new Table(jooqTable.getName());
179-
table.setFields(stream(jooqTable.fields()).map(this::mapFieldFromJooq).collect(toList()));
177+
table.setFields(stream(jooqTable.fields()).map(jooqField -> mapFieldFromJooq(jooqField, jooqTable)).collect(toList()));
180178
List<Index> indexes = jooqTable.getIndexes().stream().map(jooqIndex -> mapIndexFromJooq(table, jooqIndex)).collect(toList());
181179
if (jooqTable.getPrimaryKey() != null) {
182180
indexes.add(mapPrimaryKeyFromJooq(table, jooqTable.getPrimaryKey()));
@@ -188,7 +186,7 @@ private Table mapTableFromJooq(org.jooq.Table<?> jooqTable) {
188186

189187
private View mapViewFromJooq(org.jooq.Table<?> jooqTable) {
190188
View view = new View(jooqTable.getName());
191-
view.setFields(stream(jooqTable.fields()).map(this::mapFieldFromJooq).collect(toList()));
189+
view.setFields(stream(jooqTable.fields()).map(jooqField -> mapFieldFromJooq(jooqField, jooqTable)).collect(toList()));
192190
view.setViewDefinition(getViewDefinition(view.getName()));
193191
return view;
194192
}
@@ -231,10 +229,10 @@ private Index mapKeyFromJooq(Table table, Key<?> jooqKey) {
231229
return index;
232230
}
233231

234-
protected Field mapFieldFromJooq(org.jooq.Field<?> jooqField) {
232+
protected Field mapFieldFromJooq(org.jooq.Field<?> jooqField, org.jooq.Table<?> jooqTable) {
235233
Field field = new Field(jooqField.getName());
236234
field.setArray(jooqField.getDataType().isArray());
237-
field.setDataType(mapDataTypeFromJooq(jooqField));
235+
field.setDataType(mapDataTypeFromJooq(jooqField, jooqTable));
238236
org.jooq.DataType<?> jooqType = jooqField.getDataType(getContext().configuration());
239237
field.setNullable(jooqType.nullable());
240238

@@ -266,7 +264,7 @@ protected boolean isSequence(org.jooq.Field<?> jooqField) {
266264
return jooqField.getDataType().identity();
267265
}
268266

269-
protected DataType mapDataTypeFromJooq(org.jooq.Field<?> jooqField) {
267+
protected DataType mapDataTypeFromJooq(org.jooq.Field<?> jooqField, org.jooq.Table<?> jooqTable) {
270268
org.jooq.DataType<?> sqlDataType = jooqField.getDataType().getSQLDataType();
271269
if (sqlDataType.isInterval()) {
272270
Class<?> type = sqlDataType.getType();

mariadb/src/main/java/ch/ergon/adam/mariadb/MariaDbSink.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import ch.ergon.adam.core.db.schema.*;
44
import ch.ergon.adam.jooq.JooqSink;
55
import org.jooq.CreateTableElementListStep;
6+
import org.jooq.impl.DSL;
67
import org.jooq.impl.DefaultDataType;
78
import org.jooq.impl.SQLDataType;
89

910
import java.sql.Connection;
1011

12+
import static ch.ergon.adam.core.db.schema.DataType.ENUM;
13+
import static ch.ergon.adam.core.helper.CollectorsHelper.createQuotedList;
14+
import static java.lang.String.format;
1115
import static org.jooq.SQLDialect.MARIADB;
1216
import static org.jooq.impl.SQLDataType.VARCHAR;
1317

@@ -40,6 +44,10 @@ public void createTable(Table table) {
4044
}
4145

4246
addRows.execute();
47+
48+
table.getFields()
49+
.stream().filter(Field::isEnum)
50+
.forEach(this::modifyEnumColumnType);
4351
}
4452

4553
@Override
@@ -80,6 +88,11 @@ public void createIndex(Index index) {
8088
super.createIndex(index);
8189
}
8290

91+
@Override
92+
public void dropDefault(Field field) {
93+
context.execute(format("ALTER TABLE `%s` ALTER `%s` DROP DEFAULT", field.getTable().getName(), field.getName()));
94+
}
95+
8396
@Override
8497
public void dropEnum(DbEnum dbEnum) {
8598
// In MariaDB ENUM types are defined as part of the column definition, not as separate types
@@ -90,15 +103,36 @@ public void createEnum(DbEnum dbEnum) {
90103
// In MariaDB ENUM types are defined as part of the column definition, not as separate types
91104
}
92105

106+
@Override
107+
public void changeFieldType(Field oldField, Field newField, ch.ergon.adam.core.db.schema.DataType targetDataType) {
108+
if (targetDataType == ENUM) {
109+
modifyEnumColumnType(newField);
110+
} else {
111+
super.changeFieldType(oldField, newField, targetDataType);
112+
}
113+
}
114+
93115
@Override
94116
protected org.jooq.DataType<?> mapFieldToJooqType(Field field) {
95117
switch (field.getDataType()) {
96118
case TIMESTAMPWITHTIMEZONE:
97119
return SQLDataType.TIMESTAMP(6);
98120
case ENUM:
99-
return new DefaultDataType<>(null, VARCHAR, field.getDbEnum().getName(), field.getDbEnum().getName());
121+
return SQLDataType.CLOB;
100122
default:
101123
return super.mapFieldToJooqType(field);
102124
}
103125
}
126+
127+
private void modifyEnumColumnType(Field enumField) {
128+
String newTypeName = format("enum(%s)", createQuotedList(enumField.getDbEnum().getValues(), "'"));
129+
String statement = format("ALTER TABLE `%s` MODIFY `%s` %s", enumField.getTable().getName(), enumField.getName(), newTypeName);
130+
if (!enumField.isNullable()) {
131+
statement += " NOT NULL";
132+
}
133+
if (enumField.getDefaultValue() != null) {
134+
statement += " DEFAULT " + enumField.getDefaultValue();
135+
}
136+
context.execute(statement);
137+
}
104138
}

0 commit comments

Comments
 (0)