autosql
Version:
An auto-parser of JSON into SQL.
305 lines (304 loc) • 15.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MySQLTableQueryBuilder = void 0;
const mysqlConfig_1 = require("../../config/mysqlConfig");
const alterTableTypeConversion_1 = require("./alterTableTypeConversion");
const utilities_1 = require("../../../helpers/utilities");
const dialectConfig = mysqlConfig_1.mysqlConfig;
class MySQLTableQueryBuilder {
static getCreateTableQuery(table, headers, databaseConfig) {
const maxIndexCount = dialectConfig.maxIndexCount || 64;
let remainingIndexSlots = maxIndexCount;
let sqlQueries = [];
const schemaPrefix = databaseConfig?.schema ? `\`${databaseConfig.schema}\`.` : "";
let sqlQuery = `CREATE TABLE IF NOT EXISTS ${schemaPrefix}\`${table}\` (\n`;
let primaryKeys = [];
let uniqueKeys = [];
let indexes = [];
for (const [columnName, column] of Object.entries(headers)) {
if (!column.type)
throw new Error(`Missing type for column ${columnName}`);
let columnType = column.type.toLowerCase();
if (dialectConfig.translate.localToServer[columnType]) {
columnType = dialectConfig.translate.localToServer[columnType];
}
let columnDef = `\`${columnName}\` ${columnType}`;
// Handle column lengths
if (column.length && !dialectConfig.noLength.includes(columnType)) {
columnDef += `(${column.length}${column.decimal && dialectConfig.decimals.includes(columnType) ? `,${column.decimal || 0}` : ""})`;
}
// Convert BOOLEAN → TINYINT(1) for MySQL
if (column.type === "boolean") {
columnDef = `\`${columnName}\` TINYINT(1)`;
}
// Apply AUTO_INCREMENT only to valid integer types
if (column.autoIncrement) {
if (["int", "bigint", "smallint", "tinyint"].includes(columnType)) {
columnDef += " AUTO_INCREMENT";
}
else {
throw new Error(`AUTO_INCREMENT is not supported on type ${columnType} in MySQL`);
}
}
if (!column.allowNull)
columnDef += " NOT NULL";
if (column.default !== undefined) {
const replacement = dialectConfig.defaultTranslation[column.default];
columnDef += ` DEFAULT ${replacement ? replacement : column.default}`;
}
if (column.primary)
primaryKeys.push(`\`${columnName}\``);
if (column.unique)
uniqueKeys.push(`\`${columnName}\``);
if (column.index)
indexes.push(`\`${columnName}\``);
sqlQuery += `${columnDef},\n`;
}
if (primaryKeys.length) {
sqlQuery += `PRIMARY KEY (${primaryKeys.join(", ")}),\n`;
remainingIndexSlots--; // 🔢 count primary key toward the limit
}
const includedUniqueKeys = uniqueKeys.slice(0, remainingIndexSlots);
if (includedUniqueKeys.length) {
sqlQuery += `${includedUniqueKeys.map((key) => `UNIQUE(${key})`).join(", ")},\n`;
remainingIndexSlots -= includedUniqueKeys.length;
}
sqlQuery = sqlQuery.slice(0, -2) + `) ENGINE=${databaseConfig?.engine || dialectConfig.engine}
DEFAULT CHARSET=${databaseConfig?.charset || dialectConfig.charset}
COLLATE=${databaseConfig?.collate || dialectConfig.collate};`;
sqlQueries.push({ query: sqlQuery, params: [] }); // Store CREATE TABLE query as first item
// Create indexes separately
const limitedIndexes = indexes.slice(0, remainingIndexSlots);
for (const index of limitedIndexes) {
const indexName = (0, utilities_1.generateSafeConstraintName)(table, index, "index").replace(/`/g, "");
sqlQueries.push({
query: `CREATE INDEX \`${indexName}\` ON ${schemaPrefix}\`${table}\` (\`${index.replace(/`/g, "")}\`);`,
params: []
});
}
return sqlQueries;
}
static getAlterTableQuery(table, changes, schema, databaseConfig) {
let queries = [];
let alterStatements = [];
// ✅ Handle `DROP COLUMN`
if (databaseConfig?.deleteColumns) {
changes.dropColumns.forEach(columnName => {
alterStatements.push(`DROP COLUMN \`${columnName}\``);
});
}
// ✅ Handle `RENAME COLUMN`
changes.renameColumns.forEach(({ oldName, newName }) => {
const columnType = databaseConfig?.metaData?.[table]?.[newName]?.type || databaseConfig?.metaData?.[table]?.[oldName]?.type || 'varchar';
const columnLength = databaseConfig?.metaData?.[table]?.[newName]?.length || databaseConfig?.metaData?.[table]?.[oldName]?.length || 1;
const columnDecimal = databaseConfig?.metaData?.[table]?.[newName]?.decimal || databaseConfig?.metaData?.[table]?.[oldName]?.decimal || 0;
const columnNullable = databaseConfig?.metaData?.[table]?.[newName]?.allowNull || databaseConfig?.metaData?.[table]?.[oldName]?.allowNull || false;
let columnLengthSQL = '';
if (columnLength && !dialectConfig.noLength.includes(columnType || "")) {
columnLengthSQL += `(${columnLength}${columnDecimal ? `,${columnDecimal}` : ""})`;
}
alterStatements.push(`CHANGE COLUMN \`${oldName}\` \`${newName}\` ${columnType}${columnLengthSQL} ${columnNullable ? 'NULL' : 'NOT NULL'}`);
});
// ✅ Handle `ADD COLUMN`
for (const [columnName, column] of Object.entries(changes.addColumns)) {
if (!column.type) {
throw new Error(`Attempted to add a new column '${columnName}' without a type`);
}
let columnType = column.type.toLowerCase();
if (dialectConfig.translate.localToServer[columnType]) {
columnType = dialectConfig.translate.localToServer[columnType];
}
let columnDef = `\`${columnName}\` ${columnType}`;
if (column.length && !dialectConfig.noLength.includes(column.type || "")) {
columnDef += `(${column.length}${column.decimal && dialectConfig.decimals.includes(columnType) ? `,${column.decimal || 0}` : ""})`;
}
if (!column.allowNull)
columnDef += " NOT NULL";
if (column.default !== undefined)
columnDef += ` DEFAULT '${column.default}'`;
alterStatements.push(`ADD COLUMN ${columnDef}`);
}
;
// ✅ Handle `MODIFY COLUMN` - Include `NULL` conditionally
for (const [columnName, column] of Object.entries(changes.modifyColumns)) {
if (!column.type) {
throw new Error(`Attempted to add a new column '${columnName}' without a type`);
}
let columnType = column.type.toLowerCase();
if (dialectConfig.translate.localToServer[columnType]) {
columnType = dialectConfig.translate.localToServer[columnType];
}
let columnDef = `\`${columnName}\` ${columnType}`;
if (column.length && !dialectConfig.noLength.includes(column.type || "")) {
columnDef += `(${column.length}${column.decimal && dialectConfig.decimals.includes(columnType) ? `,${column.decimal || 0}` : ""})`;
}
// ✅ Apply NULL or NOT NULL depending on whether the column is in nullableColumns
if (changes.nullableColumns.includes(columnName)) {
columnDef += " NULL";
}
else if (!column.allowNull) {
columnDef += " NOT NULL";
}
if (column.default !== undefined) {
columnDef += ` DEFAULT '${column.default}'`;
}
if (column.previousType && column.previousType !== column.type) {
const updateQuery = (0, alterTableTypeConversion_1.getUsingClause)(columnName, column.previousType, column.type, table, schema);
if (updateQuery) {
queries.push({ query: updateQuery, params: [] });
}
}
alterStatements.push(`MODIFY COLUMN ${columnDef}`);
}
;
// ✅ Handle standalone `NULLABLE COLUMNS` not in modifyColumns
changes.nullableColumns.forEach(columnName => {
if (!Object.prototype.hasOwnProperty.call(changes.modifyColumns, columnName)) {
alterStatements.push(`MODIFY COLUMN \`${columnName}\` DROP NOT NULL`);
}
});
const schemaPrefix = schema ? `\`${schema}\`.` : "";
// ✅ Combine all `ALTER TABLE` statements
if (alterStatements.length > 0) {
queries.push({ query: `ALTER TABLE ${schemaPrefix}\`${table}\` ${alterStatements.join(", ")};`, params: [] });
}
return queries;
}
static getDropTableQuery(table, schema) {
const schemaPrefix = schema ? `\`${schema}\`.` : "";
return { query: `DROP TABLE IF EXISTS ${schemaPrefix}\`${table}\`;`, params: [] };
}
static getCreateTempTableQuery(table, schema) {
const tempTableName = (0, utilities_1.getTempTableName)(table);
const schemaPrefix = schema ? `\`${schema}\`.` : "";
return { query: `CREATE TABLE IF NOT EXISTS ${schemaPrefix}\`${tempTableName}\`
AS SELECT * FROM ${schemaPrefix}\`${table}\` LIMIT 0;`, params: [] };
}
static getTableExistsQuery(schema, table) {
return {
query: "SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?",
params: [schema, table],
};
}
static getTableMetaDataQuery(schema, table) {
return {
query: `SELECT
c.COLUMN_NAME,
c.DATA_TYPE,
c.EXTRA,
CASE
WHEN c.NUMERIC_PRECISION IS NOT NULL AND c.NUMERIC_SCALE IS NOT NULL THEN CONCAT(c.NUMERIC_PRECISION,',',c.NUMERIC_SCALE)
WHEN c.NUMERIC_PRECISION IS NOT NULL AND c.NUMERIC_SCALE IS NULL THEN c.NUMERIC_PRECISION
WHEN c.CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN c.CHARACTER_MAXIMUM_LENGTH
ELSE NULL
END AS LENGTH,
c.IS_NULLABLE,
CASE
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
ON tc.TABLE_SCHEMA = k.TABLE_SCHEMA
AND tc.TABLE_NAME = k.TABLE_NAME
AND tc.CONSTRAINT_NAME = k.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = c.TABLE_SCHEMA
AND tc.TABLE_NAME = c.TABLE_NAME
AND k.COLUMN_NAME = c.COLUMN_NAME
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
) THEN 'PRIMARY'
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
ON tc.TABLE_SCHEMA = k.TABLE_SCHEMA
AND tc.TABLE_NAME = k.TABLE_NAME
AND tc.CONSTRAINT_NAME = k.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = c.TABLE_SCHEMA
AND tc.TABLE_NAME = c.TABLE_NAME
AND k.COLUMN_NAME = c.COLUMN_NAME
AND tc.CONSTRAINT_TYPE = 'UNIQUE'
) THEN 'UNIQUE'
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.STATISTICS AS i
WHERE i.TABLE_SCHEMA = c.TABLE_SCHEMA
AND i.TABLE_NAME = c.TABLE_NAME
AND i.COLUMN_NAME = c.COLUMN_NAME
AND i.INDEX_NAME <> 'PRIMARY'
) THEN 'INDEX'
ELSE NULL
END AS COLUMN_KEY
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE c.TABLE_SCHEMA = ?
AND c.TABLE_NAME = ?;
`,
params: [schema, table],
};
}
static getSplitTablesQuery(table, schema) {
return {
query: `
SELECT
c.COLUMN_NAME,
c.TABLE_NAME,
c.DATA_TYPE,
c.EXTRA,
CASE
WHEN c.NUMERIC_PRECISION IS NOT NULL AND c.NUMERIC_SCALE IS NOT NULL
THEN CONCAT(c.NUMERIC_PRECISION, ',', c.NUMERIC_SCALE)
WHEN c.NUMERIC_PRECISION IS NOT NULL AND c.NUMERIC_SCALE IS NULL
THEN c.NUMERIC_PRECISION
WHEN c.CHARACTER_MAXIMUM_LENGTH IS NOT NULL
THEN c.CHARACTER_MAXIMUM_LENGTH
ELSE NULL
END AS LENGTH,
c.IS_NULLABLE,
CASE
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
ON tc.TABLE_SCHEMA = k.TABLE_SCHEMA
AND tc.TABLE_NAME = k.TABLE_NAME
AND tc.CONSTRAINT_NAME = k.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = c.TABLE_SCHEMA
AND tc.TABLE_NAME = c.TABLE_NAME
AND k.COLUMN_NAME = c.COLUMN_NAME
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
) THEN 'PRIMARY'
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS k
ON tc.TABLE_SCHEMA = k.TABLE_SCHEMA
AND tc.TABLE_NAME = k.TABLE_NAME
AND tc.CONSTRAINT_NAME = k.CONSTRAINT_NAME
WHERE tc.TABLE_SCHEMA = c.TABLE_SCHEMA
AND tc.TABLE_NAME = c.TABLE_NAME
AND k.COLUMN_NAME = c.COLUMN_NAME
AND tc.CONSTRAINT_TYPE = 'UNIQUE'
) THEN 'UNIQUE'
WHEN EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.STATISTICS AS i
WHERE i.TABLE_SCHEMA = c.TABLE_SCHEMA
AND i.TABLE_NAME = c.TABLE_NAME
AND i.COLUMN_NAME = c.COLUMN_NAME
AND i.INDEX_NAME <> 'PRIMARY'
) THEN 'INDEX'
ELSE NULL
END AS COLUMN_KEY
FROM INFORMATION_SCHEMA.COLUMNS AS c
JOIN INFORMATION_SCHEMA.TABLES AS t
ON c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.TABLE_NAME = t.TABLE_NAME
WHERE t.TABLE_NAME LIKE CONCAT(?, '__part_%')
AND t.TABLE_NAME REGEXP CONCAT('^', ?, '__part_[0-9]+$')
${schema ? `AND t.TABLE_SCHEMA = ?` : ""}
ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION;
`,
params: schema ? [table, table, schema] : [table, table],
};
}
}
exports.MySQLTableQueryBuilder = MySQLTableQueryBuilder;