tspace-mysql
Version:
Tspace MySQL is a promise-based ORM for Node.js, designed with modern TypeScript and providing type safety for schema databases.
657 lines (654 loc) • 23 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgresQueryBuilder = void 0;
const __1 = require("..");
class PostgresQueryBuilder 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")].map(String)
: [this.$state.get("TABLE_NAME"), ...this.$state.get("FROM")].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,
"RETURNING *"
]);
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")) postgres does't support limit when update */
"RETURNING *",
]);
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")),
"RETURNING *",
]);
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
COLUMN_NAME as "Field",
CASE
WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN
DATA_TYPE || '(' || CHARACTER_MAXIMUM_LENGTH || ')'
WHEN NUMERIC_PRECISION IS NOT NULL THEN
DATA_TYPE || '(' || NUMERIC_PRECISION || COALESCE(',' || NUMERIC_SCALE, '') || ')'
ELSE DATA_TYPE
END AS "ColumnType",
DATA_TYPE as "Type",
IS_NULLABLE as "Nullable",
COLUMN_DEFAULT as "Default"
`,
`FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '${table.replace(/\`/g, "")}'
AND TABLE_CATALOG = '${database.replace(/\`/g, "")}'
ORDER BY ORDINAL_POSITION
`,
];
return this.format(sql);
}
getSchema({ database, table }) {
const sql = [
`SELECT
COLUMN_NAME as "Field",
CASE
WHEN column_default LIKE 'nextval(%' THEN 'PRI'
ELSE NULL
END AS "Key",
CASE
WHEN
DATA_TYPE = 'character varying' AND CHARACTER_MAXIMUM_LENGTH IS NOT NULL
THEN DATA_TYPE || '(' || CHARACTER_MAXIMUM_LENGTH || ')'
ELSE DATA_TYPE
END AS "Type",
IS_NULLABLE as "Nullable",
CASE
WHEN COLUMN_DEFAULT LIKE 'nextval(%' THEN NULL
WHEN COLUMN_DEFAULT = 'CURRENT_TIMESTAMP' THEN 'IS_CONST:CURRENT_TIMESTAMP'
ELSE COLUMN_DEFAULT
END AS "Default",
CASE
WHEN COLUMN_DEFAULT LIKE 'nextval(%' THEN 'AUTO_INCREMENT'
ELSE NULL
END AS "Extra"
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '${table.replace(/\`/g, "")}'
AND TABLE_CATALOG = '${database.replace(/\`/g, "")}'
ORDER BY ORDINAL_POSITION
`,
];
return this.format(sql);
}
getTables(database) {
const sql = [
`
SELECT
TABLE_NAME AS "Tables"
FROM
INFORMATION_SCHEMA.TABLES
WHERE
TABLE_TYPE = 'BASE TABLE'
AND TABLE_CATALOG = '${database.replace(/\`/g, "")}'
AND UPPER(TABLE_SCHEMA) NOT IN (
'PG_CATALOG',
'INFORMATION_SCHEMA',
'PG_TOAST'
)
`,
];
return this.format(sql);
}
getTable({ database, table }) {
const sql = [
`
SELECT
TABLE_NAME AS "TABLES"
FROM INFORMATION_SCHEMA.TABLES
WHERE
TABLE_TYPE = 'BASE TABLE'
AND TABLE_CATALOG = '${database.replace(/\`/g, "")}'
AND TABLE_NAME LIKE '${table.replace(/\`/g, "")}'
AND UPPER(TABLE_SCHEMA) NOT IN (
'PG_CATALOG',
'INFORMATION_SCHEMA',
'PG_TOAST'
)
`,
];
return this.format(sql);
}
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} (${columns.join(", ")})`,
];
return this.format(sql);
}
addColumn({ table, column, type, attributes, after, }) {
const { formatedAttributes, formatedType } = this._formatedTypeAndAttributes({
type,
attributes,
key: column,
});
const sql = [
this.$constants("ALTER_TABLE"),
`\`${table}\``,
this.$constants("ADD"),
"COLUMN",
`\`${column}\` ${formatedType} ${formatedAttributes != null && formatedAttributes.length
? `${formatedAttributes.join(" ")}`
: ""}`,
`${after ? "" : ""}`,
];
return this.format(sql);
}
changeColumn({ table, column, type, attributes, }) {
const { formatedAttributes, formatedType } = this._formatedTypeAndAttributes({
type,
attributes,
key: column,
});
const sql = [
this.$constants("ALTER_TABLE"),
`\`${table}\``,
this.$constants("ALTER_COLUMN"),
`\`${column}\``,
`TYPE ${formatedType}`,
];
const sqlAttr = [];
for (const formatedAttribute of formatedAttributes) {
if (formatedAttribute.startsWith("PRIMARY KEY") ||
formatedAttribute.startsWith("NULL")) {
continue;
}
sqlAttr.push([
this.$constants("ALTER_TABLE"),
`\`${table}\``,
this.$constants("ALTER_COLUMN"),
`\`${column}\``,
`SET ${formatedAttribute}`,
].join(" "));
}
if (!sqlAttr.length) {
return this.format(sql);
}
return [this.format(sql), this.format(sqlAttr)].join("; ");
}
getChildFKs({ database, table }) {
const sql = [
`
SELECT
con.CONNAME AS "Constraint",
conrel.RELNAME AS "ChildTable",
STRING_AGG(att2.ATTNAME, ',') AS "ChildColumn",
confrel.RELNAME AS "ParentTable",
STRING_AGG(att.ATTNAME, ',') AS "ParentColumn"
FROM
PG_CONSTRAINT con
JOIN
PG_CLASS conrel ON con.CONRELID = conrel.OID
JOIN
PG_CLASS confrel ON con.CONFRELID = confrel.OID
JOIN
UNNEST(con.CONFKEY) WITH ORDINALITY AS cols(CHILD_ATTNUM, ord) ON TRUE
JOIN
PG_ATTRIBUTE att2 ON att2.ATTNUM = cols.CHILD_ATTNUM AND att2.ATTRELID = conrel.OID
JOIN
UNNEST(con.CONFKEY) WITH ORDINALITY AS refcols(PARENT_ATTNUM, ORD2) ON cols.ORD = refcols.ORD2
JOIN
PG_ATTRIBUTE att ON att.ATTNUM = refcols.PARENT_ATTNUM AND att.ATTRELID = confrel.OID
WHERE
con.CONTYPE = 'f'
AND confrel.RELNAME = '${table.replace(/\`/g, "")}'
AND CURRENT_DATABASE() = '${database.replace(/\`/g, "")}'
GROUP
BY con.CONNAME, conrel.RELNAME, confrel.RELNAME
ORDER
BY conrel.RELNAME, con.CONNAME
`,
];
return this.format(sql);
}
getFKs({ database, table }) {
const sql = [
`
SELECT
ccu.TABLE_NAME AS "RefTable",
ccu.COLUMN_NAME AS "RefColumn",
kcu.COLUMN_NAME AS "Column",
tc.CONSTRAINT_NAME AS "Constraint"
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS tc
JOIN
INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu
ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
AND tc.TABLE_SCHEMA = kcu.TABLE_SCHEMA
JOIN
INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu
ON ccu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND ccu.TABLE_SCHEMA = tc.TABLE_SCHEMA
WHERE
tc.CONSTRAINT_TYPE = 'FOREIGN KEY'
AND CURRENT_DATABASE() = '${database.replace(/\`/g, "")}'
AND tc.TABLE_NAME = '${table.replace(/\`/g, "")}'
`,
];
return this.format(sql);
}
hasFK({ database, table, constraint, }) {
const sql = [
`
SELECT EXISTS(
SELECT
1
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
POSITION_IN_UNIQUE_CONSTRAINT IS NOT NULL
AND TABLE_CATALOG = '${database.replace(/\`/g, "")}'
AND TABLE_NAME = '${table.replace(/\`/g, "")}'
AND CONSTRAINT_NAME = '${constraint}'
) AS "IS_EXISTS"
`,
];
return this.format(sql);
}
createFK({ 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 CONSTRAINT`,
`\`${constraint}\``,
].join(" ");
return this.format(sql);
}
getIndexes({ database, table }) {
const sql = [
`
SELECT
DISTINCT ON (a.ATTNAME, i.RELNAME)
a.ATTNAME AS "Column",
i.RELNAME AS "IndexName",
UPPER(am.AMNAME) AS "IndexType",
CASE WHEN a.ATTNOTNULL = false THEN 'YES' ELSE 'NO' END AS "Nullable",
CASE WHEN ix.INDISUNIQUE = true THEN 'YES' ELSE 'NO' END AS "Unique"
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN
PG_CLASS t ON t.RELNAME = kcu.TABLE_NAME
JOIN
PG_NAMESPACE n ON t.RELNAMESPACE = n.OID
JOIN
PG_INDEX ix ON t.OID = ix.INDRELID
JOIN
PG_CLASS i ON i.OID = ix.INDEXRELID
JOIN
PG_AM am ON i.RELAM = am.OID
JOIN
LATERAL (
SELECT
UNNEST(ix.INDKEY) AS ATTR_NUM,
GENERATE_SERIES(1, ARRAY_LENGTH(ix.INDKEY, 1)) AS NUMBER
) AS seq ON TRUE
JOIN
PG_ATTRIBUTE a ON a.ATTRELID = t.OID AND a.ATTNUM = seq.ATTR_NUM
WHERE
t.RELKIND = 'r'
AND kcu.TABLE_CATALOG = '${database.replace(/\`/g, "")}'
AND t.RELNAME = '${table.replace(/\`/g, "")}'
`,
];
return this.format(sql);
}
hasIndex({ database, table, index, }) {
const sql = [
`
SELECT EXISTS(
SELECT 1
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN
PG_CLASS t ON t.RELNAME = kcu.TABLE_NAME
JOIN
PG_NAMESPACE n ON t.RELNAMESPACE = n.OID
JOIN
PG_INDEX ix ON t.OID = ix.INDRELID
JOIN
PG_CLASS i ON i.OID = ix.INDEXRELID
WHERE
t.RELKIND = 'r'
AND kcu.TABLE_CATALOG = '${database.replace(/\`/g, "")}'
AND t.RELNAME = '${table.replace(/\`/g, "")}'
AND i.RELNAME = '${index}'
) AS "IS_EXISTS"
`,
];
return this.format(sql);
}
createIndex({ table, index, key, }) {
const sql = [
`${this.$constants("CREATE_INDEX")}`,
`\`${index.replace(/`/g, "")}\``,
`${this.$constants("ON")}`,
`${table}(\`${key.replace(/`/g, "")}\`)`,
];
return this.format(sql);
}
getDatabase(database) {
const sql = [
`SELECT
DATNAME AS Database
FROM PG_DATABASE
WHERE DATNAME = '${database.replace(/`/g, "")}'`,
].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 = [
`${this.$constants("TRUNCATE_TABLE")}`,
`\`${table.replace(/`/g, "")}\``
].join(" ");
return this.format(sql);
}
sleep(second) {
return `PG_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 (insertRegex.test(sqlString) ||
updateRegex.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);
}
bindJoin(values) {
if (!Array.isArray(values) || !values.length)
return null;
return values.join(" ");
}
bindWhere(values) {
if (!Array.isArray(values) || !values.length)
return null;
return `${this.$constants("WHERE")} ${values
.map((v) => v.replace(/^\s/, "").replace(/\s+/g, " "))
.join(" ")}`;
}
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(mode) {
if (mode == null)
return '';
const modeLock = mode === "FOR_UPDATE"
? this.$constants("ROW_LEVEL_LOCK").update
: this.$constants("ROW_LEVEL_LOCK").share;
return modeLock;
}
_formatedTypeAndAttributes({ type, attributes, key, }) {
let formatedType = type;
let formatedAttributes = attributes;
if (type.startsWith("INT") && attributes.some((v) => v === "PRIMARY KEY")) {
formatedType = "SERIAL";
formatedAttributes = attributes.filter((attr) => {
return !attr.startsWith("AUTO_INCREMENT");
});
}
if (type.startsWith("TINYINT")) {
formatedType = "SMALLINT";
}
if (type.startsWith("LONGTEXT")) {
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,
};
}
}
exports.PostgresQueryBuilder = PostgresQueryBuilder;
//# sourceMappingURL=PostgresQueryBuilder.js.map