use latest effective model instead of current config
This commit is contained in:
parent
140b195111
commit
5693657b54
@ -45,6 +45,25 @@ class EntityDefinitionRepository(private val database: Database) {
|
||||
}
|
||||
}
|
||||
|
||||
/** The last model whose physical database migration completed successfully. */
|
||||
fun findEffectiveAll(): List<EntityDefinition> {
|
||||
val fields = database.queryList(
|
||||
"""
|
||||
SELECT fields.id, fields.entity_id, fields.name, fields.identifier,
|
||||
fields.field_type, fields.target_entity_id, fields.relationship_type,
|
||||
targets.name AS target_entity_name
|
||||
FROM effective_entity_fields fields
|
||||
LEFT JOIN effective_entity_definitions targets ON targets.id = fields.target_entity_id
|
||||
ORDER BY fields.id
|
||||
""".trimIndent(),
|
||||
) { FieldRow(getLong("entity_id"), toEntityField()) }.groupBy(FieldRow::entityId)
|
||||
|
||||
return database.queryList("SELECT id, name, identifier FROM effective_entity_definitions ORDER BY id") {
|
||||
val id = getLong("id")
|
||||
EntityDefinition(id, getString("name"), getString("identifier"), fields[id].orEmpty().map(FieldRow::field))
|
||||
}
|
||||
}
|
||||
|
||||
fun create(name: String, identifier: String): EntityDefinition {
|
||||
val sql = "INSERT INTO entity_definitions (name, identifier) VALUES (?, ?) RETURNING id, name, identifier"
|
||||
return checkNotNull(database.queryOne(sql, bind = {
|
||||
|
||||
@ -27,7 +27,8 @@ class EntityMigrationService(
|
||||
fun migrate(): EntityMigrationResult =
|
||||
try {
|
||||
database.transaction { connection ->
|
||||
val plan = plan(connection, loadEntities(connection))
|
||||
val definitions = loadEntities(connection)
|
||||
val plan = plan(connection, definitions)
|
||||
if (!plan.validation.valid) {
|
||||
return@transaction EntityMigrationResult(
|
||||
success = false,
|
||||
@ -40,6 +41,7 @@ class EntityMigrationService(
|
||||
connection.createStatement().use { statement ->
|
||||
plan.statements.forEach { statement.execute(it.sql) }
|
||||
}
|
||||
saveEffectiveModel(connection, definitions)
|
||||
EntityMigrationResult(
|
||||
success = true,
|
||||
message = "Entity migration completed successfully.",
|
||||
@ -55,6 +57,43 @@ class EntityMigrationService(
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveEffectiveModel(connection: Connection, definitions: List<EntityDefinition>) {
|
||||
connection.createStatement().use { statement ->
|
||||
statement.executeUpdate("DELETE FROM effective_entity_fields")
|
||||
statement.executeUpdate("DELETE FROM effective_entity_definitions")
|
||||
}
|
||||
connection.prepareStatement(
|
||||
"INSERT INTO effective_entity_definitions (id, name, identifier) VALUES (?, ?, ?)",
|
||||
).use { statement ->
|
||||
definitions.forEach { entity ->
|
||||
statement.setLong(1, entity.id)
|
||||
statement.setString(2, entity.name)
|
||||
statement.setString(3, entity.identifier)
|
||||
statement.addBatch()
|
||||
}
|
||||
statement.executeBatch()
|
||||
}
|
||||
connection.prepareStatement(
|
||||
"""INSERT INTO effective_entity_fields
|
||||
(id, entity_id, name, identifier, field_type, target_entity_id, relationship_type)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
||||
).use { statement ->
|
||||
definitions.forEach { entity ->
|
||||
entity.fields.forEach { field ->
|
||||
statement.setLong(1, field.id)
|
||||
statement.setLong(2, entity.id)
|
||||
statement.setString(3, field.name)
|
||||
statement.setString(4, field.identifier)
|
||||
statement.setString(5, field.type.name)
|
||||
statement.setObject(6, field.targetEntityId)
|
||||
statement.setString(7, field.relationshipType?.name)
|
||||
statement.addBatch()
|
||||
}
|
||||
}
|
||||
statement.executeBatch()
|
||||
}
|
||||
}
|
||||
|
||||
private fun plan(connection: Connection, definitions: List<EntityDefinition>): MigrationPlan {
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
@ -62,6 +101,7 @@ class EntityMigrationService(
|
||||
val identifierPattern = Regex("[A-Za-z_][A-Za-z0-9_]*")
|
||||
val reservedTables = setOf(
|
||||
"entity_definitions", "entity_fields", "entity_indexes", "entity_index_fields",
|
||||
"effective_entity_definitions", "effective_entity_fields",
|
||||
"schema_migrations", "sessions", "users",
|
||||
)
|
||||
val duplicateEntities = definitions.groupBy { it.identifier.lowercase() }.filterValues { it.size > 1 }
|
||||
|
||||
@ -34,7 +34,7 @@ class EntityRecordRepository(
|
||||
buildSet { while (result.next()) add(result.getString("TABLE_NAME").lowercase()) }
|
||||
}
|
||||
|
||||
entities.findAll()
|
||||
entities.findEffectiveAll()
|
||||
.filter { entity ->
|
||||
entity.identifier.lowercase() in tables && entity.fields.any { it.type == FieldType.USER }
|
||||
}
|
||||
@ -58,7 +58,7 @@ class EntityRecordRepository(
|
||||
userFieldId: Long,
|
||||
submittedValues: Map<String, String>,
|
||||
): EntityRecord? {
|
||||
val child = entities.findAll().firstOrNull { it.name == childEntityName } ?: return null
|
||||
val child = entities.findEffectiveAll().firstOrNull { it.name == childEntityName } ?: return null
|
||||
val userField = child.fields.firstOrNull {
|
||||
it.id == userFieldId && it.type == FieldType.USER
|
||||
} ?: return null
|
||||
@ -75,7 +75,7 @@ class EntityRecordRepository(
|
||||
}
|
||||
|
||||
fun findRecordLinkedToUser(userId: Long, entityName: String, recordId: Long): EntityRecord? {
|
||||
val definitions = entities.findAll()
|
||||
val definitions = entities.findEffectiveAll()
|
||||
val entity = definitions.firstOrNull { it.name == entityName } ?: return null
|
||||
val values = findValues(entity, recordId) ?: return null
|
||||
if (!isAccessibleToUser(entity, values, userId, definitions, mutableSetOf())) return null
|
||||
@ -111,7 +111,7 @@ class EntityRecordRepository(
|
||||
relationshipFieldId: Long,
|
||||
submittedValues: Map<String, String>,
|
||||
): EntityRecord? {
|
||||
val definitions = entities.findAll()
|
||||
val definitions = entities.findEffectiveAll()
|
||||
val parent = definitions.firstOrNull { it.name == parentEntityName } ?: return null
|
||||
if (findRecordLinkedToUser(userId, parentEntityName, parentRecordId) == null) return null
|
||||
val child = definitions.firstOrNull { it.name == childEntityName } ?: return null
|
||||
@ -144,7 +144,7 @@ class EntityRecordRepository(
|
||||
}
|
||||
|
||||
private fun findChildren(parentEntityId: Long, parentRecordId: Long): List<ChildEntityRecords> {
|
||||
val definitions = entities.findAll()
|
||||
val definitions = entities.findEffectiveAll()
|
||||
val parent = definitions.firstOrNull { it.id == parentEntityId } ?: return emptyList()
|
||||
return parent.fields
|
||||
.filter { it.type == FieldType.RELATIONSHIP && it.targetEntityId != null }
|
||||
@ -159,9 +159,9 @@ class EntityRecordRepository(
|
||||
}
|
||||
|
||||
private fun outboundChildIds(fieldId: Long, sourceRecordId: Long): List<Long> =
|
||||
entities.findAll().firstNotNullOfOrNull { parent ->
|
||||
entities.findEffectiveAll().firstNotNullOfOrNull { parent ->
|
||||
val relationship = parent.fields.firstOrNull { it.id == fieldId } ?: return@firstNotNullOfOrNull null
|
||||
val child = entities.findAll().firstOrNull { it.id == relationship.targetEntityId }
|
||||
val child = entities.findEffectiveAll().firstOrNull { it.id == relationship.targetEntityId }
|
||||
?: return@firstNotNullOfOrNull emptyList()
|
||||
database.queryList(
|
||||
"SELECT \"id\" FROM ${quote(child.identifier)} WHERE ${quote(parentColumn(fieldId))} = ? ORDER BY \"id\"",
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
CREATE TABLE IF NOT EXISTS effective_entity_definitions (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
identifier VARCHAR(255) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS effective_entity_fields (
|
||||
id BIGINT PRIMARY KEY,
|
||||
entity_id BIGINT NOT NULL REFERENCES effective_entity_definitions(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
identifier VARCHAR(255) NOT NULL,
|
||||
field_type VARCHAR(32) NOT NULL,
|
||||
target_entity_id BIGINT,
|
||||
relationship_type VARCHAR(32)
|
||||
);
|
||||
|
||||
-- Seed upgrades from only the parts of the configured model that are already
|
||||
-- present in the physical database. Future snapshots are written after a
|
||||
-- successful dynamic-entity migration.
|
||||
INSERT INTO effective_entity_definitions (id, name, identifier)
|
||||
SELECT d.id, d.name, d.identifier
|
||||
FROM entity_definitions d
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM information_schema.tables t
|
||||
WHERE t.table_schema = current_schema() AND lower(t.table_name) = lower(d.identifier)
|
||||
);
|
||||
|
||||
INSERT INTO effective_entity_fields (
|
||||
id, entity_id, name, identifier, field_type, target_entity_id, relationship_type
|
||||
)
|
||||
SELECT f.id, f.entity_id, f.name, f.identifier, f.field_type, f.target_entity_id, f.relationship_type
|
||||
FROM entity_fields f
|
||||
JOIN effective_entity_definitions d ON d.id = f.entity_id
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM information_schema.columns c
|
||||
WHERE c.table_schema = current_schema()
|
||||
AND lower(c.table_name) = lower(d.identifier)
|
||||
AND lower(c.column_name) = lower(f.identifier)
|
||||
)
|
||||
AND (f.target_entity_id IS NULL OR EXISTS (
|
||||
SELECT 1 FROM effective_entity_definitions target WHERE target.id = f.target_entity_id
|
||||
));
|
||||
@ -1,5 +1,7 @@
|
||||
DROP TABLE IF EXISTS entity_index_fields;
|
||||
DROP TABLE IF EXISTS entity_indexes;
|
||||
DROP TABLE IF EXISTS effective_entity_fields;
|
||||
DROP TABLE IF EXISTS effective_entity_definitions;
|
||||
DROP TABLE IF EXISTS entity_fields;
|
||||
DROP TABLE IF EXISTS entity_definitions;
|
||||
DROP TABLE IF EXISTS sessions;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user