@@ -422,21 +422,13 @@ protected function createDatabase(Database $resource): bool
422422 $ createdAt = $ this ->normalizeDateTime ($ resource ->getCreatedAt ());
423423 $ updatedAt = $ this ->normalizeDateTime ($ resource ->getUpdatedAt (), $ createdAt );
424424
425- // Skip/Upsert: pre-check an existing database. Databases contain the
426- // entire tree of collections + rows, so they are never destructively
427- // reconciled — under Upsert-newer the container metadata is updated
428- // in place (name, enabled, etc.) but children are untouched.
429- //
430- // Fail mode short-circuits: no pre-check, let the create below throw
431- // DuplicateException as designed. Saves N metadata reads on the
432- // common first-time-migration path.
425+ // Fail mode skips the pre-check so library's DuplicateException surfaces on re-migration.
433426 if ($ this ->onDuplicate !== OnDuplicate::Fail) {
434427 $ existing = $ this ->dbForProject ->getDocument ('databases ' , $ resource ->getId ());
435428 $ action = $ this ->onDuplicate ->resolveSchemaAction (
436429 !$ existing ->isEmpty (),
437430 $ updatedAt ,
438431 $ existing ->getUpdatedAt (),
439- // canDrop: false by default — databases hold child tables + rows.
440432 );
441433
442434 if ($ action === SchemaAction::Tolerate) {
@@ -457,7 +449,6 @@ protected function createDatabase(Database $resource): bool
457449 $ resource ->setSequence ($ existing ->getSequence ());
458450 return true ;
459451 }
460- // SchemaAction::Create → fall through to the normal create flow.
461452 }
462453
463454 $ database = $ this ->dbForProject ->createDocument ('databases ' , new UtopiaDocument ([
@@ -541,12 +532,6 @@ protected function createEntity(Table $resource): bool
541532 $ dbForDatabases ->create ();
542533 }
543534
544- // Skip/Upsert: pre-check an existing table. Like databases, tables
545- // contain user data (rows) so they are never destructively
546- // reconciled — under Upsert-newer the container metadata is
547- // updated in place (name, enabled, permissions, etc.) but child
548- // rows are untouched. Attribute/index reconciliation happens
549- // per-resource at a lower layer. Fail short-circuits.
550535 if ($ this ->onDuplicate !== OnDuplicate::Fail) {
551536 $ existing = $ this ->dbForProject ->getDocument (
552537 'database_ ' . $ database ->getSequence (),
@@ -556,7 +541,6 @@ protected function createEntity(Table $resource): bool
556541 !$ existing ->isEmpty (),
557542 $ updatedAt ,
558543 $ existing ->getUpdatedAt (),
559- // canDrop: false by default — tables hold child rows.
560544 );
561545
562546 if ($ action === SchemaAction::Tolerate) {
@@ -580,7 +564,6 @@ protected function createEntity(Table $resource): bool
580564 $ resource ->setSequence ($ existing ->getSequence ());
581565 return true ;
582566 }
583- // SchemaAction::Create → fall through to the normal create flow.
584567 }
585568
586569 $ table = $ this ->dbForProject ->createDocument ('database_ ' . $ database ->getSequence (), new UtopiaDocument ([
@@ -723,17 +706,14 @@ protected function createField(Column|Attribute $resource): bool
723706 $ updatedAt = $ this ->normalizeDateTime ($ resource ->getUpdatedAt (), $ createdAt );
724707 $ dbForDatabases = ($ this ->getDatabasesDB )($ database );
725708
726- // Skip/Upsert: pre-check against `attributes` metadata via one
727- // resolveSchemaAction call. Fail mode short-circuits to preserve
728- // zero-overhead fresh-migration behavior.
729709 $ attributeMetaId = $ database ->getSequence () . '_ ' . $ table ->getSequence () . '_ ' . $ resource ->getKey ();
730710 if ($ this ->onDuplicate !== OnDuplicate::Fail) {
731711 $ existingAttr = $ this ->dbForProject ->getDocument ('attributes ' , $ attributeMetaId );
732712 $ action = $ this ->onDuplicate ->resolveSchemaAction (
733713 !$ existingAttr ->isEmpty (),
734714 $ updatedAt ,
735715 $ existingAttr ->getUpdatedAt (),
736- canDrop: true , // attributes are leaves; column data repopulates via row Upsert
716+ canDrop: true ,
737717 );
738718
739719 if ($ action === SchemaAction::Tolerate) {
@@ -752,7 +732,6 @@ protected function createField(Column|Attribute $resource): bool
752732 $ dbForDatabases ,
753733 );
754734 }
755- // SchemaAction::Create → fall through to the normal create flow.
756735 }
757736
758737 try {
@@ -1033,18 +1012,14 @@ protected function createIndex(Index $resource): bool
10331012 );
10341013 }
10351014
1036- // Skip/Upsert: pre-check against `indexes` metadata via one
1037- // resolveSchemaAction call. Same rule as attributes, except that
1038- // index drops are non-destructive (no row data lives in indexes) so
1039- // rebuild cost is just the index build time. Fail short-circuits.
10401015 $ indexMetaId = $ database ->getSequence () . '_ ' . $ table ->getSequence () . '_ ' . $ resource ->getKey ();
10411016 if ($ this ->onDuplicate !== OnDuplicate::Fail) {
10421017 $ existingIdx = $ this ->dbForProject ->getDocument ('indexes ' , $ indexMetaId );
10431018 $ action = $ this ->onDuplicate ->resolveSchemaAction (
10441019 !$ existingIdx ->isEmpty (),
10451020 $ updatedAt ,
10461021 $ existingIdx ->getUpdatedAt (),
1047- canDrop: true , // indexes are leaves; carry no user data
1022+ canDrop: true ,
10481023 );
10491024
10501025 if ($ action === SchemaAction::Tolerate) {
@@ -1066,7 +1041,6 @@ protected function createIndex(Index $resource): bool
10661041 $ table ->getId ()
10671042 );
10681043 }
1069- // SchemaAction::Create → fall through to the normal create flow.
10701044 }
10711045
10721046 $ index = $ this ->dbForProject ->createDocument ('indexes ' , $ index );
@@ -1244,26 +1218,11 @@ protected function createRecord(Row $resource, bool $isLast): bool
12441218 }
12451219
12461220 /**
1247- * Fully remove an attribute from destination — both metadata documents
1248- * and the physical column(s) — so the caller can create it fresh without
1249- * colliding with any remnants.
1221+ * Drop an attribute (metadata doc + physical column) so it can be recreated.
12501222 *
1251- * For plain attributes this is a parent-side cleanup. For two-way
1252- * relationship attributes, createField writes a second `attributes`
1253- * document on the related table (under {dbSeq}_{relatedTableSeq}_{twoWayKey})
1254- * in addition to the parent-side document. Dropping only the parent
1255- * side leaves the child document dangling and a subsequent recreate
1256- * collides on it with DuplicateException. This method mirrors
1257- * createField's two-doc write so the "reconcile" path is symmetric.
1258- *
1259- * Physical cleanup on the related table is best-effort: the library's
1260- * deleteAttribute on the parent side can cascade to the child column
1261- * depending on relationship handling, so the second deleteAttribute may
1262- * no-op or NotFoundException — both swallowed.
1263- *
1264- * @param UtopiaDocument|null $relatedTable null when the attribute isn't
1265- * a two-way relationship;
1266- * required otherwise.
1223+ * Two-way relationships require mirroring: createField writes a second
1224+ * `attributes` document on the related table keyed by twoWayKey. Dropping
1225+ * only the parent leaves that child doc dangling and the recreate collides.
12671226 */
12681227 private function deleteAttributeCompletely (
12691228 UtopiaDocument $ database ,
@@ -1276,15 +1235,11 @@ private function deleteAttributeCompletely(
12761235 $ collectionId = 'database_ ' . $ database ->getSequence () . '_collection_ ' . $ table ->getSequence ();
12771236 $ attributeMetaId = $ database ->getSequence () . '_ ' . $ table ->getSequence () . '_ ' . $ resource ->getKey ();
12781237
1279- // Parent side.
12801238 $ dbForDatabases ->deleteAttribute ($ collectionId , $ resource ->getKey ());
12811239 $ this ->dbForProject ->deleteDocument ('attributes ' , $ attributeMetaId );
12821240 $ this ->dbForProject ->purgeCachedDocument ('database_ ' . $ database ->getSequence (), $ table ->getId ());
12831241 $ dbForDatabases ->purgeCachedCollection ($ collectionId );
12841242
1285- // Child side, only for two-way relationships. createField writes a
1286- // second `attributes` document keyed on the related table; mirror
1287- // that write here.
12881243 if ($ type !== UtopiaDatabase::VAR_RELATIONSHIP || $ relatedTable === null ) {
12891244 return ;
12901245 }
@@ -1303,13 +1258,12 @@ private function deleteAttributeCompletely(
13031258 try {
13041259 $ this ->dbForProject ->deleteDocument ('attributes ' , $ childMetaId );
13051260 } catch (\Throwable ) {
1306- // Child metadata already gone — interrupted prior run.
1261+ // already gone
13071262 }
13081263 try {
13091264 $ dbForDatabases ->deleteAttribute ($ childCollectionId , $ twoWayKey );
13101265 } catch (\Throwable ) {
1311- // Physical column already gone — parent-side deleteAttribute may
1312- // have cascaded via utopia-php/database relationship handling.
1266+ // parent-side delete may have cascaded
13131267 }
13141268 $ this ->dbForProject ->purgeCachedDocument ('database_ ' . $ database ->getSequence (), $ relatedTable ->getId ());
13151269 $ dbForDatabases ->purgeCachedCollection ($ childCollectionId );
0 commit comments