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.
|
|
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.
|
|
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
|
-
|
|
516
|
-
|
|
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
|