From 8babc02747fd5d27663dd04f3a358f6ab231c0a8 Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Thu, 19 Jun 2025 23:33:13 -0700 Subject: [PATCH 1/2] feat(sqlite): add join names to allow same-table association querying #590 --- packages/brick_sqlite/CHANGELOG.md | 2 ++ .../src/helpers/query_sql_transformer.dart | 26 +++++++++++++------ .../brick_sqlite/lib/src/sqlite_provider.dart | 10 +++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/packages/brick_sqlite/CHANGELOG.md b/packages/brick_sqlite/CHANGELOG.md index 9ab5ab37..678cb0bf 100644 --- a/packages/brick_sqlite/CHANGELOG.md +++ b/packages/brick_sqlite/CHANGELOG.md @@ -1,3 +1,5 @@ +- `#queryToSql` is added to support Brick Query debugging + ## 4.0.2 - Fix query statements with mixed non-association and association fields to permit any order (#573) diff --git a/packages/brick_sqlite/lib/src/helpers/query_sql_transformer.dart b/packages/brick_sqlite/lib/src/helpers/query_sql_transformer.dart index 277201f4..93aea066 100644 --- a/packages/brick_sqlite/lib/src/helpers/query_sql_transformer.dart +++ b/packages/brick_sqlite/lib/src/helpers/query_sql_transformer.dart @@ -181,9 +181,9 @@ class QuerySqlTransformer<_Model extends SqliteModel> { /// Finally add the column to the complete phrase final sqliteColumn = passedAdapter.tableName != adapter.tableName || _queryHasAssociations - ? '`${passedAdapter.tableName}`.${definition.columnName}' + ? '${AssociationFragment.joinName(passedAdapter.tableName, definition.columnName)}.${definition.columnName}' : _queryHasAssociations - ? '`${adapter.tableName}`.${definition.columnName}' + ? '${AssociationFragment.joinName(adapter.tableName, definition.columnName)}.${definition.columnName}' : definition.columnName; final where = WhereColumnFragment(condition, sqliteColumn); _values.addAll(where.values); @@ -218,7 +218,7 @@ class AssociationFragment { if (oneToOneAssociation) { return [ - 'INNER JOIN `$foreignTableName` ON $localTableColumn = `$foreignTableName`.$primaryKeyColumn', + 'INNER JOIN `$foreignTableName` AS ${joinName(foreignTableName, definition.columnName)} ON $localTableColumn = `$foreignTableName`.$primaryKeyColumn', ]; } @@ -226,10 +226,16 @@ class AssociationFragment { InsertForeignKey.joinsTableName(localColumnName, localTableName: localTableName); // ['1','2','3','4'] return [ - 'INNER JOIN `$joinsTableName` ON `$localTableName`.$primaryKeyColumn = `$joinsTableName`.${InsertForeignKey.joinsTableLocalColumnName(localTableName)}', - 'INNER JOIN `$foreignTableName` ON `$foreignTableName`.$primaryKeyColumn = `$joinsTableName`.${InsertForeignKey.joinsTableForeignColumnName(foreignTableName)}', + 'INNER JOIN `$joinsTableName` AS ${joinName(localTableName, definition.columnName)} ON `$localTableName`.$primaryKeyColumn = `$joinsTableName`.${InsertForeignKey.joinsTableLocalColumnName(localTableName)}', + 'INNER JOIN `$foreignTableName` AS ${joinName(foreignTableName, definition.columnName)} ON `$foreignTableName`.$primaryKeyColumn = `$joinsTableName`.${InsertForeignKey.joinsTableForeignColumnName(foreignTableName)}', ]; } + + /// Generate a unique name for the join table association. + /// This naming permits queries to reference associations of the same table. + /// Further discussion: https://github.com/GetDutchie/brick/issues/590 + static String joinName(String tableName, String columnName) => + '`_brick_join_${columnName}_$tableName`'; } /// Column and iterable comparison @@ -375,7 +381,11 @@ class AllOtherClausesFragment { final orderBy = query?.orderBy.map((p) { final fieldDefinition = fieldsToColumns[p.evaluatedField]; final isAssociation = fieldDefinition?.association ?? false; - final field = '`${adapter.tableName}`.${fieldDefinition?.columnName ?? p.evaluatedField}'; + final tablePrefix = AssociationFragment.joinName( + adapter.tableName, + fieldDefinition?.columnName ?? p.evaluatedField, + ); + final field = '$tablePrefix.${fieldDefinition?.columnName ?? p.evaluatedField}'; if (!isAssociation) { if (fieldDefinition?.type == DateTime) { return 'datetime($field) ${p.ascending ? 'ASC' : 'DESC'}'; @@ -389,7 +399,7 @@ class AllOtherClausesFragment { final associationAdapter = modelDictionary.adapterFor[fieldDefinition?.type]; final associationField = - '`${associationAdapter?.tableName}`.${associationAdapter?.fieldsToSqliteColumns[p.associationField]?.columnName ?? p.associationField}'; + '$tablePrefix.${associationAdapter?.fieldsToSqliteColumns[p.associationField]?.columnName ?? p.associationField}'; if (fieldDefinition?.type == DateTime) { return 'datetime($associationField) ${p.ascending ? 'ASC' : 'DESC'}'; } @@ -418,7 +428,7 @@ class AllOtherClausesFragment { if (definition != null) { replacement = replacement.replaceAll( part, - '`${adapter.tableName}`.${definition.columnName}', + '${AssociationFragment.joinName(adapter.tableName, definition.columnName)}.${definition.columnName}', ); } } diff --git a/packages/brick_sqlite/lib/src/sqlite_provider.dart b/packages/brick_sqlite/lib/src/sqlite_provider.dart index f3e95c93..7c57e699 100644 --- a/packages/brick_sqlite/lib/src/sqlite_provider.dart +++ b/packages/brick_sqlite/lib/src/sqlite_provider.dart @@ -209,6 +209,16 @@ class SqliteProvider implements Provider(Query query) { + return QuerySqlTransformer( + modelDictionary: modelDictionary, + query: query, + ).statement; + } + /// Fetch results for model with a custom SQL statement. /// It is recommended to use [get] whenever possible. **Advanced use only**. Future> rawGet( From 5a2844168930319e73775479da4a6e92db84087d Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Thu, 19 Jun 2025 23:33:55 -0700 Subject: [PATCH 2/2] add to changelog --- packages/brick_sqlite/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/brick_sqlite/CHANGELOG.md b/packages/brick_sqlite/CHANGELOG.md index 678cb0bf..65649364 100644 --- a/packages/brick_sqlite/CHANGELOG.md +++ b/packages/brick_sqlite/CHANGELOG.md @@ -1,4 +1,5 @@ - `#queryToSql` is added to support Brick Query debugging +- Fix querying associations of the same table by specifying an `AS` name ## 4.0.2