diff --git a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlMutationSession.java b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlMutationSession.java index ff6005535..f356b1161 100644 --- a/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlMutationSession.java +++ b/xtraplatform-features-sql/src/main/java/de/ii/xtraplatform/features/sql/app/SqlMutationSession.java @@ -509,8 +509,11 @@ public FeatureTransactions.MutationResult assertNoConflictingVersion( return builder.build(); } - // Batch variant of the versioned-Insert pre-flight: one SELECT on the main table for all ids, the - // ids found are returned in `ids`. + // Batch variant of the versioned-Insert pre-flight: one query for all ids, the ids of which a + // version exists are returned in `ids`, as they were given. Each id is checked for existence as + // in the single-id check and the query returns the given literal, not the column value: the text + // form of the column value can differ from the given id, e.g. 7 for '007' in an integer column, a + // UUID in lower case or a char(n) value with padding. @Override public FeatureTransactions.MutationResult findConflictingVersions( String featureType, Collection featureIds) { @@ -533,20 +536,26 @@ public FeatureTransactions.MutationResult findConflictingVersions( + "' has no id column; cannot run the versioned-insert pre-flight.")) .build(); } + String mainTableName = idColumn.get().first().getName(); String idCol = idColumn.get().second().getName(); String sql = - "SELECT DISTINCT " - + idCol - + SQL_FROM - + idColumn.get().first().getName() - + SQL_WHERE - + idCol - + " IN (" - + featureIds.stream() + featureIds.stream() .distinct() .map(SqlMutationSession::sqlString) - .collect(Collectors.joining(", ")) - + ");"; + .map( + id -> + SQL_SELECT + + id + + " WHERE EXISTS (SELECT 1" + + SQL_FROM + + mainTableName + + SQL_WHERE + + idCol + + SQL_EQ + + id + + ")") + .collect(Collectors.joining(" UNION ALL ")) + + ";"; try { builder.ids(sqlSession.runReturning(sql)); } catch (RuntimeException e) { diff --git a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/VersionedMutationSqlSpec.groovy b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/VersionedMutationSqlSpec.groovy index b9c8779f3..e9e6fab05 100644 --- a/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/VersionedMutationSqlSpec.groovy +++ b/xtraplatform-features-sql/src/test/groovy/de/ii/xtraplatform/features/sql/app/VersionedMutationSqlSpec.groovy @@ -76,20 +76,53 @@ class VersionedMutationSqlSpec extends Specification { }) >> [] } - def 'Insert pre-flight for a batch: findConflictingVersions checks all ids with one SELECT'() { + def 'Insert pre-flight for a batch: findConflictingVersions checks all ids with one query'() { when: FeatureTransactions.MutationResult result = session.findConflictingVersions( FEATURE_TYPE, ['DEABCDEF12345678', 'DEABCDEF00000001', 'DEABCDEF12345678']) then: 1 * sqlSession.runReturning({ String sql -> - sql == "SELECT DISTINCT ${COL_ID} FROM ${TABLE} WHERE ${COL_ID} IN " + - "('DEABCDEF12345678', 'DEABCDEF00000001');" + sql == "SELECT 'DEABCDEF12345678' WHERE EXISTS " + + "(SELECT 1 FROM ${TABLE} WHERE ${COL_ID} = 'DEABCDEF12345678')" + + " UNION ALL SELECT 'DEABCDEF00000001' WHERE EXISTS " + + "(SELECT 1 FROM ${TABLE} WHERE ${COL_ID} = 'DEABCDEF00000001');" }) >> ['DEABCDEF12345678'] result.ids == ['DEABCDEF12345678'] result.error.isEmpty() } + def 'Insert pre-flight for a batch: an id is found also in another text form of the database: #given'() { + when: + FeatureTransactions.MutationResult result = session.findConflictingVersions(FEATURE_TYPE, given) + + then: + 1 * sqlSession.runReturning(_) >> { String sql -> integerColumnWithSeven(sql) } + result.ids as Set == found as Set + + where: + given | found + ['007', '8'] | ['007'] + ['7', '007'] | ['7', '007'] + ['8'] | [] + } + + /** + * Answers the query as PostgreSQL does for an integer id column with the row 7: a literal is + * compared as a number, the column value is returned as '7'. + */ + private static List integerColumnWithSeven(String sql) { + def isSeven = { String literal -> literal.isInteger() && literal.toInteger() == 7 } + def inList = sql =~ /^SELECT DISTINCT \w+ FROM \w+ WHERE \w+ IN \((.*)\);$/ + if (inList.matches()) { + def literals = (inList.group(1) =~ /'([^']*)'/).collect { it[1] } + return literals.any(isSeven) ? ['7'] : [] + } + return (sql =~ /SELECT '([^']*)' WHERE EXISTS \(SELECT 1 FROM \w+ WHERE \w+ = '([^']*)'\)/) + .findAll { isSeven(it[2]) } + .collect { it[1] } + } + def 'Insert pre-flight for a batch: no ids, no query'() { when: FeatureTransactions.MutationResult result = session.findConflictingVersions(FEATURE_TYPE, []) diff --git a/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/FeatureTransactions.java b/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/FeatureTransactions.java index 612ea31b2..a07a4d19c 100644 --- a/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/FeatureTransactions.java +++ b/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/FeatureTransactions.java @@ -212,9 +212,11 @@ default MutationResult assertNoConflictingVersion(String featureType, String fea /** * Batch variant of {@link #assertNoConflictingVersion(String, String)}: returns in {@code ids} - * those of the {@code featureIds} of which a version already exists (open or retired), with one - * query instead of one per id. {@code error} is set when the check itself fails. The default - * implementation returns no ids (no check) for providers that have not adopted the API. + * those of the {@code featureIds} of which a version already exists (open or retired), as they + * were given, also when the stored id has another text form (e.g. {@code 7} for {@code 007} in + * an integer column), with one query instead of one per id. {@code error} is set when the check + * itself fails. The default implementation returns no ids (no check) for providers that have + * not adopted the API. */ default MutationResult findConflictingVersions( String featureType, Collection featureIds) {