@happyvertical/smrt-core 0.43.6 → 0.43.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -318,7 +318,7 @@ var SchemaComparer = class {
318
318
  diff.added_tables.push(schema);
319
319
  diff.has_changes = true;
320
320
  } else {
321
- const tableChanges = await this.compareTable(tableName, schema);
321
+ const tableChanges = await this.compareTable(tableName, schema, manifestSchemas);
322
322
  if (tableChanges.length > 0) {
323
323
  diff.changes.push(...tableChanges);
324
324
  if (tableChanges.some((change) => !isInfoOnlyChange(change))) diff.has_changes = true;
@@ -340,7 +340,7 @@ var SchemaComparer = class {
340
340
  /**
341
341
  * Compare a single table's schema to manifest
342
342
  */
343
- async compareTable(tableName, manifest) {
343
+ async compareTable(tableName, manifest, manifestSchemas = {}) {
344
344
  const changes = [];
345
345
  const dbSchema = await this.db.getTableSchema?.(tableName);
346
346
  if (!dbSchema) return changes;
@@ -354,10 +354,10 @@ var SchemaComparer = class {
354
354
  const dbIndexPredicates = await this.getDbIndexPredicates(tableName);
355
355
  const indexChanges = this.compareIndexes(tableName, manifest, dbSchema, dbIndexPredicates);
356
356
  changes.push(...indexChanges);
357
- changes.push(...await this.compareForeignKeys(tableName, manifest, dbSchema));
357
+ changes.push(...await this.compareForeignKeys(tableName, manifest, dbSchema, manifestSchemas));
358
358
  return changes;
359
359
  }
360
- async compareForeignKeys(tableName, manifest, dbSchema) {
360
+ async compareForeignKeys(tableName, manifest, dbSchema, manifestSchemas) {
361
361
  const changes = [];
362
362
  const liveForeignKeys = dbSchema.foreignKeys || [];
363
363
  const declaredForeignKeys = schemaForeignKeys(manifest);
@@ -392,8 +392,18 @@ var SchemaComparer = class {
392
392
  const expectedDelete = foreignKey.onDelete === void 0 ? "NO ACTION" : requireForeignKeyAction(foreignKey.onDelete, `${tableName}.${foreignKey.column} ON DELETE`);
393
393
  const expectedUpdate = foreignKey.onUpdate === void 0 ? "NO ACTION" : requireForeignKeyAction(foreignKey.onUpdate, `${tableName}.${foreignKey.column} ON UPDATE`);
394
394
  if (liveForeignKeys.some((live) => live.column === foreignKey.column && live.referencesTable === foreignKey.referencesTable && live.referencesColumn === foreignKey.referencesColumn && (normalizeForeignKeyAction(live.onDelete) || "NO ACTION") === expectedDelete && (normalizeForeignKeyAction(live.onUpdate) || "NO ACTION") === expectedUpdate)) continue;
395
- const detectorSql = renderForeignKeyOrphanDetector(tableName, foreignKey);
396
- const repairSql = renderForeignKeyOrphanRepair(tableName, foreignKey);
395
+ const orphanOptions = await this.getForeignKeyOrphanOptions(tableName, manifest, dbSchema, foreignKey, manifestSchemas);
396
+ const detectorSql = renderForeignKeyOrphanDetector(tableName, foreignKey, {
397
+ engine: this.engine,
398
+ uuidComparison: orphanOptions.uuidComparison,
399
+ uuidCastSide: orphanOptions.uuidCastSide
400
+ });
401
+ const repairSql = renderForeignKeyOrphanRepair(tableName, foreignKey, {
402
+ engine: this.engine,
403
+ uuidComparison: orphanOptions.uuidComparison,
404
+ uuidCastSide: orphanOptions.uuidCastSide,
405
+ nullable: orphanOptions.nullable
406
+ });
397
407
  if (liveForeignKeys.some((live) => live.column === foreignKey.column)) {
398
408
  changes.push({
399
409
  type: "add_foreign_key",
@@ -423,7 +433,7 @@ var SchemaComparer = class {
423
433
  });
424
434
  continue;
425
435
  }
426
- if (Boolean(dbSchema.columns[foreignKey.column]) && await this.foreignKeyHasOrphans(tableName, foreignKey)) {
436
+ if (Boolean(dbSchema.columns[foreignKey.column]) && await this.foreignKeyHasOrphans(tableName, foreignKey, orphanOptions)) {
427
437
  changes.push({
428
438
  type: "add_foreign_key",
429
439
  table: tableName,
@@ -448,15 +458,57 @@ var SchemaComparer = class {
448
458
  }
449
459
  return changes;
450
460
  }
451
- async foreignKeyHasOrphans(tableName, foreignKey) {
461
+ async foreignKeyHasOrphans(tableName, foreignKey, options) {
452
462
  try {
453
- const result = await this.db.query(renderForeignKeyOrphanDetector(tableName, foreignKey, { limitOne: true }));
463
+ const result = await this.db.query(renderForeignKeyOrphanDetector(tableName, foreignKey, {
464
+ engine: this.engine,
465
+ limitOne: true,
466
+ uuidComparison: options.uuidComparison,
467
+ uuidCastSide: options.uuidCastSide
468
+ }));
454
469
  return (Array.isArray(result) ? result : result.rows || []).length > 0;
455
470
  } catch (error) {
456
- logger.debug(`[SchemaComparer] Foreign-key orphan probe unavailable for ${tableName}.${foreignKey.column}; refusing automatic constraint addition`, { error: error instanceof Error ? error.message : String(error) });
457
- return true;
471
+ throw new Error(`[SchemaComparer] Cannot probe ${tableName}.${foreignKey.column} for orphan rows; refusing automatic constraint addition: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
458
472
  }
459
473
  }
474
+ async getForeignKeyOrphanOptions(tableName, manifest, dbSchema, foreignKey, manifestSchemas) {
475
+ const sourceColumn = manifest.columns[foreignKey.column];
476
+ const targetColumn = manifestSchemas[foreignKey.referencesTable]?.columns[foreignKey.referencesColumn];
477
+ const nullable = sourceColumn?.notNull !== true;
478
+ const uuidComparison = sourceColumn?.type === "UUID" && (targetColumn === void 0 || targetColumn.type === "UUID");
479
+ if (!uuidComparison) return {
480
+ nullable,
481
+ uuidComparison
482
+ };
483
+ const parentSchema = foreignKey.referencesTable === tableName ? dbSchema : await this.db.getTableSchema?.(foreignKey.referencesTable);
484
+ const childType = dbSchema.columns[foreignKey.column]?.type;
485
+ const parentType = parentSchema?.columns[foreignKey.referencesColumn]?.type;
486
+ if (!childType || !parentType) return {
487
+ nullable,
488
+ uuidComparison
489
+ };
490
+ const childTypeNormalized = this.normalizeType(childType);
491
+ const parentTypeNormalized = this.normalizeType(parentType);
492
+ if (childTypeNormalized === parentTypeNormalized) return {
493
+ nullable,
494
+ uuidComparison: false
495
+ };
496
+ if (childTypeNormalized === "UUID") return {
497
+ nullable,
498
+ uuidComparison,
499
+ uuidCastSide: "parent"
500
+ };
501
+ if (parentTypeNormalized === "UUID") return {
502
+ nullable,
503
+ uuidComparison,
504
+ uuidCastSide: "child"
505
+ };
506
+ return {
507
+ nullable,
508
+ uuidComparison,
509
+ uuidCastSide: "both"
510
+ };
511
+ }
460
512
  /**
461
513
  * Compare columns between manifest and database.
462
514
  *