Skip to content

Commit 0b666ec

Browse files
fix: Allow only defaultPersist and defaultModel keys on id field (serverpod#3416)
1 parent 1d5189c commit 0b666ec

File tree

36 files changed

+797
-490
lines changed

36 files changed

+797
-490
lines changed

packages/serverpod/lib/src/database/sql_query_builder.dart

+30-6
Original file line numberDiff line numberDiff line change
@@ -323,16 +323,23 @@ class InsertQueryBuilder {
323323
}
324324

325325
/// Builds the insert SQL query.
326-
String build() {
327-
var selectedColumns = _table.columns.where(
328-
(column) => column.columnName != 'id',
329-
);
326+
String? _build(bool onlyWithIdNull) {
327+
var selectedColumns = onlyWithIdNull
328+
? _table.columns.where((column) => column.columnName != 'id')
329+
: _table.columns;
330330

331331
var columnNames =
332332
selectedColumns.map((e) => '"${e.columnName}"').join(', ');
333333

334-
var values =
335-
_rows.map((row) => row.toJson() as Map<String, dynamic>).map((row) {
334+
var filteredValues = onlyWithIdNull
335+
? _rows.where((row) => row.id == null)
336+
: _rows.where((row) => row.id != null);
337+
338+
if (filteredValues.isEmpty) return null;
339+
340+
var values = filteredValues
341+
.map((row) => row.toJson() as Map<String, dynamic>)
342+
.map((row) {
336343
var values = selectedColumns.map((column) {
337344
var unformattedValue = row[column.columnName];
338345
return DatabasePoolManager.encoder.convert(
@@ -347,6 +354,23 @@ class InsertQueryBuilder {
347354
? 'INSERT INTO "${_table.tableName}" DEFAULT VALUES RETURNING *'
348355
: 'INSERT INTO "${_table.tableName}" ($columnNames) VALUES $values RETURNING *';
349356
}
357+
358+
/// Builds the insert SQL query.
359+
String build() {
360+
// Can not be empty because the constructor checks for empty rows.
361+
var insertQueries = [true, false].map(_build).nonNulls;
362+
if (insertQueries.length == 1) return insertQueries.single;
363+
364+
return '''
365+
WITH
366+
insertWithIdNull AS (${insertQueries.first}),
367+
insertWithIdNotNull AS (${insertQueries.last})
368+
369+
SELECT * FROM insertWithIdNull
370+
UNION ALL
371+
SELECT * FROM insertWithIdNotNull
372+
''';
373+
}
350374
}
351375

352376
/// Builds a SQL query for a count statement.

packages/serverpod/test/database/database_query/insert_sql_query_test.dart

+48
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,52 @@ void main() {
9494
expect(query, 'INSERT INTO "only_id" DEFAULT VALUES RETURNING *');
9595
});
9696
});
97+
98+
test(
99+
'Given model with multiple columns and id column having value when building insert query then its id is used in the query.',
100+
() {
101+
var query = InsertQueryBuilder(
102+
table: PersonTable(),
103+
rows: [PersonClass(id: 33, name: 'Alex', age: 33)],
104+
).build();
105+
106+
expect(query,
107+
'INSERT INTO "person" ("id", "name", "age") VALUES (33, \'Alex\', 33) RETURNING *');
108+
});
109+
110+
test(
111+
'Given model with only id column and id column having value when building insert query then its id is used in the query.',
112+
() {
113+
var query = InsertQueryBuilder(
114+
table: Table<int>(tableName: 'only_id'),
115+
rows: [OnlyIdClass(id: 33)],
116+
).build();
117+
118+
expect(query, 'INSERT INTO "only_id" ("id") VALUES (33) RETURNING *');
119+
});
120+
121+
test(
122+
'Given models a list of models with and without id column having value when building insert query then two separate insert queries are generated.',
123+
() {
124+
var query = InsertQueryBuilder(
125+
table: PersonTable(),
126+
rows: [
127+
PersonClass(id: 33, name: 'Alex', age: 33),
128+
PersonClass(name: 'Isak', age: 33),
129+
],
130+
).build();
131+
132+
expect(
133+
query,
134+
'''
135+
WITH
136+
insertWithIdNull AS (INSERT INTO "person" ("name", "age") VALUES ('Isak', 33) RETURNING *),
137+
insertWithIdNotNull AS (INSERT INTO "person" ("id", "name", "age") VALUES (33, 'Alex', 33) RETURNING *)
138+
139+
SELECT * FROM insertWithIdNull
140+
UNION ALL
141+
SELECT * FROM insertWithIdNotNull
142+
''',
143+
);
144+
});
97145
}

tests/serverpod_test_client/lib/src/protocol/changed_id_type/many_to_many/student.dart

+9-10
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,19 @@
1010

1111
// ignore_for_file: no_leading_underscores_for_library_prefixes
1212
import 'package:serverpod_client/serverpod_client.dart' as _i1;
13-
import 'package:uuid/uuid.dart' as _i2;
14-
import '../../changed_id_type/many_to_many/enrollment.dart' as _i3;
13+
import '../../changed_id_type/many_to_many/enrollment.dart' as _i2;
1514

1615
abstract class StudentUuid implements _i1.SerializableModel {
1716
StudentUuid._({
18-
_i1.UuidValue? id,
17+
this.id,
1918
required this.name,
2019
this.enrollments,
21-
}) : id = id ?? _i2.Uuid().v4obj();
20+
});
2221

2322
factory StudentUuid({
2423
_i1.UuidValue? id,
2524
required String name,
26-
List<_i3.EnrollmentInt>? enrollments,
25+
List<_i2.EnrollmentInt>? enrollments,
2726
}) = _StudentUuidImpl;
2827

2928
factory StudentUuid.fromJson(Map<String, dynamic> jsonSerialization) {
@@ -33,7 +32,7 @@ abstract class StudentUuid implements _i1.SerializableModel {
3332
: _i1.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
3433
name: jsonSerialization['name'] as String,
3534
enrollments: (jsonSerialization['enrollments'] as List?)
36-
?.map((e) => _i3.EnrollmentInt.fromJson((e as Map<String, dynamic>)))
35+
?.map((e) => _i2.EnrollmentInt.fromJson((e as Map<String, dynamic>)))
3736
.toList(),
3837
);
3938
}
@@ -45,15 +44,15 @@ abstract class StudentUuid implements _i1.SerializableModel {
4544

4645
String name;
4746

48-
List<_i3.EnrollmentInt>? enrollments;
47+
List<_i2.EnrollmentInt>? enrollments;
4948

5049
/// Returns a shallow copy of this [StudentUuid]
5150
/// with some or all fields replaced by the given arguments.
5251
@_i1.useResult
5352
StudentUuid copyWith({
5453
_i1.UuidValue? id,
5554
String? name,
56-
List<_i3.EnrollmentInt>? enrollments,
55+
List<_i2.EnrollmentInt>? enrollments,
5756
});
5857
@override
5958
Map<String, dynamic> toJson() {
@@ -77,7 +76,7 @@ class _StudentUuidImpl extends StudentUuid {
7776
_StudentUuidImpl({
7877
_i1.UuidValue? id,
7978
required String name,
80-
List<_i3.EnrollmentInt>? enrollments,
79+
List<_i2.EnrollmentInt>? enrollments,
8180
}) : super._(
8281
id: id,
8382
name: name,
@@ -96,7 +95,7 @@ class _StudentUuidImpl extends StudentUuid {
9695
return StudentUuid(
9796
id: id is _i1.UuidValue? ? id : this.id,
9897
name: name ?? this.name,
99-
enrollments: enrollments is List<_i3.EnrollmentInt>?
98+
enrollments: enrollments is List<_i2.EnrollmentInt>?
10099
? enrollments
101100
: this.enrollments?.map((e0) => e0.copyWith()).toList(),
102101
);

tests/serverpod_test_client/lib/src/protocol/changed_id_type/nested_one_to_many/player.dart

+9-10
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,21 @@
1010

1111
// ignore_for_file: no_leading_underscores_for_library_prefixes
1212
import 'package:serverpod_client/serverpod_client.dart' as _i1;
13-
import 'package:uuid/uuid.dart' as _i2;
14-
import '../../changed_id_type/nested_one_to_many/team.dart' as _i3;
13+
import '../../changed_id_type/nested_one_to_many/team.dart' as _i2;
1514

1615
abstract class PlayerUuid implements _i1.SerializableModel {
1716
PlayerUuid._({
18-
_i1.UuidValue? id,
17+
this.id,
1918
required this.name,
2019
this.teamId,
2120
this.team,
22-
}) : id = id ?? _i2.Uuid().v4obj();
21+
});
2322

2423
factory PlayerUuid({
2524
_i1.UuidValue? id,
2625
required String name,
2726
int? teamId,
28-
_i3.TeamInt? team,
27+
_i2.TeamInt? team,
2928
}) = _PlayerUuidImpl;
3029

3130
factory PlayerUuid.fromJson(Map<String, dynamic> jsonSerialization) {
@@ -37,7 +36,7 @@ abstract class PlayerUuid implements _i1.SerializableModel {
3736
teamId: jsonSerialization['teamId'] as int?,
3837
team: jsonSerialization['team'] == null
3938
? null
40-
: _i3.TeamInt.fromJson(
39+
: _i2.TeamInt.fromJson(
4140
(jsonSerialization['team'] as Map<String, dynamic>)),
4241
);
4342
}
@@ -51,7 +50,7 @@ abstract class PlayerUuid implements _i1.SerializableModel {
5150

5251
int? teamId;
5352

54-
_i3.TeamInt? team;
53+
_i2.TeamInt? team;
5554

5655
/// Returns a shallow copy of this [PlayerUuid]
5756
/// with some or all fields replaced by the given arguments.
@@ -60,7 +59,7 @@ abstract class PlayerUuid implements _i1.SerializableModel {
6059
_i1.UuidValue? id,
6160
String? name,
6261
int? teamId,
63-
_i3.TeamInt? team,
62+
_i2.TeamInt? team,
6463
});
6564
@override
6665
Map<String, dynamic> toJson() {
@@ -85,7 +84,7 @@ class _PlayerUuidImpl extends PlayerUuid {
8584
_i1.UuidValue? id,
8685
required String name,
8786
int? teamId,
88-
_i3.TeamInt? team,
87+
_i2.TeamInt? team,
8988
}) : super._(
9089
id: id,
9190
name: name,
@@ -107,7 +106,7 @@ class _PlayerUuidImpl extends PlayerUuid {
107106
id: id is _i1.UuidValue? ? id : this.id,
108107
name: name ?? this.name,
109108
teamId: teamId is int? ? teamId : this.teamId,
110-
team: team is _i3.TeamInt? ? team : this.team?.copyWith(),
109+
team: team is _i2.TeamInt? ? team : this.team?.copyWith(),
111110
);
112111
}
113112
}

tests/serverpod_test_client/lib/src/protocol/changed_id_type/one_to_one/company.dart

+9-10
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,21 @@
1010

1111
// ignore_for_file: no_leading_underscores_for_library_prefixes
1212
import 'package:serverpod_client/serverpod_client.dart' as _i1;
13-
import 'package:uuid/uuid.dart' as _i2;
14-
import '../../changed_id_type/one_to_one/town.dart' as _i3;
13+
import '../../changed_id_type/one_to_one/town.dart' as _i2;
1514

1615
abstract class CompanyUuid implements _i1.SerializableModel {
1716
CompanyUuid._({
18-
_i1.UuidValue? id,
17+
this.id,
1918
required this.name,
2019
required this.townId,
2120
this.town,
22-
}) : id = id ?? _i2.Uuid().v4obj();
21+
});
2322

2423
factory CompanyUuid({
2524
_i1.UuidValue? id,
2625
required String name,
2726
required int townId,
28-
_i3.TownInt? town,
27+
_i2.TownInt? town,
2928
}) = _CompanyUuidImpl;
3029

3130
factory CompanyUuid.fromJson(Map<String, dynamic> jsonSerialization) {
@@ -37,7 +36,7 @@ abstract class CompanyUuid implements _i1.SerializableModel {
3736
townId: jsonSerialization['townId'] as int,
3837
town: jsonSerialization['town'] == null
3938
? null
40-
: _i3.TownInt.fromJson(
39+
: _i2.TownInt.fromJson(
4140
(jsonSerialization['town'] as Map<String, dynamic>)),
4241
);
4342
}
@@ -51,7 +50,7 @@ abstract class CompanyUuid implements _i1.SerializableModel {
5150

5251
int townId;
5352

54-
_i3.TownInt? town;
53+
_i2.TownInt? town;
5554

5655
/// Returns a shallow copy of this [CompanyUuid]
5756
/// with some or all fields replaced by the given arguments.
@@ -60,7 +59,7 @@ abstract class CompanyUuid implements _i1.SerializableModel {
6059
_i1.UuidValue? id,
6160
String? name,
6261
int? townId,
63-
_i3.TownInt? town,
62+
_i2.TownInt? town,
6463
});
6564
@override
6665
Map<String, dynamic> toJson() {
@@ -85,7 +84,7 @@ class _CompanyUuidImpl extends CompanyUuid {
8584
_i1.UuidValue? id,
8685
required String name,
8786
required int townId,
88-
_i3.TownInt? town,
87+
_i2.TownInt? town,
8988
}) : super._(
9089
id: id,
9190
name: name,
@@ -107,7 +106,7 @@ class _CompanyUuidImpl extends CompanyUuid {
107106
id: id is _i1.UuidValue? ? id : this.id,
108107
name: name ?? this.name,
109108
townId: townId ?? this.townId,
110-
town: town is _i3.TownInt? ? town : this.town?.copyWith(),
109+
town: town is _i2.TownInt? ? town : this.town?.copyWith(),
111110
);
112111
}
113112
}

0 commit comments

Comments
 (0)