UNPKG

tspace-mysql

Version:

Tspace MySQL is a promise-based ORM for Node.js, designed with modern TypeScript and providing type safety for schema databases.

689 lines (677 loc) 23.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SqliteQueryBuilder = void 0; const __1 = require(".."); class SqliteQueryBuilder extends __1.QueryBuilder { constructor(state) { super(state); } select = () => { const combindSQL = [ this.bindSelect(this.$state.get("SELECT")), this.bindFrom({ from: !this.$state.get("FROM").length ? [this.$state.get("TABLE_NAME")].filter(Boolean).map(String) : [this.$state.get("TABLE_NAME"), ...this.$state.get("FROM")].filter(Boolean).map(String), alias: this.$state.get("ALIAS"), rawAlias: this.$state.get("RAW_ALIAS"), }), this.bindJoin(this.$state.get("JOIN")), this.bindWhere(this.$state.get("WHERE")), this.bindGroupBy(this.$state.get("GROUP_BY")), this.bindHaving(this.$state.get("HAVING")), this.bindOrderBy(this.$state.get("ORDER_BY")), this.bindLimit(this.$state.get("LIMIT")), this.bindOffset(this.$state.get("OFFSET")), this.bindRowLevelLock(this.$state.get("ROW_LEVEL_LOCK")), ]; let sql = this.format(combindSQL).trimEnd(); if (this.$state.get("UNION").length) { sql = `(${sql}) ${this.$state .get("UNION") .map((union) => `${this.$constants("UNION")} (${union})`) .join(" ")}`; } if (this.$state.get("UNION_ALL").length) { sql = `(${sql}) ${this.$state .get("UNION_ALL") .map((union) => `${this.$constants("UNION_ALL")} (${union})`) .join(" ")}`; } if (this.$state.get("CTE").length) { sql = `${this.$constants("WITH")} ${this.$state .get("CTE") .join(", ")} ${sql}`; } return sql; }; insert() { const query = this.$state.get("INSERT"); if (!query) return ''; const table = this.$state.get("TABLE_NAME"); const columns = `(${query.columns})`; const values = query.values.map(v => `(${v})`).join(', '); const sql = this.format([ this.$constants("INSERT"), table, columns, this.$constants("VALUES"), values ]); return sql; } update() { const query = this.$state.get("UPDATE"); if (query == null) { return ''; } const sql = this.format([ `${this.$constants("UPDATE")}`, `${this.$state.get("TABLE_NAME")}`, `${this.$constants("SET")}`, `${query}`, this.bindWhere(this.$state.get("WHERE")), this.bindOrderBy(this.$state.get("ORDER_BY")), this.bindLimit(this.$state.get("LIMIT")), ]); return sql; } remove() { const query = this.$state.get("DELETE"); if (!query) { throw new Error("Bad query builder: DELETE state not found. Please check your query configuration."); } let sql = this.format([ this.$constants("DELETE"), this.$constants("FROM"), this.$state.get("TABLE_NAME"), this.bindWhere(this.$state.get("WHERE")), this.bindOrderBy(this.$state.get("ORDER_BY")), this.bindLimit(this.$state.get("LIMIT")), ]); if (this.$state.get("CTE").length) { sql = `${this.$constants("WITH")} ${this.$state .get("CTE") .join(", ")} ${sql}`; } return sql; } any() { if (this.$state.get("INSERT")) return this.insert(); if (this.$state.get("UPDATE")) return this.update(); if (this.$state.get("DELETE")) return this.remove(); return this.select(); } getColumns({ database, table }) { const sql = [` SELECT ti.name AS "Field", ti.type AS "ColumnType", LOWER(ti.type) AS "Type", CASE WHEN ti."notnull" = 0 THEN 'YES' ELSE 'NO' END AS "Nullable", ti.dflt_value AS "Default" FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS ti WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' ORDER BY ti.cid `]; return this.format(sql); } getSchema({ database, table }) { const sql = [` SELECT ti.name AS "Field", CASE WHEN ti.pk = 1 THEN 'PRI' WHEN il."unique" = 1 THEN 'UNI' ELSE NULL END AS "Key", ti.type AS "Type", CASE WHEN ti."notnull" = 0 THEN 'YES' ELSE 'NO' END AS "Nullable", CASE WHEN ti.dflt_value = 'CURRENT_TIMESTAMP' THEN 'IS_CONST:CURRENT_TIMESTAMP' ELSE ti.dflt_value END AS "Default", NULL AS "Extra", CASE WHEN ti.type LIKE '%(%' THEN substr(ti.type, instr(ti.type, '(') + 1, instr(ti.type, ')') - instr(ti.type, '(') - 1) ELSE NULL END AS "TypeValue" FROM sqlite_master AS m JOIN pragma_table_info(m.name) AS ti LEFT JOIN pragma_index_list(m.name) AS il ON il."unique" = 1 LEFT JOIN pragma_index_info(il.name) AS ii ON ii.name = ti.name WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' GROUP BY ti.cid ORDER BY ti.cid `]; return this.format(sql); } getTables(database) { const sql = [ `SELECT name AS table_name FROM sqlite_master WHERE type = 'table'`, ]; return this.format(sql); } hasTable({ database, table }) { const sql = [` SELECT EXISTS( SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '${table.replace(/["`]/g, "")}' ) AS "IS_EXISTS" `]; return this.format(sql); } createDatabase(database) { throw new Error("Method not implemented."); return ''; } createTable({ database, table, schema, }) { let columns = []; if (Array.isArray(schema)) { const sql = [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${database.replace(/`/g, "")}\`.\`${table.replace(/`/g, "")}\``, `(${schema.join(", ")})` ]; return this.format(sql); } const detectSchema = (schema) => { try { return { type: schema?.type ?? schema["_type"] ?? null, attributes: schema?.attributes ?? schema["_attributes"] ?? null, }; } catch (e) { return { type: null, attributes: null, }; } }; for (const key in schema) { const data = schema[key]; const { type, attributes } = detectSchema(data); if (type == null || attributes == null) continue; const { formatedAttributes, formatedType } = this._formatedTypeAndAttributes({ type, attributes, key, }); columns = [ ...columns, `\`${key}\` ${formatedType} ${formatedAttributes.join(" ")}`, ]; } const sql = [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${table.replace(/`/g, "")}\` (${columns.join(", ")})`, ]; return this.format(sql); } addColumn({ table, column, type, attributes, after, }) { const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("ADD"), `\`${column}\` ${type} ${attributes != null && attributes.length ? `${attributes.join(" ")}` : ""}`, this.$constants("AFTER"), `\`${after}\``, ]; return this.format(sql); } changeColumn({ table, column, type, attributes, }) { const sql = [ this.$constants("ALTER_TABLE"), `\`${table.replace(/`/g, "")}\``, this.$constants("CHANGE"), `\`${column}\``, `\`${column}\` ${type} ${attributes != null && attributes.length ? `${attributes .filter((v) => !["PRIMARY KEY"].includes(v)) .join(" ")}` : ""}`, ]; return this.format(sql); } getChildFKs({ database, table }) { const sql = [` SELECT 'fk_' || m.name || '_' || f."from" AS "Constraint", m.name AS "ChildTable", f."from" AS "ChildColumn", f."table" AS "ParentTable", f."to" AS "ParentColumn" FROM sqlite_master AS m JOIN pragma_foreign_key_list(m.name) AS f WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' AND f."table" = '${table.replace(/"/g, "")}' `]; return this.format(sql); } getFKs({ database, table }) { const sql = [` SELECT f."table" AS "RefTable", f."to" AS "RefColumn", f."from" AS "Column", 'fk_' || m.name || '_' || f."from" AS "Constraint" FROM sqlite_master AS m JOIN pragma_foreign_key_list(m.name) AS f WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' AND f."table" IS NOT NULL `]; return this.format(sql); } hasFK({ database, table, constraint, }) { const sql = [` SELECT EXISTS( SELECT 1 FROM sqlite_master AS m JOIN pragma_foreign_key_list(m.name) AS f WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' AND ('fk_' || m.name || '_' || f."from") = '${constraint.replace(/["`]/g, "")}' ) AS "IS_EXISTS" `]; return this.format(sql); } addFK({ table, tableRef, key, constraint, foreign, }) { const sql = [ `${this.$constants("ALTER_TABLE")}`, `\`${table}\``, `${this.$constants("ADD_CONSTRAINT")}`, `\`${constraint}\``, `${this.$constants("FOREIGN_KEY")}(\`${key}\`)`, `${this.$constants("REFERENCES")} \`${tableRef}\`(\`${foreign.references}\`)`, `${this.$constants("ON_DELETE")} ${foreign.onDelete}`, `${this.$constants("ON_UPDATE")} ${foreign.onUpdate}`, ].join(" "); return this.format(sql); } dropFK({ table, constraint, }) { const sql = [ `${this.$constants("ALTER_TABLE")}`, `\`${table}\``, `DROP FOREIGN KEY`, `\`${constraint}\``, ].join(" "); return this.format(sql); } getIndexes({ database, table }) { const sql = [` SELECT ii.name AS "Column", il.name AS "IndexName", il.origin AS "IndexType", CASE WHEN ti."notnull" = 0 THEN 'YES' ELSE 'NO' END AS "Nullable", CASE WHEN il."unique" = 1 THEN 'YES' ELSE 'NO' END AS "Unique" FROM sqlite_master AS m JOIN pragma_index_list(m.name) AS il JOIN pragma_index_info(il.name) AS ii LEFT JOIN pragma_table_info(m.name) AS ti ON ti.name = ii.name WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' AND il.origin != 'pk' `]; return this.format(sql); } hasIndex({ database, table, name, }) { const sql = [` SELECT EXISTS( SELECT 1 FROM sqlite_master AS m JOIN pragma_index_list(m.name) AS il WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' AND il.name = '${name.replace(/["`]/g, "")}' AND il.origin != 'pk' ) AS "IS_EXISTS" `]; return this.format(sql); } addIndex({ table, name, columns, }) { const cols = columns .map(col => `\`${col}\``) .join(", "); const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("ADD_INDEX"), `\`${name}\``, `(${cols})` ]; return this.format(sql); } dropIndex({ table, name, }) { const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("DROP"), this.$constants("INDEX"), `\`${name}\`` ]; return this.format(sql); } hasUnique({ database, table, name, }) { const sql = [` SELECT EXISTS( SELECT 1 FROM sqlite_master AS m WHERE m.type = 'table' AND m.name = '${table.replace(/["`]/g, "")}' AND ( ('${name}' = 'PRIMARY KEY' AND m.sql LIKE '%PRIMARY KEY%') OR ('${name}' = 'UNIQUE' AND EXISTS ( SELECT 1 FROM pragma_index_list(m.name) il WHERE il."unique" = 1 )) OR ('${name}' = 'FOREIGN KEY' AND EXISTS ( SELECT 1 FROM pragma_foreign_key_list(m.name) )) ) ) AS "IS_EXISTS" `]; return this.format(sql); } addUnique({ table, name, columns }) { const cols = columns .map(col => `\`${col}\``) .join(", "); const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("ADD_CONSTRAINT"), `\`${name}\``, this.$constants("UNIQUE"), `(${cols})` ]; return this.format(sql); } dropUnique({ table, name, }) { const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("DROP"), this.$constants("INDEX"), `\`${name}\`` ]; return this.format(sql); } hasPrimaryKey({ database, table, }) { const sql = ` SELECT EXISTS( SELECT 1 FROM pragma_table_info('${table.replace(/["'`]/g, "")}') WHERE pk = 1 ) AS IS_EXISTS `; return this.format(sql); } addPrimaryKey({ table, columns, }) { const cols = columns .map(col => `\`${col}\``) .join(", "); const sql = ` ${this.$constants('ALTER_TABLE')} \`${table}\` ${this.$constants('ADD')} ${this.$constants('PRIMARY_KEY')} (${cols}) `; return this.format(sql); } dropPrimaryKey({ table }) { const sql = [ this.$constants("ALTER_TABLE"), `\`${table}\``, this.$constants("DROP"), this.$constants("PRIMARY_KEY") ]; return this.format(sql); } getDatabase(database) { const sql = [ `SELECT '${database.replace(/`/g, "")}' AS DB`, ].join(" "); return this.format(sql); } dropDatabase(database) { const sql = [ `${this.$constants("DROP_DATABASE")}`, `\`${database.replace(/`/g, "")}\`` ].join(" "); return this.format(sql); } dropView(view) { const sql = [ `${this.$constants("DROP_VIEW")}`, `\`${view.replace(/`/g, "")}\`` ].join(" "); return this.format(sql); } dropTable(table) { const sql = [ `${this.$constants("DROP_TABLE")}`, `\`${table.replace(/`/g, "")}\`` ].join(" "); return this.format(sql); } truncate(table) { const sql = [ `DELETE FROM "${table}"`, ].join(" "); return this.format(sql); } sleep(second) { return `SLEEP(${second})`; } format(sql) { if (typeof sql === "string") sql = [sql]; const formated = sql .filter((s) => s !== "" || s == null) .join(" ") .replace(/\s+/g, " "); const replaceBackticksWithDoubleQuotes = (sqlString) => { const updateRegex = /^UPDATE\b/i; const insertRegex = /^INSERT\b/i; const deleteRegex = /^DELETE\b/i; const truncateRegex = /^TRUNCATE\b/i; if (updateRegex.test(sqlString)) { sqlString = sqlString.replace(/(SET\s+)(.*?)(\s+WHERE)/is, (_, start, setPart, end) => { const cleaned = setPart.replace(/`[\w$_]+`\./g, ''); return start + cleaned + end; }); } if (insertRegex.test(sqlString) || deleteRegex.test(sqlString) || truncateRegex.test(sqlString)) { return sqlString .replace(/`[\w$_]+`\.`([\w$_]+)`/g, "`$1`") .replace(/`([^`]+)`/g, '"$1"'); } return sqlString.replace(/`([^`]+)`/g, '"$1"'); }; return replaceBackticksWithDoubleQuotes(formated); } getActiveConnections() { const sql = `SELECT 1 AS Connections`; return this.format(sql); } getMaxConnections() { const sql = `SELECT 151 AS MaxConnections`; return this.format(sql); } bindJoin(values) { if (!Array.isArray(values) || !values.length) return null; return values.join(" "); } bindWhere(values) { if (!Array.isArray(values) || !values.length) return null; const serializeWhere = (wheres) => { const resolveValue = ({ operator, value }) => { let valueStr = ''; if (operator?.toUpperCase() === this.$constants('IN') && Array.isArray(value)) { valueStr = `(${value.map((v) => v).join(',')})`; } else if (operator?.toUpperCase() === this.$constants('IS_NULL') || operator?.toUpperCase() === this.$constants('IS_NOT_NULL')) { valueStr = ''; } else { valueStr = `${value}`; } return valueStr; }; const conditionToSQL = (cond, isFirst = false) => { const { column = '', operator = '', condition, value, nested } = cond; if (nested && nested.length) { const nestedSQL = nested .map((c) => conditionToSQL(c)) .join(' '); const valueStr = resolveValue({ operator, value }); if (!isFirst) { return `${condition ?? this.$constants('AND')} (${column} ${operator} ${valueStr} ${nestedSQL})`; } return `(${column} ${operator} ${valueStr} ${nestedSQL})`; } const valueStr = resolveValue({ operator, value }); if (!isFirst) { return `${condition ?? this.$constants('AND')} ${column} ${operator} ${valueStr}`.trim(); } return `${column} ${operator} ${valueStr}`.trim(); }; return wheres.map((cond, i) => conditionToSQL(cond, !i)).join(' '); }; return `${this.$constants("WHERE")} ${serializeWhere(values)}`; } bindOrderBy(values) { if (!Array.isArray(values) || !values.length) return null; return `${this.$constants("ORDER_BY")} ${values .map((v) => v.replace(/^\s/, "").replace(/\s+/g, " ")) .join(", ")}`; } bindGroupBy(values) { if (!Array.isArray(values) || !values.length) return null; return `${this.$constants("GROUP_BY")} ${values .map((v) => v.replace(/^\s/, "").replace(/\s+/g, " ")) .join(", ")}`; } bindSelect(values, { distinct } = {}) { if (!values.length) { if (!distinct) return `${this.$constants("SELECT")} *`; return `${this.$constants("SELECT")} ${this.$constants("DISTINCT")} *`; } const findIndex = values.indexOf("*"); if (findIndex > -1) { const removed = values.splice(findIndex, 1); values.unshift(removed[0]); } return `${this.$constants("SELECT")} ${values.join(", ")}`; } bindFrom({ from, alias, rawAlias, }) { if (!from.length || from.every((f) => f == null || f === "")) { return ""; } if (alias != null && alias !== "") { if (rawAlias != null && rawAlias !== "") { const raw = String(rawAlias) .replace(/^\(\s*|\s*\)$/g, "") .trim(); const normalizedRawAlias = raw.startsWith("(") && raw.endsWith(")") ? raw.slice(1, -1) : raw; raw.startsWith("(") && raw.endsWith(")") ? raw.slice(1, -1) : raw; return `${this.$constants("FROM")} (${normalizedRawAlias}) ${this.$constants("AS")} \`${alias}\``; } return `${this.$constants("FROM")} ${from.join(", ")} ${this.$constants("AS")} \`${alias}\``; } return `${this.$constants("FROM")} ${from.join(", ")}`; } bindLimit(limit) { if (limit === "" || limit == null) return ""; return `${this.$constants("LIMIT")} ${limit}`; } bindOffset(offset) { if (offset === "" || offset == null) return ""; return `${this.$constants("OFFSET")} ${offset}`; } bindHaving(having) { if (having == null || having === '') return ""; return `${this.$constants("HAVING")} ${having}`; } bindRowLevelLock(rowLevelLock) { // SQLite does NOT support SELECT … FOR UPDATE or SKIP LOCKED return ''; } _formatedTypeAndAttributes({ table, type, attributes, key, changed }) { let formatedType = type; let formatedAttributes = attributes; let raws = null; if (type.startsWith("INT") && !attributes.some((v) => v === "PRIMARY KEY")) { formatedType = "INTEGER"; } if (type.startsWith("INT") && attributes.some((v) => v === "PRIMARY KEY")) { formatedType = "INTEGER"; formatedAttributes = attributes.filter((attr) => { return !attr.startsWith("AUTO_INCREMENT"); }); } if (type.startsWith("TINYINT")) { formatedType = "SMALLINT"; } if (type.startsWith("LONGTEXT") || type.startsWith("MEDIUMTEXT")) { formatedType = "TEXT"; } if (type.startsWith("ENUM")) { const enums = type.replace("ENUM", ""); const totalLength = enums .slice(1, -1) .split(",") .map((s) => s.replace(/'/g, "")) .reduce((sum, item) => sum + item.length, 0); formatedType = `VARCHAR(${totalLength}) CHECK (${key} IN ${enums})`; } if (type.startsWith("BOOLEAN")) { formatedAttributes = attributes.map((attr) => { if (attr.startsWith("DEFAULT")) { return attr.replace(/\b0\b/, "false").replace(/\b1\b/, "true"); } return attr; }); } return { formatedType, formatedAttributes, raws }; } } exports.SqliteQueryBuilder = SqliteQueryBuilder; //# sourceMappingURL=SqliteQueryBuilder.js.map