autosql
Version:
An auto-parser of JSON into SQL.
706 lines (705 loc) • 30.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeKeysArray = void 0;
exports.isObject = isObject;
exports.shuffleArray = shuffleArray;
exports.validateConfig = validateConfig;
exports.calculateColumnLength = calculateColumnLength;
exports.normalizeNumber = normalizeNumber;
exports.mergeColumnLengths = mergeColumnLengths;
exports.setToArray = setToArray;
exports.parseDatabaseLength = parseDatabaseLength;
exports.parseDatabaseMetaData = parseDatabaseMetaData;
exports.generateCombinations = generateCombinations;
exports.isCombinationUnique = isCombinationUnique;
exports.tableChangesExist = tableChangesExist;
exports.isMetaDataHeader = isMetaDataHeader;
exports.estimateRowSize = estimateRowSize;
exports.isValidDataFormat = isValidDataFormat;
exports.organizeSplitTable = organizeSplitTable;
exports.organizeSplitData = organizeSplitData;
exports.splitInsertData = splitInsertData;
exports.getInsertValues = getInsertValues;
exports.sqlize = sqlize;
exports.getNextTableName = getNextTableName;
exports.getTempTableName = getTempTableName;
exports.getTrueTableName = getTrueTableName;
exports.getHistoryTableName = getHistoryTableName;
exports.wait_x_mseconds = wait_x_mseconds;
exports.generateSafeConstraintName = generateSafeConstraintName;
exports.normalizeResultKeys = normalizeResultKeys;
exports.throwIfFailedResults = throwIfFailedResults;
exports.normalizeName = normalizeName;
const defaults_1 = require("../config/defaults");
const groupings_1 = require("../config/groupings");
const crypto_1 = __importDefault(require("crypto"));
function isObject(val) {
return val !== null && typeof val === "object";
}
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function validateConfig(config) {
try {
if (!config.sqlDialect) {
throw new Error("Please provide a sqlDialect (such as pgsql, mysql) as part of the configuration object.");
}
// Define default values
const defaultConfig = {
sqlDialect: config.sqlDialect, // Keep required field
pseudoUnique: defaults_1.defaults.pseudoUnique,
autoIndexing: defaults_1.defaults.autoIndexing,
sampling: defaults_1.defaults.sampling,
samplingMinimum: defaults_1.defaults.samplingMinimum,
metaData: config.metaData || {}, // Ensuring headers remain intact
maxKeyLength: defaults_1.defaults.maxKeyLength,
autoSplit: defaults_1.defaults.autoSplit,
useWorkers: defaults_1.defaults.useWorkers,
maxWorkers: defaults_1.defaults.maxWorkers,
useStagingInsert: defaults_1.defaults.useStagingInsert,
addHistory: defaults_1.defaults.addHistory,
addTimestamps: defaults_1.defaults.addTimestamps,
decimalMaxLength: defaults_1.defaults.decimalMaxLength,
addNested: defaults_1.defaults.addNested,
excludeBlankColumns: defaults_1.defaults.excludeBlankColumns,
};
// Merge provided config with defaults
return { ...defaultConfig, ...config };
}
catch (error) {
throw error;
}
}
function calculateColumnLength(column, dataPoint, sqlLookupTable) {
if (sqlLookupTable.decimals.includes(column.type)) {
column.decimal = column.decimal ?? 0;
const decimalLen = dataPoint.includes(".") ? dataPoint.split(".")[1].length + 1 : 0;
column.decimal = Math.max(column.decimal, decimalLen);
column.decimal = Math.min(column.decimal, sqlLookupTable.decimals_max_length || 10);
const integerLen = dataPoint.split(".")[0].length;
column.length = Math.max(column.length, integerLen + column.decimal + 3);
}
else {
column.length = Math.max(column.length, dataPoint.length);
}
}
function normalizeNumber(input, thousandsIndicatorOverride, decimalIndicatorOverride) {
if ((thousandsIndicatorOverride && !decimalIndicatorOverride) || (!thousandsIndicatorOverride && decimalIndicatorOverride)) {
throw new Error("Both 'thousandsIndicatorOverride' and 'decimalIndicatorOverride' must be provided together.");
}
let inputStr = String(input);
let overridden = false;
if (thousandsIndicatorOverride && decimalIndicatorOverride) {
const THOUSANDS_INDICATORS = [",", "#*#*", "%*%*"];
const DECIMAL_INDICATORS = [".", "%*%*", "#*#*"];
const usedThousands = thousandsIndicatorOverride;
const usedDecimal = decimalIndicatorOverride;
const unusedThousands = THOUSANDS_INDICATORS.filter(ind => ind !== usedThousands && ind !== usedDecimal)[0];
const unusedDecimal = DECIMAL_INDICATORS.filter(ind => ind !== usedThousands && ind !== usedDecimal)[0];
overridden = true;
// Temporarily replace thousands and decimal indicators with placeholders
let tempinputStr = inputStr.replaceAll(usedThousands, unusedThousands);
tempinputStr = tempinputStr.replaceAll(usedDecimal, unusedDecimal);
// Replace placeholders with final characters (comma for thousands, dot for decimal)
tempinputStr = tempinputStr.replaceAll(unusedThousands, ",").replaceAll(unusedDecimal, ".");
inputStr = tempinputStr;
}
// 🚨 Ensure `-` appears only at the start
if (inputStr.includes("-") && inputStr.indexOf("-") !== 0)
return null;
const isNegative = inputStr.startsWith("-");
if (isNegative)
inputStr = inputStr.slice(1); // Remove `-` temporarily for processing
if (!inputStr || /[^0-9., `']/.test(inputStr))
return null; // Reject if non-numeric characters exist. Allowing ` and ' as part of the Swiss number format
const dotCount = (inputStr.match(/\./g) || []).length;
let commaCount = (inputStr.match(/,/g) || []).length;
// 🔍 Detect and normalize Swiss format if no commas are present but apostrophes exist
if (commaCount === 0 && inputStr.includes("'")) {
inputStr = inputStr.replace(/'/g, ","); // ✅ Convert apostrophes to commas
commaCount = (inputStr.match(/,/g) || []).length;
}
if (commaCount === 0 && inputStr.includes("`")) {
inputStr = inputStr.replace(/`/g, ",");
commaCount = (inputStr.match(/,/g) || []).length;
}
inputStr = inputStr.replace(/ /g, "");
// 🚨 Reject cases
if (!/\d/.test(inputStr) || // No digits present
(dotCount > 1 && commaCount > 1) || // Too many of both
inputStr.includes(".,") || inputStr.includes(",.") || // Misplaced combinations
/\d[.,]{2,}\d/.test(inputStr) // Double separators like "1..234"
) {
return null;
}
// 🚨 Check incorrect ordering of separators
const firstComma = inputStr.indexOf(",");
const lastComma = inputStr.lastIndexOf(",");
const firstDot = inputStr.indexOf(".");
const lastDot = inputStr.lastIndexOf(".");
if (firstComma !== -1 && firstDot !== -1 && // Both exist
((firstComma < firstDot && dotCount > 1) || // Comma first, but multiple dots
(firstDot < firstComma && commaCount > 1) || // Dot first, but multiple commas
(firstComma < firstDot && firstDot < lastComma) || // Comma first, but comma after first dot
(firstDot < firstComma && firstComma < lastDot) // Dot first, but dot after first comma
)) {
return null;
}
// Determine thousands and decimal indicators
let thousandsIndicator = "";
let decimalIndicator = "";
if (overridden) {
thousandsIndicator = ",";
decimalIndicator = ".";
}
else if (dotCount === 1 && commaCount === 1) {
thousandsIndicator = firstComma < firstDot ? "," : ".";
decimalIndicator = thousandsIndicator === "," ? "." : ",";
}
else if (dotCount > 1) {
thousandsIndicator = ".";
decimalIndicator = ",";
}
else if (commaCount > 1) {
thousandsIndicator = ",";
decimalIndicator = ".";
}
else {
// Only one separator exists, assume it is the decimal separator
thousandsIndicator = "";
decimalIndicator = dotCount === 1 ? "." : ",";
}
const decimalSplit = inputStr.split(decimalIndicator);
if (decimalSplit.length > 2)
return null; // More than one decimal, invalid
let preDecimal = decimalSplit[0];
let postDecimal = decimalSplit[1] || ""; // Optional decimal part
// Validate thousands separator formatting
if (thousandsIndicator) {
const thousandsSplit = preDecimal.split(thousandsIndicator);
if (thousandsSplit.length == 1) {
const part = thousandsSplit[0];
if (part.length > 3) {
return null;
}
}
else {
// 🔍 Detect if the format is Indian-style or Western-style
const isWesternFormat = thousandsSplit.length > 1 && thousandsSplit.every((part, i) => (i === 0 ? part.length <= 3 : part.length === 3));
const isIndianFormat = thousandsSplit.length > 1 && thousandsSplit.every((part, i) => (i === 0 ? part.length <= 2 : i === thousandsSplit.length - 1 ? part.length === 3 : part.length === 2));
if (!isWesternFormat && !isIndianFormat)
return null; // ❌ Reject if it fits neither format
// ✅ If valid, remove thousands separators
}
preDecimal = thousandsSplit.join("");
}
const normalized = `${isNegative ? "-" : ""}${preDecimal}${postDecimal ? "." + postDecimal : ""}`;
return normalized;
}
function mergeColumnLengths(lengthA, lengthB) {
if (!lengthA && !lengthB)
return undefined;
const parseLength = (length) => {
const parts = length.split(",").map(Number);
return parts.length === 2 ? parts : [parts[0], 0]; // Ensure decimal part exists
};
const [lenA, decA] = lengthA ? parseLength(lengthA) : [0, 0];
const [lenB, decB] = lengthB ? parseLength(lengthB) : [0, 0];
return `${Math.max(lenA, lenB)},${Math.max(decA, decB)}`;
}
function setToArray(inputSet) {
return [...inputSet]; // Spread operator converts Set to an array
}
function parseDatabaseLength(lengthStr) {
if (!lengthStr)
return {};
const parts = lengthStr.split(",").map(Number);
const length = isNaN(parts[0]) ? undefined : parts[0];
const decimal = parts.length === 2 && !isNaN(parts[1]) ? parts[1] : undefined;
return { length, decimal };
}
function parseDatabaseMetaData(rows, dialectConfig) {
if (!rows || rows.length === 0)
return null; // Return null if no data
const hasTableName = rows.some(row => "table_name" in row || "TABLE_NAME" in row);
const hasNoTableName = rows.some(row => !("table_name" in row) && !("TABLE_NAME" in row));
if (hasTableName && hasNoTableName) {
throw new Error("Inconsistent data: Some rows contain 'table_name' while others do not.");
}
const metadata = {};
rows.forEach((row) => {
const normalizedRow = Object.keys(row).reduce((acc, key) => {
acc[key.toLowerCase()] = row[key];
return acc;
}, {});
if (!normalizedRow.column_name)
return; // Skip invalid rows
const lengthInfo = parseDatabaseLength(String(normalizedRow["length"]));
const dataType = dialectConfig?.translate?.serverToLocal[normalizedRow.data_type.toLowerCase()] ||
normalizedRow["data_type"].toLowerCase();
const columnKey = (normalizedRow["column_key"] || "").toUpperCase();
let normalizedLength = lengthInfo.length;
if (dialectConfig?.noLength.includes(dataType)) {
normalizedLength = undefined;
}
else if (dialectConfig?.optionalLength.includes(dataType) && lengthInfo.length === undefined) {
normalizedLength = undefined;
}
const autoIncrement = String(normalizedRow["extra"] || "").includes("auto_increment") ||
String(normalizedRow["column_default"] || "").includes("nextval");
const tableName = normalizedRow.table_name || "noTableName"; // Default for single-table case
if (!metadata[tableName]) {
metadata[tableName] = {};
}
metadata[tableName][normalizedRow.column_name] = {
type: dataType,
length: normalizedLength,
allowNull: normalizedRow["is_nullable"] === "YES",
unique: columnKey === "UNIQUE",
primary: columnKey === "PRIMARY",
index: columnKey === "INDEX",
autoIncrement: autoIncrement,
decimal: lengthInfo.decimal ?? undefined,
default: normalizedRow["column_default"],
};
});
return hasTableName ? metadata : metadata["noTableName"] || null;
}
function generateCombinations(array, length) {
if (length === 1)
return array.map(el => [el]);
const combinations = [];
for (let i = 0; i < array.length; i++) {
const smallerCombinations = generateCombinations(array.slice(i + 1), length - 1);
for (const smaller of smallerCombinations) {
combinations.push([array[i], ...smaller]);
}
}
return combinations;
}
function isCombinationUnique(data, columns) {
const seenValues = new Set();
for (const row of data) {
const key = columns.map(col => row[col]).join("|");
if (seenValues.has(key))
return false;
seenValues.add(key);
}
return true;
}
function tableChangesExist(alterTableChanges) {
if (Object.keys(alterTableChanges.addColumns).length > 0 ||
Object.keys(alterTableChanges.modifyColumns).length > 0 ||
alterTableChanges.dropColumns.length > 0 ||
alterTableChanges.renameColumns.length > 0 ||
alterTableChanges.nullableColumns.length > 0 ||
alterTableChanges.noLongerUnique.length > 0 ||
alterTableChanges.primaryKeyChanges.length > 0) {
return true;
}
else {
return false;
}
}
function isMetaDataHeader(input) {
if (typeof input !== "object" || input === null || Array.isArray(input)) {
return false; // ❌ Must be a non-null object
}
for (const key in input) {
if (typeof key !== "string")
return false; // ❌ Keys must be strings
const column = input[key];
if (typeof column !== "object" || column === null ||
(!("type" in column) || typeof column.type !== "string") // ✅ "type" is required and must be a string
) {
return false;
}
// ✅ Optional fields must match expected types
if (("length" in column && column.length != null && typeof column.length !== "number") ||
("allowNull" in column && column.allowNull != null && typeof column.allowNull !== "boolean") ||
("unique" in column && column.unique != null && typeof column.unique !== "boolean") ||
("index" in column && column.index != null && typeof column.index !== "boolean") ||
("pseudounique" in column && column.pseudounique != null && typeof column.pseudounique !== "boolean") ||
("primary" in column && column.primary != null && typeof column.primary !== "boolean") ||
("autoIncrement" in column && column.autoIncrement != null && typeof column.autoIncrement !== "boolean") ||
("decimal" in column && column.decimal != null && typeof column.decimal !== "number")) {
return false;
}
}
return true; // ✅ Passed all checks
}
function estimateRowSize(mergedMetaData, dbType) {
let totalSize = 0;
for (const columnName in mergedMetaData) {
const column = mergedMetaData[columnName];
const type = column.type?.toLowerCase() || "varchar";
let columnSize = 0;
if (["boolean", "binary", "tinyint"].includes(type)) {
columnSize = 1;
}
else if (["smallint"].includes(type)) {
columnSize = 2;
}
else if (["int", "numeric"].includes(type)) {
columnSize = 4;
}
else if (["bigint"].includes(type)) {
columnSize = 8;
}
else if (["decimal", "double", "exponent"].includes(type)) {
columnSize = column.decimal ? Math.ceil(column.decimal / 2) + 1 : defaults_1.DEFAULT_LENGTHS.decimal;
}
else if (["varchar"].includes(type)) {
columnSize = column.length ?? defaults_1.DEFAULT_LENGTHS.varchar;
}
else if (["text", "mediumtext", "longtext", "json"].includes(type)) {
columnSize = defaults_1.DEFAULT_LENGTHS[type] ?? 4; // Only store pointer size
}
else if (["date"].includes(type)) {
columnSize = 3;
}
else if (["time"].includes(type)) {
columnSize = 3;
}
else if (["datetime", "datetimetz"].includes(type)) {
columnSize = 8;
}
if (column.allowNull) {
columnSize += 1; // Add 1 byte for NULL flag
}
if (column.primary || column.unique || column.index) {
columnSize += 8; // Approximate index storage
}
totalSize += columnSize;
}
// Add row overhead (~20 bytes for metadata, depends on storage engine)
const rowOverhead = 20;
totalSize += rowOverhead;
let maxRowSize;
if (dbType === 'mysql') {
maxRowSize = defaults_1.MYSQL_MAX_ROW_SIZE;
}
else if (dbType === 'pgsql') {
maxRowSize = defaults_1.POSTGRES_MAX_ROW_SIZE;
}
else {
maxRowSize = defaults_1.POSTGRES_MAX_ROW_SIZE;
}
return { rowSize: totalSize, exceedsLimit: totalSize > maxRowSize, nearlyExceedsLimit: totalSize > maxRowSize * 0.8 };
}
function isValidDataFormat(data) {
return Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null && !Array.isArray(data[0]);
}
const normalizeKeysArray = (data) => {
return data.map(obj => Object.keys(obj).reduce((acc, key) => {
acc[key.toLowerCase()] = obj[key];
return acc;
}, {}));
};
exports.normalizeKeysArray = normalizeKeysArray;
function organizeSplitTable(table, newMetaData, currentMetaData, dialectConfig) {
let normalizedMetaData;
// ✅ Check if currentMetaData is already in structured format
if (typeof currentMetaData === "object" && !Array.isArray(currentMetaData)) {
if (Object.values(currentMetaData).some(value => typeof value === "object" && !Array.isArray(value))) {
// ✅ Already `Record<string, MetadataHeader>`, use it directly
normalizedMetaData = currentMetaData;
}
else {
// ✅ If it's `MetadataHeader`, wrap it in `{ table: MetadataHeader }`
normalizedMetaData = { [table]: currentMetaData };
}
}
else {
// ✅ Otherwise, assume it's raw DB results and parse
const parsedMetadata = parseDatabaseMetaData(currentMetaData, dialectConfig);
if (!parsedMetadata) {
normalizedMetaData = { [table]: {} }; // ✅ Ensure it has a valid structure
}
else if (Object.values(parsedMetadata).some(value => typeof value === "object" && !Array.isArray(value))) {
normalizedMetaData = parsedMetadata; // ✅ Multiple tables
}
else {
normalizedMetaData = { [table]: parsedMetadata }; // ✅ Single table
}
}
const primaryKeys = {};
const newColumns = {};
const allTablesEmpty = Object.values(normalizedMetaData).every(table => Object.keys(table).length === 0);
const newGroupedByTable = Object.entries(newMetaData).reduce((acc, [columnName, columnDef]) => {
if (allTablesEmpty) {
if (columnDef.primary) {
primaryKeys[columnName] = columnDef;
}
else {
newColumns[columnName] = columnDef;
}
return acc;
}
const matchingTables = Object.keys(normalizedMetaData).filter(table => Object.prototype.hasOwnProperty.call(normalizedMetaData[table], columnName));
if (matchingTables.length > 0) {
matchingTables.forEach(tableName => {
if (!acc[tableName])
acc[tableName] = {};
acc[tableName][columnName] = columnDef;
});
if (columnDef.primary) {
primaryKeys[columnName] = columnDef;
}
}
else {
newColumns[columnName] = columnDef;
}
return acc;
}, {});
let tableName = Object.keys(newGroupedByTable).pop() || getNextTableName(Object.keys(newGroupedByTable).pop() || table);
const unallocatedColumns = { ...newColumns };
while (Object.keys(unallocatedColumns).length > 0) {
// ✅ Check the row size before adding new columns
for (var i = 0; i < Object.keys(unallocatedColumns).length; i++) {
const currentTableData = newGroupedByTable[tableName] || { ...primaryKeys };
const columnName = Object.keys(unallocatedColumns)[i];
const columnDef = unallocatedColumns[columnName];
const mergedMetaData = { ...currentTableData, [columnName]: columnDef }; // Simulate adding column
const columnCount = Object.keys(mergedMetaData).length;
const exceedsColumnLimit = columnCount >= defaults_1.MAX_COLUMN_COUNT;
const { exceedsLimit, nearlyExceedsLimit } = estimateRowSize(mergedMetaData, dialectConfig.dialect);
if (!nearlyExceedsLimit && !exceedsColumnLimit) {
// ✅ Add the column if within limits
if (!newGroupedByTable[tableName]) {
newGroupedByTable[tableName] = { ...primaryKeys }; // Ensure primary keys exist in new table
}
newGroupedByTable[tableName][columnName] = columnDef;
delete unallocatedColumns[columnName]; // ✅ Remove from unallocated list
i--;
}
else {
tableName = getNextTableName(tableName);
i--;
}
}
}
return newGroupedByTable;
}
function organizeSplitData(data, splitMetaData) {
const groupedData = {};
data.forEach((row) => {
// ✅ Initialize an object for each table's row data
const rowDataByTable = {};
Object.entries(splitMetaData).forEach(([tableName, columns]) => {
rowDataByTable[tableName] = {}; // ✅ Ensure each table has a row initialized
Object.keys(columns).forEach((columnName) => {
if (row.hasOwnProperty(columnName)) {
rowDataByTable[tableName][columnName] = row[columnName];
}
});
// ✅ Only add to groupedData if it has at least one column
if (Object.keys(rowDataByTable[tableName]).length > 0) {
if (!groupedData[tableName]) {
groupedData[tableName] = [];
}
groupedData[tableName].push(rowDataByTable[tableName]);
}
});
});
return groupedData;
}
function splitInsertData(data, config) {
const { insertStack = 1000 } = config;
const chunks = [];
for (let i = 0; i < data.length; i += insertStack) {
chunks.push(data.slice(i, i + insertStack));
}
return chunks;
}
function getInsertValues(metaData, row, dialectConfig, databaseConfig, sqlizeValues = false) {
const newRow = Object.entries(metaData).map(([column, meta]) => {
let value = row[column];
if (value === null || value === undefined) {
// Use calculated default if provided
if (meta.calculatedDefault !== undefined) {
value = meta.calculatedDefault;
}
else if (meta.default !== undefined) {
value = meta.default;
}
else {
value = null;
}
}
if (sqlizeValues && dialectConfig) {
const sqlizedValue = sqlize(value, meta.type, dialectConfig, databaseConfig);
return sqlizedValue;
}
else {
return value;
}
});
return newRow;
}
function sqlize(value, columnType, dialectConfig, databaseConfig) {
try {
if (value === null)
return null;
if (!columnType) {
return value;
}
;
const type = columnType.toLowerCase();
const rules = dialectConfig.sqlize;
if (type === "json") {
try {
if (typeof value === "string") {
try {
// Try parsing it first (in case it's a JSON string)
const parsed = JSON.parse(value);
return JSON.stringify(parsed); // ✅ Store re-stringified version
}
catch {
// ❌ Failed to parse: just return original string
return value;
}
}
else if (typeof value === "object") {
// ✅ Valid object → stringify
return JSON.stringify(value);
}
else {
// ⚠️ Unexpected type (number, boolean, etc.)
return JSON.stringify({ value });
}
}
catch (err) {
console.warn(`[sqlize] Failed to handle JSON value for column:`, {
value,
error: err.message || err
});
return null; // ❌ Fallback to NULL if completely unusable
}
}
let strValue = typeof value === "string" ? value : String(value);
const isDateLike = groupings_1.groupings.dateGroup.includes(columnType);
if (isDateLike) {
const match = strValue.match(/\/Date\((\d+)(?:[+-]\d+)?\)\//);
if (match) {
const millis = parseInt(match[1], 10);
strValue = new Date(millis).toISOString();
}
else {
const cleaned = strValue.replace(/[^\d:-\sT]/g, "");
const parsedDate = new Date(cleaned);
if (!isNaN(parsedDate.getTime())) {
strValue = parsedDate.toISOString();
}
}
}
const isNumberLike = groupings_1.groupings.intGroup.includes(columnType) || groupings_1.groupings.specialIntGroup.includes(columnType);
if (isNumberLike) {
const normalised = normalizeNumber(value) || strValue;
const precision = databaseConfig?.decimalMaxLength ?? defaults_1.defaults.decimalMaxLength;
strValue = roundStringDecimal(normalised, precision);
}
for (const rule of rules) {
const appliesToType = rule.type === true || (Array.isArray(rule.type) && rule.type.includes(type));
if (appliesToType) {
const regex = new RegExp(rule.regex, "g");
strValue = strValue.replace(regex, rule.replace);
}
}
if (strValue === '' || strValue === 'null') {
return null;
}
return strValue;
}
catch (error) {
return value;
}
}
function getNextTableName(tableName) {
const match = tableName.match(/^(.*?)(__part_(\d+))?$/); // Match `table__part_001`
if (match && match[3]) {
const baseName = match[1]; // Extract "table"
const num = parseInt(match[3], 10) + 1; // Increment existing number
return `${baseName}__part_${String(num).padStart(3, "0")}`; // Zero-padded
}
return `${tableName}__part_001`; // If no number exists, start at __part_001
}
;
function getTempTableName(tableName) {
const TEMP_PREFIX = "temp_staging__";
return tableName.startsWith(TEMP_PREFIX) ? tableName : `${TEMP_PREFIX}${tableName}`;
}
function getTrueTableName(tableName) {
const TEMP_PREFIX = "temp_staging__";
const HISTORY_SUFFIX = "__history";
let result = tableName;
if (result.startsWith(TEMP_PREFIX)) {
result = result.slice(TEMP_PREFIX.length);
}
if (result.endsWith(HISTORY_SUFFIX)) {
result = result.slice(0, -HISTORY_SUFFIX.length);
}
return result;
}
function getHistoryTableName(tableName) {
const HISTORY_SUFFIX = "__history";
return tableName.endsWith(HISTORY_SUFFIX) ? tableName : `${tableName}${HISTORY_SUFFIX}`;
}
async function wait_x_mseconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(null);
}, x);
});
}
function roundStringDecimal(valueStr, precision) {
if (!valueStr.includes('.'))
return valueStr;
const [intPart, decimalPartRaw] = valueStr.split('.');
const decimalPart = decimalPartRaw.slice(0, precision);
const nextDigit = decimalPartRaw.charAt(precision);
if (!nextDigit || parseInt(nextDigit, 10) < 5) {
// No rounding needed, just trim excess
return decimalPart.length > 0
? `${intPart}.${decimalPart}`
: intPart;
}
// Perform manual rounding
let full = `${intPart}.${decimalPart}`;
let roundedNum = Number(full);
const multiplier = Math.pow(10, precision);
roundedNum = Math.round(roundedNum * multiplier) / multiplier;
return roundedNum.toString();
}
function generateSafeConstraintName(table, column, type = 'unique') {
const base = `${table}_${column}_${type}`;
if (base.length <= 63)
return base;
// Truncate and append a hash for uniqueness
const hash = crypto_1.default.createHash('md5').update(base).digest('hex').slice(0, 6);
const truncated = base.slice(0, 63 - hash.length - 1); // -1 for underscore
return `${truncated}_${hash}`;
}
function normalizeResultKeys(row) {
return Object.fromEntries(Object.entries(row).map(([key, value]) => [key.toLowerCase(), value]));
}
function throwIfFailedResults(results, action = "operation") {
const failed = results.filter(r => !r.success);
if (failed.length > 0) {
const message = `One or more ${action} failed (${failed.length}):\n` +
failed
.map(r => `- ${r.table || "Unknown Table"}: ${r.error || "Unknown Error"}`)
.join("\n");
throw new Error(message);
}
}
function normalizeName(name) {
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
}