[jOOQ/jOOQ#11286] Use org.jooq.Meta to discover SQLite primary key, unique key, and foreign key names in code generator

This commit is contained in:
Lukas Eder 2021-01-22 17:34:28 +01:00
parent 35a70eb4bc
commit c06bcfeec4
3 changed files with 89 additions and 74 deletions

View File

@ -47,6 +47,7 @@ import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.POSTGRES;
// ...
import static org.jooq.SQLDialect.SQLITE;
// ...
import static org.jooq.codegen.AbstractGenerator.Language.KOTLIN;
@ -189,7 +190,7 @@ public class DefaultGeneratorStrategy extends AbstractGeneratorStrategy {
// [#9758] And then also for foreign keys
else if (definition instanceof ForeignKeyDefinition && asList(POSTGRES).contains(definition.getDatabase().getDialect().family()))
else if (definition instanceof ForeignKeyDefinition && asList(AURORA_POSTGRES, COCKROACHDB, DB2, SQLITE, POSTGRES).contains(definition.getDatabase().getDialect().family()))
return ((ForeignKeyDefinition) definition).getTable().getOutputName().toUpperCase(targetLocale) + "__" + definition.getOutputName().toUpperCase(targetLocale);
// [#10481] Embeddables have a defining name (class name) and a referencing name (identifier name, member name).

View File

@ -64,6 +64,8 @@ import org.jooq.Check;
import org.jooq.CommonTableExpression;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Meta;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.Record1;
@ -73,7 +75,9 @@ import org.jooq.Select;
import org.jooq.SelectConditionStep;
import org.jooq.SortOrder;
import org.jooq.Table;
import org.jooq.TableField;
import org.jooq.TableOptions.TableType;
import org.jooq.UniqueKey;
import org.jooq.conf.SettingsTools;
import org.jooq.exception.DataDefinitionException;
import org.jooq.impl.DSL;
@ -98,6 +102,7 @@ import org.jooq.meta.UDTDefinition;
import org.jooq.meta.jaxb.SchemaMappingType;
import org.jooq.meta.sqlite.sqlite_master.SQLiteMaster;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
/**
* SQLite implementation of {@link AbstractDatabase}
@ -210,103 +215,81 @@ public class SQLiteDatabase extends AbstractDatabase {
@Override
protected void loadPrimaryKeys(DefaultRelations relations) throws SQLException {
for (String tableName : create()
.select(SQLiteMaster.NAME)
.from(SQLITE_MASTER)
.where(SQLiteMaster.TYPE.in("table"))
.orderBy(SQLiteMaster.NAME)
.fetch(SQLiteMaster.NAME)) {
for (Record record : create().fetch("pragma table_info({0})", inline(tableName))) {
if (record.get("pk", int.class) > 0) {
String columnName = record.get("name", String.class);
// [#11294] Cannot use Meta.getPrimaryKeys() here, yet
for (Table<?> t : snapshot().getTables()) {
UniqueKey<?> pk = t.getPrimaryKey();
// Generate a primary key name
String key = "pk_" + tableName;
TableDefinition table = getTable(getSchemata().get(0), tableName);
if (pk != null) {
TableDefinition table = getTable(getSchemata().get(0), pk.getTable().getName());
if (table != null)
relations.addPrimaryKey(key, table, table.getColumn(columnName));
}
if (table != null)
for (Field<?> f : pk.getFields())
relations.addPrimaryKey(pk.getName(), table, table.getColumn(f.getName()));
}
}
}
@Override
protected void loadUniqueKeys(DefaultRelations relations) throws SQLException {
for (Record record : create().fetch(
"SELECT "
+ " m.tbl_name AS table_name, "
+ " il.name AS key_name, "
+ " ii.name AS column_name "
+ "FROM "
+ " sqlite_master AS m, "
+ " pragma_index_list(m.name) AS il, "
+ " pragma_index_info(il.name) AS ii "
+ "WHERE "
+ " m.type = 'table' AND "
+ " il.origin = 'u' "
+ "ORDER BY table_name, key_name, ii.seqno"
)) {
String tableName = record.get("table_name", String.class);
String keyName = record.get("key_name", String.class);
String columnName = record.get("column_name", String.class);
TableDefinition table = getTable(getSchemata().get(0), tableName);
if (table != null)
relations.addUniqueKey(keyName, table, table.getColumn(columnName));
// [#11294] Cannot use Meta.getUniqueKeys() here, yet
for (Table<?> t : snapshot().getTables()) {
for (UniqueKey<?> uk : t.getUniqueKeys()) {
TableDefinition table = getTable(getSchemata().get(0), uk.getTable().getName());
if (table != null)
for (Field<?> f : uk.getFields())
relations.addUniqueKey(uk.getName(), table, table.getColumn(f.getName()));
}
}
}
@Override
protected void loadForeignKeys(DefaultRelations relations) throws SQLException {
for (TableDefinition table : getTables(getSchemata().get(0))) {
Map<String, Integer> map = new HashMap<>();
for (Record record : create().fetch("pragma foreign_key_list(" + table.getName() + ")")) {
String foreignKeyPrefix =
"fk_" + table.getName() +
"_" + record.get("table");
Integer sequence = map.get(foreignKeyPrefix);
if (sequence == null)
sequence = 0;
// [#11294] Cannot use Meta.getUniqueKeys() here, yet
for (Table<?> t : snapshot().getTables()) {
if (0 == record.get("seq", Integer.class))
sequence = sequence + 1;
fkLoop:
for (ForeignKey<?, ?> fk : t.getReferences()) {
UniqueKey<?> uk = fk.getKey();
map.put(foreignKeyPrefix, sequence);
String foreignKey =
"fk_" + table.getName() +
"_" + record.get("table") +
"_" + sequence;
String foreignKeyTableName = table.getName();
String foreignKeyColumn = record.get("from", String.class);
String uniqueKeyColumn = record.get("to", String.class);
if (uk == null)
continue fkLoop;
// SQLite mixes up cases from the actual declaration and the
// reference definition! It's possible that a table is declared
// in lower case, and the foreign key in upper case. Hence,
// correct the foreign key
TableDefinition foreignKeyTable = getTable(getSchemata().get(0), foreignKeyTableName);
TableDefinition uniqueKeyTable = getTable(getSchemata().get(0), record.get("table", String.class), true);
TableDefinition fkTable = getTable(getSchemata().get(0), fk.getTable().getName(), true);
TableDefinition ukTable = getTable(getSchemata().get(0), uk.getTable().getName(), true);
if (uniqueKeyTable != null) {
String uniqueKey =
"pk_" + uniqueKeyTable.getName();
if (fkTable == null || ukTable == null)
continue fkLoop;
if (foreignKeyTable != null)
relations.addForeignKey(
foreignKey,
foreignKeyTable,
foreignKeyTable.getColumn(foreignKeyColumn, true),
uniqueKey,
uniqueKeyTable,
uniqueKeyTable.getColumn(uniqueKeyColumn, true),
true
);
String ukName = StringUtils.isBlank(uk.getName())
? "pk_" + ukTable.getName()
: uk.getName();
String fkName = StringUtils.isBlank(fk.getName())
? "fk_" + fkTable.getName() +
"_" + ukName
: fk.getName();
TableField<?, ?>[] fkFields = fk.getFieldsArray();
TableField<?, ?>[] ukFields = fk.getKeyFieldsArray();
for (int i = 0; i < fkFields.length; i++) {
relations.addForeignKey(
fkName,
fkTable,
fkTable.getColumn(fkFields[i].getName(), true),
ukName,
ukTable,
ukTable.getColumn(ukFields[i].getName(), true),
true
);
}
}
}
@ -458,4 +441,13 @@ public class SQLiteDatabase extends AbstractDatabase {
List<ArrayDefinition> result = new ArrayList<>();
return result;
}
private Meta snapshot;
Meta snapshot() {
if (snapshot == null)
snapshot = create().meta().snapshot();
return snapshot;
}
}

View File

@ -861,8 +861,20 @@ final class MetaImpl extends AbstractMeta {
for (int i = 0; i < v.size(); i++) {
Record record = v.get(i);
pkFields[i] = (TableField<Record, ?>) pkTable.field(record.get(3, String.class));
fkFields[i] = (TableField<Record, ?>) field(record.get(7, String.class));
String pkFieldName = record.get(3, String.class);
String fkFieldName = record.get(7, String.class);
pkFields[i] = (TableField<Record, ?>) pkTable.field(pkFieldName);
fkFields[i] = (TableField<Record, ?>) field(fkFieldName);
// [#2656] TODO: Find a more generally reusable way to perform case insensitive lookups
if (pkFields[i] == null)
if ((pkFields[i] = lookup(pkTable, pkFieldName)) == null)
return;
if (fkFields[i] == null)
if ((fkFields[i] = lookup(this, fkFieldName)) == null)
return;
}
references.add(new ReferenceImpl<>(
@ -878,6 +890,16 @@ final class MetaImpl extends AbstractMeta {
return references;
}
@SuppressWarnings("unchecked")
private final TableField<Record, ?> lookup(Table<?> table, String fieldName) {
for (Field<?> field : table.fields())
if (field.getName().equalsIgnoreCase(fieldName))
return (TableField<Record, ?>) field;
log.info("Could not look up key field : " + fieldName + " in table : " + table);
return null;
}