feat: Require scopeId cascade FK relation to crdt_scopes for synced tables (#79)
Co-authored-by: Marcelo Soares <marcelo.me.soares@gmail.com>
This commit is contained in:
parent
38fa00ddfc
commit
5642d9b94d
1 changed files with 393 additions and 35 deletions
|
|
@ -32,14 +32,8 @@ class CrdtSchemaRegistry {
|
|||
'CRDT can only synchronize tables with a nullable int scopeId column, '
|
||||
'but ${tablesWithoutScopeId.length} table(s) are missing it or declare '
|
||||
'it with the wrong type: '
|
||||
'${tablesWithoutScopeId.map((t) => '"${t.tableName}"').join(', ')}\n\n'
|
||||
'Add this field to every synced model:\n'
|
||||
'fields:\n'
|
||||
' id: UuidValue?, defaultPersist=random_v7\n'
|
||||
' ### Owner scope of this row. Maintained by the CRDT sync layer.\n'
|
||||
' scopeId: int?\n\n'
|
||||
'If the column is declared with scope=serverOnly, remove the scope; '
|
||||
'the column must exist on every database.',
|
||||
'${tablesWithoutScopeId.map((t) => '"${t.tableName}"').join(', ')}'
|
||||
'$_scopeIdFieldHelp',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -63,8 +57,34 @@ class CrdtSchemaRegistry {
|
|||
'whose target tables are also synchronized.',
|
||||
);
|
||||
}
|
||||
|
||||
final tablesMissingScopeIdRelation = _missingCrdtScopeRelations(
|
||||
syncTables,
|
||||
tableDefinitionsByName,
|
||||
);
|
||||
if (tablesMissingScopeIdRelation.isNotEmpty) {
|
||||
throw StateError(
|
||||
'CRDT synced tables must declare scopeId as a cascade relation to '
|
||||
'crdt_scopes, but ${tablesMissingScopeIdRelation.length} table(s) are '
|
||||
'missing this relation: '
|
||||
'${tablesMissingScopeIdRelation.map((t) => '"$t"').join(', ')}'
|
||||
'$_scopeIdFieldHelp',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Help text appended to schema validation errors that describes the required
|
||||
/// `scopeId` ownership field on every synced model.
|
||||
static const _scopeIdFieldHelp =
|
||||
'\n\n'
|
||||
'Add this field to every synced model:\n'
|
||||
'fields:\n'
|
||||
' id: UuidValue?, defaultPersist=random_v7\n'
|
||||
' ### Owner scope of this row. Maintained by the CRDT sync layer.\n'
|
||||
' scopeId: int?, relation(parent=crdt_scopes, onDelete=Cascade)\n\n'
|
||||
'If the column is declared with scope=serverOnly, remove the scope; '
|
||||
'the column must exist on every database.';
|
||||
|
||||
final DatabaseSession _session;
|
||||
|
||||
/// The list of tables to sync with CRDT.
|
||||
|
|
@ -210,3 +230,27 @@ bool _isForbiddenGlobalUniqueIndex(
|
|||
syncTableNames,
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _missingCrdtScopeRelations(
|
||||
List<Table> syncTables,
|
||||
Map<String, TableDefinition> tableDefinitionsByName,
|
||||
) {
|
||||
return [
|
||||
for (final table in syncTables)
|
||||
if (tableDefinitionsByName.containsKey(table.tableName) &&
|
||||
!_hasCrdtScopeRelation(tableDefinitionsByName[table.tableName]!))
|
||||
table.tableName,
|
||||
];
|
||||
}
|
||||
|
||||
bool _hasCrdtScopeRelation(TableDefinition tableDefinition) {
|
||||
return tableDefinition.foreignKeys.any(
|
||||
(fk) =>
|
||||
fk.columns.length == 1 &&
|
||||
fk.columns.single == 'scopeId' &&
|
||||
fk.referenceTable == 'crdt_scopes' &&
|
||||
fk.referenceColumns.length == 1 &&
|
||||
fk.referenceColumns.single == 'id' &&
|
||||
fk.onDelete == ForeignKeyAction.cascade,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue