midql-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +152 -0
- package/dist/App-W6AAY42M.js +568 -0
- package/dist/chunk-3CIK5I4M.js +396 -0
- package/dist/chunk-3FPZQHHV.js +77 -0
- package/dist/chunk-3QAR6XBK.js +200 -0
- package/dist/chunk-7WNCISNV.js +106 -0
- package/dist/chunk-ATZPL4EX.js +35 -0
- package/dist/chunk-BAAEZXFH.js +73 -0
- package/dist/chunk-GWY47EDH.js +11 -0
- package/dist/chunk-NCN3ZBOJ.js +93 -0
- package/dist/chunk-PXDMSYWH.js +18 -0
- package/dist/chunk-TLTYYFT5.js +237 -0
- package/dist/chunk-VFC3HWTF.js +52 -0
- package/dist/chunk-WN6T44OZ.js +1643 -0
- package/dist/chunk-XJAN6OQ7.js +25 -0
- package/dist/cli.js +101 -0
- package/dist/controller-HDNDKU6K.js +18 -0
- package/dist/csv-F2MFR3XB.js +7 -0
- package/dist/dump-LQ7ULAAL.js +11 -0
- package/dist/execute-E45HSHNE.js +155 -0
- package/dist/humanize-UG6KBAFO.js +100 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +55 -0
- package/dist/json-Z56V7OEB.js +7 -0
- package/dist/maintenance-QHIUTVV3.js +49 -0
- package/dist/meta-NZ5Z6NYN.js +369 -0
- package/dist/mysql-GQSXUUEK.js +204 -0
- package/dist/profiles-4QGMCJAO.js +67 -0
- package/dist/query-RCUXCOAF.js +122 -0
- package/dist/restore-4QXG2WRR.js +9 -0
- package/package.json +74 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/repl/format.ts
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
var MAX_ROWS_SHOWN = 50;
|
|
7
|
+
var ROW_LIMIT_BEFORE_TRUNCATION = 200;
|
|
8
|
+
var MAX_CELL_WIDTH = 40;
|
|
9
|
+
function formatCell(value) {
|
|
10
|
+
if (value === null || value === void 0) {
|
|
11
|
+
return pc.dim("\u2205");
|
|
12
|
+
}
|
|
13
|
+
if (value instanceof Date) {
|
|
14
|
+
return value.toISOString();
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === "object") {
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
}
|
|
19
|
+
const text = String(value);
|
|
20
|
+
if (text.length > MAX_CELL_WIDTH) {
|
|
21
|
+
return `${text.slice(0, MAX_CELL_WIDTH - 1)}\u2026`;
|
|
22
|
+
}
|
|
23
|
+
return text;
|
|
24
|
+
}
|
|
25
|
+
function plainTable(head, rows) {
|
|
26
|
+
const table = new Table({
|
|
27
|
+
head: head.map((column) => pc.cyan(column)),
|
|
28
|
+
style: { head: [], border: [] }
|
|
29
|
+
});
|
|
30
|
+
for (const row of rows) {
|
|
31
|
+
table.push(row);
|
|
32
|
+
}
|
|
33
|
+
return table.toString();
|
|
34
|
+
}
|
|
35
|
+
function describeTable(table) {
|
|
36
|
+
const rows = table.columns.map((column) => {
|
|
37
|
+
const flags = [];
|
|
38
|
+
if (column.isPrimaryKey) {
|
|
39
|
+
flags.push("PK");
|
|
40
|
+
}
|
|
41
|
+
for (const fk of table.foreignKeys) {
|
|
42
|
+
const index = fk.columns.indexOf(column.name);
|
|
43
|
+
if (index >= 0) {
|
|
44
|
+
flags.push(`FK \u2192 ${fk.referencedTable}.${fk.referencedColumns[index]}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [
|
|
48
|
+
column.name,
|
|
49
|
+
column.dataType,
|
|
50
|
+
column.nullable ? "yes" : "no",
|
|
51
|
+
column.hasDefault ? "yes" : "no",
|
|
52
|
+
flags.join(", ")
|
|
53
|
+
];
|
|
54
|
+
});
|
|
55
|
+
return plainTable(["column", "type", "nullable", "default", "keys"], rows);
|
|
56
|
+
}
|
|
57
|
+
function formatTableList(schema) {
|
|
58
|
+
const rows = schema.tables.map((table) => [
|
|
59
|
+
table.name,
|
|
60
|
+
String(table.columns.length),
|
|
61
|
+
`~${table.approxRowCount}`
|
|
62
|
+
]);
|
|
63
|
+
return plainTable(["table", "columns", "rows"], rows);
|
|
64
|
+
}
|
|
65
|
+
function formatResultSet(result) {
|
|
66
|
+
if (result.columns.length === 0) {
|
|
67
|
+
const verb = result.command || "OK";
|
|
68
|
+
return pc.green(
|
|
69
|
+
`${verb} \xB7 ${result.rowCount} row${result.rowCount === 1 ? "" : "s"} affected \xB7 ${result.durationMs}ms`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (result.rows.length === 0) {
|
|
73
|
+
return pc.dim(`(no rows) \xB7 ${result.durationMs}ms`);
|
|
74
|
+
}
|
|
75
|
+
const truncate = result.rows.length > ROW_LIMIT_BEFORE_TRUNCATION;
|
|
76
|
+
const shown = truncate ? result.rows.slice(0, MAX_ROWS_SHOWN) : result.rows;
|
|
77
|
+
const table = new Table({
|
|
78
|
+
head: result.columns.map((column) => pc.cyan(column)),
|
|
79
|
+
style: { head: [], border: [] },
|
|
80
|
+
wordWrap: true
|
|
81
|
+
});
|
|
82
|
+
for (const row of shown) {
|
|
83
|
+
table.push(result.columns.map((column) => formatCell(row[column])));
|
|
84
|
+
}
|
|
85
|
+
const lines = [table.toString()];
|
|
86
|
+
if (truncate) {
|
|
87
|
+
lines.push(
|
|
88
|
+
pc.yellow(
|
|
89
|
+
`\u2026 showing first ${MAX_ROWS_SHOWN} of ${result.rows.length} rows (add 'first N' or use \\export)`
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
lines.push(
|
|
94
|
+
pc.dim(
|
|
95
|
+
`${result.rows.length} row${result.rows.length === 1 ? "" : "s"} \xB7 ${result.durationMs}ms`
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
return lines.join("\n");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
plainTable,
|
|
103
|
+
describeTable,
|
|
104
|
+
formatTableList,
|
|
105
|
+
formatResultSet
|
|
106
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/safety/preview.ts
|
|
4
|
+
var SAMPLE_SIZE = 10;
|
|
5
|
+
function previewSelect(ast) {
|
|
6
|
+
return {
|
|
7
|
+
kind: "select",
|
|
8
|
+
table: ast.table,
|
|
9
|
+
columns: "*",
|
|
10
|
+
aggregate: null,
|
|
11
|
+
joins: ast.joins,
|
|
12
|
+
where: ast.where,
|
|
13
|
+
orderBy: [],
|
|
14
|
+
limit: SAMPLE_SIZE,
|
|
15
|
+
offset: null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function countSelect(ast) {
|
|
19
|
+
return {
|
|
20
|
+
kind: "select",
|
|
21
|
+
table: ast.table,
|
|
22
|
+
columns: "*",
|
|
23
|
+
aggregate: { fn: "count", column: null },
|
|
24
|
+
joins: ast.joins,
|
|
25
|
+
where: ast.where,
|
|
26
|
+
orderBy: [],
|
|
27
|
+
limit: null,
|
|
28
|
+
offset: null
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
previewSelect,
|
|
34
|
+
countSelect
|
|
35
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findTool,
|
|
4
|
+
runTool
|
|
5
|
+
} from "./chunk-NCN3ZBOJ.js";
|
|
6
|
+
import {
|
|
7
|
+
ExecutionError
|
|
8
|
+
} from "./chunk-VFC3HWTF.js";
|
|
9
|
+
|
|
10
|
+
// src/backup/dump.ts
|
|
11
|
+
import { statSync } from "fs";
|
|
12
|
+
function defaultBackupFile(database, dialect, format) {
|
|
13
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:T]/g, "-").slice(0, 19);
|
|
14
|
+
const extension = dialect === "postgres" && format === "custom" ? "dump" : "sql";
|
|
15
|
+
return `midql-${database}-${stamp}.${extension}`;
|
|
16
|
+
}
|
|
17
|
+
async function backupDatabase(dialect, options, file, format, tools) {
|
|
18
|
+
if (dialect === "postgres") {
|
|
19
|
+
await backupPostgres(options, file, format, tools);
|
|
20
|
+
} else {
|
|
21
|
+
await backupMysql(options, file, tools);
|
|
22
|
+
}
|
|
23
|
+
return { file, sizeBytes: statSync(file).size };
|
|
24
|
+
}
|
|
25
|
+
async function backupPostgres(options, file, format, tools) {
|
|
26
|
+
const binary = await findTool("pg_dump", tools.pgDumpPath);
|
|
27
|
+
const args = [
|
|
28
|
+
format === "custom" ? "-Fc" : "-Fp",
|
|
29
|
+
"--no-password",
|
|
30
|
+
"-h",
|
|
31
|
+
options.host,
|
|
32
|
+
"-p",
|
|
33
|
+
String(options.port),
|
|
34
|
+
"-U",
|
|
35
|
+
options.user,
|
|
36
|
+
"-d",
|
|
37
|
+
options.database,
|
|
38
|
+
"-f",
|
|
39
|
+
file
|
|
40
|
+
];
|
|
41
|
+
const run = await runTool(binary, args, { PGPASSWORD: options.password ?? void 0 }, {});
|
|
42
|
+
if (run.code !== 0) {
|
|
43
|
+
throw new ExecutionError(`pg_dump failed: ${run.stderr.trim() || `exit code ${run.code}`}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function backupMysql(options, file, tools) {
|
|
47
|
+
const binary = await findTool("mysqldump", tools.mysqldumpPath);
|
|
48
|
+
const args = [
|
|
49
|
+
"--single-transaction",
|
|
50
|
+
"--routines",
|
|
51
|
+
"-h",
|
|
52
|
+
options.host,
|
|
53
|
+
"-P",
|
|
54
|
+
String(options.port),
|
|
55
|
+
"-u",
|
|
56
|
+
options.user,
|
|
57
|
+
options.database
|
|
58
|
+
];
|
|
59
|
+
const run = await runTool(
|
|
60
|
+
binary,
|
|
61
|
+
args,
|
|
62
|
+
{ MYSQL_PWD: options.password ?? void 0 },
|
|
63
|
+
{ toFile: file }
|
|
64
|
+
);
|
|
65
|
+
if (run.code !== 0) {
|
|
66
|
+
throw new ExecutionError(`mysqldump failed: ${run.stderr.trim() || `exit code ${run.code}`}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
defaultBackupFile,
|
|
72
|
+
backupDatabase
|
|
73
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ToolNotFoundError
|
|
4
|
+
} from "./chunk-VFC3HWTF.js";
|
|
5
|
+
|
|
6
|
+
// src/backup/tools.ts
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { existsSync, readdirSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
var installHints = {
|
|
11
|
+
pg_dump: "Install PostgreSQL client tools (winget install PostgreSQL.PostgreSQL / apt install postgresql-client) or set tools.pgDumpPath in ~/.midql/config.json.",
|
|
12
|
+
pg_restore: "Install PostgreSQL client tools or set tools.pgRestorePath in ~/.midql/config.json.",
|
|
13
|
+
psql: "Install PostgreSQL client tools or set tools.psqlPath in ~/.midql/config.json.",
|
|
14
|
+
mysqldump: "Install MySQL client tools (winget install Oracle.MySQL / apt install mysql-client) or set tools.mysqldumpPath in ~/.midql/config.json.",
|
|
15
|
+
mysql: "Install MySQL client tools or set tools.mysqlPath in ~/.midql/config.json."
|
|
16
|
+
};
|
|
17
|
+
function windowsCandidates(tool) {
|
|
18
|
+
const roots = tool.startsWith("pg") || tool === "psql" ? ["C:\\Program Files\\PostgreSQL", "C:\\Program Files (x86)\\PostgreSQL"] : ["C:\\Program Files\\MySQL", "C:\\Program Files (x86)\\MySQL"];
|
|
19
|
+
const candidates = [];
|
|
20
|
+
for (const root of roots) {
|
|
21
|
+
if (!existsSync(root)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
for (const version of readdirSync(root).sort().reverse()) {
|
|
25
|
+
const binary = join(root, version, "bin", `${tool}.exe`);
|
|
26
|
+
if (existsSync(binary)) {
|
|
27
|
+
candidates.push(binary);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return candidates;
|
|
32
|
+
}
|
|
33
|
+
async function isRunnable(command) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const child = spawn(command, ["--version"], { stdio: "ignore", shell: false });
|
|
36
|
+
child.on("error", () => resolve(false));
|
|
37
|
+
child.on("exit", (code) => resolve(code === 0));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function findTool(tool, configuredPath) {
|
|
41
|
+
if (configuredPath) {
|
|
42
|
+
if (await isRunnable(configuredPath)) {
|
|
43
|
+
return configuredPath;
|
|
44
|
+
}
|
|
45
|
+
throw new ToolNotFoundError(tool, `Configured path "${configuredPath}" is not runnable.`);
|
|
46
|
+
}
|
|
47
|
+
if (await isRunnable(tool)) {
|
|
48
|
+
return tool;
|
|
49
|
+
}
|
|
50
|
+
if (process.platform === "win32") {
|
|
51
|
+
for (const candidate of windowsCandidates(tool)) {
|
|
52
|
+
if (await isRunnable(candidate)) {
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new ToolNotFoundError(
|
|
58
|
+
tool,
|
|
59
|
+
installHints[tool] ?? "Install it and make sure it is on PATH."
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function runTool(command, args, env, stdio) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = spawn(command, args, {
|
|
65
|
+
env: { ...process.env, ...env },
|
|
66
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
67
|
+
shell: false
|
|
68
|
+
});
|
|
69
|
+
let stderr = "";
|
|
70
|
+
child.stderr?.on("data", (chunk) => {
|
|
71
|
+
stderr += chunk.toString();
|
|
72
|
+
});
|
|
73
|
+
if (stdio.toFile) {
|
|
74
|
+
import("fs").then(({ createWriteStream }) => {
|
|
75
|
+
child.stdout?.pipe(createWriteStream(stdio.toFile));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (stdio.fromFile) {
|
|
79
|
+
import("fs").then(({ createReadStream }) => {
|
|
80
|
+
createReadStream(stdio.fromFile).pipe(child.stdin);
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
child.stdin?.end();
|
|
84
|
+
}
|
|
85
|
+
child.on("error", reject);
|
|
86
|
+
child.on("exit", (code) => resolve({ code: code ?? 1, stderr }));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
findTool,
|
|
92
|
+
runTool
|
|
93
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/adapters/adapter.ts
|
|
4
|
+
var beginPattern = /^\s*(begin|start\s+transaction)\b/i;
|
|
5
|
+
var endPattern = /^\s*(commit|rollback|end)\b/i;
|
|
6
|
+
function trackTransactionState(sql, current) {
|
|
7
|
+
if (beginPattern.test(sql)) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (endPattern.test(sql)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
trackTransactionState
|
|
18
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ParseError
|
|
4
|
+
} from "./chunk-VFC3HWTF.js";
|
|
5
|
+
|
|
6
|
+
// src/sql/builder.ts
|
|
7
|
+
var ParamCollector = class {
|
|
8
|
+
constructor(dialect) {
|
|
9
|
+
this.dialect = dialect;
|
|
10
|
+
}
|
|
11
|
+
dialect;
|
|
12
|
+
params = [];
|
|
13
|
+
add(value) {
|
|
14
|
+
this.params.push(value);
|
|
15
|
+
return this.dialect.placeholder(this.params.length);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
function ref(column, dialect, qualify) {
|
|
19
|
+
if (!qualify) {
|
|
20
|
+
return dialect.quoteIdent(column.column);
|
|
21
|
+
}
|
|
22
|
+
return `${dialect.quoteIdent(column.table)}.${dialect.quoteIdent(column.column)}`;
|
|
23
|
+
}
|
|
24
|
+
function buildJoins(joins, dialect) {
|
|
25
|
+
return joins.map(
|
|
26
|
+
(join) => ` JOIN ${dialect.quoteIdent(join.table)} ON ${ref(join.leftColumn, dialect, true)} = ${ref(join.rightColumn, dialect, true)}`
|
|
27
|
+
).join("");
|
|
28
|
+
}
|
|
29
|
+
function buildCondition(node, dialect, params, qualify) {
|
|
30
|
+
if (node.type === "group") {
|
|
31
|
+
const parts = node.items.map((item) => buildCondition(item, dialect, params, qualify));
|
|
32
|
+
return `(${parts.join(` ${node.combinator.toUpperCase()} `)})`;
|
|
33
|
+
}
|
|
34
|
+
const column = ref(node.column, dialect, qualify);
|
|
35
|
+
switch (node.operator) {
|
|
36
|
+
case "isNull":
|
|
37
|
+
return `${column} IS NULL`;
|
|
38
|
+
case "isNotNull":
|
|
39
|
+
return `${column} IS NOT NULL`;
|
|
40
|
+
case "between":
|
|
41
|
+
return `${column} BETWEEN ${params.add(node.values[0])} AND ${params.add(node.values[1])}`;
|
|
42
|
+
case "in":
|
|
43
|
+
return `${column} IN (${node.values.map((value) => params.add(value)).join(", ")})`;
|
|
44
|
+
case "ilike": {
|
|
45
|
+
const keyword = dialect.supportsIlike ? "ILIKE" : "LIKE";
|
|
46
|
+
return `${column} ${keyword} ${params.add(node.values[0])}`;
|
|
47
|
+
}
|
|
48
|
+
case "like":
|
|
49
|
+
return `${column} LIKE ${params.add(node.values[0])}`;
|
|
50
|
+
default:
|
|
51
|
+
return `${column} ${node.operator} ${params.add(node.values[0])}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function buildWhere(where, dialect, params, qualify) {
|
|
55
|
+
if (!where) {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
const clause = buildCondition(where, dialect, params, qualify);
|
|
59
|
+
const trimmed = clause.startsWith("(") && clause.endsWith(")") ? clause.slice(1, -1) : clause;
|
|
60
|
+
return ` WHERE ${trimmed}`;
|
|
61
|
+
}
|
|
62
|
+
function buildSelect(ast, dialect) {
|
|
63
|
+
const params = new ParamCollector(dialect);
|
|
64
|
+
const qualify = ast.joins.length > 0;
|
|
65
|
+
let projection;
|
|
66
|
+
if (ast.aggregate) {
|
|
67
|
+
const fn = ast.aggregate.fn.toUpperCase();
|
|
68
|
+
projection = ast.aggregate.column ? `${fn}(${ref(ast.aggregate.column, dialect, qualify)})` : "COUNT(*)";
|
|
69
|
+
} else if (ast.columns === "*") {
|
|
70
|
+
projection = qualify ? `${dialect.quoteIdent(ast.table)}.*` : "*";
|
|
71
|
+
} else {
|
|
72
|
+
projection = ast.columns.map((column) => ref(column, dialect, qualify)).join(", ");
|
|
73
|
+
}
|
|
74
|
+
let sql = `SELECT ${projection} FROM ${dialect.quoteIdent(ast.table)}`;
|
|
75
|
+
sql += buildJoins(ast.joins, dialect);
|
|
76
|
+
sql += buildWhere(ast.where, dialect, params, qualify);
|
|
77
|
+
if (ast.orderBy.length > 0) {
|
|
78
|
+
const orderParts = ast.orderBy.map(
|
|
79
|
+
(order) => `${ref(order.column, dialect, qualify)} ${order.direction.toUpperCase()}`
|
|
80
|
+
);
|
|
81
|
+
sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
82
|
+
}
|
|
83
|
+
if (ast.limit !== null) {
|
|
84
|
+
sql += ` LIMIT ${Math.max(0, Math.floor(ast.limit))}`;
|
|
85
|
+
}
|
|
86
|
+
if (ast.offset !== null) {
|
|
87
|
+
sql += ` OFFSET ${Math.max(0, Math.floor(ast.offset))}`;
|
|
88
|
+
}
|
|
89
|
+
return { sql, params: params.params };
|
|
90
|
+
}
|
|
91
|
+
function buildInsert(ast, dialect) {
|
|
92
|
+
const params = new ParamCollector(dialect);
|
|
93
|
+
const columns = Object.keys(ast.values);
|
|
94
|
+
const columnList = columns.map((column) => dialect.quoteIdent(column)).join(", ");
|
|
95
|
+
const valueList = columns.map((column) => params.add(ast.values[column])).join(", ");
|
|
96
|
+
let sql = `INSERT INTO ${dialect.quoteIdent(ast.table)} (${columnList}) VALUES (${valueList})`;
|
|
97
|
+
if (dialect.supportsReturning) {
|
|
98
|
+
sql += " RETURNING *";
|
|
99
|
+
}
|
|
100
|
+
return { sql, params: params.params };
|
|
101
|
+
}
|
|
102
|
+
function subqueryFilter(table, keyColumn, joins, where, dialect, params) {
|
|
103
|
+
if (!keyColumn) {
|
|
104
|
+
throw new ParseError(
|
|
105
|
+
`"${table}" needs a primary key for cross-table updates or deletes; write the statement in raw SQL`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
const key = dialect.quoteIdent(keyColumn);
|
|
109
|
+
const qualifiedKey = `${dialect.quoteIdent(table)}.${key}`;
|
|
110
|
+
let subquery = `SELECT ${qualifiedKey} FROM ${dialect.quoteIdent(table)}`;
|
|
111
|
+
subquery += buildJoins(joins, dialect);
|
|
112
|
+
subquery += buildWhere(where, dialect, params, true);
|
|
113
|
+
if (dialect.name === "mysql") {
|
|
114
|
+
return ` WHERE ${key} IN (SELECT ${key} FROM (${subquery}) AS midql_filter)`;
|
|
115
|
+
}
|
|
116
|
+
return ` WHERE ${key} IN (${subquery})`;
|
|
117
|
+
}
|
|
118
|
+
function buildUpdate(ast, dialect) {
|
|
119
|
+
const params = new ParamCollector(dialect);
|
|
120
|
+
const assignments = Object.entries(ast.set).map(([column, value]) => `${dialect.quoteIdent(column)} = ${params.add(value)}`).join(", ");
|
|
121
|
+
let sql = `UPDATE ${dialect.quoteIdent(ast.table)} SET ${assignments}`;
|
|
122
|
+
if (ast.joins.length > 0) {
|
|
123
|
+
sql += subqueryFilter(ast.table, ast.keyColumn, ast.joins, ast.where, dialect, params);
|
|
124
|
+
} else {
|
|
125
|
+
sql += buildWhere(ast.where, dialect, params, false);
|
|
126
|
+
}
|
|
127
|
+
return { sql, params: params.params };
|
|
128
|
+
}
|
|
129
|
+
function buildDelete(ast, dialect) {
|
|
130
|
+
const params = new ParamCollector(dialect);
|
|
131
|
+
let sql = `DELETE FROM ${dialect.quoteIdent(ast.table)}`;
|
|
132
|
+
if (ast.joins.length > 0) {
|
|
133
|
+
sql += subqueryFilter(ast.table, ast.keyColumn, ast.joins, ast.where, dialect, params);
|
|
134
|
+
} else {
|
|
135
|
+
sql += buildWhere(ast.where, dialect, params, false);
|
|
136
|
+
}
|
|
137
|
+
return { sql, params: params.params };
|
|
138
|
+
}
|
|
139
|
+
function buildCreateTable(ast, dialect) {
|
|
140
|
+
const definitions = ast.columns.map((column) => {
|
|
141
|
+
let definition = `${dialect.quoteIdent(column.name)} ${dialect.typeName(column.type)}`;
|
|
142
|
+
if (!column.nullable && column.type !== "serial") {
|
|
143
|
+
definition += " NOT NULL";
|
|
144
|
+
}
|
|
145
|
+
if (column.references) {
|
|
146
|
+
definition += ` REFERENCES ${dialect.quoteIdent(column.references.table)}(${dialect.quoteIdent(column.references.column)})`;
|
|
147
|
+
}
|
|
148
|
+
return definition;
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
sql: `CREATE TABLE ${dialect.quoteIdent(ast.table)} (${definitions.join(", ")})`,
|
|
152
|
+
params: []
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function buildDropTable(ast, dialect) {
|
|
156
|
+
return { sql: `DROP TABLE ${dialect.quoteIdent(ast.table)}`, params: [] };
|
|
157
|
+
}
|
|
158
|
+
function buildQuery(ast, dialect) {
|
|
159
|
+
switch (ast.kind) {
|
|
160
|
+
case "select":
|
|
161
|
+
return buildSelect(ast, dialect);
|
|
162
|
+
case "insert":
|
|
163
|
+
return buildInsert(ast, dialect);
|
|
164
|
+
case "update":
|
|
165
|
+
return buildUpdate(ast, dialect);
|
|
166
|
+
case "delete":
|
|
167
|
+
return buildDelete(ast, dialect);
|
|
168
|
+
case "createTable":
|
|
169
|
+
return buildCreateTable(ast, dialect);
|
|
170
|
+
case "dropTable":
|
|
171
|
+
return buildDropTable(ast, dialect);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/sql/dialect-mysql.ts
|
|
176
|
+
var typeNames = {
|
|
177
|
+
text: "TEXT",
|
|
178
|
+
number: "INT",
|
|
179
|
+
decimal: "DECIMAL(12,2)",
|
|
180
|
+
boolean: "BOOLEAN",
|
|
181
|
+
date: "DATE",
|
|
182
|
+
timestamp: "DATETIME",
|
|
183
|
+
json: "JSON",
|
|
184
|
+
uuid: "CHAR(36)",
|
|
185
|
+
serial: "INT AUTO_INCREMENT PRIMARY KEY"
|
|
186
|
+
};
|
|
187
|
+
var mysqlDialect = {
|
|
188
|
+
name: "mysql",
|
|
189
|
+
quoteIdent(name) {
|
|
190
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
191
|
+
},
|
|
192
|
+
placeholder() {
|
|
193
|
+
return "?";
|
|
194
|
+
},
|
|
195
|
+
supportsIlike: false,
|
|
196
|
+
supportsReturning: false,
|
|
197
|
+
typeName(type) {
|
|
198
|
+
return typeNames[type];
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/sql/dialect-postgres.ts
|
|
203
|
+
var typeNames2 = {
|
|
204
|
+
text: "TEXT",
|
|
205
|
+
number: "INTEGER",
|
|
206
|
+
decimal: "NUMERIC",
|
|
207
|
+
boolean: "BOOLEAN",
|
|
208
|
+
date: "DATE",
|
|
209
|
+
timestamp: "TIMESTAMPTZ",
|
|
210
|
+
json: "JSONB",
|
|
211
|
+
uuid: "UUID",
|
|
212
|
+
serial: "SERIAL PRIMARY KEY"
|
|
213
|
+
};
|
|
214
|
+
var postgresDialect = {
|
|
215
|
+
name: "postgres",
|
|
216
|
+
quoteIdent(name) {
|
|
217
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
218
|
+
},
|
|
219
|
+
placeholder(index) {
|
|
220
|
+
return `$${index}`;
|
|
221
|
+
},
|
|
222
|
+
supportsIlike: true,
|
|
223
|
+
supportsReturning: true,
|
|
224
|
+
typeName(type) {
|
|
225
|
+
return typeNames2[type];
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// src/sql/dialects.ts
|
|
230
|
+
function dialectFor(name) {
|
|
231
|
+
return name === "mysql" ? mysqlDialect : postgresDialect;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export {
|
|
235
|
+
buildQuery,
|
|
236
|
+
dialectFor
|
|
237
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/core/errors.ts
|
|
4
|
+
var MidqlError = class extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = new.target.name;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var ParseError = class extends MidqlError {
|
|
11
|
+
position;
|
|
12
|
+
suggestions;
|
|
13
|
+
constructor(message, options) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.position = options?.position ?? null;
|
|
16
|
+
this.suggestions = options?.suggestions ?? [];
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var AmbiguityError = class extends MidqlError {
|
|
20
|
+
token;
|
|
21
|
+
candidates;
|
|
22
|
+
constructor(token, candidates) {
|
|
23
|
+
super(`"${token}" is ambiguous`);
|
|
24
|
+
this.token = token;
|
|
25
|
+
this.candidates = candidates;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var ConnectionError = class extends MidqlError {
|
|
29
|
+
};
|
|
30
|
+
var ExecutionError = class extends MidqlError {
|
|
31
|
+
};
|
|
32
|
+
var ToolNotFoundError = class extends MidqlError {
|
|
33
|
+
tool;
|
|
34
|
+
hint;
|
|
35
|
+
constructor(tool, hint) {
|
|
36
|
+
super(`${tool} not found. ${hint}`);
|
|
37
|
+
this.tool = tool;
|
|
38
|
+
this.hint = hint;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var ConfigError = class extends MidqlError {
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
MidqlError,
|
|
46
|
+
ParseError,
|
|
47
|
+
AmbiguityError,
|
|
48
|
+
ConnectionError,
|
|
49
|
+
ExecutionError,
|
|
50
|
+
ToolNotFoundError,
|
|
51
|
+
ConfigError
|
|
52
|
+
};
|