Skip to content

Commit

Permalink
Fix wrong SQL statement when creating foreign keys for DB2
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbuehlmann committed Feb 22, 2024
1 parent 991b62d commit 877d314
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import ch.admin.bar.siard2.cmd.sql.IdEncoder;
import ch.admin.bar.siard2.cmd.utils.ListAssembler;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.sql.Connection;
Expand All @@ -25,6 +26,7 @@ public SqlExecutor createExecutor(IdMapper idMapper) {
return new Db2Executor(idMapper, connection, dbMetaData, properties.getQueryTimeout());
}

@Slf4j
public static class Db2Executor extends DefaultSqlExecutor {

public Db2Executor(@NonNull IdMapper idMapper, @NonNull Connection connection, @NonNull DatabaseMetaData databaseMetaData, @NonNull Duration queryTimeout) {
Expand All @@ -39,26 +41,39 @@ public void addForeignKeys(MetaTable tableMetadata) throws SQLException {
.table(tableMetadata.getName())
.build();

/*
[CONSTRAINT constraint_name]
FOREIGN KEY (fk1, fk2,...)
REFERENCES parent_table(c1,2,..)
ON UPDATE [ NO ACTION | RESTRICT]
ON DELETE [ NO ACTION | RESTRICT | CASCADE | SET NULL];
*/

val sqlGenerator = CreateForeignKeySqlGenerator.builder()
.tableId(QualifiedTableId.builder()
.schema(tableMetadata.getParentMetaSchema().getName())
.table(tableMetadata.getName())
.build())
.idEncoder(new IdEncoder())
.referentialActionsMapper(action -> {
// RESTRICT is unknown for MS SQL
// if (ReferentialActionType.fromValue(action).equals(ReferentialActionType.RESTRICT)) {
// return ReferentialActionType.NO_ACTION.value();
// }
return action;
.onUpdateActionMapper(referentialActionType -> {
/*
DB2 does support NO-ACTION and RESTRICT as update action. When you update the row in the
parent key column of the parent table, Db2 rejects the update if there is the corresponding
row exists in the child table for both RESTRICT and NO ACTION option.
*/
if (!referentialActionType.equals(ReferentialActionType.RESTRICT)) {
log.warn("Tried to use {} as on-update action (not supported in DB2). Used {} instead.",
referentialActionType,
ReferentialActionType.NO_ACTION);
return ReferentialActionType.NO_ACTION;
}

return referentialActionType;
})
.onDeleteActionMapper(referentialActionType -> {
/*
DB2 does support NO-ACTION/RESTRICT, CASCADE and SET-NULL as delete action.
*/
if (referentialActionType.equals(ReferentialActionType.SET_DEFAULT)) {
log.warn("Tried to use {} as on-delete action (not supported in DB2). Used {} instead.",
referentialActionType,
ReferentialActionType.NO_ACTION);

return ReferentialActionType.NO_ACTION;
}
return referentialActionType;
})
.idMapper(idMapper)
.build();
Expand All @@ -74,26 +89,5 @@ REFERENCES parent_table(c1,2,..)
}
}
}

private String getReferentialAction(int iReferentialAction) {
ReferentialActionType rat = null;
switch (iReferentialAction) {
case DatabaseMetaData.importedKeyCascade:
rat = ReferentialActionType.CASCADE;
break;
case DatabaseMetaData.importedKeySetNull:
rat = ReferentialActionType.SET_NULL;
break;
case DatabaseMetaData.importedKeySetDefault:
rat = ReferentialActionType.SET_DEFAULT;
break;
case DatabaseMetaData.importedKeyRestrict:
rat = ReferentialActionType.RESTRICT;
break;
case DatabaseMetaData.importedKeyNoAction:
rat = ReferentialActionType.NO_ACTION;
}
return rat.value();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.admin.bar.siard2.cmd.db.connector;

import ch.admin.bar.siard2.api.MetaTable;
import ch.admin.bar.siard2.api.generated.ReferentialActionType;
import ch.admin.bar.siard2.cmd.db.features.DbFeatures;
import ch.admin.bar.siard2.cmd.db.features.DbFeaturesChecker;
import ch.admin.bar.siard2.cmd.mapping.IdMapper;
Expand Down Expand Up @@ -77,19 +78,27 @@ public static class DefaultSqlExecutor implements SqlExecutor {
@Override
public void addForeignKeys(MetaTable tableMetadata) throws SQLException {
if (tableMetadata.getMetaForeignKeys() > 0) {
val tableId = QualifiedTableId.builder()
.schema(tableMetadata.getParentMetaSchema().getName())
.table(tableMetadata.getName())
.build();

val sqlGenerator = CreateForeignKeySqlGenerator.builder()
.tableId(QualifiedTableId.builder()
.schema(tableMetadata.getParentMetaSchema().getName())
.table(tableMetadata.getName())
.build())
.idEncoder(new IdEncoder())
.referentialActionsMapper(action -> action)
.idMapper(idMapper)
.build();

val sql = sqlGenerator.create(ListAssembler.assemble(tableMetadata.getMetaForeignKeys(), tableMetadata::getMetaForeignKey));
val foreignKeysMetaData = ListAssembler.assemble(
tableMetadata.getMetaForeignKeys(),
tableMetadata::getMetaForeignKey);

executeSql(sql);
for (val foreignKeyMetaData : foreignKeysMetaData) {
executeSql(sqlGenerator.create(tableId, foreignKeyMetaData));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ public void addForeignKeys(MetaTable tableMetadata) throws SQLException {
.table(tableMetadata.getName())
.build())
.idEncoder(new IdEncoder())
.referentialActionsMapper(action -> {
.referentialActionsMapper(referentialActionType -> {
// RESTRICT is unknown for MS SQL
// if (ReferentialActionType.fromValue(action).equals(ReferentialActionType.RESTRICT)) {
// return ReferentialActionType.NO_ACTION.value();
// }
return action;
if (referentialActionType.equals(ReferentialActionType.RESTRICT)) {
return ReferentialActionType.NO_ACTION;
}
return referentialActionType;
})
.idMapper(idMapper)
.build();
Expand All @@ -63,32 +63,9 @@ public void addForeignKeys(MetaTable tableMetadata) throws SQLException {
tableMetadata::getMetaForeignKey);

for (val foreignKeyMetaData : foreignKeysMetaData) {
val sql = sqlGenerator.create(tableId, foreignKeyMetaData);

executeSql(sql);
executeSql(sqlGenerator.create(tableId, foreignKeyMetaData));
}
}
}

private String getReferentialAction(int iReferentialAction) {
ReferentialActionType rat = null;
switch (iReferentialAction) {
case DatabaseMetaData.importedKeyCascade:
rat = ReferentialActionType.CASCADE;
break;
case DatabaseMetaData.importedKeySetNull:
rat = ReferentialActionType.SET_NULL;
break;
case DatabaseMetaData.importedKeySetDefault:
rat = ReferentialActionType.SET_DEFAULT;
break;
case DatabaseMetaData.importedKeyRestrict:
rat = ReferentialActionType.RESTRICT;
break;
case DatabaseMetaData.importedKeyNoAction:
rat = ReferentialActionType.NO_ACTION;
}
return rat.value();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.admin.bar.siard2.cmd.sql;

import ch.admin.bar.siard2.api.MetaForeignKey;
import ch.admin.bar.siard2.api.generated.ReferentialActionType;
import ch.admin.bar.siard2.cmd.mapping.IdMapper;
import ch.admin.bar.siard2.cmd.model.QualifiedColumnId;
import ch.admin.bar.siard2.cmd.model.QualifiedTableId;
Expand Down Expand Up @@ -47,8 +48,13 @@ public class CreateForeignKeySqlGenerator {
@NonNull
private final IdMapper idMapper;

@Builder.Default
@NonNull
private final Function<String, String> referentialActionsMapper;
private final Function<ReferentialActionType, ReferentialActionType> onUpdateActionMapper = type -> type;

@Builder.Default
@NonNull
private final Function<ReferentialActionType, ReferentialActionType> onDeleteActionMapper = type -> type;

/**
* The identifier encoder for encoding keys.
Expand All @@ -57,7 +63,7 @@ public class CreateForeignKeySqlGenerator {
private final IdEncoder idEncoder;

/**
* Generates SQL statements to create foreign key constraints based on the provided SIARD metadata.
* Generates SQL statement to create foreign key constraint based on the provided SIARD metadata.
*
* <p>Example:
* <pre>
Expand All @@ -69,52 +75,18 @@ public class CreateForeignKeySqlGenerator {
* </pre>
* </p>
*
* @param foreignKeyMetaData List of SIARD metadata for foreign keys.
* @return The generated SQL statement for creating foreign key constraints.
* @param tableId The qualified identifier of the table for which foreign key constraint is being generated.
* @param foreignKeyMetaData SIARD metadata for foreign key.
* @return The generated SQL statement for creating foreign key constraint.
*/
public String create(final List<MetaForeignKey> foreignKeyMetaData) {
if (foreignKeyMetaData.isEmpty()) {
return "";
}

val mappedTableId = idMapper.map(tableId);

val stringBuilder = new StringBuilder()
.append("ALTER TABLE ")
.append(idEncoder.encodeKeySensitive(mappedTableId));

val addConstraintStatements = foreignKeyMetaData.stream()
.map(this::addConstraintStatement)
.collect(Collectors.joining(", "));

stringBuilder.append(" ")
.append(addConstraintStatements);

log.info("SQL statement for creating foreign-keys: {}", stringBuilder);

return stringBuilder.toString();
}

public String create(final QualifiedTableId tableId, final MetaForeignKey foreignKeyMetaData) {
val mappedTableId = idMapper.map(tableId);

val stringBuilder = new StringBuilder()
.append("ALTER TABLE ")
.append(idEncoder.encodeKeySensitive(mappedTableId))
.append(" ")
.append(addConstraintStatement(foreignKeyMetaData));

log.info("SQL statement for creating foreign-keys: {}", stringBuilder);

return stringBuilder.toString();
}

private String addConstraintStatement(final MetaForeignKey foreignKeyMetaData) {
if (foreignKeyMetaData.getReferences() == 0) {
log.error("SIARD metadata for foreign-key {} has no references and will be ignored.", foreignKeyMetaData.getName());
return "";
}

val mappedTableId = idMapper.map(tableId);

val references = resolveReferences(foreignKeyMetaData);

val referencedTable = references.stream()
Expand All @@ -123,7 +95,9 @@ private String addConstraintStatement(final MetaForeignKey foreignKeyMetaData) {
.orElseThrow(() -> new IllegalStateException("No references found"));

val stringBuilder = new StringBuilder()
.append("ADD CONSTRAINT ")
.append("ALTER TABLE ")
.append(idEncoder.encodeKeySensitive(mappedTableId))
.append(" ADD CONSTRAINT ")
.append(foreignKeyMetaData.getName())
.append(" FOREIGN KEY (")
.append(references.stream()
Expand All @@ -141,11 +115,13 @@ private String addConstraintStatement(final MetaForeignKey foreignKeyMetaData) {
// actions
Optional.ofNullable(foreignKeyMetaData.getDeleteAction())
.ifPresent(action -> stringBuilder.append(" ON DELETE ")
.append(referentialActionsMapper.apply(action)));
.append(onDeleteActionMapper.apply(ReferentialActionType.fromValue(action)).value()));

Optional.ofNullable(foreignKeyMetaData.getUpdateAction())
.ifPresent(action -> stringBuilder.append(" ON UPDATE ")
.append(referentialActionsMapper.apply(action)));
.append(onUpdateActionMapper.apply(ReferentialActionType.fromValue(action)).value()));

log.info("SQL statement for creating foreign-key: {}", stringBuilder);

return stringBuilder.toString();
}
Expand Down Expand Up @@ -189,4 +165,13 @@ private static class ForeignKeyReference {
@NonNull QualifiedColumnId column;
@NonNull QualifiedColumnId referenced;
}

public static class CreateForeignKeySqlGeneratorBuilder {
public CreateForeignKeySqlGeneratorBuilder referentialActionsMapper(final Function<ReferentialActionType, ReferentialActionType> mapper) {
onDeleteActionMapper(mapper);
onUpdateActionMapper(mapper);

return this;
}
}
}

0 comments on commit 877d314

Please sign in to comment.