fix(education): validate adopted unique indexes
This commit is contained in:
@@ -184,13 +184,6 @@ public class EducationTenantController {
|
||||
return EducationTenantRespVO.builder().tenantId(tenant.getId()).displayName(tenant.getName()).build();
|
||||
}
|
||||
|
||||
private boolean isLocalHostnameClaim(String hostname) {
|
||||
if (!educationProperties.getTenantResolution().isLocalDevelopmentEnabled() || StrUtil.isBlank(hostname)) {
|
||||
return false;
|
||||
}
|
||||
return isLocalHost(normalizeHost(hostname));
|
||||
}
|
||||
|
||||
static String normalizeHost(String value) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
-- Fail closed when an adopted Practice schema already contains a same-named
|
||||
-- index whose uniqueness or ordered key columns differ from the application contract.
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
expected RECORD;
|
||||
actual_columns TEXT[];
|
||||
actual_unique BOOLEAN;
|
||||
BEGIN
|
||||
FOR expected IN
|
||||
SELECT * FROM (VALUES
|
||||
('uk_education_practice_session_tenant_client', 'education_practice_session', ARRAY['tenant_id', 'client_session_id']),
|
||||
('uk_education_practice_question_session_sequence', 'education_practice_question', ARRAY['session_id', 'sequence']),
|
||||
('uk_education_practice_report_tenant_session', 'education_practice_report', ARRAY['tenant_id', 'session_id']),
|
||||
('uk_education_practice_report_detail_report_sequence', 'education_practice_report_detail', ARRAY['report_id', 'sequence']),
|
||||
('uk_education_wrong_question_tenant_user_question', 'education_wrong_question', ARRAY['tenant_id', 'user_id', 'question_id']),
|
||||
('uk_education_wrong_idempotency_tenant_user_question_report', 'education_wrong_question_idempotency', ARRAY['tenant_id', 'user_id', 'question_id', 'report_id']),
|
||||
('uk_education_favorite_tenant_user_target', 'education_favorite', ARRAY['tenant_id', 'user_id', 'target_type', 'target_id']),
|
||||
('uk_education_idempotency_tenant_user_operation_key', 'education_idempotency', ARRAY['tenant_id', 'user_id', 'operation', 'idempotency_key'])
|
||||
) AS definitions(index_name, table_name, column_names)
|
||||
LOOP
|
||||
SELECT index_metadata.indisunique,
|
||||
ARRAY_AGG(attribute.attname ORDER BY key_position.ordinality)
|
||||
INTO actual_unique, actual_columns
|
||||
FROM pg_class index_relation
|
||||
JOIN pg_namespace index_namespace ON index_namespace.oid = index_relation.relnamespace
|
||||
JOIN pg_index index_metadata ON index_metadata.indexrelid = index_relation.oid
|
||||
JOIN pg_class table_relation ON table_relation.oid = index_metadata.indrelid
|
||||
JOIN LATERAL UNNEST(index_metadata.indkey::SMALLINT[]) WITH ORDINALITY
|
||||
AS key_position(attribute_number, ordinality) ON true
|
||||
JOIN pg_attribute attribute
|
||||
ON attribute.attrelid = table_relation.oid
|
||||
AND attribute.attnum = key_position.attribute_number
|
||||
WHERE index_namespace.nspname = current_schema()
|
||||
AND index_relation.relname = expected.index_name
|
||||
AND table_relation.relname = expected.table_name
|
||||
GROUP BY index_metadata.indisunique;
|
||||
|
||||
IF actual_unique IS DISTINCT FROM true OR actual_columns IS DISTINCT FROM expected.column_names THEN
|
||||
RAISE EXCEPTION 'Practice unique index % must be UNIQUE on %.%, found unique=% columns=%',
|
||||
expected.index_name, expected.table_name, expected.column_names, actual_unique, actual_columns;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END $$;
|
||||
@@ -83,6 +83,31 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
.isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailClosedWhenAdoptedUniqueIndexHasWrongDefinition() throws SQLException {
|
||||
String schema = createSchema("wrong_index");
|
||||
createCompatibleManualPracticeFixture(schema);
|
||||
execute(schema, """
|
||||
CREATE TABLE education_favorite (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL, user_id BIGINT NOT NULL,
|
||||
target_type VARCHAR(32) NOT NULL, target_id VARCHAR(64) NOT NULL,
|
||||
creator VARCHAR(64) DEFAULT '', create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updater VARCHAR(64) DEFAULT '', update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
CREATE INDEX uk_education_favorite_tenant_user_target
|
||||
ON education_favorite (tenant_id, user_id, target_type, target_id);
|
||||
""");
|
||||
|
||||
assertThatThrownBy(() -> configureFlyway(schema, true).load().migrate())
|
||||
.hasMessageContaining("Practice unique index uk_education_favorite_tenant_user_target")
|
||||
.hasMessageContaining("must be UNIQUE");
|
||||
assertThat(queryLong(schema,
|
||||
"SELECT COUNT(*) FROM flyway_schema_history WHERE version = '4060' AND success = TRUE"))
|
||||
.isZero();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMigratePlatformSchemaFromBaseline4009() throws SQLException {
|
||||
String schema = createSchema("baseline");
|
||||
@@ -94,7 +119,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT COALESCE(version, 'BASELINE') FROM flyway_schema_history ORDER BY installed_rank"))
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040", "4050");
|
||||
.containsExactly("4009", "4010", "4020", "4030", "4040", "4050", "4060");
|
||||
assertThat(queryLong(schema,
|
||||
"SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = current_schema() " +
|
||||
"AND table_name = 'education_idempotency'"))
|
||||
@@ -156,7 +181,7 @@ class EducationFlywayMigrationIntegrationTest {
|
||||
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT version FROM flyway_schema_history WHERE success = TRUE ORDER BY installed_rank"))
|
||||
.containsExactly("4010", "4020", "4030", "4040", "4050");
|
||||
.containsExactly("4010", "4020", "4030", "4040", "4050", "4060");
|
||||
assertThat(queryStrings(schema,
|
||||
"SELECT table_name FROM information_schema.tables " +
|
||||
"WHERE table_schema = current_schema() AND table_name IN (" +
|
||||
|
||||
Reference in New Issue
Block a user