From 9083f2ccb38a5bc72f1031e74826a0133b0df0a3 Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 20 Jun 2024 21:14:04 +0200 Subject: [PATCH 1/5] dbSchema include relations --- .../lib/src/client/model/schema.dart | 105 ++++++++++++++++-- .../electricsql/lib/src/satellite/client.dart | 2 +- .../electricsql/lib/src/satellite/mock.dart | 2 +- .../lib/src/satellite/shapes/cache.dart | 2 +- .../test/client/model/shapes_test.dart | 2 +- .../test/satellite/client_test.dart | 70 +++++++----- .../electricsql/test/satellite/common.dart | 40 ++++--- .../test/satellite/registry_test.dart | 2 +- .../test/satellite/serialization.dart | 91 ++++++++------- .../test/support/satellite_helpers.dart | 14 +-- 10 files changed, 221 insertions(+), 109 deletions(-) diff --git a/packages/electricsql/lib/src/client/model/schema.dart b/packages/electricsql/lib/src/client/model/schema.dart index ce9a4bb4..d263b41e 100644 --- a/packages/electricsql/lib/src/client/model/schema.dart +++ b/packages/electricsql/lib/src/client/model/schema.dart @@ -6,6 +6,7 @@ import 'package:electricsql/src/satellite/shapes/types.dart'; import 'package:meta/meta.dart'; typedef FieldName = String; +typedef RelationName = String; typedef Fields = Map; @@ -23,8 +24,18 @@ class ElectricMigrations { }); } +class TableSchema { + final Fields fields; + final List relations; + + TableSchema({ + required this.fields, + required this.relations, + }); +} + abstract class DBSchema { - final Map _fieldsByTable; + final Map _tableSchemas; final List _migrations; final List _pgMigrations; @@ -35,19 +46,56 @@ abstract class DBSchema { /// @param migrations Bundled SQLite migrations /// @param pgMigrations Bundled Postgres migrations DBSchema({ - required Map fieldsByTable, + required Map tableSchemas, required List migrations, required List pgMigrations, - }) : _fieldsByTable = fieldsByTable, + }) : _tableSchemas = tableSchemas, _migrations = migrations, _pgMigrations = pgMigrations; bool hasTable(String table) { - return _fieldsByTable.containsKey(table); + return _tableSchemas.containsKey(table); + } + + TableSchema getTableSchema(String table) { + return _tableSchemas[table]!; } Fields getFields(String table) { - return _fieldsByTable[table]!; + return getTableSchema(table).fields; + } + + List getRelations(String table) { + return getTableSchema(table).relations; + } + + RelationName getRelationName(TableName table, FieldName field) { + return getRelations(table) + .firstWhere((r) => r.relationField == field) + .relationName; + } + + Relation getRelation(String table, RelationName relationName) { + return getRelations(table) + .firstWhere((r) => r.relationName == relationName); + } + + TableName getRelatedTable(TableName table, FieldName field) { + final relationName = getRelationName(table, field); + final relation = getRelation(table, relationName); + return relation.relatedTable; + } + + FieldName getForeignKey(TableName table, FieldName field) { + final relationName = getRelationName(table, field); + final relation = getRelation(table, relationName); + if (relation.isOutgoingRelation()) { + return relation.fromField; + } + // it's an incoming relation + // we need to fetch the `fromField` from the outgoing relation + final oppositeRelation = relation.getOppositeRelation(this); + return oppositeRelation.fromField; } } @@ -62,13 +110,17 @@ class DBSchemaDrift extends DBSchema { // ignore: invalid_use_of_visible_for_overriding_member final driftDb = db.attachedDatabase; - final _fieldsByTable = { + final _tableSchemas = { for (final table in driftDb.allTables) - table.actualTableName: _buildFieldsForTable(table, driftDb), + table.actualTableName: TableSchema( + fields: _buildFieldsForTable(table, driftDb), + // TODO: get relations + relations: [], + ), }; return DBSchemaDrift._( - fieldsByTable: _fieldsByTable, + tableSchemas: _tableSchemas, migrations: migrations, pgMigrations: pgMigrations, db: db, @@ -76,7 +128,7 @@ class DBSchemaDrift extends DBSchema { } DBSchemaDrift._({ - required super.fieldsByTable, + required super.tableSchemas, required super.migrations, required super.pgMigrations, required this.db, @@ -135,13 +187,14 @@ class DBSchemaDrift extends DBSchema { @visibleForTesting class DBSchemaRaw extends DBSchema { - Map get fields => _fieldsByTable; + Map get fields => + _tableSchemas.map((k, v) => MapEntry(k, v.fields)); DBSchemaRaw({ - required Map fields, + required super.tableSchemas, required super.migrations, required super.pgMigrations, - }) : super(fieldsByTable: fields); + }); } @protected @@ -233,3 +286,31 @@ List> _extractWhereConditionsFor( return conditions; } + +class Relation { + final String relationField; + final String fromField; + final String toField; + final String relationName; + final String relatedTable; + + const Relation({ + required this.relationField, + required this.fromField, + required this.toField, + required this.relationName, + required this.relatedTable, + }); + + bool isIncomingRelation() { + return fromField == '' && toField == ''; + } + + bool isOutgoingRelation() { + return !isIncomingRelation(); + } + + Relation getOppositeRelation(DBSchema dbDescription) { + return dbDescription.getRelation(relatedTable, relationName); + } +} diff --git a/packages/electricsql/lib/src/satellite/client.dart b/packages/electricsql/lib/src/satellite/client.dart index 4750679b..eafe3dbb 100644 --- a/packages/electricsql/lib/src/satellite/client.dart +++ b/packages/electricsql/lib/src/satellite/client.dart @@ -5,7 +5,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:electricsql/src/auth/auth.dart'; import 'package:electricsql/src/client/conversions/types.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/migrators/query_builder/query_builder.dart'; import 'package:electricsql/src/proto/satellite.pb.dart'; import 'package:electricsql/src/satellite/config.dart'; diff --git a/packages/electricsql/lib/src/satellite/mock.dart b/packages/electricsql/lib/src/satellite/mock.dart index 8d32a991..452d962c 100644 --- a/packages/electricsql/lib/src/satellite/mock.dart +++ b/packages/electricsql/lib/src/satellite/mock.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:electricsql/src/auth/auth.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/client/model/shapes.dart'; import 'package:electricsql/src/config/config.dart'; import 'package:electricsql/src/electric/adapter.dart'; diff --git a/packages/electricsql/lib/src/satellite/shapes/cache.dart b/packages/electricsql/lib/src/satellite/shapes/cache.dart index 2e584761..200b56af 100644 --- a/packages/electricsql/lib/src/satellite/shapes/cache.dart +++ b/packages/electricsql/lib/src/satellite/shapes/cache.dart @@ -1,4 +1,4 @@ -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/proto/satellite.pb.dart'; import 'package:electricsql/src/satellite/client.dart'; import 'package:electricsql/src/satellite/shapes/types.dart'; diff --git a/packages/electricsql/test/client/model/shapes_test.dart b/packages/electricsql/test/client/model/shapes_test.dart index caa850db..4274a4d9 100644 --- a/packages/electricsql/test/client/model/shapes_test.dart +++ b/packages/electricsql/test/client/model/shapes_test.dart @@ -6,7 +6,7 @@ import 'package:electricsql/electricsql.dart'; import 'package:electricsql/migrators.dart'; import 'package:electricsql/satellite.dart'; import 'package:electricsql/src/client/model/client.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/drivers/drift/sync_input.dart'; import 'package:electricsql/src/notifiers/mock.dart'; import 'package:electricsql/src/proto/satellite.pb.dart'; diff --git a/packages/electricsql/test/satellite/client_test.dart b/packages/electricsql/test/satellite/client_test.dart index fa255425..ff44b6e7 100644 --- a/packages/electricsql/test/satellite/client_test.dart +++ b/packages/electricsql/test/satellite/client_test.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'package:electricsql/src/auth/auth.dart'; import 'package:electricsql/src/client/conversions/types.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/migrators/query_builder/query_builder.dart'; import 'package:electricsql/src/proto/satellite.pb.dart'; import 'package:electricsql/src/satellite/client.dart'; @@ -272,11 +272,14 @@ void main() { await connectAndAuth(); final dbDescription = DBSchemaRaw( - fields: { - 'table': { - 'name1': PgType.text, - 'name2': PgType.text, - }, + tableSchemas: { + 'table': TableSchema( + fields: { + 'name1': PgType.text, + 'name2': PgType.text, + }, + relations: [], + ), }, migrations: [], pgMigrations: [], @@ -708,19 +711,22 @@ void main() { ], ); - final Fields tblFields = { - 'id': PgType.uuid, - 'content': PgType.varchar, - 'text_null': PgType.text, - 'text_null_default': PgType.text, - 'intvalue_null': PgType.int4, - 'intvalue_null_default': PgType.int4, - }; + final TableSchema tbl = TableSchema( + fields: { + 'id': PgType.uuid, + 'content': PgType.varchar, + 'text_null': PgType.text, + 'text_null_default': PgType.text, + 'intvalue_null': PgType.int4, + 'intvalue_null_default': PgType.int4, + }, + relations: [], + ); final dbDescription = DBSchemaRaw( - fields: { - 'table': tblFields, - 'Items': tblFields, + tableSchemas: { + 'table': tbl, + 'Items': tbl, }, migrations: [], pgMigrations: [], @@ -1088,15 +1094,18 @@ void main() { const tablename = 'THE_TABLE_ID'; - final Fields tblFields = { - 'name1': PgType.text, - 'name2': PgType.text, - }; + final TableSchema tbl = TableSchema( + fields: { + 'name1': PgType.text, + 'name2': PgType.text, + }, + relations: [], + ); final dbDescription = DBSchemaRaw( - fields: { - 'table': tblFields, - tablename: tblFields, + tableSchemas: { + 'table': tbl, + tablename: tbl, }, migrations: [], pgMigrations: [], @@ -1196,11 +1205,14 @@ void main() { await connectAndAuth(); final dbDescription = DBSchemaRaw( - fields: { - 'table': { - 'name1': PgType.text, - 'name2': PgType.text, - }, + tableSchemas: { + 'table': TableSchema( + fields: { + 'name1': PgType.text, + 'name2': PgType.text, + }, + relations: [], + ) }, migrations: [], pgMigrations: [], diff --git a/packages/electricsql/test/satellite/common.dart b/packages/electricsql/test/satellite/common.dart index 1ea22d42..9c6c5088 100644 --- a/packages/electricsql/test/satellite/common.dart +++ b/packages/electricsql/test/satellite/common.dart @@ -6,7 +6,7 @@ import 'package:electricsql/migrators.dart'; import 'package:electricsql/satellite.dart'; import 'package:electricsql/src/client/conversions/types.dart'; import 'package:electricsql/src/client/model/client.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/drivers/drift/drift_adapter.dart'; import 'package:electricsql/src/drivers/sqlite3/sqlite3_adapter.dart'; import 'package:electricsql/src/migrators/bundle.dart'; @@ -34,19 +34,28 @@ SatelliteOpts opts(String namespace) => satelliteDefaults(namespace).copyWith( ); DBSchema kTestDbDescription = DBSchemaRaw( - fields: { - 'child': { - 'id': PgType.integer, - 'parent': PgType.integer, - }, - 'parent': { - 'id': PgType.integer, - 'value': PgType.text, - 'other': PgType.integer, - }, - 'another': { - 'id': PgType.integer, - }, + tableSchemas: { + 'child': TableSchema( + fields: { + 'id': PgType.integer, + 'parent': PgType.integer, + }, + relations: [], + ), + 'parent': TableSchema( + fields: { + 'id': PgType.integer, + 'value': PgType.text, + 'other': PgType.integer, + }, + relations: [], + ), + 'another': TableSchema( + fields: { + 'id': PgType.integer, + }, + relations: [], + ), }, migrations: [], pgMigrations: [], @@ -414,7 +423,8 @@ Future mockElectricClient( notifier: notifier, registry: registry, satellite: satellite, - dbDescription: DBSchemaRaw(fields: {}, migrations: [], pgMigrations: []), + dbDescription: + DBSchemaRaw(tableSchemas: {}, migrations: [], pgMigrations: []), dialect: Dialect.sqlite, ); diff --git a/packages/electricsql/test/satellite/registry_test.dart b/packages/electricsql/test/satellite/registry_test.dart index 5cc695b4..ec57da90 100644 --- a/packages/electricsql/test/satellite/registry_test.dart +++ b/packages/electricsql/test/satellite/registry_test.dart @@ -17,7 +17,7 @@ const dbName = 'test.db'; final DatabaseAdapter adapter = MockDatabaseAdapter(); final DBSchema dbDescription = - DBSchemaRaw(fields: {}, migrations: [], pgMigrations: []); + DBSchemaRaw(tableSchemas: {}, migrations: [], pgMigrations: []); final Migrator migrator = MockMigrator(queryBuilder: kSqliteQueryBuilder); final SocketFactory socketFactory = WebSocketIOFactory(); final notifier = MockNotifier(dbName); diff --git a/packages/electricsql/test/satellite/serialization.dart b/packages/electricsql/test/satellite/serialization.dart index 96d23e90..62e1cb3e 100644 --- a/packages/electricsql/test/satellite/serialization.dart +++ b/packages/electricsql/test/satellite/serialization.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:electricsql/src/client/conversions/types.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/client/model/schema.dart' hide Relation; import 'package:electricsql/src/electric/adapter.dart'; import 'package:electricsql/src/migrators/query_builder/query_builder.dart'; import 'package:electricsql/src/proto/satellite.pb.dart'; @@ -50,28 +50,31 @@ void serializationTests({ ); final dbDescription = DBSchemaRaw( - fields: { - 'table': { - 'name1': PgType.text, - 'name2': PgType.text, - 'name3': PgType.text, - 'blob1': PgType.bytea, - 'blob2': PgType.bytea, - 'blob3': PgType.bytea, - 'int1': PgType.integer, - 'int2': PgType.integer, - 'bigint1': PgType.int8, - 'bigint2': PgType.int8, - 'float1': PgType.real, - 'float2': PgType.float4, - 'float3': PgType.float4, - 'bool1': PgType.bool, - 'bool2': PgType.bool, - 'bool3': PgType.bool, - // enum types are transformed to text type by our generator - 'enum1': PgType.text, - 'enum2': PgType.text, - }, + tableSchemas: { + 'table': TableSchema( + fields: { + 'name1': PgType.text, + 'name2': PgType.text, + 'name3': PgType.text, + 'blob1': PgType.bytea, + 'blob2': PgType.bytea, + 'blob3': PgType.bytea, + 'int1': PgType.integer, + 'int2': PgType.integer, + 'bigint1': PgType.int8, + 'bigint2': PgType.int8, + 'float1': PgType.real, + 'float2': PgType.float4, + 'float3': PgType.float4, + 'bool1': PgType.bool, + 'bool2': PgType.bool, + 'bool3': PgType.bool, + // enum types are transformed to text type by our generator + 'enum1': PgType.text, + 'enum2': PgType.text, + }, + relations: [], + ), }, migrations: [], pgMigrations: [], @@ -216,18 +219,21 @@ void serializationTests({ ); final dbDescription = DBSchemaRaw( - fields: { - 'table': { - 'bit0': PgType.text, - 'bit1': PgType.text, - 'bit2': PgType.text, - 'bit3': PgType.text, - 'bit4': PgType.text, - 'bit5': PgType.text, - 'bit6': PgType.text, - 'bit7': PgType.text, - 'bit8': PgType.text, - }, + tableSchemas: { + 'table': TableSchema( + fields: { + 'bit0': PgType.text, + 'bit1': PgType.text, + 'bit2': PgType.text, + 'bit3': PgType.text, + 'bit4': PgType.text, + 'bit5': PgType.text, + 'bit6': PgType.text, + 'bit7': PgType.text, + 'bit8': PgType.text, + }, + relations: [], + ), }, migrations: [], pgMigrations: [], @@ -273,11 +279,14 @@ void serializationTests({ // Db schema holds the correct Postgres types final boolsDbDescription = DBSchemaRaw( - fields: { - 'bools': { - 'id': PgType.integer, - 'b': PgType.bool, - }, + tableSchemas: { + 'bools': TableSchema( + fields: { + 'id': PgType.integer, + 'b': PgType.bool, + }, + relations: [], + ), }, migrations: [], pgMigrations: [], @@ -326,7 +335,7 @@ void serializationTests({ // Empty Db schema final testDbDescription = DBSchemaRaw( - fields: {}, + tableSchemas: {}, migrations: [], pgMigrations: [], ); diff --git a/packages/electricsql/test/support/satellite_helpers.dart b/packages/electricsql/test/support/satellite_helpers.dart index baf8b5ab..d675376d 100644 --- a/packages/electricsql/test/support/satellite_helpers.dart +++ b/packages/electricsql/test/support/satellite_helpers.dart @@ -6,26 +6,26 @@ import 'package:electricsql/src/satellite/oplog.dart'; import 'package:electricsql/src/util/converters/helpers.dart'; import 'package:electricsql/src/util/types.dart'; -typedef TableInfo = Map; +typedef TableInfo = Map; -class TableSchema { +class TableSchemaBasic { final List primaryKey; final List columns; - TableSchema({required this.primaryKey, required this.columns}); + TableSchemaBasic({required this.primaryKey, required this.columns}); } TableInfo initTableInfo(String namespace) { return { - '$namespace.parent': TableSchema( + '$namespace.parent': TableSchemaBasic( primaryKey: ['id'], columns: ['id', 'value', 'other'], ), - '$namespace.child': TableSchema( + '$namespace.child': TableSchemaBasic( primaryKey: ['id'], columns: ['id', 'parent'], ), - '$namespace.Items': TableSchema( + '$namespace.Items': TableSchemaBasic( primaryKey: ['value'], columns: ['value', 'other'], ), @@ -135,7 +135,7 @@ OplogEntry generateRemoteOplogEntry( return result; } -GenerateFromResult generateFrom(TableSchema schema, Row values) { +GenerateFromResult generateFrom(TableSchemaBasic schema, Row values) { final columns = schema.columns.fold({}, (acc, column) { if (values.containsKey(column)) { acc[column] = values[column]; From fd276ac7fd2e792030f8f356f1765a6077ccceee Mon Sep 17 00:00:00 2001 From: David Martos Date: Thu, 20 Jun 2024 21:44:33 +0200 Subject: [PATCH 2/5] wip --- .../lib/src/client/model/relation.dart | 6 +++-- .../lib/src/client/model/schema.dart | 27 ++++++++++++++++--- .../lib/src/drivers/drift/drift.dart | 12 +++++++++ .../lib/src/drivers/drift/sync_input.dart | 27 +++++++++---------- .../test/client/model/shapes_test.dart | 1 + 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/packages/electricsql/lib/src/client/model/relation.dart b/packages/electricsql/lib/src/client/model/relation.dart index fe5d844d..a7398d64 100644 --- a/packages/electricsql/lib/src/client/model/relation.dart +++ b/packages/electricsql/lib/src/client/model/relation.dart @@ -13,6 +13,8 @@ class TableRelation { required this.relationName, }); + + // TODO: Remove these methods bool isIncomingRelation() { return fromField == '' && toField == ''; } @@ -21,11 +23,11 @@ class TableRelation { return !isIncomingRelation(); } - T getDriftTable(DB db) { + TableInfo getDriftTable(DB db) { final TableInfo genTable = db.allTables.firstWhere((t) { return t is T; }); - return genTable as T; + return genTable as TableInfo; } TableRelation getOppositeRelation(GeneratedDatabase db) { diff --git a/packages/electricsql/lib/src/client/model/schema.dart b/packages/electricsql/lib/src/client/model/schema.dart index d263b41e..5e8d2112 100644 --- a/packages/electricsql/lib/src/client/model/schema.dart +++ b/packages/electricsql/lib/src/client/model/schema.dart @@ -2,6 +2,7 @@ import 'package:drift/drift.dart'; import 'package:electricsql/electricsql.dart'; import 'package:electricsql/src/client/conversions/custom_types.dart'; import 'package:electricsql/src/client/conversions/types.dart'; +import 'package:electricsql/src/client/model/relation.dart'; import 'package:electricsql/src/satellite/shapes/types.dart'; import 'package:meta/meta.dart'; @@ -88,6 +89,13 @@ abstract class DBSchema { FieldName getForeignKey(TableName table, FieldName field) { final relationName = getRelationName(table, field); + return getForeignKeyFromRelationName(table, relationName); + } + + FieldName getForeignKeyFromRelationName( + TableName table, + RelationName relationName, + ) { final relation = getRelation(table, relationName); if (relation.isOutgoingRelation()) { return relation.fromField; @@ -100,10 +108,10 @@ abstract class DBSchema { } class DBSchemaDrift extends DBSchema { - final DatabaseConnectionUser db; + final GeneratedDatabase db; factory DBSchemaDrift({ - required DatabaseConnectionUser db, + required GeneratedDatabase db, required List migrations, required List pgMigrations, }) { @@ -114,8 +122,19 @@ class DBSchemaDrift extends DBSchema { for (final table in driftDb.allTables) table.actualTableName: TableSchema( fields: _buildFieldsForTable(table, driftDb), - // TODO: get relations - relations: [], + relations: getTableRelations(table) + ?.$relationsList + .map( + (tr) => Relation( + fromField: tr.fromField, + toField: tr.toField, + relationName: tr.relationName, + relationField: "", + relatedTable: tr.getDriftTable(db).actualTableName, + ), + ) + .toList() ?? + [], ), }; diff --git a/packages/electricsql/lib/src/drivers/drift/drift.dart b/packages/electricsql/lib/src/drivers/drift/drift.dart index 0168a8b9..ead8760f 100644 --- a/packages/electricsql/lib/src/drivers/drift/drift.dart +++ b/packages/electricsql/lib/src/drivers/drift/drift.dart @@ -269,6 +269,7 @@ class DriftElectricClient }) { final shape = computeShapeForDrift( db, + dbDescription, table, include: include, where: where, @@ -404,3 +405,14 @@ Object? _expressionToValue(Expression expression) { throw ArgumentError('Unsupported expression type: $expression'); } } + +TableInfo + findDriftTableInfo( + DB db, + T table, +) { + final TableInfo genTable = db.allTables.firstWhere((t) { + return t.asDslTable == table; + }); + return genTable as TableInfo; +} diff --git a/packages/electricsql/lib/src/drivers/drift/sync_input.dart b/packages/electricsql/lib/src/drivers/drift/sync_input.dart index 60802ffe..7e4c8c23 100644 --- a/packages/electricsql/lib/src/drivers/drift/sync_input.dart +++ b/packages/electricsql/lib/src/drivers/drift/sync_input.dart @@ -1,5 +1,7 @@ import 'package:drift/drift.dart'; import 'package:electricsql/electricsql.dart'; +import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/drivers/drift/drift.dart'; import 'package:electricsql/src/satellite/shapes/types.dart'; typedef SyncIncludeBuilder = List Function( @@ -42,17 +44,23 @@ class SyncInputRelation { Shape computeShapeForDrift( GeneratedDatabase db, + DBSchema dbDescription, T table, { SyncIncludeBuilder? include, SyncWhereBuilder? where, }) { final relationsToInclude = include?.call(table); + final tableInfo = findDriftTableInfo(db, table); + final tableName = tableInfo.actualTableName; + final List? rels = relationsToInclude?.map((syncRel) { - final relation = syncRel.relation; - final relatedDriftTable = relation.getDriftTable(db); + final relationDrift = syncRel.relation; - final String foreignKey = _getForeignKey(db, relation); + final String foreignKey = dbDescription.getForeignKeyFromRelationName( + tableName, + relationDrift.relationName, + ); return Rel( foreignKey: [ @@ -60,7 +68,8 @@ Shape computeShapeForDrift( ], select: computeShapeForDrift( db, - relatedDriftTable, + dbDescription, + relationDrift.getDriftTable(db), include: syncRel._genericInclude, where: syncRel._genericWhere, ), @@ -96,16 +105,6 @@ Shape computeShapeForDrift( ); } -String _getForeignKey(GeneratedDatabase db, TableRelation
relation) { - if (relation.isOutgoingRelation()) { - return relation.fromField; - } - // it's an incoming relation - // we need to fetch the `fromField` from the outgoing relation - final oppositeRelation = relation.getOppositeRelation(db); - return oppositeRelation.fromField; -} - class _PGGenerationContext extends GenerationContext { _PGGenerationContext.fromDb(super.executor, {super.supportsVariables}) : super.fromDb(); diff --git a/packages/electricsql/test/client/model/shapes_test.dart b/packages/electricsql/test/client/model/shapes_test.dart index 4274a4d9..d87a13cf 100644 --- a/packages/electricsql/test/client/model/shapes_test.dart +++ b/packages/electricsql/test/client/model/shapes_test.dart @@ -274,6 +274,7 @@ void main() { test('shape from drift', () async { final shape = computeShapeForDrift( db, + electric.dbDescription, db.post, where: (p) => p.title.equals('foo') & From f6b50db334d8e4906cd5f46827df38357d5d1321 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 21 Jun 2024 10:18:45 +0200 Subject: [PATCH 3/5] wip --- .../lib/src/client/model/relation.dart | 33 ------------------- .../lib/src/client/model/schema.dart | 2 +- .../lib/src/drivers/drift/drift.dart | 7 ++-- .../test/client/model/table_test.dart | 5 +-- 4 files changed, 7 insertions(+), 40 deletions(-) diff --git a/packages/electricsql/lib/src/client/model/relation.dart b/packages/electricsql/lib/src/client/model/relation.dart index a7398d64..87271667 100644 --- a/packages/electricsql/lib/src/client/model/relation.dart +++ b/packages/electricsql/lib/src/client/model/relation.dart @@ -1,4 +1,3 @@ -import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:electricsql/src/client/model/schema.dart'; @@ -13,44 +12,12 @@ class TableRelation { required this.relationName, }); - - // TODO: Remove these methods - bool isIncomingRelation() { - return fromField == '' && toField == ''; - } - - bool isOutgoingRelation() { - return !isIncomingRelation(); - } - TableInfo getDriftTable(DB db) { final TableInfo genTable = db.allTables.firstWhere((t) { return t is T; }); return genTable as TableInfo; } - - TableRelation
getOppositeRelation(GeneratedDatabase db) { - final relatedTable = getDriftTable(db); - final TableRelations? relations = getTableRelations(relatedTable); - - Never throwError() => throw Exception( - 'Unexpected state: Table does not have an opposite relation', - ); - - if (relations == null) { - throwError(); - } - - final oppositeRelation = relations.$relationsList - .firstWhereOrNull((rel) => relationName == rel.relationName); - - if (oppositeRelation == null) { - throwError(); - } - - return oppositeRelation; - } } abstract interface class TableRelations { diff --git a/packages/electricsql/lib/src/client/model/schema.dart b/packages/electricsql/lib/src/client/model/schema.dart index 5e8d2112..25df65b9 100644 --- a/packages/electricsql/lib/src/client/model/schema.dart +++ b/packages/electricsql/lib/src/client/model/schema.dart @@ -125,7 +125,7 @@ class DBSchemaDrift extends DBSchema { relations: getTableRelations(table) ?.$relationsList .map( - (tr) => Relation( + (TableRelation tr) => Relation( fromField: tr.fromField, toField: tr.toField, relationName: tr.relationName, diff --git a/packages/electricsql/lib/src/drivers/drift/drift.dart b/packages/electricsql/lib/src/drivers/drift/drift.dart index ead8760f..300db940 100644 --- a/packages/electricsql/lib/src/drivers/drift/drift.dart +++ b/packages/electricsql/lib/src/drivers/drift/drift.dart @@ -3,7 +3,6 @@ import 'package:electricsql/drivers/drift.dart'; import 'package:electricsql/electricsql.dart'; import 'package:electricsql/migrators.dart'; import 'package:electricsql/src/client/model/client.dart'; -import 'package:electricsql/src/client/model/relation.dart'; import 'package:electricsql/src/client/model/schema.dart'; import 'package:electricsql/src/client/model/transform.dart'; import 'package:electricsql/src/config/config.dart'; @@ -296,7 +295,7 @@ class DriftElectricClient // forbid transforming relation keys to avoid breaking // referential integrity - final tableRelations = getTableRelations(table)?.$relationsList ?? []; + final tableRelations = dbDescription.getRelations(table.actualTableName); final outgoingRelations = tableRelations.where((r) => r.isOutgoingRelation()); @@ -310,8 +309,8 @@ class DriftElectricClient // Incoming relations don't have the `fromField` and `toField` filled in // so we need to fetch the `toField` from the opposite relation // which is effectively a column in this table to which the FK points - final pkCols = - incomingRelations.map((r) => r.getOppositeRelation(db).toField); + final pkCols = incomingRelations + .map((r) => r.getOppositeRelation(dbDescription).toField); // Merge all columns that are part of a FK relation. // Remove duplicate columns in case a column has both an outgoing FK and an incoming FK. diff --git a/packages/electricsql/test/client/model/table_test.dart b/packages/electricsql/test/client/model/table_test.dart index 65fa4cf3..6af95507 100644 --- a/packages/electricsql/test/client/model/table_test.dart +++ b/packages/electricsql/test/client/model/table_test.dart @@ -1,4 +1,5 @@ import 'package:electricsql/src/client/model/client.dart'; +import 'package:electricsql/src/client/model/schema.dart'; import 'package:electricsql/src/client/validation/validation.dart'; import 'package:electricsql/src/drivers/drift/drift.dart'; import 'package:electricsql/src/migrators/query_builder/query_builder.dart'; @@ -39,7 +40,7 @@ void main() async { final electric = DriftElectricClient( ElectricClientImpl.create( dbName: 'testDB', - dbDescription: kTestDbDescription, + dbDescription: DBSchemaDrift(db: db, migrations: [], pgMigrations: []), adapter: adapter, notifier: notifier, satellite: satellite, @@ -105,7 +106,7 @@ void main() async { final electric = DriftElectricClient( ElectricClientImpl.create( dbName: 'testDB', - dbDescription: kTestDbDescription, + dbDescription: DBSchemaDrift(db: db, migrations: [], pgMigrations: []), adapter: adapter, notifier: notifier, satellite: satellite, From 2e0102e890db233d1ea9cc292e03fbb830cdb75b Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 21 Jun 2024 10:20:28 +0200 Subject: [PATCH 4/5] wip --- .../lib/src/client/model/schema.dart | 34 +++++++++---------- .../test/satellite/client_test.dart | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/electricsql/lib/src/client/model/schema.dart b/packages/electricsql/lib/src/client/model/schema.dart index 25df65b9..24156067 100644 --- a/packages/electricsql/lib/src/client/model/schema.dart +++ b/packages/electricsql/lib/src/client/model/schema.dart @@ -70,27 +70,27 @@ abstract class DBSchema { return getTableSchema(table).relations; } - RelationName getRelationName(TableName table, FieldName field) { - return getRelations(table) - .firstWhere((r) => r.relationField == field) - .relationName; - } + // RelationName getRelationName(TableName table, FieldName field) { + // return getRelations(table) + // .firstWhere((r) => r.relationField == field) + // .relationName; + // } Relation getRelation(String table, RelationName relationName) { return getRelations(table) .firstWhere((r) => r.relationName == relationName); } - TableName getRelatedTable(TableName table, FieldName field) { - final relationName = getRelationName(table, field); - final relation = getRelation(table, relationName); - return relation.relatedTable; - } + // TableName getRelatedTable(TableName table, FieldName field) { + // final relationName = getRelationName(table, field); + // final relation = getRelation(table, relationName); + // return relation.relatedTable; + // } - FieldName getForeignKey(TableName table, FieldName field) { - final relationName = getRelationName(table, field); - return getForeignKeyFromRelationName(table, relationName); - } + // FieldName getForeignKey(TableName table, FieldName field) { + // final relationName = getRelationName(table, field); + // return getForeignKeyFromRelationName(table, relationName); + // } FieldName getForeignKeyFromRelationName( TableName table, @@ -129,7 +129,7 @@ class DBSchemaDrift extends DBSchema { fromField: tr.fromField, toField: tr.toField, relationName: tr.relationName, - relationField: "", + // relationField: "", relatedTable: tr.getDriftTable(db).actualTableName, ), ) @@ -307,14 +307,14 @@ List> _extractWhereConditionsFor( } class Relation { - final String relationField; + // final String relationField; final String fromField; final String toField; final String relationName; final String relatedTable; const Relation({ - required this.relationField, + // required this.relationField, required this.fromField, required this.toField, required this.relationName, diff --git a/packages/electricsql/test/satellite/client_test.dart b/packages/electricsql/test/satellite/client_test.dart index ff44b6e7..76c01966 100644 --- a/packages/electricsql/test/satellite/client_test.dart +++ b/packages/electricsql/test/satellite/client_test.dart @@ -1212,7 +1212,7 @@ void main() { 'name2': PgType.text, }, relations: [], - ) + ), }, migrations: [], pgMigrations: [], From 971d0adb99df491e596944078f12ea878375fb88 Mon Sep 17 00:00:00 2001 From: David Martos Date: Fri, 21 Jun 2024 10:57:26 +0200 Subject: [PATCH 5/5] move drift related files --- packages/electricsql/lib/drivers/drift.dart | 6 +- .../lib/src/client/model/index.dart | 4 +- .../lib/src/client/model/schema.dart | 104 ----------------- .../drift}/custom_types.dart | 0 .../model => drivers/drift}/relation.dart | 2 +- .../lib/src/drivers/drift/schema.dart | 107 ++++++++++++++++++ .../lib/src/drivers/drift/sync_input.dart | 2 +- .../test/client/drift/database.dart | 2 +- .../test/client/model/datatype.dart | 2 +- .../test/client/model/table_test.dart | 2 +- 10 files changed, 118 insertions(+), 113 deletions(-) rename packages/electricsql/lib/src/{client/conversions => drivers/drift}/custom_types.dart (100%) rename packages/electricsql/lib/src/{client/model => drivers/drift}/relation.dart (92%) create mode 100644 packages/electricsql/lib/src/drivers/drift/schema.dart diff --git a/packages/electricsql/lib/drivers/drift.dart b/packages/electricsql/lib/drivers/drift.dart index 61ce36e6..1cf7ba7d 100644 --- a/packages/electricsql/lib/drivers/drift.dart +++ b/packages/electricsql/lib/drivers/drift.dart @@ -1,10 +1,12 @@ /// drift Driver library driver_drift; -export '../src/client/conversions/custom_types.dart'; - +export '../src/drivers/drift/custom_types.dart'; export '../src/drivers/drift/drift.dart' show DriftElectricClient, ElectricClient, electrify; export '../src/drivers/drift/drift_adapter.dart' show DriftAdapter; +export '../src/drivers/drift/relation.dart' show TableRelation, TableRelations; +export '../src/drivers/drift/schema.dart' + show DBSchemaDrift, ElectricTableMixin; export '../src/drivers/drift/sync_input.dart' show SyncIncludeBuilder, SyncInputRelation, SyncWhereBuilder; diff --git a/packages/electricsql/lib/src/client/model/index.dart b/packages/electricsql/lib/src/client/model/index.dart index b104c0a0..dd41f306 100644 --- a/packages/electricsql/lib/src/client/model/index.dart +++ b/packages/electricsql/lib/src/client/model/index.dart @@ -1,6 +1,6 @@ export 'package:electricsql/src/client/model/schema.dart' - show ElectricMigrations, ElectricTableMixin; + show ElectricMigrations; + export 'client.dart' show BaseElectricClient, ElectricClientRaw; -export 'relation.dart' show TableRelation, TableRelations; export 'shapes.dart' show IShapeManager, SyncManager, SyncStatus, SyncStatusType; diff --git a/packages/electricsql/lib/src/client/model/schema.dart b/packages/electricsql/lib/src/client/model/schema.dart index 24156067..29a31048 100644 --- a/packages/electricsql/lib/src/client/model/schema.dart +++ b/packages/electricsql/lib/src/client/model/schema.dart @@ -1,8 +1,5 @@ -import 'package:drift/drift.dart'; import 'package:electricsql/electricsql.dart'; -import 'package:electricsql/src/client/conversions/custom_types.dart'; import 'package:electricsql/src/client/conversions/types.dart'; -import 'package:electricsql/src/client/model/relation.dart'; import 'package:electricsql/src/satellite/shapes/types.dart'; import 'package:meta/meta.dart'; @@ -11,10 +8,6 @@ typedef RelationName = String; typedef Fields = Map; -mixin ElectricTableMixin on Table { - TableRelations? get $relations => null; -} - class ElectricMigrations { final List sqliteMigrations; final List pgMigrations; @@ -107,103 +100,6 @@ abstract class DBSchema { } } -class DBSchemaDrift extends DBSchema { - final GeneratedDatabase db; - - factory DBSchemaDrift({ - required GeneratedDatabase db, - required List migrations, - required List pgMigrations, - }) { - // ignore: invalid_use_of_visible_for_overriding_member - final driftDb = db.attachedDatabase; - - final _tableSchemas = { - for (final table in driftDb.allTables) - table.actualTableName: TableSchema( - fields: _buildFieldsForTable(table, driftDb), - relations: getTableRelations(table) - ?.$relationsList - .map( - (TableRelation tr) => Relation( - fromField: tr.fromField, - toField: tr.toField, - relationName: tr.relationName, - // relationField: "", - relatedTable: tr.getDriftTable(db).actualTableName, - ), - ) - .toList() ?? - [], - ), - }; - - return DBSchemaDrift._( - tableSchemas: _tableSchemas, - migrations: migrations, - pgMigrations: pgMigrations, - db: db, - ); - } - - DBSchemaDrift._({ - required super.tableSchemas, - required super.migrations, - required super.pgMigrations, - required this.db, - }); - - static Fields _buildFieldsForTable( - TableInfo table, - GeneratedDatabase genDb, - ) { - final Map fields = {}; - - for (final column in table.$columns) { - final pgType = _getPgTypeFromGeneratedDriftColumn(genDb, column); - if (pgType != null) { - fields[column.name] = pgType; - } - } - return fields; - } - - static PgType? _getPgTypeFromGeneratedDriftColumn( - GeneratedDatabase genDb, - GeneratedColumn c, - ) { - //print("Column: ${c.name} ${c.type} ${c.driftSqlType}"); - final type = c.type; - switch (type) { - case CustomElectricType(): - return type.pgType; - case CustomSqlType(): - return null; - case DriftSqlType.bool: - return PgType.bool; - case DriftSqlType.string: - return PgType.text; - case DriftSqlType.int: - return PgType.integer; - case DriftSqlType.dateTime: - return genDb.typeMapping.storeDateTimesAsText - ? PgType.text - : PgType.integer; - case DriftSqlType.double: - return PgType.real; - case DriftSqlType.bigInt: - return PgType.int8; - case DriftSqlType.blob: - return PgType.bytea; - case DriftSqlType.any: - // Unsupported - return null; - default: - return null; - } - } -} - @visibleForTesting class DBSchemaRaw extends DBSchema { Map get fields => diff --git a/packages/electricsql/lib/src/client/conversions/custom_types.dart b/packages/electricsql/lib/src/drivers/drift/custom_types.dart similarity index 100% rename from packages/electricsql/lib/src/client/conversions/custom_types.dart rename to packages/electricsql/lib/src/drivers/drift/custom_types.dart diff --git a/packages/electricsql/lib/src/client/model/relation.dart b/packages/electricsql/lib/src/drivers/drift/relation.dart similarity index 92% rename from packages/electricsql/lib/src/client/model/relation.dart rename to packages/electricsql/lib/src/drivers/drift/relation.dart index 87271667..6c77b62b 100644 --- a/packages/electricsql/lib/src/client/model/relation.dart +++ b/packages/electricsql/lib/src/drivers/drift/relation.dart @@ -1,5 +1,5 @@ import 'package:drift/drift.dart'; -import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/drivers/drift.dart'; class TableRelation { final String fromField; diff --git a/packages/electricsql/lib/src/drivers/drift/schema.dart b/packages/electricsql/lib/src/drivers/drift/schema.dart new file mode 100644 index 00000000..b78839ea --- /dev/null +++ b/packages/electricsql/lib/src/drivers/drift/schema.dart @@ -0,0 +1,107 @@ +import 'package:drift/drift.dart'; +import 'package:electricsql/drivers/drift.dart'; +import 'package:electricsql/electricsql.dart'; +import 'package:electricsql/src/client/conversions/types.dart'; +import 'package:electricsql/src/client/model/schema.dart'; +import 'package:electricsql/src/drivers/drift/relation.dart'; + +mixin ElectricTableMixin on Table { + TableRelations? get $relations => null; +} + +class DBSchemaDrift extends DBSchema { + final GeneratedDatabase db; + + factory DBSchemaDrift({ + required GeneratedDatabase db, + required List migrations, + required List pgMigrations, + }) { + // ignore: invalid_use_of_visible_for_overriding_member + final driftDb = db.attachedDatabase; + + final _tableSchemas = { + for (final table in driftDb.allTables) + table.actualTableName: TableSchema( + fields: _buildFieldsForTable(table, driftDb), + relations: getTableRelations(table) + ?.$relationsList + .map( + (TableRelation tr) => Relation( + fromField: tr.fromField, + toField: tr.toField, + relationName: tr.relationName, + // relationField: "", + relatedTable: tr.getDriftTable(db).actualTableName, + ), + ) + .toList() ?? + [], + ), + }; + + return DBSchemaDrift._( + tableSchemas: _tableSchemas, + migrations: migrations, + pgMigrations: pgMigrations, + db: db, + ); + } + + DBSchemaDrift._({ + required super.tableSchemas, + required super.migrations, + required super.pgMigrations, + required this.db, + }); + + static Fields _buildFieldsForTable( + TableInfo table, + GeneratedDatabase genDb, + ) { + final Map fields = {}; + + for (final column in table.$columns) { + final pgType = _getPgTypeFromGeneratedDriftColumn(genDb, column); + if (pgType != null) { + fields[column.name] = pgType; + } + } + return fields; + } + + static PgType? _getPgTypeFromGeneratedDriftColumn( + GeneratedDatabase genDb, + GeneratedColumn c, + ) { + //print("Column: ${c.name} ${c.type} ${c.driftSqlType}"); + final type = c.type; + switch (type) { + case CustomElectricType(): + return type.pgType; + case CustomSqlType(): + return null; + case DriftSqlType.bool: + return PgType.bool; + case DriftSqlType.string: + return PgType.text; + case DriftSqlType.int: + return PgType.integer; + case DriftSqlType.dateTime: + return genDb.typeMapping.storeDateTimesAsText + ? PgType.text + : PgType.integer; + case DriftSqlType.double: + return PgType.real; + case DriftSqlType.bigInt: + return PgType.int8; + case DriftSqlType.blob: + return PgType.bytea; + case DriftSqlType.any: + // Unsupported + return null; + default: + return null; + } + } +} diff --git a/packages/electricsql/lib/src/drivers/drift/sync_input.dart b/packages/electricsql/lib/src/drivers/drift/sync_input.dart index 7e4c8c23..a4db9dcf 100644 --- a/packages/electricsql/lib/src/drivers/drift/sync_input.dart +++ b/packages/electricsql/lib/src/drivers/drift/sync_input.dart @@ -1,5 +1,5 @@ import 'package:drift/drift.dart'; -import 'package:electricsql/electricsql.dart'; +import 'package:electricsql/drivers/drift.dart'; import 'package:electricsql/src/client/model/schema.dart'; import 'package:electricsql/src/drivers/drift/drift.dart'; import 'package:electricsql/src/satellite/shapes/types.dart'; diff --git a/packages/electricsql/test/client/drift/database.dart b/packages/electricsql/test/client/drift/database.dart index fe1583a9..b70b0110 100644 --- a/packages/electricsql/test/client/drift/database.dart +++ b/packages/electricsql/test/client/drift/database.dart @@ -1,7 +1,7 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:drift_postgres/drift_postgres.dart'; -import 'package:electricsql/src/client/conversions/custom_types.dart'; +import 'package:electricsql/drivers/drift.dart'; import 'package:postgres/postgres.dart' as pg; import 'generated/electric/drift_schema.dart'; diff --git a/packages/electricsql/test/client/model/datatype.dart b/packages/electricsql/test/client/model/datatype.dart index d57ad2aa..e325f8b0 100644 --- a/packages/electricsql/test/client/model/datatype.dart +++ b/packages/electricsql/test/client/model/datatype.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:electricsql/src/client/conversions/custom_types.dart'; +import 'package:electricsql/drivers/drift.dart'; import 'package:electricsql/src/util/converters/codecs/float4.dart'; import 'package:electricsql/src/util/converters/codecs/json.dart'; import 'package:electricsql/src/util/converters/helpers.dart'; diff --git a/packages/electricsql/test/client/model/table_test.dart b/packages/electricsql/test/client/model/table_test.dart index 6af95507..3af4c95f 100644 --- a/packages/electricsql/test/client/model/table_test.dart +++ b/packages/electricsql/test/client/model/table_test.dart @@ -1,5 +1,5 @@ +import 'package:electricsql/drivers/drift.dart'; import 'package:electricsql/src/client/model/client.dart'; -import 'package:electricsql/src/client/model/schema.dart'; import 'package:electricsql/src/client/validation/validation.dart'; import 'package:electricsql/src/drivers/drift/drift.dart'; import 'package:electricsql/src/migrators/query_builder/query_builder.dart';