diff --git a/packages/serverpod/lib/src/database/table_relation.dart b/packages/serverpod/lib/src/database/table_relation.dart index a88bbcb456..b1f2dee283 100644 --- a/packages/serverpod/lib/src/database/table_relation.dart +++ b/packages/serverpod/lib/src/database/table_relation.dart @@ -2,8 +2,14 @@ import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:serverpod/src/database/columns.dart'; import 'package:serverpod/src/database/expressions.dart'; +import 'package:serverpod_shared/serverpod_shared.dart'; + +/// Called for all query aliases generated by [TableRelation]. +typedef TruncateFunction = String Function(String identifier); /// Records the relation between two tables. +/// All query aliases generated by [TableRelation] are truncated by the +/// [truncateFunction]. /// This is typically only used internally by the serverpod framework. @internal class TableRelation { @@ -11,12 +17,28 @@ class TableRelation { /// Order is important, as it determines the order of joins in the query. final List _tableRelationEntries; + // Default initialized to truncate to Postgres max name length. + TruncateFunction _truncateFunction = (String identifier) => + truncateIdentifier(identifier, DatabaseConstants.pgsqlMaxNameLimitation); + /// Creates a new [TableRelation]. /// Throws [ArgumentError] if [tableRelationEntries] is empty. - TableRelation(this._tableRelationEntries) { + /// + /// [truncateFunction] is used to truncate query aliases generated by + /// [TableRelation]. + /// If [truncateFunction] is not provided, the query aliases are truncated + /// to Postgres max name length. + TableRelation( + this._tableRelationEntries, { + TruncateFunction? truncateFunction, + }) { if (_tableRelationEntries.isEmpty) { throw ArgumentError('TableRelation must have at least one entry.'); } + + if (truncateFunction != null) { + _truncateFunction = truncateFunction; + } } /// Builds all table relations required to join the tables. @@ -51,7 +73,7 @@ class TableRelation { /// The field name query alias including table. String get fieldQueryAlias { - return _tableRelationEntries.last.field.queryAlias; + return _truncateFunction(_tableRelationEntries.last.field.queryAlias); } /// Field column that is joined on. @@ -61,12 +83,13 @@ class TableRelation { /// The query alias for field name to be joined on including all joins. String get fieldQueryAliasWithJoins { - return '${_fromRelationQueryAlias()}.${_tableRelationEntries.last.field.columnName}'; + return _truncateFunction( + '${_fromRelationQueryAlias()}.${_tableRelationEntries.last.field.columnName}'); } /// The field name to be joined on including all joins. String get fieldNameWithJoins { - return '"${_fromRelationQueryAlias()}"."${_tableRelationEntries.last.field.columnName}"'; + return '"${_truncateFunction(_fromRelationQueryAlias())}"."${_tableRelationEntries.last.field.columnName}"'; } /// The foreign field name joined on. @@ -81,12 +104,14 @@ class TableRelation { /// The foreign field name to be joined on including all joins. String get foreignFieldNameWithJoins { - return '"${_buildRelationQueryAlias()}"."${_tableRelationEntries.last.foreignField.columnName}"'; + return '"${_truncateFunction(_buildRelationQueryAlias())}"."${_tableRelationEntries.last.foreignField.columnName}"'; } /// The field name query alias including table. String get foreignFieldQueryAlias { - return _tableRelationEntries.last.foreignField.queryAlias; + return _truncateFunction( + _tableRelationEntries.last.foreignField.queryAlias, + ); } /// Create a new [TableRelation] with only one entry for the last table @@ -101,7 +126,8 @@ class TableRelation { } /// Retrieves the name of the table with the query prefix applied. - String get relationQueryAlias => _buildRelationQueryAlias(); + String get relationQueryAlias => + _truncateFunction(_buildRelationQueryAlias()); /// Builds the relation query alias including [TableRelationEntries] /// up until [end] index. diff --git a/packages/serverpod/test/database/database_query/long_relation_field_name/join_test.dart b/packages/serverpod/test/database/database_query/long_relation_field_name/join_test.dart new file mode 100644 index 0000000000..4851007b31 --- /dev/null +++ b/packages/serverpod/test/database/database_query/long_relation_field_name/join_test.dart @@ -0,0 +1,47 @@ +import 'package:serverpod/database.dart'; +import 'package:serverpod/src/database/database_query.dart'; +import 'package:serverpod/test_util/table_relation_builder.dart'; +import 'package:test/test.dart'; + +void main() { + var citizenTable = Table(tableName: 'citizen'); + var companyTable = Table(tableName: 'company'); + var relationTable = TableRelationBuilder(companyTable).withRelationsFrom([ + BuilderRelation( + citizenTable, + 'thisFieldIsExactly61CharactersLongAndIsThereforeValidAsNameFo', + ), + ]).build(); + + group('Given SelectQueryBuilder', () { + group('when filtering causes a long join name.', () { + var query = SelectQueryBuilder(table: citizenTable) + .withWhere(relationTable.id.equals(1)) + .build(); + + test('then join query name is truncated.', () { + expect( + query, + contains( + 'LEFT JOIN "company" AS "citizen_thisFieldIsExactly61CharactersLongAndIsThereforeVale9b4"'), + ); + }); + + test('then join query uses the truncated join name.', () { + expect( + query, + contains( + '"citizen"."id" = "citizen_thisFieldIsExactly61CharactersLongAndIsThereforeVale9b4"."id"'), + ); + }); + + test('then uses truncated name in where statements in for join.', () { + expect( + query, + contains( + '"citizen_thisFieldIsExactly61CharactersLongAndIsThereforeVale9b4"."id" = 1'), + ); + }); + }); + }); +}