retold-data-service 2.0.29 → 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",
|
|
@@ -511,66 +511,215 @@ module.exports = (pDataClonerService, pOratorServiceServer) =>
|
|
|
511
511
|
// MySQL / MSSQL / PostgreSQL: async execution via connection pool
|
|
512
512
|
if (tmpActiveProvider.pool)
|
|
513
513
|
{
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
// raw T-SQL without wrapping.
|
|
519
|
-
let tmpIsBatch = (tmpProviderName === 'MSSQL') && tmpSQL.indexOf('DECLARE') >= 0
|
|
520
|
-
&& typeof(tmpActiveProvider.pool.request) === 'function';
|
|
521
|
-
|
|
522
|
-
if (tmpIsBatch)
|
|
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) =>
|
|
523
518
|
{
|
|
524
|
-
tmpActiveProvider.pool.
|
|
525
|
-
|
|
519
|
+
let tmpQueryResult = tmpActiveProvider.pool.query(pSQL,
|
|
520
|
+
(pQueryError, pResult) =>
|
|
526
521
|
{
|
|
527
|
-
tmpFable.log.info(`Data Cloner: Migration applied: ${tmpSQL}`);
|
|
528
|
-
tmpExecutedStatements.push(tmpSQL);
|
|
529
|
-
return fExecNext(pIndex + 1);
|
|
530
|
-
})
|
|
531
|
-
.catch((pBatchError) =>
|
|
532
|
-
{
|
|
533
|
-
tmpFable.log.warn(`Data Cloner: Migration failed: ${tmpSQL} — ${pBatchError}`);
|
|
534
|
-
return fExecNext(pIndex + 1);
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
else
|
|
538
|
-
{
|
|
539
|
-
// MySQL uses callbacks; MSSQL/PostgreSQL pool.query returns a promise.
|
|
540
|
-
let tmpQueryResult = tmpActiveProvider.pool.query(tmpSQL,
|
|
541
|
-
(pQueryError) =>
|
|
542
|
-
{
|
|
543
|
-
// Callback-style (MySQL) — only fires if pool.query
|
|
544
|
-
// accepted the callback parameter
|
|
545
522
|
if (pQueryError)
|
|
546
523
|
{
|
|
547
|
-
|
|
524
|
+
return fDone(pQueryError);
|
|
548
525
|
}
|
|
549
|
-
|
|
550
|
-
{
|
|
551
|
-
tmpFable.log.info(`Data Cloner: Migration applied: ${tmpSQL}`);
|
|
552
|
-
tmpExecutedStatements.push(tmpSQL);
|
|
553
|
-
}
|
|
554
|
-
return fExecNext(pIndex + 1);
|
|
526
|
+
return fDone(null, pResult);
|
|
555
527
|
});
|
|
556
528
|
|
|
557
|
-
// Promise-style (MSSQL / PostgreSQL) — if query returned a thenable,
|
|
558
|
-
// handle it; the callback above will not fire in this case.
|
|
559
529
|
if (tmpQueryResult && typeof(tmpQueryResult.then) === 'function')
|
|
560
530
|
{
|
|
561
531
|
tmpQueryResult
|
|
562
|
-
.then(() =>
|
|
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)
|
|
563
612
|
{
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
.catch((pPromiseError) =>
|
|
613
|
+
tmpIndexes = pIxResult.recordset;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
fRunQuery(tmpIxColQuery, (pIxColErr, pIxColResult) =>
|
|
569
617
|
{
|
|
570
|
-
|
|
571
|
-
|
|
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);
|
|
572
703
|
});
|
|
573
|
-
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
else
|
|
708
|
+
{
|
|
709
|
+
// Non-MSSQL-ALTER-COLUMN: standard single-statement execution
|
|
710
|
+
fRunQuery(tmpSQL, (pQueryError) =>
|
|
711
|
+
{
|
|
712
|
+
if (pQueryError)
|
|
713
|
+
{
|
|
714
|
+
tmpFable.log.warn(`Data Cloner: Migration failed: ${tmpSQL} — ${pQueryError}`);
|
|
715
|
+
}
|
|
716
|
+
else
|
|
717
|
+
{
|
|
718
|
+
tmpFable.log.info(`Data Cloner: Migration applied: ${tmpSQL}`);
|
|
719
|
+
tmpExecutedStatements.push(tmpSQL);
|
|
720
|
+
}
|
|
721
|
+
return fExecNext(pIndex + 1);
|
|
722
|
+
});
|
|
574
723
|
}
|
|
575
724
|
}
|
|
576
725
|
else
|