fix: Drop invalid SetNull and SetDefault test models (#165)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Marcelo Mendonça Soares <marcelomendoncasoares@users.noreply.github.com>
This commit is contained in:
Marcelo Mendonça Soares 2026-10-08 18:21:10 -03:00 • committed by GitHub
commit e12c9dc9f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 21344 additions and 4036 deletions

View file

@ -291,7 +291,6 @@ class DemoDebugEndpoint extends Endpoint {
await add<Organization>();
await add<Company>();
await add<RestrictChild>();
await add<RequiredSetNullChild>();
await add<Unique>();
await add<UniqueUuid>();
await add<UniqueComposite>();

View file

@ -1,754 +0,0 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import 'package:serverpod_offline_sync_test_server/src/generated/protocol.dart'
as _ixxccm81;
import 'person.dart' as _iensfz4m;
abstract class NullableSetDefaultChild
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
NullableSetDefaultChild._({
this.id,
this.spaceId,
required this.name,
this.parentId,
this.parent,
});
factory NullableSetDefaultChild({
_is.UuidValue? id,
int? spaceId,
required String name,
_is.UuidValue? parentId,
_iensfz4m.Person? parent,
}) = _NullableSetDefaultChildImpl;
factory NullableSetDefaultChild.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return NullableSetDefaultChild(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
name: jsonSerialization['name'] as String,
parentId: jsonSerialization['parentId'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['parentId']),
parent: jsonSerialization['parent'] == null
? null
: _ixxccm81.Protocol().deserialize<_iensfz4m.Person>(
jsonSerialization['parent'],
),
);
}
static final t = NullableSetDefaultChildTable();
static const db = NullableSetDefaultChildRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
String name;
_is.UuidValue? parentId;
_iensfz4m.Person? parent;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [NullableSetDefaultChild]
/// with some or all fields replaced by the given arguments.
@_is.useResult
NullableSetDefaultChild copyWith({
_is.UuidValue? id,
int? spaceId,
String? name,
_is.UuidValue? parentId,
_iensfz4m.Person? parent,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'NullableSetDefaultChild',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'name': name,
if (parentId != null) 'parentId': parentId?.toJson(),
if (parent != null) 'parent': parent?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'NullableSetDefaultChild',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'name': name,
if (parentId != null) 'parentId': parentId?.toJson(),
if (parent != null) 'parent': parent?.toJsonForProtocol(),
};
}
static NullableSetDefaultChildInclude include({
_iensfz4m.PersonInclude? parent,
}) {
return NullableSetDefaultChildInclude._(parent: parent);
}
static NullableSetDefaultChildIncludeList includeList({
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
NullableSetDefaultChildInclude? include,
}) {
return NullableSetDefaultChildIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _NullableSetDefaultChildImpl extends NullableSetDefaultChild {
_NullableSetDefaultChildImpl({
_is.UuidValue? id,
int? spaceId,
required String name,
_is.UuidValue? parentId,
_iensfz4m.Person? parent,
}) : super._(
id: id,
spaceId: spaceId,
name: name,
parentId: parentId,
parent: parent,
);
/// Returns a shallow copy of this [NullableSetDefaultChild]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
NullableSetDefaultChild copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
String? name,
Object? parentId = _Undefined,
Object? parent = _Undefined,
}) {
return NullableSetDefaultChild(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
name: name ?? this.name,
parentId: parentId is _is.UuidValue? ? parentId : this.parentId,
parent: parent is _iensfz4m.Person? ? parent : this.parent?.copyWith(),
);
}
}
class NullableSetDefaultChildUpdateTable
extends _is.UpdateTable<NullableSetDefaultChildTable> {
NullableSetDefaultChildUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<String, String> name(String value) => _is.ColumnValue(
table.name,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> parentId(
_is.UuidValue? value,
) => _is.ColumnValue(
table.parentId,
value,
);
}
class NullableSetDefaultChildTable extends _is.Table<_is.UuidValue?> {
NullableSetDefaultChildTable({super.tableRelation})
: super(tableName: 'nullable_set_default_child') {
updateTable = NullableSetDefaultChildUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
name = _is.ColumnString(
'name',
this,
);
parentId = _is.ColumnUuid(
'parentId',
this,
);
}
late final NullableSetDefaultChildUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
late final _is.ColumnString name;
late final _is.ColumnUuid parentId;
_iensfz4m.PersonTable? _parent;
_iensfz4m.PersonTable get parent {
if (_parent != null) return _parent!;
_parent = _is.createRelationTable(
relationFieldName: 'parent',
field: NullableSetDefaultChild.t.parentId,
foreignField: _iensfz4m.Person.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_iensfz4m.PersonTable(tableRelation: foreignTableRelation),
);
return _parent!;
}
@override
List<_is.Column> get columns => [
id,
spaceId,
name,
parentId,
];
@override
_is.Table? getRelationTable(String relationField) {
if (relationField == 'parent') {
return parent;
}
return null;
}
}
class NullableSetDefaultChildInclude extends _is.IncludeObject {
NullableSetDefaultChildInclude._({_iensfz4m.PersonInclude? parent}) {
_parent = parent;
}
_iensfz4m.PersonInclude? _parent;
@override
Map<String, _is.Include?> get includes => {'parent': _parent};
@override
_is.Table<_is.UuidValue?> get table => NullableSetDefaultChild.t;
}
class NullableSetDefaultChildIncludeList extends _is.IncludeList {
NullableSetDefaultChildIncludeList._({
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(NullableSetDefaultChild.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => NullableSetDefaultChild.t;
}
class NullableSetDefaultChildRepository {
const NullableSetDefaultChildRepository._();
final attachRow = const NullableSetDefaultChildAttachRowRepository._();
final detachRow = const NullableSetDefaultChildDetachRowRepository._();
/// Returns a list of [NullableSetDefaultChild]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<NullableSetDefaultChild>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
_is.Transaction? transaction,
NullableSetDefaultChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<NullableSetDefaultChild>(
where: where?.call(NullableSetDefaultChild.t),
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
limit: limit,
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [NullableSetDefaultChild] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<NullableSetDefaultChild?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? where,
int? offset,
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
_is.Transaction? transaction,
NullableSetDefaultChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<NullableSetDefaultChild>(
where: where?.call(NullableSetDefaultChild.t),
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [NullableSetDefaultChild] by its [id] or null if no such row exists.
Future<NullableSetDefaultChild?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
NullableSetDefaultChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<NullableSetDefaultChild>(
id,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [NullableSetDefaultChild]s in the list and returns the inserted rows.
///
/// The returned [NullableSetDefaultChild]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> insert(
_is.DatabaseSession session,
List<NullableSetDefaultChild> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<NullableSetDefaultChild>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [NullableSetDefaultChild] and returns the inserted row.
///
/// The returned [NullableSetDefaultChild] will have its `id` field set.
Future<NullableSetDefaultChild> insertRow(
_is.DatabaseSession session,
NullableSetDefaultChild row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<NullableSetDefaultChild>(
row,
transaction: transaction,
);
}
/// Upserts all [NullableSetDefaultChild]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [NullableSetDefaultChild]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> upsert(
_is.DatabaseSession session,
List<NullableSetDefaultChild> rows, {
required _is.ColumnSelections<NullableSetDefaultChildTable> conflictColumns,
_is.ColumnSelections<NullableSetDefaultChildTable>? updateColumns,
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<NullableSetDefaultChild>(
rows,
conflictColumns: conflictColumns(NullableSetDefaultChild.t),
updateColumns: updateColumns?.call(NullableSetDefaultChild.t),
updateWhere: updateWhere?.call(NullableSetDefaultChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [NullableSetDefaultChild] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [NullableSetDefaultChild] will have its `id` field set.
Future<NullableSetDefaultChild?> upsertRow(
_is.DatabaseSession session,
NullableSetDefaultChild row, {
required _is.ColumnSelections<NullableSetDefaultChildTable> conflictColumns,
_is.ColumnSelections<NullableSetDefaultChildTable>? updateColumns,
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<NullableSetDefaultChild>(
row,
conflictColumns: conflictColumns(NullableSetDefaultChild.t),
updateColumns: updateColumns?.call(NullableSetDefaultChild.t),
updateWhere: updateWhere?.call(NullableSetDefaultChild.t),
transaction: transaction,
);
}
/// Updates all [NullableSetDefaultChild]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> update(
_is.DatabaseSession session,
List<NullableSetDefaultChild> rows, {
_is.ColumnSelections<NullableSetDefaultChildTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<NullableSetDefaultChild>(
rows,
columns: columns?.call(NullableSetDefaultChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [NullableSetDefaultChild]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<NullableSetDefaultChild> updateRow(
_is.DatabaseSession session,
NullableSetDefaultChild row, {
_is.ColumnSelections<NullableSetDefaultChildTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<NullableSetDefaultChild>(
row,
columns: columns?.call(NullableSetDefaultChild.t),
transaction: transaction,
);
}
/// Updates a single [NullableSetDefaultChild] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<NullableSetDefaultChild?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<NullableSetDefaultChildUpdateTable>
columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<NullableSetDefaultChild>(
id,
columnValues: columnValues(NullableSetDefaultChild.t.updateTable),
transaction: transaction,
);
}
/// Updates all [NullableSetDefaultChild]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<NullableSetDefaultChildUpdateTable>
columnValues,
required _is.WhereExpressionBuilder<NullableSetDefaultChildTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<NullableSetDefaultChild>(
columnValues: columnValues(NullableSetDefaultChild.t.updateTable),
where: where(NullableSetDefaultChild.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [NullableSetDefaultChild]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> delete(
_is.DatabaseSession session,
List<NullableSetDefaultChild> rows, {
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<NullableSetDefaultChild>(
rows,
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [NullableSetDefaultChild].
Future<NullableSetDefaultChild> deleteRow(
_is.DatabaseSession session,
NullableSetDefaultChild row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<NullableSetDefaultChild>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<NullableSetDefaultChild>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NullableSetDefaultChildTable> where,
_is.OrderByBuilder<NullableSetDefaultChildTable>? orderBy,
_is.OrderByListBuilder<NullableSetDefaultChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<NullableSetDefaultChild>(
where: where(NullableSetDefaultChild.t),
orderBy: orderBy?.call(NullableSetDefaultChild.t),
orderByList: orderByList?.call(NullableSetDefaultChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<NullableSetDefaultChildTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<NullableSetDefaultChild>(
where: where?.call(NullableSetDefaultChild.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [NullableSetDefaultChild] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<NullableSetDefaultChildTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<NullableSetDefaultChild>(
where: where(NullableSetDefaultChild.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}
class NullableSetDefaultChildAttachRowRepository {
const NullableSetDefaultChildAttachRowRepository._();
/// Creates a relation between the given [NullableSetDefaultChild] and [Person]
/// by setting the [NullableSetDefaultChild]'s foreign key `parentId` to refer to the [Person].
Future<void> parent(
_is.DatabaseSession session,
NullableSetDefaultChild nullableSetDefaultChild,
_iensfz4m.Person parent, {
_is.Transaction? transaction,
}) async {
if (nullableSetDefaultChild.id == null) {
throw ArgumentError.notNull('nullableSetDefaultChild.id');
}
if (parent.id == null) {
throw ArgumentError.notNull('parent.id');
}
var $nullableSetDefaultChild = nullableSetDefaultChild.copyWith(
parentId: parent.id,
);
await session.db.updateRow<NullableSetDefaultChild>(
$nullableSetDefaultChild,
columns: [NullableSetDefaultChild.t.parentId],
transaction: transaction,
);
}
}
class NullableSetDefaultChildDetachRowRepository {
const NullableSetDefaultChildDetachRowRepository._();
/// Detaches the relation between this [NullableSetDefaultChild] and the [Person] set in `parent`
/// by setting the [NullableSetDefaultChild]'s foreign key `parentId` to `null`.
///
/// This removes the association between the two models without deleting
/// the related record.
Future<void> parent(
_is.DatabaseSession session,
NullableSetDefaultChild nullableSetDefaultChild, {
_is.Transaction? transaction,
}) async {
if (nullableSetDefaultChild.id == null) {
throw ArgumentError.notNull('nullableSetDefaultChild.id');
}
var $nullableSetDefaultChild = nullableSetDefaultChild.copyWith(
parentId: null,
);
await session.db.updateRow<NullableSetDefaultChild>(
$nullableSetDefaultChild,
columns: [NullableSetDefaultChild.t.parentId],
transaction: transaction,
);
}
}

View file

@ -31,12 +31,10 @@ import 'fk_chain/set_null_cascade_child.dart' as _ideurard;
import 'fk_chain/set_null_middle.dart' as _icv70ksq;
import 'fk_chain/set_null_restrict_child.dart' as _ix62gjf0;
import 'fk_chain/set_null_set_null_child.dart' as _ihkyqxiw;
import 'nullable_set_default_child.dart' as _i6rvg9xd;
import 'organization.dart' as _irjtvpke;
import 'person.dart' as _iensfz4m;
import 'required_cascade_child.dart' as _ivx9jyda;
import 'required_no_action_child.dart' as _itlfbi2f;
import 'required_set_null_child.dart' as _i1huw131;
import 'restrict_child.dart' as _isrf0aof;
import 'sync_document.dart' as _ix6xayzv;
import 'town.dart' as _iytblq2r;
@ -67,12 +65,10 @@ export 'fk_chain/set_null_cascade_child.dart';
export 'fk_chain/set_null_middle.dart';
export 'fk_chain/set_null_restrict_child.dart';
export 'fk_chain/set_null_set_null_child.dart';
export 'nullable_set_default_child.dart';
export 'organization.dart';
export 'person.dart';
export 'required_cascade_child.dart';
export 'required_no_action_child.dart';
export 'required_set_null_child.dart';
export 'restrict_child.dart';
export 'sync_document.dart';
export 'town.dart';
@ -777,64 +773,6 @@ class Protocol extends _is.DatabaseSerializationManager {
indexes: [],
managed: true,
),
_isp.TableDefinition(
name: 'nullable_set_default_child',
dartName: 'NullableSetDefaultChild',
schema: 'public',
module: 'serverpod_offline_sync_test',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'spaceId',
columnType: _isp.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isp.ColumnDefinition(
name: 'name',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'String',
),
_isp.ColumnDefinition(
name: 'parentId',
columnType: _isp.ColumnType.uuid,
isNullable: true,
dartType: 'UuidValue?',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'nullable_set_default_child_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
_isp.ForeignKeyDefinition(
constraintName: 'nullable_set_default_child_fk_1',
columns: ['parentId'],
referenceTable: 'person',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.setDefault,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
],
indexes: [],
managed: true,
),
_isp.TableDefinition(
name: 'organization',
dartName: 'Organization',
@ -1106,64 +1044,6 @@ class Protocol extends _is.DatabaseSerializationManager {
indexes: [],
managed: true,
),
_isp.TableDefinition(
name: 'required_set_null_child',
dartName: 'RequiredSetNullChild',
schema: 'public',
module: 'serverpod_offline_sync_test',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'spaceId',
columnType: _isp.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isp.ColumnDefinition(
name: 'name',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'String',
),
_isp.ColumnDefinition(
name: 'parentId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'required_set_null_child_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
_isp.ForeignKeyDefinition(
constraintName: 'required_set_null_child_fk_1',
columns: ['parentId'],
referenceTable: 'person',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.setNull,
matchType: null,
deferrable: _isp.DeferrableConstraint.initiallyDeferred,
),
],
indexes: [],
managed: true,
),
_isp.TableDefinition(
name: 'restrict_child',
dartName: 'RestrictChild',
@ -2449,9 +2329,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _ihkyqxiw.FkChainSetNullSetNullChild) {
return _ihkyqxiw.FkChainSetNullSetNullChild.fromJson(data) as T;
}
if (t == _i6rvg9xd.NullableSetDefaultChild) {
return _i6rvg9xd.NullableSetDefaultChild.fromJson(data) as T;
}
if (t == _irjtvpke.Organization) {
return _irjtvpke.Organization.fromJson(data) as T;
}
@ -2464,9 +2341,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _itlfbi2f.RequiredNoActionChild) {
return _itlfbi2f.RequiredNoActionChild.fromJson(data) as T;
}
if (t == _i1huw131.RequiredSetNullChild) {
return _i1huw131.RequiredSetNullChild.fromJson(data) as T;
}
if (t == _isrf0aof.RestrictChild) {
return _isrf0aof.RestrictChild.fromJson(data) as T;
}
@ -2581,12 +2455,6 @@ class Protocol extends _is.DatabaseSerializationManager {
: null)
as T;
}
if (t == _is.getType<_i6rvg9xd.NullableSetDefaultChild?>()) {
return (data != null
? _i6rvg9xd.NullableSetDefaultChild.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_irjtvpke.Organization?>()) {
return (data != null ? _irjtvpke.Organization.fromJson(data) : null) as T;
}
@ -2605,12 +2473,6 @@ class Protocol extends _is.DatabaseSerializationManager {
: null)
as T;
}
if (t == _is.getType<_i1huw131.RequiredSetNullChild?>()) {
return (data != null
? _i1huw131.RequiredSetNullChild.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_isrf0aof.RestrictChild?>()) {
return (data != null ? _isrf0aof.RestrictChild.fromJson(data) : null)
as T;
@ -2754,12 +2616,10 @@ class Protocol extends _is.DatabaseSerializationManager {
_icv70ksq.FkChainSetNullMiddle => 'FkChainSetNullMiddle',
_ix62gjf0.FkChainSetNullRestrictChild => 'FkChainSetNullRestrictChild',
_ihkyqxiw.FkChainSetNullSetNullChild => 'FkChainSetNullSetNullChild',
_i6rvg9xd.NullableSetDefaultChild => 'NullableSetDefaultChild',
_irjtvpke.Organization => 'Organization',
_iensfz4m.Person => 'Person',
_ivx9jyda.RequiredCascadeChild => 'RequiredCascadeChild',
_itlfbi2f.RequiredNoActionChild => 'RequiredNoActionChild',
_i1huw131.RequiredSetNullChild => 'RequiredSetNullChild',
_isrf0aof.RestrictChild => 'RestrictChild',
_ix6xayzv.SyncDocument => 'SyncDocument',
_iytblq2r.Town => 'Town',
@ -2819,8 +2679,6 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'FkChainSetNullRestrictChild';
case _ihkyqxiw.FkChainSetNullSetNullChild():
return 'FkChainSetNullSetNullChild';
case _i6rvg9xd.NullableSetDefaultChild():
return 'NullableSetDefaultChild';
case _irjtvpke.Organization():
return 'Organization';
case _iensfz4m.Person():
@ -2829,8 +2687,6 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'RequiredCascadeChild';
case _itlfbi2f.RequiredNoActionChild():
return 'RequiredNoActionChild';
case _i1huw131.RequiredSetNullChild():
return 'RequiredSetNullChild';
case _isrf0aof.RestrictChild():
return 'RestrictChild';
case _ix6xayzv.SyncDocument():
@ -2935,9 +2791,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'FkChainSetNullSetNullChild') {
return deserialize<_ihkyqxiw.FkChainSetNullSetNullChild>(data['data']);
}
if (dataClassName == 'NullableSetDefaultChild') {
return deserialize<_i6rvg9xd.NullableSetDefaultChild>(data['data']);
}
if (dataClassName == 'Organization') {
return deserialize<_irjtvpke.Organization>(data['data']);
}
@ -2950,9 +2803,6 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'RequiredNoActionChild') {
return deserialize<_itlfbi2f.RequiredNoActionChild>(data['data']);
}
if (dataClassName == 'RequiredSetNullChild') {
return deserialize<_i1huw131.RequiredSetNullChild>(data['data']);
}
if (dataClassName == 'RestrictChild') {
return deserialize<_isrf0aof.RestrictChild>(data['data']);
}
@ -3092,8 +2942,6 @@ class Protocol extends _is.DatabaseSerializationManager {
return _ix62gjf0.FkChainSetNullRestrictChild.t;
case _ihkyqxiw.FkChainSetNullSetNullChild:
return _ihkyqxiw.FkChainSetNullSetNullChild.t;
case _i6rvg9xd.NullableSetDefaultChild:
return _i6rvg9xd.NullableSetDefaultChild.t;
case _irjtvpke.Organization:
return _irjtvpke.Organization.t;
case _iensfz4m.Person:
@ -3102,8 +2950,6 @@ class Protocol extends _is.DatabaseSerializationManager {
return _ivx9jyda.RequiredCascadeChild.t;
case _itlfbi2f.RequiredNoActionChild:
return _itlfbi2f.RequiredNoActionChild.t;
case _i1huw131.RequiredSetNullChild:
return _i1huw131.RequiredSetNullChild.t;
case _isrf0aof.RestrictChild:
return _isrf0aof.RestrictChild.t;
case _iytblq2r.Town:

View file

@ -1,723 +0,0 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: dead_code, unnecessary_null_comparison
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import 'package:serverpod_offline_sync_test_server/src/generated/protocol.dart'
as _ixxccm81;
import 'person.dart' as _iensfz4m;
abstract class RequiredSetNullChild
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
RequiredSetNullChild._({
this.id,
this.spaceId,
required this.name,
required this.parentId,
this.parent,
});
factory RequiredSetNullChild({
_is.UuidValue? id,
int? spaceId,
required String name,
required _is.UuidValue parentId,
_iensfz4m.Person? parent,
}) = _RequiredSetNullChildImpl;
factory RequiredSetNullChild.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return RequiredSetNullChild(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
name: jsonSerialization['name'] as String,
parentId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['parentId'],
),
parent: jsonSerialization['parent'] == null
? null
: _ixxccm81.Protocol().deserialize<_iensfz4m.Person>(
jsonSerialization['parent'],
),
);
}
static final t = RequiredSetNullChildTable();
static const db = RequiredSetNullChildRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
String name;
_is.UuidValue parentId;
_iensfz4m.Person? parent;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [RequiredSetNullChild]
/// with some or all fields replaced by the given arguments.
@_is.useResult
RequiredSetNullChild copyWith({
_is.UuidValue? id,
int? spaceId,
String? name,
_is.UuidValue? parentId,
_iensfz4m.Person? parent,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'RequiredSetNullChild',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'name': name,
'parentId': parentId.toJson(),
if (parent != null) 'parent': parent?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'RequiredSetNullChild',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
'name': name,
'parentId': parentId.toJson(),
if (parent != null) 'parent': parent?.toJsonForProtocol(),
};
}
static RequiredSetNullChildInclude include({
_iensfz4m.PersonInclude? parent,
}) {
return RequiredSetNullChildInclude._(parent: parent);
}
static RequiredSetNullChildIncludeList includeList({
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
RequiredSetNullChildInclude? include,
}) {
return RequiredSetNullChildIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _RequiredSetNullChildImpl extends RequiredSetNullChild {
_RequiredSetNullChildImpl({
_is.UuidValue? id,
int? spaceId,
required String name,
required _is.UuidValue parentId,
_iensfz4m.Person? parent,
}) : super._(
id: id,
spaceId: spaceId,
name: name,
parentId: parentId,
parent: parent,
);
/// Returns a shallow copy of this [RequiredSetNullChild]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
RequiredSetNullChild copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
String? name,
_is.UuidValue? parentId,
Object? parent = _Undefined,
}) {
return RequiredSetNullChild(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
name: name ?? this.name,
parentId: parentId ?? this.parentId,
parent: parent is _iensfz4m.Person? ? parent : this.parent?.copyWith(),
);
}
}
class RequiredSetNullChildUpdateTable
extends _is.UpdateTable<RequiredSetNullChildTable> {
RequiredSetNullChildUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<String, String> name(String value) => _is.ColumnValue(
table.name,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> parentId(_is.UuidValue value) =>
_is.ColumnValue(
table.parentId,
value,
);
}
class RequiredSetNullChildTable extends _is.Table<_is.UuidValue?> {
RequiredSetNullChildTable({super.tableRelation})
: super(tableName: 'required_set_null_child') {
updateTable = RequiredSetNullChildUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
name = _is.ColumnString(
'name',
this,
);
parentId = _is.ColumnUuid(
'parentId',
this,
);
}
late final RequiredSetNullChildUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
late final _is.ColumnString name;
late final _is.ColumnUuid parentId;
_iensfz4m.PersonTable? _parent;
_iensfz4m.PersonTable get parent {
if (_parent != null) return _parent!;
_parent = _is.createRelationTable(
relationFieldName: 'parent',
field: RequiredSetNullChild.t.parentId,
foreignField: _iensfz4m.Person.t.id,
tableRelation: tableRelation,
createTable: (foreignTableRelation) =>
_iensfz4m.PersonTable(tableRelation: foreignTableRelation),
);
return _parent!;
}
@override
List<_is.Column> get columns => [
id,
spaceId,
name,
parentId,
];
@override
_is.Table? getRelationTable(String relationField) {
if (relationField == 'parent') {
return parent;
}
return null;
}
}
class RequiredSetNullChildInclude extends _is.IncludeObject {
RequiredSetNullChildInclude._({_iensfz4m.PersonInclude? parent}) {
_parent = parent;
}
_iensfz4m.PersonInclude? _parent;
@override
Map<String, _is.Include?> get includes => {'parent': _parent};
@override
_is.Table<_is.UuidValue?> get table => RequiredSetNullChild.t;
}
class RequiredSetNullChildIncludeList extends _is.IncludeList {
RequiredSetNullChildIncludeList._({
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(RequiredSetNullChild.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => RequiredSetNullChild.t;
}
class RequiredSetNullChildRepository {
const RequiredSetNullChildRepository._();
final attachRow = const RequiredSetNullChildAttachRowRepository._();
/// Returns a list of [RequiredSetNullChild]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<RequiredSetNullChild>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
_is.Transaction? transaction,
RequiredSetNullChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<RequiredSetNullChild>(
where: where?.call(RequiredSetNullChild.t),
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
limit: limit,
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [RequiredSetNullChild] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<RequiredSetNullChild?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? where,
int? offset,
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
_is.Transaction? transaction,
RequiredSetNullChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<RequiredSetNullChild>(
where: where?.call(RequiredSetNullChild.t),
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
offset: offset,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [RequiredSetNullChild] by its [id] or null if no such row exists.
Future<RequiredSetNullChild?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
RequiredSetNullChildInclude? include,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<RequiredSetNullChild>(
id,
transaction: transaction,
include: include,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [RequiredSetNullChild]s in the list and returns the inserted rows.
///
/// The returned [RequiredSetNullChild]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> insert(
_is.DatabaseSession session,
List<RequiredSetNullChild> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<RequiredSetNullChild>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [RequiredSetNullChild] and returns the inserted row.
///
/// The returned [RequiredSetNullChild] will have its `id` field set.
Future<RequiredSetNullChild> insertRow(
_is.DatabaseSession session,
RequiredSetNullChild row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<RequiredSetNullChild>(
row,
transaction: transaction,
);
}
/// Upserts all [RequiredSetNullChild]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [RequiredSetNullChild]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> upsert(
_is.DatabaseSession session,
List<RequiredSetNullChild> rows, {
required _is.ColumnSelections<RequiredSetNullChildTable> conflictColumns,
_is.ColumnSelections<RequiredSetNullChildTable>? updateColumns,
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<RequiredSetNullChild>(
rows,
conflictColumns: conflictColumns(RequiredSetNullChild.t),
updateColumns: updateColumns?.call(RequiredSetNullChild.t),
updateWhere: updateWhere?.call(RequiredSetNullChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [RequiredSetNullChild] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [RequiredSetNullChild] will have its `id` field set.
Future<RequiredSetNullChild?> upsertRow(
_is.DatabaseSession session,
RequiredSetNullChild row, {
required _is.ColumnSelections<RequiredSetNullChildTable> conflictColumns,
_is.ColumnSelections<RequiredSetNullChildTable>? updateColumns,
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<RequiredSetNullChild>(
row,
conflictColumns: conflictColumns(RequiredSetNullChild.t),
updateColumns: updateColumns?.call(RequiredSetNullChild.t),
updateWhere: updateWhere?.call(RequiredSetNullChild.t),
transaction: transaction,
);
}
/// Updates all [RequiredSetNullChild]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> update(
_is.DatabaseSession session,
List<RequiredSetNullChild> rows, {
_is.ColumnSelections<RequiredSetNullChildTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<RequiredSetNullChild>(
rows,
columns: columns?.call(RequiredSetNullChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [RequiredSetNullChild]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<RequiredSetNullChild> updateRow(
_is.DatabaseSession session,
RequiredSetNullChild row, {
_is.ColumnSelections<RequiredSetNullChildTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<RequiredSetNullChild>(
row,
columns: columns?.call(RequiredSetNullChild.t),
transaction: transaction,
);
}
/// Updates a single [RequiredSetNullChild] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<RequiredSetNullChild?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<RequiredSetNullChildUpdateTable>
columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<RequiredSetNullChild>(
id,
columnValues: columnValues(RequiredSetNullChild.t.updateTable),
transaction: transaction,
);
}
/// Updates all [RequiredSetNullChild]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<RequiredSetNullChildUpdateTable>
columnValues,
required _is.WhereExpressionBuilder<RequiredSetNullChildTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<RequiredSetNullChild>(
columnValues: columnValues(RequiredSetNullChild.t.updateTable),
where: where(RequiredSetNullChild.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [RequiredSetNullChild]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> delete(
_is.DatabaseSession session,
List<RequiredSetNullChild> rows, {
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<RequiredSetNullChild>(
rows,
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [RequiredSetNullChild].
Future<RequiredSetNullChild> deleteRow(
_is.DatabaseSession session,
RequiredSetNullChild row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<RequiredSetNullChild>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<RequiredSetNullChild>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<RequiredSetNullChildTable> where,
_is.OrderByBuilder<RequiredSetNullChildTable>? orderBy,
_is.OrderByListBuilder<RequiredSetNullChildTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<RequiredSetNullChild>(
where: where(RequiredSetNullChild.t),
orderBy: orderBy?.call(RequiredSetNullChild.t),
orderByList: orderByList?.call(RequiredSetNullChild.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<RequiredSetNullChildTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<RequiredSetNullChild>(
where: where?.call(RequiredSetNullChild.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [RequiredSetNullChild] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<RequiredSetNullChildTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<RequiredSetNullChild>(
where: where(RequiredSetNullChild.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}
class RequiredSetNullChildAttachRowRepository {
const RequiredSetNullChildAttachRowRepository._();
/// Creates a relation between the given [RequiredSetNullChild] and [Person]
/// by setting the [RequiredSetNullChild]'s foreign key `parentId` to refer to the [Person].
Future<void> parent(
_is.DatabaseSession session,
RequiredSetNullChild requiredSetNullChild,
_iensfz4m.Person parent, {
_is.Transaction? transaction,
}) async {
if (requiredSetNullChild.id == null) {
throw ArgumentError.notNull('requiredSetNullChild.id');
}
if (parent.id == null) {
throw ArgumentError.notNull('parent.id');
}
var $requiredSetNullChild = requiredSetNullChild.copyWith(
parentId: parent.id,
);
await session.db.updateRow<RequiredSetNullChild>(
$requiredSetNullChild,
columns: [RequiredSetNullChild.t.parentId],
transaction: transaction,
);
}
}

View file

@ -25,12 +25,10 @@ import 'fk_chain/set_null_cascade_child.dart' as _ideurard;
import 'fk_chain/set_null_middle.dart' as _icv70ksq;
import 'fk_chain/set_null_restrict_child.dart' as _ix62gjf0;
import 'fk_chain/set_null_set_null_child.dart' as _ihkyqxiw;
import 'nullable_set_default_child.dart' as _i6rvg9xd;
import 'organization.dart' as _irjtvpke;
import 'person.dart' as _iensfz4m;
import 'required_cascade_child.dart' as _ivx9jyda;
import 'required_no_action_child.dart' as _itlfbi2f;
import 'required_set_null_child.dart' as _i1huw131;
import 'restrict_child.dart' as _isrf0aof;
import 'town.dart' as _iytblq2r;
import 'types.dart' as _iwxwszsz;
@ -63,12 +61,10 @@ final List<_is.Table> syncTables = [
_icv70ksq.FkChainSetNullMiddle.t,
_ix62gjf0.FkChainSetNullRestrictChild.t,
_ihkyqxiw.FkChainSetNullSetNullChild.t,
_i6rvg9xd.NullableSetDefaultChild.t,
_irjtvpke.Organization.t,
_iensfz4m.Person.t,
_ivx9jyda.RequiredCascadeChild.t,
_itlfbi2f.RequiredNoActionChild.t,
_i1huw131.RequiredSetNullChild.t,
_isrf0aof.RestrictChild.t,
_i2ap9bqs.SharedChild.t,
_i2ap9bqs.SharedParent.t,

View file

@ -1,6 +0,0 @@
class: NullableSetDefaultChild
table: nullable_set_default_child
database: sync
fields:
name: String
parent: Person?, relation(fk, optional, onDelete=SetDefault, deferred)

View file

@ -1,6 +0,0 @@
class: RequiredSetNullChild
table: required_set_null_child
database: sync
fields:
name: String
parent: Person?, relation(fk, onDelete=SetNull, deferred)

View file

@ -0,0 +1,34 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "deleteTable",
"deleteTable": "required_set_null_child"
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "deleteTable",
"deleteTable": "nullable_set_default_child"
}
],
"warnings": [
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "tableDropped",
"message": "Table \"required_set_null_child\" will be dropped.",
"table": "required_set_null_child",
"columns": [],
"destrucive": true
},
{
"__className__": "serverpod.DatabaseMigrationWarning",
"type": "tableDropped",
"message": "Table \"nullable_set_default_child\" will be dropped.",
"table": "nullable_set_default_child",
"columns": [],
"destrucive": true
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,194 @@
BEGIN;
--
-- ACTION DROP TABLE
--
DROP TABLE "required_set_null_child";
--
-- ACTION DROP TABLE
--
DROP TABLE "nullable_set_default_child";
--
-- STORE COLUMN TYPES FOR MIGRATIONS
--
DROP TABLE IF EXISTS "serverpod_sqlite_schema";
CREATE TABLE "serverpod_sqlite_schema" (
"table_name" TEXT NOT NULL,
"column_name" TEXT NOT NULL,
"column_type" TEXT NOT NULL,
"column_vector_dimension" INTEGER,
PRIMARY KEY ("table_name", "column_name")
);
INSERT INTO "serverpod_sqlite_schema" VALUES
('address', 'id', 'uuid', NULL),
('address', 'inhabitantId', 'uuid', NULL),
('city', 'id', 'uuid', NULL),
('company', 'id', 'uuid', NULL),
('company', 'townId', 'uuid', NULL),
('fk_chain_cascade_middle', 'id', 'uuid', NULL),
('fk_chain_cascade_middle', 'rootId', 'uuid', NULL),
('fk_chain_middle_cascade_child', 'id', 'uuid', NULL),
('fk_chain_middle_cascade_child', 'restrictBlockerId', 'uuid', NULL),
('fk_chain_middle_set_null_child', 'id', 'uuid', NULL),
('fk_chain_middle_set_null_child', 'restrictBlockerId', 'uuid', NULL),
('fk_chain_restrict_blocker', 'id', 'uuid', NULL),
('fk_chain_restrict_blocker', 'cascadeMiddleId', 'uuid', NULL),
('fk_chain_root', 'id', 'uuid', NULL),
('fk_chain_set_null_cascade_child', 'id', 'uuid', NULL),
('fk_chain_set_null_cascade_child', 'setNullMiddleId', 'uuid', NULL),
('fk_chain_set_null_middle', 'id', 'uuid', NULL),
('fk_chain_set_null_middle', 'cascadeMiddleId', 'uuid', NULL),
('fk_chain_set_null_restrict_child', 'id', 'uuid', NULL),
('fk_chain_set_null_restrict_child', 'setNullMiddleId', 'uuid', NULL),
('fk_chain_set_null_set_null_child', 'id', 'uuid', NULL),
('fk_chain_set_null_set_null_child', 'setNullMiddleId', 'uuid', NULL),
('organization', 'id', 'uuid', NULL),
('organization', 'cityId', 'uuid', NULL),
('person', 'id', 'uuid', NULL),
('person', 'organizationId', 'uuid', NULL),
('person', 'oldCompanyId', 'uuid', NULL),
('person', 'cityId', 'uuid', NULL),
('required_cascade_child', 'id', 'uuid', NULL),
('required_cascade_child', 'parentId', 'uuid', NULL),
('required_no_action_child', 'id', 'uuid', NULL),
('required_no_action_child', 'parentId', 'uuid', NULL),
('restrict_child', 'id', 'uuid', NULL),
('restrict_child', 'parentId', 'uuid', NULL),
('shared_child', 'id', 'uuid', NULL),
('shared_child', 'parentId', 'uuid', NULL),
('shared_parent', 'id', 'uuid', NULL),
('town', 'id', 'uuid', NULL),
('town', 'cityId', 'uuid', NULL),
('town', 'mayorId', 'uuid', NULL),
('types', 'id', 'uuid', NULL),
('types', 'aBool', 'boolean', NULL),
('types', 'aDateTime', 'timestampWithoutTimeZone', NULL),
('types', 'optionalUuid', 'uuid', NULL),
('types', 'parentId', 'uuid', NULL),
('types', 'jsonDocument', 'json', NULL),
('types', 'jsonbDocument', 'jsonb', NULL),
('types', 'jsonbNumbers', 'jsonb', NULL),
('unique', 'id', 'uuid', NULL),
('unique_cascade_child', 'id', 'uuid', NULL),
('unique_cascade_child', 'parentId', 'uuid', NULL),
('unique_cascade_reference', 'id', 'uuid', NULL),
('unique_cascade_reference', 'parentId', 'uuid', NULL),
('unique_composite', 'id', 'uuid', NULL),
('unique_discriminator', 'id', 'uuid', NULL),
('unique_fk_pair', 'id', 'uuid', NULL),
('unique_fk_pair', 'leftId', 'uuid', NULL),
('unique_fk_pair', 'rightId', 'uuid', NULL),
('unique_mixed_fk', 'id', 'uuid', NULL),
('unique_mixed_fk', 'parentId', 'uuid', NULL),
('unique_no_release', 'id', 'uuid', NULL),
('unique_nullable', 'id', 'uuid', NULL),
('unique_overlapping', 'id', 'uuid', NULL),
('unique_set_default_child', 'id', 'uuid', NULL),
('unique_set_default_child', 'parentId', 'uuid', NULL),
('unique_set_null_child', 'id', 'uuid', NULL),
('unique_set_null_child', 'parentId', 'uuid', NULL),
('unique_uuid', 'id', 'uuid', NULL),
('unique_uuid', 'value', 'uuid', NULL),
('serverpod_cloud_storage', 'addedTime', 'timestampWithoutTimeZone', NULL),
('serverpod_cloud_storage', 'expiration', 'timestampWithoutTimeZone', NULL),
('serverpod_cloud_storage', 'verified', 'boolean', NULL),
('serverpod_cloud_storage_direct_download', 'expiration', 'timestampWithoutTimeZone', NULL),
('serverpod_cloud_storage_direct_upload', 'expiration', 'timestampWithoutTimeZone', NULL),
('serverpod_cloud_storage_direct_upload', 'preventOverwrite', 'boolean', NULL),
('serverpod_future_call', 'time', 'timestampWithoutTimeZone', NULL),
('serverpod_future_call', 'scheduling', 'json', NULL),
('serverpod_future_call_claim', 'lastHeartbeatTime', 'timestampWithoutTimeZone', NULL),
('serverpod_health_connection_info', 'timestamp', 'timestampWithoutTimeZone', NULL),
('serverpod_health_metric', 'timestamp', 'timestampWithoutTimeZone', NULL),
('serverpod_health_metric', 'isHealthy', 'boolean', NULL),
('serverpod_log', 'time', 'timestampWithoutTimeZone', NULL),
('serverpod_message_log', 'slow', 'boolean', NULL),
('serverpod_migrations', 'timestamp', 'timestampWithoutTimeZone', NULL),
('serverpod_query_log', 'slow', 'boolean', NULL),
('serverpod_runtime_settings', 'logSettings', 'json', NULL),
('serverpod_runtime_settings', 'logSettingsOverrides', 'json', NULL),
('serverpod_runtime_settings', 'logServiceCalls', 'boolean', NULL),
('serverpod_runtime_settings', 'logMalformedCalls', 'boolean', NULL),
('serverpod_session_log', 'time', 'timestampWithoutTimeZone', NULL),
('serverpod_session_log', 'slow', 'boolean', NULL),
('serverpod_session_log', 'isOpen', 'boolean', NULL),
('serverpod_session_log', 'touched', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_jwt_refresh_token', 'id', 'uuid', NULL),
('serverpod_auth_core_jwt_refresh_token', 'authUserId', 'uuid', NULL),
('serverpod_auth_core_jwt_refresh_token', 'scopeNames', 'json', NULL),
('serverpod_auth_core_jwt_refresh_token', 'lastUpdatedAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_jwt_refresh_token', 'createdAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_profile', 'id', 'uuid', NULL),
('serverpod_auth_core_profile', 'authUserId', 'uuid', NULL),
('serverpod_auth_core_profile', 'createdAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_profile', 'imageId', 'uuid', NULL),
('serverpod_auth_core_profile_image', 'id', 'uuid', NULL),
('serverpod_auth_core_profile_image', 'userProfileId', 'uuid', NULL),
('serverpod_auth_core_profile_image', 'createdAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_session', 'id', 'uuid', NULL),
('serverpod_auth_core_session', 'authUserId', 'uuid', NULL),
('serverpod_auth_core_session', 'scopeNames', 'json', NULL),
('serverpod_auth_core_session', 'createdAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_session', 'lastUsedAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_session', 'expiresAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_user', 'id', 'uuid', NULL),
('serverpod_auth_core_user', 'createdAt', 'timestampWithoutTimeZone', NULL),
('serverpod_auth_core_user', 'scopeNames', 'json', NULL),
('serverpod_auth_core_user', 'blocked', 'boolean', NULL),
('crdt_data_attempted_value', 'value', 'jsonb', NULL),
('crdt_data_fields', 'hlcDatetime', 'timestampWithoutTimeZone', NULL),
('crdt_data_rows', 'hlcDatetime', 'timestampWithoutTimeZone', NULL),
('crdt_data_rows', 'uuidRowId', 'uuid', NULL),
('crdt_data_tombstone', 'hlcDatetime', 'timestampWithoutTimeZone', NULL),
('crdt_nodes', 'uuidNodeId', 'uuid', NULL),
('crdt_nodes', 'lastHlc', 'jsonb', NULL),
('crdt_schema_columns', 'isNullable', 'boolean', NULL),
('offline_sync_integrity_violations', 'uuidRowId', 'uuid', NULL),
('offline_sync_integrity_violations', 'ownerSpaceUuid', 'uuid', NULL),
('offline_sync_integrity_violations', 'incomingSpaceUuid', 'uuid', NULL),
('offline_sync_integrity_violations', 'uuidNodeId', 'uuid', NULL),
('offline_sync_integrity_violations', 'hlcDatetime', 'timestampWithoutTimeZone', NULL),
('offline_sync_integrity_violations', 'firstSeenAt', 'timestampWithoutTimeZone', NULL),
('offline_sync_integrity_violations', 'lastSeenAt', 'timestampWithoutTimeZone', NULL),
('offline_sync_space_members', 'userUuid', 'uuid', NULL),
('offline_sync_space_nodes', 'lastReceivedHlc', 'jsonb', NULL),
('offline_sync_spaces', 'uuidSpaceId', 'uuid', NULL);
--
-- MIGRATION VERSION FOR serverpod_offline_sync_test
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_offline_sync_test', '20261007215503877', (unixepoch('now', 'subsecond') * 1000))
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261007215503877', "timestamp" = (unixepoch('now', 'subsecond') * 1000);
--
-- MIGRATION VERSION FOR serverpod
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod', '20260824182259319', (unixepoch('now', 'subsecond') * 1000))
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260824182259319', "timestamp" = (unixepoch('now', 'subsecond') * 1000);
--
-- MIGRATION VERSION FOR serverpod_auth_core
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_core', '20260924105232991', (unixepoch('now', 'subsecond') * 1000))
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105232991', "timestamp" = (unixepoch('now', 'subsecond') * 1000);
--
-- MIGRATION VERSION FOR serverpod_offline_sync
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_offline_sync', '20260914143806119', (unixepoch('now', 'subsecond') * 1000))
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260914143806119', "timestamp" = (unixepoch('now', 'subsecond') * 1000);
COMMIT;

View file

@ -15,3 +15,4 @@
20260914144112288
20260914165608984
20260930185557154
20261007215503877

View file

@ -8,8 +8,8 @@ environment:
resolution: workspace
dependencies:
serverpod: ^4.0.0
serverpod_auth_core_server: ^4.0.0
serverpod: '>=4.0.4 <4.1.0'
serverpod_auth_core_server: '>=4.0.4 <4.1.0'
serverpod_offline_sync:
path: ../../packages/serverpod_offline_sync
serverpod_offline_sync_server:
@ -21,12 +21,12 @@ dev_dependencies:
clock: ^1.1.2
lints: '>=3.0.0 <7.0.0'
path: ^1.9.1
serverpod_database: ^4.0.0
serverpod_database: '>=4.0.4 <4.1.0'
serverpod_offline_sync_client:
path: ../../packages/serverpod_offline_sync_client
serverpod_offline_sync_test_client:
path: ../serverpod_offline_sync_test_client
serverpod_test: ^4.0.0
serverpod_test: '>=4.0.4 <4.1.0'
test: ^1.25.5
uuid: ^4.5.3

View file

@ -1,4 +1,3 @@
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart';
import 'package:serverpod_offline_sync_test_client/serverpod_offline_sync_test_client.dart';
import 'package:test/test.dart';
@ -8,23 +7,25 @@ import 'framework/dst_snapshot.dart';
import 'framework/dst_world.dart';
void main() {
initTestClientSession();
initTestClientSession(createSessionPerTest: false);
test(
'Given a unique nullable set-default child referencing its default town, '
'when the child is deleted and its released field is read back, '
'then the hidden row retains null while the schema retains its non-null default.',
() async {
group('Given a unique nullable set-default child referencing its default town,', () {
late DstReplica replica;
late UuidValue space;
late Town parent;
late UniqueSetDefaultChild child;
setUpAll(() async {
final ids = DstIds(DstRandom(118));
final space = ids.next();
final replica = await DstReplica.create(
space = ids.next();
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
final parent = Town(id: dstDefaultTownId, name: 'default');
final child = UniqueSetDefaultChild(
parent = Town(id: dstDefaultTownId, name: 'default');
child = UniqueSetDefaultChild(
id: ids.next(),
name: 'child',
parentId: parent.id,
@ -39,30 +40,44 @@ void main() {
);
}),
);
});
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await UniqueSetDefaultChild.db.deleteRow(
replica.session,
child,
transaction: tx,
);
}),
);
final snapshot = await DstSnapshot.capture(replica);
group('when the child is deleted and its released field is read back,', () {
late DstSnapshot snapshot;
final hidden = snapshot.rows['unique_set_default_child']![child.id]!;
expect(hidden.visible, isFalse);
expect(hidden.columns['parentId'], isNull);
expect(
dstForeignKeys
.singleWhere((edge) => edge.child == DstTable.uniqueSetDefaultChild)
.defaultValue,
parent.id,
);
expect(DstOracle.invariants(snapshot), isEmpty);
},
);
setUpAll(() async {
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await UniqueSetDefaultChild.db.deleteRow(
replica.session,
child,
transaction: tx,
);
}),
);
snapshot = await DstSnapshot.capture(replica);
});
test('then the hidden row retains a null reference.', () {
final hidden = snapshot.rows['unique_set_default_child']![child.id]!;
expect(hidden.visible, isFalse);
expect(hidden.columns['parentId'], isNull);
});
test('then the schema retains its non-null default.', () {
expect(
dstForeignKeys
.singleWhere((edge) => edge.child == DstTable.uniqueSetDefaultChild)
.defaultValue,
parent.id,
);
});
test('then the snapshot satisfies the oracle invariants.', () {
expect(DstOracle.invariants(snapshot), isEmpty);
});
});
});
test(
'Given a visible person referencing a hidden company, '
@ -94,62 +109,6 @@ void main() {
},
);
test(
'Given a visible required set-null child referencing a hidden person, '
'when its snapshot is checked, '
'then the oracle rejects the unrepaired reference.',
() {
final ids = DstIds(DstRandom(3));
final space = ids.next();
final parentId = ids.next();
final childId = ids.next();
final snapshot = DstSnapshot(
rows: {
'person': {parentId: (spaceUuid: space, columns: {}, visible: false)},
'required_set_null_child': {
childId: (spaceUuid: space, columns: {'parentId': parentId}, visible: true),
},
},
projections: {},
causalLengths: {},
);
final violations = DstOracle.foreignKeyClosure(snapshot);
expect(violations, hasLength(1));
},
);
test(
'Given a hidden nullable set-default child whose null repair was skipped, '
'when its snapshot is checked, '
'then the oracle rejects the unrepaired reference.',
() {
final ids = DstIds(DstRandom(3));
final space = ids.next();
final parentId = ids.next();
final childId = ids.next();
final snapshot = DstSnapshot(
rows: {
'person': {parentId: (spaceUuid: space, columns: {}, visible: false)},
'nullable_set_default_child': {
childId: (
spaceUuid: space,
columns: {'parentId': parentId},
visible: false,
),
},
},
projections: {},
causalLengths: {},
);
final violations = DstOracle.projectionPurity(snapshot);
expect(violations, hasLength(1));
},
);
test(
'Given a visible no-action descendant of a hidden cascade middle, '
'when its snapshot is checked, '
@ -182,259 +141,195 @@ void main() {
},
);
test(
'Given a nullable set-default child repaired to its null default, '
'when its snapshot is checked, '
'then the oracle accepts its set-default projection.',
group(
'Given a person and visible city, organization, company, and town parents,',
() {
final ids = DstIds(DstRandom(3));
final space = ids.next();
final parentId = ids.next();
final childId = ids.next();
final snapshot = DstSnapshot(
rows: {
'person': {parentId: (spaceUuid: space, columns: {}, visible: false)},
'nullable_set_default_child': {
childId: (spaceUuid: space, columns: {'parentId': null}, visible: true),
},
},
projections: {
('nullable_set_default_child', childId, 'parentId'): (
attemptedValue: parentId,
projectionReason: CrdtProjectionReason.foreignKeySetDefault,
),
},
causalLengths: {},
);
late DstReplica replica;
late UuidValue space;
late City city;
late Organization organization;
late Town town;
late Company company;
late Person person;
late DstOperations operations;
final violations = DstOracle.projectionPurity(snapshot);
setUpAll(() async {
final random = DstRandom(300);
final ids = DstIds(random);
space = ids.next();
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
city = City(id: ids.next(), name: 'city');
organization = Organization(
id: ids.next(),
name: 'organization',
cityId: city.id,
);
town = Town(id: ids.next(), name: 'town', cityId: city.id);
company = Company(id: ids.next(), name: 'company', townId: town.id);
person = Person(id: ids.next(), name: 'person');
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await City.db.insertRow(replica.session, city, transaction: tx);
await Organization.db.insertRow(
replica.session,
organization,
transaction: tx,
);
await Town.db.insertRow(replica.session, town, transaction: tx);
await Company.db.insertRow(replica.session, company, transaction: tx);
await Person.db.insertRow(replica.session, person, transaction: tx);
}),
);
operations = DstOperations(random, ids);
});
expect(violations, isEmpty);
group('when the DST repeatedly retargets the person and town,', () {
late Set<String> references;
late bool cycleAuthored;
setUpAll(() async {
references = <String>{};
cycleAuthored = false;
for (var index = 0; index < 60; index++) {
await operations.apply(
replica,
space,
table: DstTable.person,
action: DstAction.update,
);
await operations.apply(
replica,
space,
table: DstTable.town,
action: DstAction.update,
);
final actualPerson = (await Person.db.findById(
replica.session,
person.id!,
))!;
final actualTown = (await Town.db.findById(replica.session, town.id!))!;
if (actualPerson.organizationId == organization.id) {
references.add('organization');
}
if (actualPerson.oldCompanyId == company.id) references.add('company');
if (actualPerson.cityId == city.id) references.add('city');
if (actualPerson.oldCompanyId == company.id &&
actualTown.mayorId == person.id) {
cycleAuthored = true;
}
}
});
test('then every person reference can be authored.', () {
expect(references, {'organization', 'company', 'city'});
});
test('then the company-town-person cycle can be authored.', () {
expect(cycleAuthored, isTrue);
});
});
},
);
test(
'Given a hidden required set-null child and its hidden parent, '
'when its snapshot is checked, '
'then the oracle accepts the unchanged non-null reference.',
group(
'Given visible person and town parents for required cascade, required no-action, unique set-default, and unique cascade children,',
() {
final ids = DstIds(DstRandom(3));
final space = ids.next();
final parentId = ids.next();
final childId = ids.next();
final snapshot = DstSnapshot(
rows: {
'person': {parentId: (spaceUuid: space, columns: {}, visible: false)},
'required_set_null_child': {
childId: (
spaceUuid: space,
columns: {'parentId': parentId},
visible: false,
),
},
},
projections: {},
causalLengths: {},
);
late DstReplica replica;
late UuidValue space;
late DstOperations operations;
final violations = DstOracle.projectionPurity(snapshot);
expect(violations, isEmpty);
},
);
test(
'Given a replica holding a required set-null child, '
'when its snapshot is captured, '
'then the child and its reference are available to the oracle.',
() async {
final ids = DstIds(DstRandom(3));
final space = ids.next();
final replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
final parent = Person(id: ids.next(), name: 'parent');
final child = RequiredSetNullChild(
id: ids.next(),
name: 'child',
parentId: parent.id!,
);
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Person.db.insertRow(replica.session, parent, transaction: tx);
await RequiredSetNullChild.db.insertRow(
replica.session,
child,
transaction: tx,
);
}),
);
final snapshot = await DstSnapshot.capture(replica);
expect(
snapshot.rows['required_set_null_child']?[child.id]?.columns['parentId'],
parent.id.toString(),
);
},
);
test(
'Given a person and visible city, organization, company, and town parents, '
'when the DST repeatedly retargets the person and town, '
'then every person reference and the company-town-person cycle can be authored.',
() async {
final random = DstRandom(300);
final ids = DstIds(random);
final space = ids.next();
final replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
final city = City(id: ids.next(), name: 'city');
final organization = Organization(
id: ids.next(),
name: 'organization',
cityId: city.id,
);
final town = Town(id: ids.next(), name: 'town', cityId: city.id);
final company = Company(id: ids.next(), name: 'company', townId: town.id);
final person = Person(id: ids.next(), name: 'person');
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await City.db.insertRow(replica.session, city, transaction: tx);
await Organization.db.insertRow(
replica.session,
organization,
transaction: tx,
);
await Town.db.insertRow(replica.session, town, transaction: tx);
await Company.db.insertRow(replica.session, company, transaction: tx);
await Person.db.insertRow(replica.session, person, transaction: tx);
}),
);
final operations = DstOperations(random, ids);
final references = <String>{};
var cycleAuthored = false;
for (var index = 0; index < 60; index++) {
await operations.apply(
replica,
space,
table: DstTable.person,
action: DstAction.update,
setUpAll(() async {
final random = DstRandom(301);
final ids = DstIds(random);
space = ids.next();
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
await operations.apply(
replica,
space,
table: DstTable.town,
action: DstAction.update,
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Person.db.insertRow(
replica.session,
Person(id: ids.next(), name: 'parent'),
transaction: tx,
);
await Town.db.insertRow(
replica.session,
Town(id: dstDefaultTownId, name: 'default'),
transaction: tx,
);
}),
);
final actualPerson = (await Person.db.findById(replica.session, person.id!))!;
final actualTown = (await Town.db.findById(replica.session, town.id!))!;
if (actualPerson.organizationId == organization.id) {
references.add('organization');
}
if (actualPerson.oldCompanyId == company.id) references.add('company');
if (actualPerson.cityId == city.id) references.add('city');
if (actualPerson.oldCompanyId == company.id &&
actualTown.mayorId == person.id) {
cycleAuthored = true;
}
}
operations = DstOperations(random, ids);
});
expect(references, {'organization', 'company', 'city'});
expect(cycleAuthored, isTrue);
},
);
group('when the DST inserts the children,', () {
late DstOperationOutcome requiredCascade;
late DstOperationOutcome requiredNoAction;
late DstOperationOutcome uniqueDefault;
late DstOperationOutcome uniqueCascade;
late DstSnapshot snapshot;
test(
'Given visible person and town parents in one space, '
'when the DST inserts children of every required and nullable FK shape, '
'then each shape is authored and captured with valid references.',
() async {
final random = DstRandom(301);
final ids = DstIds(random);
final space = ids.next();
final replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Person.db.insertRow(
replica.session,
Person(id: ids.next(), name: 'parent'),
transaction: tx,
setUpAll(() async {
requiredCascade = await operations.apply(
replica,
space,
table: DstTable.requiredCascadeChild,
action: DstAction.insert,
);
await Town.db.insertRow(
replica.session,
Town(id: dstDefaultTownId, name: 'default'),
transaction: tx,
requiredNoAction = await operations.apply(
replica,
space,
table: DstTable.requiredNoActionChild,
action: DstAction.insert,
);
}),
);
final operations = DstOperations(random, ids);
uniqueDefault = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.insert,
);
uniqueCascade = await operations.apply(
replica,
space,
table: DstTable.uniqueCascadeReference,
action: DstAction.insert,
);
snapshot = await DstSnapshot.capture(replica);
});
final requiredNull = await operations.apply(
replica,
space,
table: DstTable.requiredSetNullChild,
action: DstAction.insert,
);
final requiredCascade = await operations.apply(
replica,
space,
table: DstTable.requiredCascadeChild,
action: DstAction.insert,
);
final requiredNoAction = await operations.apply(
replica,
space,
table: DstTable.requiredNoActionChild,
action: DstAction.insert,
);
final nullableDefault = await operations.apply(
replica,
space,
table: DstTable.nullableSetDefaultChild,
action: DstAction.insert,
);
final uniqueDefault = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.insert,
);
final uniqueCascade = await operations.apply(
replica,
space,
table: DstTable.uniqueCascadeReference,
action: DstAction.insert,
);
final snapshot = await DstSnapshot.capture(replica);
test('then the required cascade child is authored and captured.', () {
expect(requiredCascade, DstOperationOutcome.applied);
expect(snapshot.rows['required_cascade_child'], hasLength(1));
});
expect([
requiredNull,
requiredCascade,
requiredNoAction,
nullableDefault,
uniqueDefault,
uniqueCascade,
], everyElement(DstOperationOutcome.applied));
expect(snapshot.rows['required_set_null_child'], hasLength(1));
expect(snapshot.rows['required_cascade_child'], hasLength(1));
expect(snapshot.rows['required_no_action_child'], hasLength(1));
expect(snapshot.rows['nullable_set_default_child'], hasLength(1));
expect(snapshot.rows['unique_set_default_child'], hasLength(1));
expect(snapshot.rows['unique_cascade_reference'], hasLength(1));
expect(DstOracle.invariants(snapshot), isEmpty);
test('then the required no-action child is authored and captured.', () {
expect(requiredNoAction, DstOperationOutcome.applied);
expect(snapshot.rows['required_no_action_child'], hasLength(1));
});
test('then the unique set-default child is authored and captured.', () {
expect(uniqueDefault, DstOperationOutcome.applied);
expect(snapshot.rows['unique_set_default_child'], hasLength(1));
});
test('then the unique cascade child is authored and captured.', () {
expect(uniqueCascade, DstOperationOutcome.applied);
expect(snapshot.rows['unique_cascade_reference'], hasLength(1));
});
test('then every child reference satisfies the oracle invariants.', () {
expect(DstOracle.invariants(snapshot), isEmpty);
});
});
},
);
}

View file

@ -8,65 +8,23 @@ import 'framework/dst_snapshot.dart';
import 'framework/dst_world.dart';
void main() {
initTestClientSession();
initTestClientSession(createSessionPerTest: false);
test(
'Given a person referenced by a required set-null child, '
'when the DST deletes the person in a batch, '
'then the operation is rejected and both rows remain unchanged.',
() async {
final random = DstRandom(114);
final ids = DstIds(random);
final space = ids.next();
final replica = await _replica(ids, space);
final operations = DstOperations(random, ids);
final parent = Person(id: ids.next(), name: 'parent');
final child = RequiredSetNullChild(
id: ids.next(),
name: 'child',
parentId: parent.id!,
);
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Person.db.insertRow(replica.session, parent, transaction: tx);
await RequiredSetNullChild.db.insertRow(
replica.session,
child,
transaction: tx,
);
}),
);
final before = (await replica.collect(space)).map(dstChangeKey).toSet();
group('Given a deleted unique claimant and a newer claimant of the same name,', () {
late UuidValue space;
late DstReplica replica;
late DstOperations operations;
late Unique original;
late Unique peer;
final outcome = await operations.apply(
replica,
space,
table: DstTable.person,
action: DstAction.deleteBatch,
);
expect(outcome, DstOperationOutcome.rejected);
expect(await Person.db.findById(replica.session, parent.id!), isNotNull);
expect(
(await RequiredSetNullChild.db.findById(replica.session, child.id!))!.parentId,
parent.id,
);
expect((await replica.collect(space)).map(dstChangeKey).toSet(), before);
},
);
test(
'Given a deleted unique claimant and a newer claimant of the same name, '
'when the DST restores the deleted identity, '
'then the renewed claim yields the name to its earlier peer.',
() async {
setUpAll(() async {
final random = DstRandom(4);
final ids = DstIds(random);
final space = ids.next();
final replica = await _replica(ids, space);
final operations = DstOperations(random, ids);
final original = Unique(id: ids.next(), name: 'shared');
final peer = Unique(id: ids.next(), name: 'shared');
space = ids.next();
replica = await _replica(ids, space);
operations = DstOperations(random, ids);
original = Unique(id: ids.next(), name: 'shared');
peer = Unique(id: ids.next(), name: 'shared');
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Unique.db.insertRow(replica.session, original, transaction: tx);
@ -78,27 +36,44 @@ void main() {
await Unique.db.insertRow(replica.session, peer, transaction: tx);
}),
);
});
final outcome = await operations.apply(
replica,
space,
table: DstTable.unique,
action: _action('restore'),
);
group('when the DST restores the deleted identity,', () {
late DstOperationOutcome outcome;
late Unique? restored;
late Unique? other;
late DstSnapshot snapshot;
expect(outcome, DstOperationOutcome.applied);
final restored = await Unique.db.findById(replica.session, original.id!);
final other = await Unique.db.findById(replica.session, peer.id!);
expect(restored?.name, 'shared__conflict__${original.id}');
expect(other?.name, 'shared');
final snapshot = await DstSnapshot.capture(replica);
expect(snapshot.authoredValue(('unique', original.id!, 'name')), 'shared');
expect(
snapshot.fieldHlc(('unique', original.id!, 'name')),
snapshot.rowHlcs['unique/${original.id}'],
);
},
);
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.unique,
action: _action('restore'),
);
restored = await Unique.db.findById(replica.session, original.id!);
other = await Unique.db.findById(replica.session, peer.id!);
snapshot = await DstSnapshot.capture(replica);
});
test('then the restoration is applied.', () {
expect(outcome, DstOperationOutcome.applied);
});
test('then the renewed claim yields the name to its earlier peer.', () {
expect(restored?.name, 'shared__conflict__${original.id}');
expect(other?.name, 'shared');
});
test('then the authored name is renewed at the restored row clock.', () {
expect(snapshot.authoredValue(('unique', original.id!, 'name')), 'shared');
expect(
snapshot.fieldHlc(('unique', original.id!, 'name')),
snapshot.rowHlcs['unique/${original.id}'],
);
});
});
});
test(
'Given an empty city table, '
@ -391,19 +366,22 @@ void main() {
},
);
test(
'Given a town with a set-null projection after a remote mayor deletion, '
'when the DST performs a full-row update of an unrelated field, '
'then the town changes name while preserving the authored mayor in its export.',
() async {
group('Given a town with a set-null projection after a remote mayor deletion,', () {
late UuidValue space;
late DstReplica replica;
late DstOperations operations;
late Person mayor;
late Town town;
setUpAll(() async {
final random = DstRandom(4);
final ids = DstIds(random);
final space = ids.next();
final replica = await _replica(ids, space);
final operations = DstOperations(random, ids);
space = ids.next();
replica = await _replica(ids, space);
operations = DstOperations(random, ids);
final source = await _replica(ids, space);
final mayor = Person(id: ids.next(), name: 'mayor');
final town = Town(id: ids.next(), name: 'original-town', mayorId: mayor.id);
mayor = Person(id: ids.next(), name: 'mayor');
town = Town(id: ids.next(), name: 'original-town', mayorId: mayor.id);
await source.withReplicaClock(
() => source.session.db.transactionForUser(space, (tx) async {
await Person.db.insertRow(source.session, mayor, transaction: tx);
@ -421,51 +399,141 @@ void main() {
}),
);
await replica.merge(await source.collect(space), space);
});
final outcome = await operations.apply(
replica,
space,
table: DstTable.town,
action: _action('fullRowUpdate'),
group('when the DST performs a full-row update of an unrelated field,', () {
late DstOperationOutcome outcome;
late Town visible;
late CrdtMergeSet changes;
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.town,
action: _action('fullRowUpdate'),
);
visible = (await Town.db.findById(replica.session, town.id!))!;
changes = await replica.collect(space);
});
test('then the update is applied.', () {
expect(outcome, DstOperationOutcome.applied);
});
test('then the town changes name and retains its projected null mayor.', () {
expect(visible.name, isNot(town.name));
expect(visible.mayorId, isNull);
});
test('then the exported insert preserves the authored mayor.', () {
final insert = changes.whereType<CrdtMergeInsert>().singleWhere(
(change) => change.uuidRowId == town.id,
);
expect((insert.data as Town).mayorId, mayor.id);
});
});
});
group('Given a detached defaulted FK with a local parent and no default target,', () {
late UuidValue space;
late DstReplica replica;
late Town parent;
late UniqueSetDefaultChild child;
late DstOperations operations;
setUpAll(() async {
final ids = DstIds(DstRandom(910));
space = ids.next();
final otherSpace = ids.next();
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space, otherSpace],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
expect(outcome, DstOperationOutcome.applied);
final visible = (await Town.db.findById(replica.session, town.id!))!;
expect(visible.name, isNot(town.name));
expect(visible.mayorId, isNull);
final changes = await replica.collect(space);
final insert = changes.whereType<CrdtMergeInsert>().singleWhere(
(change) => change.uuidRowId == town.id,
parent = Town(id: ids.next(), name: 'available-parent');
child = UniqueSetDefaultChild(
id: ids.next(),
name: 'original',
parentId: parent.id,
);
expect((insert.data as Town).mayorId, mayor.id);
},
);
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Town.db.insertRow(replica.session, parent, transaction: tx);
await UniqueSetDefaultChild.db.insertRow(
replica.session,
child,
transaction: tx,
);
await UniqueSetDefaultChild.db.updateRow(
replica.session,
child.copyWith(parentId: null),
columns: (t) => [t.parentId],
transaction: tx,
);
}),
);
// This seed changes only the name during the full-row upsert.
operations = DstOperations(DstRandom(5), ids);
});
for (final defaultLocation in ['missing', 'another space', 'the acting space']) {
final defaultDescription = defaultLocation == 'missing'
? 'no default target'
: 'a default target in $defaultLocation';
test(
'Given a detached defaulted FK with a local parent and $defaultDescription, '
'when the DST upserts an unrelated field, '
'then the submitted reference resolves to a visible parent in the acting space.',
() async {
group('when the DST upserts an unrelated field,', () {
late DstOperationOutcome outcome;
late UniqueSetDefaultChild updated;
late List<String> rejections;
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
);
updated = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
rejections = List.of(operations.rejections);
});
test('then the upsert is applied without a rejection.', () {
expect(outcome, DstOperationOutcome.applied);
expect(rejections, isEmpty);
});
test('then the unrelated name changes.', () {
expect(updated.name, isNot(child.name));
});
test('then the reference resolves to a visible parent in the acting space.', () {
expect(updated.parentId, parent.id);
});
});
});
group(
'Given a detached defaulted FK with a local parent and a default target in another space,',
() {
late UuidValue space;
late DstReplica replica;
late Town parent;
late UniqueSetDefaultChild child;
late DstOperations operations;
setUpAll(() async {
final ids = DstIds(DstRandom(910));
final space = ids.next();
space = ids.next();
final otherSpace = ids.next();
final replica = await DstReplica.create(
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space, otherSpace],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
if (defaultLocation != 'missing') {
await replica.seedDefaultTown(
defaultLocation == 'the acting space' ? space : otherSpace,
);
}
final parent = Town(id: ids.next(), name: 'available-parent');
final child = UniqueSetDefaultChild(
await replica.seedDefaultTown(otherSpace);
parent = Town(id: ids.next(), name: 'available-parent');
child = UniqueSetDefaultChild(
id: ids.next(),
name: 'original',
parentId: parent.id,
@ -486,42 +554,145 @@ void main() {
);
}),
);
// This seed changes only name, leaving the full-row upsert to apply
// the unchanged null FK's persisted default.
final operations = DstOperations(DstRandom(5), ids);
// This seed changes only the name during the full-row upsert.
operations = DstOperations(DstRandom(5), ids);
});
final outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
group('when the DST upserts an unrelated field,', () {
late DstOperationOutcome outcome;
late UniqueSetDefaultChild updated;
late List<String> rejections;
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
);
updated = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
rejections = List.of(operations.rejections);
});
test('then the upsert is applied without a rejection.', () {
expect(outcome, DstOperationOutcome.applied);
expect(rejections, isEmpty);
});
test('then the unrelated name changes.', () {
expect(updated.name, isNot(child.name));
});
test(
'then the reference resolves to a visible parent in the acting space.',
() {
expect(updated.parentId, parent.id);
},
);
});
},
);
expect(outcome, DstOperationOutcome.applied);
final updated = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
expect(updated.name, isNot(child.name));
expect(
updated.parentId,
defaultLocation == 'the acting space' ? dstDefaultTownId : parent.id,
group(
'Given a detached defaulted FK with a local parent and a default target in the acting space,',
() {
late UuidValue space;
late DstReplica replica;
late Town parent;
late UniqueSetDefaultChild child;
late DstOperations operations;
setUpAll(() async {
final ids = DstIds(DstRandom(910));
space = ids.next();
final otherSpace = ids.next();
replica = await DstReplica.create(
name: 'replica',
spaceUuids: [space, otherSpace],
nodeUuid: ids.next(),
clock: DstClock().clock,
);
expect(operations.rejections, isEmpty);
},
);
}
await replica.seedDefaultTown(space);
parent = Town(id: ids.next(), name: 'available-parent');
child = UniqueSetDefaultChild(
id: ids.next(),
name: 'original',
parentId: parent.id,
);
await replica.withReplicaClock(
() => replica.session.db.transactionForUser(space, (tx) async {
await Town.db.insertRow(replica.session, parent, transaction: tx);
await UniqueSetDefaultChild.db.insertRow(
replica.session,
child,
transaction: tx,
);
await UniqueSetDefaultChild.db.updateRow(
replica.session,
child.copyWith(parentId: null),
columns: (t) => [t.parentId],
transaction: tx,
);
}),
);
// This seed changes only the name during the full-row upsert.
operations = DstOperations(DstRandom(5), ids);
});
test(
'Given a detached defaulted FK with no visible parent or default target, '
'when the DST tries to upsert an unrelated field, '
'then it skips the unavailable reference without changing authored facts.',
() async {
group('when the DST upserts an unrelated field,', () {
late DstOperationOutcome outcome;
late UniqueSetDefaultChild updated;
late List<String> rejections;
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
);
updated = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
rejections = List.of(operations.rejections);
});
test('then the upsert is applied without a rejection.', () {
expect(outcome, DstOperationOutcome.applied);
expect(rejections, isEmpty);
});
test('then the unrelated name changes.', () {
expect(updated.name, isNot(child.name));
});
test(
'then the reference resolves to a visible parent in the acting space.',
() {
expect(updated.parentId, dstDefaultTownId);
},
);
});
},
);
group('Given a detached defaulted FK with no visible parent or default target,', () {
late UuidValue space;
late DstReplica replica;
late UniqueSetDefaultChild child;
late Set<String> before;
late DstOperations operations;
setUpAll(() async {
final ids = DstIds(DstRandom(910));
final space = ids.next();
final replica = await _replica(ids, space);
space = ids.next();
replica = await _replica(ids, space);
final parent = Town(id: ids.next(), name: 'removed-parent');
final child = UniqueSetDefaultChild(
child = UniqueSetDefaultChild(
id: ids.next(),
name: 'original',
parentId: parent.id,
@ -543,26 +714,43 @@ void main() {
await Town.db.deleteRow(replica.session, parent, transaction: tx);
}),
);
final before = (await replica.collect(space)).map(dstChangeKey).toSet();
final operations = DstOperations(DstRandom(5), ids);
before = (await replica.collect(space)).map(dstChangeKey).toSet();
operations = DstOperations(DstRandom(5), ids);
});
final outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
);
group('when the DST tries to upsert an unrelated field,', () {
late DstOperationOutcome outcome;
late Set<String> after;
late UniqueSetDefaultChild retained;
expect(outcome, DstOperationOutcome.skipped);
expect((await replica.collect(space)).map(dstChangeKey).toSet(), before);
final retained = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
expect(retained.name, child.name);
expect(retained.parentId, isNull);
},
);
setUpAll(() async {
outcome = await operations.apply(
replica,
space,
table: DstTable.uniqueSetDefaultChild,
action: DstAction.upsert,
);
after = (await replica.collect(space)).map(dstChangeKey).toSet();
retained = (await UniqueSetDefaultChild.db.findById(
replica.session,
child.id!,
))!;
});
test('then it skips the unavailable reference.', () {
expect(outcome, DstOperationOutcome.skipped);
});
test('then the authored facts remain unchanged.', () {
expect(after, before);
});
test('then the child retains its name and null reference.', () {
expect(retained.name, child.name);
expect(retained.parentId, isNull);
});
});
});
}
DstAction _action(String name) =>

View file

@ -25,10 +25,8 @@ enum DstTable {
uniqueFkPair('unique_fk_pair'),
uniqueMixedFk('unique_mixed_fk'),
organization('organization'),
requiredSetNullChild('required_set_null_child'),
requiredCascadeChild('required_cascade_child'),
requiredNoActionChild('required_no_action_child'),
nullableSetDefaultChild('nullable_set_default_child'),
uniqueSetDefaultChild('unique_set_default_child'),
uniqueCascadeReference('unique_cascade_reference'),
restrictChild('restrict_child'),
@ -216,10 +214,6 @@ final dstModels = <DstTable, DstModel<db.TableRow<models.UuidValue?>>>{
table: models.Organization.t,
fromJson: models.Organization.fromJson,
),
DstTable.requiredSetNullChild: DstModel<models.RequiredSetNullChild>(
table: models.RequiredSetNullChild.t,
fromJson: models.RequiredSetNullChild.fromJson,
),
DstTable.requiredCascadeChild: DstModel<models.RequiredCascadeChild>(
table: models.RequiredCascadeChild.t,
fromJson: models.RequiredCascadeChild.fromJson,
@ -228,10 +222,6 @@ final dstModels = <DstTable, DstModel<db.TableRow<models.UuidValue?>>>{
table: models.RequiredNoActionChild.t,
fromJson: models.RequiredNoActionChild.fromJson,
),
DstTable.nullableSetDefaultChild: DstModel<models.NullableSetDefaultChild>(
table: models.NullableSetDefaultChild.t,
fromJson: models.NullableSetDefaultChild.fromJson,
),
DstTable.uniqueSetDefaultChild: DstModel<models.UniqueSetDefaultChild>(
table: models.UniqueSetDefaultChild.t,
fromJson: models.UniqueSetDefaultChild.fromJson,

View file

@ -694,14 +694,11 @@ class DstOracle {
static bool _canRepair(DstSnapshot snapshot, DstForeignKey edge, DstRow child) =>
switch (edge.action) {
'setNull' => edge.nullable,
'setDefault' =>
edge.defaultValue == null
? edge.nullable
: _available(
snapshot.rows[edge.parent.tableName]?[edge.defaultValue],
child,
),
'setNull' => true,
'setDefault' => _available(
snapshot.rows[edge.parent.tableName]?[edge.defaultValue],
child,
),
_ => false,
};
@ -735,14 +732,12 @@ class DstOracle {
/// repair.
static bool _reasonFitsEdge(CrdtProjectionReason reason, DstForeignKey edge) {
return switch (reason) {
CrdtProjectionReason.foreignKeySetNull =>
edge.action == 'setNull' && edge.nullable,
CrdtProjectionReason.foreignKeySetNull => edge.action == 'setNull',
CrdtProjectionReason.foreignKeySetDefault => edge.action == 'setDefault',
CrdtProjectionReason.foreignKeyMissingParent =>
edge.action == 'cascade' ||
edge.action == 'noAction' ||
edge.action == 'setDefault' ||
(edge.action == 'setNull' && !edge.nullable),
edge.action == 'setDefault',
CrdtProjectionReason.uniqueConflict ||
CrdtProjectionReason.hiddenUniqueRelease => _isUniqueForeignKey(edge),
};

View file

@ -236,8 +236,8 @@ enum DstOperationOutcome {
/// The operation committed.
applied,
/// The engine refused the operation by design - a no-action violation, a
/// required set-null delete, or a restored reference unavailable in space.
/// The engine refused the operation by design - a no-action violation or a
/// restored reference unavailable in space.
rejected,
/// There was nothing to act on (for example a delete with no rows yet).

View file

@ -435,64 +435,6 @@ void main() {
},
);
group(
'Given a parent with a required (non-nullable) set-null child insert,',
() {
late Person parent;
late RequiredSetNullChild child;
late CrdtMergeDelete remoteParentDelete;
setUp(() async {
await session.db.transactionForUser(testCrdtUserId, (tx) async {
parent = await Person.db.insertRow(
session,
Person(id: const Uuid().v7obj(), name: 'required set-null parent'),
transaction: tx,
);
child = await RequiredSetNullChild.db.insertRow(
session,
RequiredSetNullChild(
id: const Uuid().v7obj(),
name: 'required set-null child',
parentId: parent.id!,
),
transaction: tx,
);
});
});
group('when a concurrent remote parent delete is merged,', () {
setUp(() async {
remoteParentDelete = _deleteChange(
tableName: Person.t.tableName,
rowId: parent.id!,
after: await rowHlc(parent.id!),
);
await session.db.mergeChanges(
[remoteParentDelete],
spaceId: testCrdtUserId,
);
});
test(
'then the parent remains visible because set-null cannot repair the child.',
() async {
final visibleParent = await Person.db.findById(session, parent.id!);
final visibleChild = await RequiredSetNullChild.db.findById(
session,
child.id!,
);
expect(visibleParent, isNotNull);
expect(visibleChild, isNotNull);
expect(visibleChild!.parentId, parent.id);
},
);
});
},
);
group(
'Given a parent with a nullable set-null foreign key whose attempted value was stored on update,',
() {
@ -2093,17 +2035,16 @@ void main() {
// transaction commits: drop `DEFERRABLE INITIALLY DEFERRED` from the schema
// and this group fails with a foreign key violation.
group(
'Given a merge set whose non-nullable child insert carries a foreign key '
'to a parent that is inserted after it,',
'Given a merge set whose non-nullable child insert carries a foreign key to a parent that is inserted after it,',
() {
late Person parent;
late RequiredSetNullChild child;
late RequiredNoActionChild child;
late CrdtMergeSet mergeSet;
setUp(() {
final remoteNodeId = const Uuid().v7obj();
parent = Person(id: const Uuid().v7obj(), name: 'late required parent');
child = RequiredSetNullChild(
child = RequiredNoActionChild(
id: const Uuid().v7obj(),
name: 'early required child',
parentId: parent.id!,
@ -2115,7 +2056,7 @@ void main() {
mergeSet = [
CrdtMergeInsert(
uuidSpaceId: testCrdtUserId,
tableName: RequiredSetNullChild.t.tableName,
tableName: RequiredNoActionChild.t.tableName,
uuidRowId: child.id!,
uuidNodeId: remoteNodeId,
hlcDatetime: childHlc.datetime,
@ -2143,7 +2084,7 @@ void main() {
'then both rows are visible and the child keeps its foreign key.',
() async {
final visibleParent = await Person.db.findById(session, parent.id!);
final visibleChild = await RequiredSetNullChild.db.findById(
final visibleChild = await RequiredNoActionChild.db.findById(
session,
child.id!,
);
@ -2320,7 +2261,7 @@ void main() {
);
});
group('when the root delete replayed after merged,', () {
group('when the root delete is merged twice,', () {
late List<String> visibilityAfterMerge;
late List<String> visibilityAfterReplay;
late List<String> foreignKeyProjectionAfterMerge;
@ -2398,8 +2339,7 @@ void main() {
});
group(
'when a newer remote update for a cascade-hidden descendant is merged '
'after the root delete,',
'when the root delete and a newer descendant update are merged in order,',
() {
late CrdtMergeUpdate remoteOrganizationUpdate;
@ -2434,33 +2374,53 @@ void main() {
expect(await Person.db.findById(session, person.id!), isNull);
},
);
},
);
group('when the root restore is merged afterwards,', () {
setUp(() async {
final remoteCityRestore = _restoreChange(
tableName: City.t.tableName,
rowId: city.id!,
after: remoteOrganizationUpdate.hlc,
);
await session.db.mergeChanges(
[remoteCityRestore],
spaceId: testCrdtUserId,
);
});
group(
'when the root delete, a newer descendant update, and the root restore are merged in order,',
() {
late Organization? visibleOrganization;
test(
'then the descendant becomes visible with the merged update value.',
() async {
final visibleOrganization = await Organization.db.findById(
session,
organization.id!,
);
expect(visibleOrganization, isNotNull);
expect(visibleOrganization!.name, 'renamed while hidden');
expect(visibleOrganization.cityId, city.id);
},
setUp(() async {
await session.db.mergeChanges(
[remoteCityDelete],
spaceId: testCrdtUserId,
);
final remoteOrganizationUpdate = _updateChange(
tableName: Organization.t.tableName,
rowId: organization.id!,
columnName: Organization.t.name.columnName,
value: 'renamed while hidden',
after: (await rowHlc(organization.id!)).maxBetween(
remoteCityDelete.hlc,
),
);
await session.db.mergeChanges(
[remoteOrganizationUpdate],
spaceId: testCrdtUserId,
);
final remoteCityRestore = _restoreChange(
tableName: City.t.tableName,
rowId: city.id!,
after: remoteOrganizationUpdate.hlc,
);
await session.db.mergeChanges(
[remoteCityRestore],
spaceId: testCrdtUserId,
);
visibleOrganization = await Organization.db.findById(
session,
organization.id!,
);
});
test('then the descendant becomes visible with the merged update value.', () {
expect(visibleOrganization, isNotNull);
expect(visibleOrganization!.name, 'renamed while hidden');
expect(visibleOrganization!.cityId, city.id);
});
},
);

Some files were not shown because too many files have changed in this diff Show more