retold-data-service 2.0.28 → 2.0.30

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-data-service",
3
- "version": "2.0.28",
3
+ "version": "2.0.30",
4
4
  "description": "Serve up a whole model!",
5
5
  "main": "source/Retold-Data-Service.js",
6
6
  "bin": {
@@ -73,7 +73,7 @@
73
73
  "meadow-connection-mysql": "^1.0.14",
74
74
  "meadow-endpoints": "^4.0.15",
75
75
  "meadow-integration": "^1.0.21",
76
- "meadow-migrationmanager": "^0.0.6",
76
+ "meadow-migrationmanager": "^0.0.7",
77
77
  "orator": "^6.0.4",
78
78
  "orator-http-proxy": "^1.0.5",
79
79
  "orator-serviceserver-restify": "^2.0.10",
@@ -509,14 +509,206 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
509
509
  }
510
510
 
511
511
  // MySQL / MSSQL / PostgreSQL: async execution via connection pool
512
- // MySQL pool.query uses callbacks; MSSQL/PostgreSQL pool.query returns a promise.
513
512
  if (tmpActiveProvider.pool)
514
513
  {
515
- let tmpQueryResult = tmpActiveProvider.pool.query(tmpSQL,
516
- (pQueryError) =>
514
+ // Helper: run a single SQL statement via pool.query,
515
+ // handling both callback-style (MySQL) and promise-style
516
+ // (MSSQL / PostgreSQL) drivers.
517
+ let fRunQuery = (pSQL, fDone) =>
518
+ {
519
+ let tmpQueryResult = tmpActiveProvider.pool.query(pSQL,
520
+ (pQueryError, pResult) =>
521
+ {
522
+ if (pQueryError)
523
+ {
524
+ return fDone(pQueryError);
525
+ }
526
+ return fDone(null, pResult);
527
+ });
528
+
529
+ if (tmpQueryResult && typeof(tmpQueryResult.then) === 'function')
530
+ {
531
+ tmpQueryResult
532
+ .then((pResult) => fDone(null, pResult))
533
+ .catch((pError) => fDone(pError));
534
+ }
535
+ };
536
+
537
+ // For MSSQL ALTER COLUMN, drop dependent objects first,
538
+ // then alter, then restore them. Each step is a simple
539
+ // individual query that works with pool.query().
540
+ let tmpIsMSSQLAlterColumn = (tmpProviderName === 'MSSQL')
541
+ && tmpSQL.indexOf('ALTER COLUMN') >= 0;
542
+
543
+ if (tmpIsMSSQLAlterColumn)
544
+ {
545
+ // Parse table and column names from the statement:
546
+ // ALTER TABLE [TableName] ALTER COLUMN [ColName] TYPE...
547
+ let tmpMatch = tmpSQL.match(/ALTER TABLE \[([^\]]+)\] ALTER COLUMN \[([^\]]+)\] (.+)/);
548
+ if (!tmpMatch)
549
+ {
550
+ tmpFable.log.warn(`Data Cloner: Could not parse ALTER COLUMN statement: ${tmpSQL}`);
551
+ return fExecNext(pIndex + 1);
552
+ }
553
+ let tmpTblName = tmpMatch[1];
554
+ let tmpColNameParsed = tmpMatch[2];
555
+ let tmpNewType = tmpMatch[3];
556
+
557
+ // Determine the default value to re-add after ALTER.
558
+ // Look it up from the diff's target schema via the
559
+ // MigrationGenerator helpers.
560
+ let tmpMigGen = tmpMM._migrationGenerator;
561
+ let tmpDefaultValue = null;
562
+ // Find the column in the diff to get its DataType and Size
563
+ for (let t = 0; t < tmpDiff.TablesModified.length; t++)
564
+ {
565
+ let tmpMod = tmpDiff.TablesModified[t];
566
+ if (tmpMod.TableName !== tmpTblName) continue;
567
+ let tmpAllModCols = (tmpMod.ColumnsModified || []);
568
+ for (let mc = 0; mc < tmpAllModCols.length; mc++)
569
+ {
570
+ if (tmpAllModCols[mc].Column === tmpColNameParsed)
571
+ {
572
+ let tmpTargetDataType = tmpAllModCols[mc].Changes.DataType
573
+ ? tmpAllModCols[mc].Changes.DataType.To
574
+ : (tmpAllModCols[mc].DataType || null);
575
+ let tmpTargetSize = tmpAllModCols[mc].Changes.Size
576
+ ? tmpAllModCols[mc].Changes.Size.To
577
+ : (tmpAllModCols[mc].hasOwnProperty('Size') ? tmpAllModCols[mc].Size : null);
578
+ if (tmpTargetDataType)
579
+ {
580
+ let tmpFullNative = tmpMigGen._mapDataTypeToNative(tmpTargetDataType, tmpTargetSize, 'MSSQL');
581
+ tmpDefaultValue = tmpMigGen._extractDefault(tmpFullNative);
582
+ }
583
+ break;
584
+ }
585
+ }
586
+ }
587
+
588
+ // Step 1: Query default constraint names on this column
589
+ let tmpDCQuery = `SELECT dc.name FROM sys.default_constraints dc INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id WHERE dc.parent_object_id = OBJECT_ID(N'${tmpTblName}') AND c.name = N'${tmpColNameParsed}'`;
590
+
591
+ // Step 2: Query index info for indexes containing this column
592
+ let tmpIxQuery = `SELECT DISTINCT i.name AS IndexName, i.is_unique AS IsUnique FROM sys.indexes i INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = OBJECT_ID(N'${tmpTblName}') AND c.name = N'${tmpColNameParsed}' AND i.is_primary_key = 0 AND i.type IN (1, 2)`;
593
+
594
+ // Step 3: Query index column details (for recreation)
595
+ let tmpIxColQuery = `SELECT i.name AS IndexName, c.name AS ColName, ic.is_included_column AS IsIncluded, ic.key_ordinal AS KeyOrdinal, ic.index_column_id AS ColOrder FROM sys.indexes i INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE i.object_id = OBJECT_ID(N'${tmpTblName}') AND i.is_primary_key = 0 AND i.type IN (1, 2) AND i.index_id IN (SELECT ic2.index_id FROM sys.index_columns ic2 INNER JOIN sys.columns c2 ON ic2.object_id = c2.object_id AND ic2.column_id = c2.column_id WHERE ic2.object_id = OBJECT_ID(N'${tmpTblName}') AND c2.name = N'${tmpColNameParsed}') ORDER BY i.name, ic.is_included_column, ic.key_ordinal, ic.index_column_id`;
596
+
597
+ tmpFable.log.info(`Data Cloner: MSSQL ALTER COLUMN [${tmpColNameParsed}] — querying dependent objects...`);
598
+
599
+ // Run queries in sequence: get constraints, get indexes, drop, alter, recreate
600
+ fRunQuery(tmpDCQuery, (pDCErr, pDCResult) =>
601
+ {
602
+ let tmpConstraintNames = [];
603
+ if (!pDCErr && pDCResult && pDCResult.recordset)
604
+ {
605
+ tmpConstraintNames = pDCResult.recordset.map((pRow) => pRow.name);
606
+ }
607
+
608
+ fRunQuery(tmpIxQuery, (pIxErr, pIxResult) =>
609
+ {
610
+ let tmpIndexes = [];
611
+ if (!pIxErr && pIxResult && pIxResult.recordset)
612
+ {
613
+ tmpIndexes = pIxResult.recordset;
614
+ }
615
+
616
+ fRunQuery(tmpIxColQuery, (pIxColErr, pIxColResult) =>
617
+ {
618
+ let tmpIndexColumns = [];
619
+ if (!pIxColErr && pIxColResult && pIxColResult.recordset)
620
+ {
621
+ tmpIndexColumns = pIxColResult.recordset;
622
+ }
623
+
624
+ // Build the list of statements to execute in sequence:
625
+ // 1. Drop default constraints
626
+ // 2. Drop indexes
627
+ // 3. ALTER COLUMN
628
+ // 4. Recreate indexes
629
+ // 5. Re-add default constraint
630
+ let tmpPrePostStatements = [];
631
+
632
+ for (let dc = 0; dc < tmpConstraintNames.length; dc++)
633
+ {
634
+ tmpPrePostStatements.push({ phase: 'pre', sql: `ALTER TABLE [${tmpTblName}] DROP CONSTRAINT [${tmpConstraintNames[dc]}]` });
635
+ }
636
+
637
+ for (let ix = 0; ix < tmpIndexes.length; ix++)
638
+ {
639
+ tmpPrePostStatements.push({ phase: 'pre', sql: `DROP INDEX [${tmpIndexes[ix].IndexName}] ON [${tmpTblName}]` });
640
+ }
641
+
642
+ tmpPrePostStatements.push({ phase: 'alter', sql: tmpSQL });
643
+
644
+ // Recreate indexes
645
+ for (let ix = 0; ix < tmpIndexes.length; ix++)
646
+ {
647
+ let tmpIxName = tmpIndexes[ix].IndexName;
648
+ let tmpKeyCols = tmpIndexColumns
649
+ .filter((pR) => pR.IndexName === tmpIxName && !pR.IsIncluded)
650
+ .sort((a, b) => a.KeyOrdinal - b.KeyOrdinal)
651
+ .map((pR) => `[${pR.ColName}]`);
652
+ let tmpInclCols = tmpIndexColumns
653
+ .filter((pR) => pR.IndexName === tmpIxName && pR.IsIncluded)
654
+ .sort((a, b) => a.ColOrder - b.ColOrder)
655
+ .map((pR) => `[${pR.ColName}]`);
656
+ let tmpCreateIdx = (tmpIndexes[ix].IsUnique ? 'CREATE UNIQUE' : 'CREATE')
657
+ + ` INDEX [${tmpIxName}] ON [${tmpTblName}] (${tmpKeyCols.join(', ')})`;
658
+ if (tmpInclCols.length > 0)
659
+ {
660
+ tmpCreateIdx += ` INCLUDE (${tmpInclCols.join(', ')})`;
661
+ }
662
+ tmpPrePostStatements.push({ phase: 'post', sql: tmpCreateIdx });
663
+ }
664
+
665
+ // Re-add default
666
+ if (tmpDefaultValue)
667
+ {
668
+ tmpPrePostStatements.push({ phase: 'post', sql: `ALTER TABLE [${tmpTblName}] ADD DEFAULT ${tmpDefaultValue} FOR [${tmpColNameParsed}]` });
669
+ }
670
+
671
+ if (tmpConstraintNames.length > 0 || tmpIndexes.length > 0)
672
+ {
673
+ tmpFable.log.info(`Data Cloner: MSSQL ALTER COLUMN [${tmpColNameParsed}] — dropping ${tmpConstraintNames.length} constraint(s), ${tmpIndexes.length} index(es) before alter`);
674
+ }
675
+
676
+ // Execute all statements in sequence
677
+ let fRunStep = (pStepIndex) =>
678
+ {
679
+ if (pStepIndex >= tmpPrePostStatements.length)
680
+ {
681
+ tmpFable.log.info(`Data Cloner: Migration applied: ${tmpSQL}`);
682
+ tmpExecutedStatements.push(tmpSQL);
683
+ return fExecNext(pIndex + 1);
684
+ }
685
+ let tmpStep = tmpPrePostStatements[pStepIndex];
686
+ fRunQuery(tmpStep.sql, (pStepErr) =>
687
+ {
688
+ if (pStepErr)
689
+ {
690
+ tmpFable.log.warn(`Data Cloner: Migration step failed (${tmpStep.phase}): ${tmpStep.sql} — ${pStepErr}`);
691
+ if (tmpStep.phase === 'alter')
692
+ {
693
+ // If the ALTER itself failed, skip post steps
694
+ tmpFable.log.warn(`Data Cloner: Migration failed: ${tmpSQL} — ${pStepErr}`);
695
+ return fExecNext(pIndex + 1);
696
+ }
697
+ }
698
+ return fRunStep(pStepIndex + 1);
699
+ });
700
+ };
701
+
702
+ fRunStep(0);
703
+ });
704
+ });
705
+ });
706
+ }
707
+ else
708
+ {
709
+ // Non-MSSQL-ALTER-COLUMN: standard single-statement execution
710
+ fRunQuery(tmpSQL, (pQueryError) =>
517
711
  {
518
- // Callback-style (MySQL) — only fires if pool.query
519
- // accepted the callback parameter
520
712
  if (pQueryError)
521
713
  {
522
714
  tmpFable.log.warn(`Data Cloner: Migration failed: ${tmpSQL} — ${pQueryError}`);
@@ -528,23 +720,6 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
528
720
  }
529
721
  return fExecNext(pIndex + 1);
530
722
  });
531
-
532
- // Promise-style (MSSQL / PostgreSQL) — if query returned a thenable,
533
- // handle it; the callback above will not fire in this case.
534
- if (tmpQueryResult && typeof(tmpQueryResult.then) === 'function')
535
- {
536
- tmpQueryResult
537
- .then(() =>
538
- {
539
- tmpFable.log.info(`Data Cloner: Migration applied: ${tmpSQL}`);
540
- tmpExecutedStatements.push(tmpSQL);
541
- return fExecNext(pIndex + 1);
542
- })
543
- .catch((pPromiseError) =>
544
- {
545
- tmpFable.log.warn(`Data Cloner: Migration failed: ${tmpSQL} — ${pPromiseError}`);
546
- return fExecNext(pIndex + 1);
547
- });
548
723
  }
549
724
  }
550
725
  else