@sqg/sqg 0.4.0 → 0.6.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/README.md +6 -1
- package/dist/sqg.mjs +1118 -651
- package/dist/templates/node-sqlite.hbs +108 -0
- package/package.json +3 -2
package/dist/sqg.mjs
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
import { exit } from "node:process";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import consola, { LogLevels } from "consola";
|
|
5
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
-
import {
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { homedir, tmpdir } from "node:os";
|
|
7
8
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
8
|
-
import
|
|
9
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
12
|
import YAML from "yaml";
|
|
13
|
+
import Handlebars from "handlebars";
|
|
10
14
|
import { z } from "zod";
|
|
11
15
|
import { DuckDBEnumType, DuckDBInstance, DuckDBListType, DuckDBMapType, DuckDBStructType } from "@duckdb/node-api";
|
|
12
16
|
import { LRParser } from "@lezer/lr";
|
|
@@ -22,8 +26,10 @@ import estree from "prettier/plugins/estree";
|
|
|
22
26
|
|
|
23
27
|
//#region src/constants.ts
|
|
24
28
|
/**
|
|
25
|
-
* SQG Constants - Centralized definitions for supported
|
|
26
|
-
*
|
|
29
|
+
* SQG Constants - Centralized definitions for supported generators
|
|
30
|
+
*
|
|
31
|
+
* Generator format: <language>/<engine>/<driver>
|
|
32
|
+
* Short format: <language>/<engine> (uses default driver)
|
|
27
33
|
*/
|
|
28
34
|
/** Supported database engines */
|
|
29
35
|
const DB_ENGINES = [
|
|
@@ -31,35 +37,136 @@ const DB_ENGINES = [
|
|
|
31
37
|
"duckdb",
|
|
32
38
|
"postgres"
|
|
33
39
|
];
|
|
34
|
-
/**
|
|
35
|
-
const
|
|
36
|
-
"typescript/better-sqlite3": {
|
|
40
|
+
/** All supported generators with their full specification */
|
|
41
|
+
const GENERATORS = {
|
|
42
|
+
"typescript/sqlite/better-sqlite3": {
|
|
43
|
+
language: "typescript",
|
|
44
|
+
engine: "sqlite",
|
|
45
|
+
driver: "better-sqlite3",
|
|
37
46
|
description: "TypeScript with better-sqlite3 driver",
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
extension: ".ts",
|
|
48
|
+
template: "better-sqlite3.hbs"
|
|
40
49
|
},
|
|
41
|
-
"typescript/
|
|
50
|
+
"typescript/sqlite/node": {
|
|
51
|
+
language: "typescript",
|
|
52
|
+
engine: "sqlite",
|
|
53
|
+
driver: "node",
|
|
54
|
+
description: "TypeScript with Node.js built-in SQLite",
|
|
55
|
+
extension: ".ts",
|
|
56
|
+
template: "node-sqlite.hbs"
|
|
57
|
+
},
|
|
58
|
+
"typescript/duckdb/node-api": {
|
|
59
|
+
language: "typescript",
|
|
60
|
+
engine: "duckdb",
|
|
61
|
+
driver: "node-api",
|
|
42
62
|
description: "TypeScript with @duckdb/node-api driver",
|
|
43
|
-
|
|
44
|
-
|
|
63
|
+
extension: ".ts",
|
|
64
|
+
template: "typescript-duckdb.hbs"
|
|
65
|
+
},
|
|
66
|
+
"java/sqlite/jdbc": {
|
|
67
|
+
language: "java",
|
|
68
|
+
engine: "sqlite",
|
|
69
|
+
driver: "jdbc",
|
|
70
|
+
description: "Java with JDBC for SQLite",
|
|
71
|
+
extension: ".java",
|
|
72
|
+
template: "java-jdbc.hbs"
|
|
45
73
|
},
|
|
46
|
-
"java/jdbc": {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
extension: ".java"
|
|
74
|
+
"java/duckdb/jdbc": {
|
|
75
|
+
language: "java",
|
|
76
|
+
engine: "duckdb",
|
|
77
|
+
driver: "jdbc",
|
|
78
|
+
description: "Java with JDBC for DuckDB",
|
|
79
|
+
extension: ".java",
|
|
80
|
+
template: "java-jdbc.hbs"
|
|
54
81
|
},
|
|
55
|
-
"java/duckdb
|
|
82
|
+
"java/duckdb/arrow": {
|
|
83
|
+
language: "java",
|
|
84
|
+
engine: "duckdb",
|
|
85
|
+
driver: "arrow",
|
|
56
86
|
description: "Java with DuckDB Arrow API",
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
extension: ".java",
|
|
88
|
+
template: "java-duckdb-arrow.hbs"
|
|
89
|
+
},
|
|
90
|
+
"java/postgres/jdbc": {
|
|
91
|
+
language: "java",
|
|
92
|
+
engine: "postgres",
|
|
93
|
+
driver: "jdbc",
|
|
94
|
+
description: "Java with JDBC for PostgreSQL",
|
|
95
|
+
extension: ".java",
|
|
96
|
+
template: "java-jdbc.hbs"
|
|
59
97
|
}
|
|
60
98
|
};
|
|
61
|
-
/**
|
|
62
|
-
const
|
|
99
|
+
/** Default drivers for language/engine combinations */
|
|
100
|
+
const DEFAULT_DRIVERS = {
|
|
101
|
+
"typescript/sqlite": "better-sqlite3",
|
|
102
|
+
"typescript/duckdb": "node-api",
|
|
103
|
+
"java/sqlite": "jdbc",
|
|
104
|
+
"java/duckdb": "jdbc",
|
|
105
|
+
"java/postgres": "jdbc"
|
|
106
|
+
};
|
|
107
|
+
/** List of all full generator names */
|
|
108
|
+
const GENERATOR_NAMES = Object.keys(GENERATORS);
|
|
109
|
+
/** List of short generator names (language/engine) */
|
|
110
|
+
const SHORT_GENERATOR_NAMES = Object.keys(DEFAULT_DRIVERS);
|
|
111
|
+
/**
|
|
112
|
+
* Resolve a generator string to its full form.
|
|
113
|
+
* Accepts both short (language/engine) and full (language/engine/driver) formats.
|
|
114
|
+
*/
|
|
115
|
+
function resolveGenerator(generator) {
|
|
116
|
+
if (generator in GENERATORS) return generator;
|
|
117
|
+
if (generator in DEFAULT_DRIVERS) return `${generator}/${DEFAULT_DRIVERS[generator]}`;
|
|
118
|
+
return generator;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parse a generator string and return its info.
|
|
122
|
+
* Throws if the generator is invalid.
|
|
123
|
+
*/
|
|
124
|
+
function parseGenerator(generator) {
|
|
125
|
+
const info = GENERATORS[resolveGenerator(generator)];
|
|
126
|
+
if (!info) throw new Error(`Invalid generator: ${generator}`);
|
|
127
|
+
return info;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Check if a generator string is valid (either short or full form).
|
|
131
|
+
*/
|
|
132
|
+
function isValidGenerator(generator) {
|
|
133
|
+
return resolveGenerator(generator) in GENERATORS;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the database engine for a generator.
|
|
137
|
+
*/
|
|
138
|
+
function getGeneratorEngine(generator) {
|
|
139
|
+
return parseGenerator(generator).engine;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Find similar generator names for typo suggestions.
|
|
143
|
+
*/
|
|
144
|
+
function findSimilarGenerators(input) {
|
|
145
|
+
const normalized = input.toLowerCase();
|
|
146
|
+
return [...GENERATOR_NAMES, ...SHORT_GENERATOR_NAMES].filter((name) => {
|
|
147
|
+
const nameLower = name.toLowerCase();
|
|
148
|
+
if (nameLower.includes(normalized) || normalized.includes(nameLower)) return true;
|
|
149
|
+
return [
|
|
150
|
+
normalized.replace(/\//g, "-"),
|
|
151
|
+
normalized.replace(/-/g, "/"),
|
|
152
|
+
normalized.replace(/_/g, "/"),
|
|
153
|
+
normalized.replace(/_/g, "-")
|
|
154
|
+
].some((v) => nameLower.includes(v) || v.includes(nameLower));
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Format generators for CLI help output.
|
|
159
|
+
*/
|
|
160
|
+
function formatGeneratorsHelp() {
|
|
161
|
+
const lines = [];
|
|
162
|
+
for (const shortName of SHORT_GENERATOR_NAMES) {
|
|
163
|
+
const fullName = `${shortName}/${DEFAULT_DRIVERS[shortName]}`;
|
|
164
|
+
const info = GENERATORS[fullName];
|
|
165
|
+
lines.push(` ${shortName.padEnd(24)} ${info.description} (default)`);
|
|
166
|
+
for (const [generatorName, generatorInfo] of Object.entries(GENERATORS)) if (generatorName.startsWith(`${shortName}/`) && generatorName !== fullName) lines.push(` ${generatorName.padEnd(24)} ${generatorInfo.description}`);
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
63
170
|
/** SQL annotation syntax reference */
|
|
64
171
|
const SQL_SYNTAX_REFERENCE = `
|
|
65
172
|
SQL Annotation Syntax:
|
|
@@ -88,34 +195,6 @@ Example:
|
|
|
88
195
|
|
|
89
196
|
-- TABLE users :appender
|
|
90
197
|
`.trim();
|
|
91
|
-
/**
|
|
92
|
-
* Find similar generator names for typo suggestions
|
|
93
|
-
*/
|
|
94
|
-
function findSimilarGenerators(input) {
|
|
95
|
-
const normalized = input.toLowerCase();
|
|
96
|
-
return GENERATOR_NAMES.filter((name) => {
|
|
97
|
-
const nameLower = name.toLowerCase();
|
|
98
|
-
if (nameLower.includes(normalized) || normalized.includes(nameLower)) return true;
|
|
99
|
-
return [
|
|
100
|
-
normalized.replace("/", "-"),
|
|
101
|
-
normalized.replace("-", "/"),
|
|
102
|
-
normalized.replace("_", "/"),
|
|
103
|
-
normalized.replace("_", "-")
|
|
104
|
-
].some((v) => nameLower.includes(v) || v.includes(nameLower));
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Format generators for CLI help output
|
|
109
|
-
*/
|
|
110
|
-
function formatGeneratorsHelp() {
|
|
111
|
-
return Object.entries(SUPPORTED_GENERATORS).map(([name, info]) => ` ${name.padEnd(28)} ${info.description} (${info.compatibleEngines.join(", ")})`).join("\n");
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Format engines for CLI help output
|
|
115
|
-
*/
|
|
116
|
-
function formatEnginesHelp() {
|
|
117
|
-
return DB_ENGINES.map((e) => ` ${e}`).join("\n");
|
|
118
|
-
}
|
|
119
198
|
|
|
120
199
|
//#endregion
|
|
121
200
|
//#region src/errors.ts
|
|
@@ -174,41 +253,20 @@ var ConfigError = class extends SqgError {
|
|
|
174
253
|
* Error for invalid generator names
|
|
175
254
|
*/
|
|
176
255
|
var InvalidGeneratorError = class extends SqgError {
|
|
177
|
-
constructor(generatorName, validGenerators, suggestion) {
|
|
256
|
+
constructor(generatorName, validGenerators$1, suggestion) {
|
|
178
257
|
const similarMsg = suggestion ? ` Did you mean '${suggestion}'?` : "";
|
|
179
|
-
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators.join(", ")}`, { generator: generatorName });
|
|
258
|
+
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators$1.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators$1.join(", ")}`, { generator: generatorName });
|
|
180
259
|
this.name = "InvalidGeneratorError";
|
|
181
260
|
}
|
|
182
261
|
};
|
|
183
262
|
/**
|
|
184
|
-
* Error for invalid engine names
|
|
185
|
-
*/
|
|
186
|
-
var InvalidEngineError = class extends SqgError {
|
|
187
|
-
constructor(engineName, validEngines) {
|
|
188
|
-
super(`Invalid engine '${engineName}'. Valid engines: ${validEngines.join(", ")}`, "INVALID_ENGINE", `Choose from: ${validEngines.join(", ")}`, { engine: engineName });
|
|
189
|
-
this.name = "InvalidEngineError";
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
/**
|
|
193
|
-
* Error for generator/engine compatibility
|
|
194
|
-
*/
|
|
195
|
-
var GeneratorEngineMismatchError = class extends SqgError {
|
|
196
|
-
constructor(generator, engine, compatibleEngines) {
|
|
197
|
-
super(`Generator '${generator}' is not compatible with engine '${engine}'`, "GENERATOR_ENGINE_MISMATCH", `Generator '${generator}' works with: ${compatibleEngines.join(", ")}`, {
|
|
198
|
-
generator,
|
|
199
|
-
engine
|
|
200
|
-
});
|
|
201
|
-
this.name = "GeneratorEngineMismatchError";
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
/**
|
|
205
263
|
* Error for database initialization/connection issues
|
|
206
264
|
*/
|
|
207
265
|
var DatabaseError = class extends SqgError {
|
|
208
266
|
constructor(message, engine, suggestion, context) {
|
|
209
267
|
super(message, "DATABASE_ERROR", suggestion, {
|
|
210
|
-
|
|
211
|
-
|
|
268
|
+
...context,
|
|
269
|
+
engine
|
|
212
270
|
});
|
|
213
271
|
this.name = "DatabaseError";
|
|
214
272
|
}
|
|
@@ -281,201 +339,468 @@ function formatErrorForOutput(err) {
|
|
|
281
339
|
}
|
|
282
340
|
|
|
283
341
|
//#endregion
|
|
284
|
-
//#region src/
|
|
285
|
-
const parser = LRParser.deserialize({
|
|
286
|
-
version: 14,
|
|
287
|
-
states: "&SOVQPOOO_QPO'#CwOdQPO'#CzOiQPO'#CvO!SQQO'#C^OOQO'#Cn'#CnQVQPOOO!aQSO,59cO!lQPO,59fOOQO'#Cp'#CpO!tQQO,59bOOQO'#C}'#C}O#iQQO'#CiOOQO,58x,58xOOQO-E6l-E6lOOQO'#Co'#CoO!aQSO1G.}O!dQSO1G.}OOQO'#Cb'#CbOOQO1G.}1G.}O#yQPO1G/QOOQO-E6n-E6nO$RQPO'#CdOOQO'#Cq'#CqO$WQQO1G.|OOQO'#Cm'#CmOOQO'#Cr'#CrO$xQQO,59TOOQO-E6m-E6mO!dQSO7+$iOOQO7+$i7+$iO%YQPO,59OOOQO-E6o-E6oOOQO-E6p-E6pOOQO<<HT<<HTO%_QQO1G.jOOQO'#Ce'#CeOiQPO7+$UO%jQQO<<Gp",
|
|
288
|
-
stateData: "&l~OiOS~ORPOVQO~OSVO~OSWO~OlXO~OYZOZZO[ZO^ZO_ZO`ZO~OR]PV]Pg]P~PnOT_OlXOmbO~OT_Olna~OlXOofORjaVjaYjaZja[ja^ja_ja`jagja~OliOR]XV]Xg]X~PnOT_Olni~OSoO~OofORjiVjiYjiZji[ji^ji_ji`jigji~OliOR]aV]ag]a~PnOpsO~OYtOZtO[tO~OlXORWyVWyYWyZWy[Wy^Wy_Wy`WygWyoWy~OR`o^iZTmYV_[~",
|
|
289
|
-
goto: "#prPPsPPPwP!R!VPPP!YPPP!]!a!g!q#T#ZPPP#a#ePP#ePP#iTTOUQcVSn`aRrmTgYhRusR]STj[kQUOR^UQ`VQdWTl`dQYRQaVWeYamvQm`RvuQhYRphQk[RqkTSOUTROUQ[STj[k",
|
|
290
|
-
nodeNames: "⚠ File QueryBlock BlockCommentStartSpecial Name Modifiers Config LineCommentStartSpecial SetVarLine Value StringLiteral StringLiteralSingle SQLText SQLBlock BlockComment LineComment VarRef BR",
|
|
291
|
-
maxTerm: 33,
|
|
292
|
-
skippedNodes: [0],
|
|
293
|
-
repeatNodeCount: 5,
|
|
294
|
-
tokenData: "$3b~RqOX#YXY'wYZ(iZ]#Y]^$W^p#Ypq'wqr#Yrs(}st#Ytu6^uw#Ywx9[xz#Yz{%_{}#Y}!OKi!O!P#Y!P!Q#%p!Q![$)l![!]$+U!]!_#Y!_!`$.U!`!b#Y!b!c$/U!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#o;'S#Y;'S;=`'q<%lO#YU#_][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YS$ZTOz$Wz{$j{;'S$W;'S;=`%X<%lO$WS$mVOz$Wz{$j{!P$W!P!Q%S!Q;'S$W;'S;=`%X<%lO$WS%XOmSS%[P;=`<%l$WU%d_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!P#Y!P!Q&c!Q;'S#Y;'S;=`'q<%lO#YU&jVmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'UV[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'nP;=`<%l'PU'tP;=`<%l#Y~'|Xi~OX$WXY'wYp$Wpq'wqz$Wz{$j{;'S$W;'S;=`%X<%lO$W~(nTl~Oz$Wz{$j{;'S$W;'S;=`%X<%lO$WU)Sb[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U*_ZOY*[YZ$WZr*[rs+Qsz*[z{+f{#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U+VTYQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU+i]OY*[YZ$WZr*[rs+Qsz*[z{+f{!P*[!P!Q,b!Q#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U,gWmSOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-SWOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-qOYQQ-tTOY-PYZ-PZ;'S-P;'S;=`.T<%lO-PQ.WP;=`<%l-PU.^VOY*[YZ*[Zz*[z{+f{;'S*[;'S;=`.s<%lO*[U.vP;=`<%l*[U/Q]YQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU0Od[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{!P(}!P!Q1^!Q#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U1e_mS[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ2i_[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ3oVYQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ4Z[[QOX2dXY-PYZ-PZ]2d]^-P^p2dpq-Pqt2dtu-Pu;'S2d;'S;=`5P<%lO2dQ5SP;=`<%l2dU5[^[QOX(}XY*[YZ*[Z](}]^*[^p(}pq*[qt(}tu*[uz(}z{/y{;'S(};'S;=`6W<%lO(}U6ZP;=`<%l(}U6cV[QOz$Wz{$j{#o$W#o#p6x#p;'S$W;'S;=`%X<%lO$WU6{]Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o;'S$W;'S;=`%X<%lO$WU7w_Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o#q$W#q#r8v#r;'S$W;'S;=`%X<%lO$WU8{T`QOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU9ab[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[U:lZOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU;dTZQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU;v]OY:iYZ$WZw:iwx;_xz:iz{;s{!P:i!P!Q<o!Q#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU<tWmSOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q=aWOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>OOZQQ>RXOY=^YZ=^Zw=^wx>nx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>sWZQOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q?`P;=`<%l=^U?fZOY:iYZ:iZw:iwx@Xxz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU@^ZZQOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iUASP;=`<%l:iUA^]ZQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YUB[d[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{!P9[!P!QCj!Q#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UCq_mS[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQDu_[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQE{VZQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQFg`[QOXDpXY=^YZ=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxGix#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQGp_ZQ[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQHrP;=`<%lDpUHzb[QOX9[XY:iYZ:iZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxJSxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UJZbZQ[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UKfP;=`<%l9[VKn_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{}#Y}!OLm!O;'S#Y;'S;=`'q<%lO#YVLtf_Q[QOXNYXY!&vYZ$WZ]NY]^! ]^pNYpq!&vqtNYtu! ]uzNYz{!#k{!gNY!g!h!8V!h!oNY!o!p!<w!p!sNY!s!t!DP!t!vNY!v!w!Hu!w;'SNY;'S;=`!&p<%lONYUNa^_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYU! bV_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]U! |X_QOY! ]YZ$WZz! ]z{! w{!P! ]!P!Q!!i!Q;'S! ];'S;=`!#e<%lO! ]U!!pSmS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#RS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#bP;=`<%l!!|U!#hP;=`<%l! ]U!#r`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!PNY!P!Q!$t!Q;'SNY;'S;=`!&p<%lONYU!$}ZmS_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!%wZ_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!&mP;=`<%l!%pU!&sP;=`<%lNYV!&{b_QOX! ]XY!&vYZ$WZp! ]pq!&vqz! ]z{! w{!g! ]!g!h!(T!h!o! ]!o!p!*u!p!s! ]!s!t!.}!t!v! ]!v!w!1s!w;'S! ];'S;=`!#e<%lO! ]V!(YX_QOY! ]YZ$WZz! ]z{! w{!z! ]!z!{!(u!{;'S! ];'S;=`!#e<%lO! ]V!(zX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!)g!h;'S! ];'S;=`!#e<%lO! ]V!)lX_QOY! ]YZ$WZz! ]z{! w{!e! ]!e!f!*X!f;'S! ];'S;=`!#e<%lO! ]V!*`VVR_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]V!*zX_QOY! ]YZ$WZz! ]z{! w{!k! ]!k!l!+g!l;'S! ];'S;=`!#e<%lO! ]V!+lX_QOY! ]YZ$WZz! ]z{! w{!i! ]!i!j!,X!j;'S! ];'S;=`!#e<%lO! ]V!,^X_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!,y!u;'S! ];'S;=`!#e<%lO! ]V!-OX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!-k!d;'S! ];'S;=`!#e<%lO! ]V!-pX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!.]!w;'S! ];'S;=`!#e<%lO! ]V!.bX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!*X!h;'S! ];'S;=`!#e<%lO! ]V!/SX_QOY! ]YZ$WZz! ]z{! w{!w! ]!w!x!/o!x;'S! ];'S;=`!#e<%lO! ]V!/tX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!0a!h;'S! ];'S;=`!#e<%lO! ]V!0fX_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!1R!u;'S! ];'S;=`!#e<%lO! ]V!1WX_QOY! ]YZ$WZz! ]z{! w{!{! ]!{!|!*X!|;'S! ];'S;=`!#e<%lO! ]V!1xZ_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!2k!d!g! ]!g!h!3}!h;'S! ];'S;=`!#e<%lO! ]V!2pX_QOY! ]YZ$WZz! ]z{! w{!d! ]!d!e!3]!e;'S! ];'S;=`!#e<%lO! ]V!3bX_QOY! ]YZ$WZz! ]z{! w{!n! ]!n!o!.]!o;'S! ];'S;=`!#e<%lO! ]V!4SX_QOY! ]YZ$WZz! ]z{! w{!u! ]!u!v!4o!v;'S! ];'S;=`!#e<%lO! ]V!4tX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!5a!w;'S! ];'S;=`!#e<%lO! ]V!5fX_QOY! ]YZ$WZz! ]z{! w{!f! ]!f!g!6R!g;'S! ];'S;=`!#e<%lO! ]V!6WX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!6s!d;'S! ];'S;=`!#e<%lO! ]V!6xX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!7e!w;'S! ];'S;=`!#e<%lO! ]V!7jX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!*X!d;'S! ];'S;=`!#e<%lO! ]V!8^`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!zNY!z!{!9`!{;'SNY;'S;=`!&p<%lONYV!9g`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!:i!h;'SNY;'S;=`!&p<%lONYV!:p`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!eNY!e!f!;r!f;'SNY;'S;=`!&p<%lONYV!;{^VR_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYV!=O`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!kNY!k!l!>Q!l;'SNY;'S;=`!&p<%lONYV!>X`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!iNY!i!j!?Z!j;'SNY;'S;=`!&p<%lONYV!?b`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!@d!u;'SNY;'S;=`!&p<%lONYV!@k`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!Am!d;'SNY;'S;=`!&p<%lONYV!At`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Bv!w;'SNY;'S;=`!&p<%lONYV!B}`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!;r!h;'SNY;'S;=`!&p<%lONYV!DW`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!wNY!w!x!EY!x;'SNY;'S;=`!&p<%lONYV!Ea`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!Fc!h;'SNY;'S;=`!&p<%lONYV!Fj`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!Gl!u;'SNY;'S;=`!&p<%lONYV!Gs`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!{NY!{!|!;r!|;'SNY;'S;=`!&p<%lONYV!H|b_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!JU!d!gNY!g!h!Lh!h;'SNY;'S;=`!&p<%lONYV!J]`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!dNY!d!e!K_!e;'SNY;'S;=`!&p<%lONYV!Kf`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!nNY!n!o!Bv!o;'SNY;'S;=`!&p<%lONYV!Lo`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!uNY!u!v!Mq!v;'SNY;'S;=`!&p<%lONYV!Mx`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Nz!w;'SNY;'S;=`!&p<%lONYV# R`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!fNY!f!g#!T!g;'SNY;'S;=`!&p<%lONYV#![`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d##^!d;'SNY;'S;=`!&p<%lONYV##e`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w#$g!w;'SNY;'S;=`!&p<%lONYV#$n`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!;r!d;'SNY;'S;=`!&p<%lONYV#%u][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{#&n{;'S#Y;'S;=`'q<%lO#YV#&sh[QOX#(_XY#,`YZ#,`Z]#(_]^#)]^p#(_pq#,`qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#9m!Q!g#(_!g!h#>j!h!o#(_!o!p#Bv!p!s#(_!s!t#I`!t!v#(_!v!w#Mp!w;'S#(_;'S;=`#,Y<%lO#(_U#(d][QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_U#)`TOz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]U#)rVOz#)]z{#)o{!P#)]!P!Q#*X!Q;'S#)];'S;=`#*`<%lO#)]U#*`O^QmSU#*cP;=`<%l#)]U#*k_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#+j!Q;'S#(_;'S;=`#,Y<%lO#(_U#+sV^QmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PU#,]P;=`<%l#(_V#,cbOX#)]XY#,`YZ#,`Zp#)]pq#,`qz#)]z{#)o{!g#)]!g!h#-k!h!o#)]!o!p#/k!p!s#)]!s!t#2q!t!v#)]!v!w#4u!w;'S#)];'S;=`#*`<%lO#)]V#-nVOz#)]z{#)o{!z#)]!z!{#.T!{;'S#)];'S;=`#*`<%lO#)]V#.WVOz#)]z{#)o{!g#)]!g!h#.m!h;'S#)];'S;=`#*`<%lO#)]V#.pVOz#)]z{#)o{!e#)]!e!f#/V!f;'S#)];'S;=`#*`<%lO#)]V#/[TRROz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]V#/nVOz#)]z{#)o{!k#)]!k!l#0T!l;'S#)];'S;=`#*`<%lO#)]V#0WVOz#)]z{#)o{!i#)]!i!j#0m!j;'S#)];'S;=`#*`<%lO#)]V#0pVOz#)]z{#)o{!t#)]!t!u#1V!u;'S#)];'S;=`#*`<%lO#)]V#1YVOz#)]z{#)o{!c#)]!c!d#1o!d;'S#)];'S;=`#*`<%lO#)]V#1rVOz#)]z{#)o{!v#)]!v!w#2X!w;'S#)];'S;=`#*`<%lO#)]V#2[VOz#)]z{#)o{!g#)]!g!h#/V!h;'S#)];'S;=`#*`<%lO#)]V#2tVOz#)]z{#)o{!w#)]!w!x#3Z!x;'S#)];'S;=`#*`<%lO#)]V#3^VOz#)]z{#)o{!g#)]!g!h#3s!h;'S#)];'S;=`#*`<%lO#)]V#3vVOz#)]z{#)o{!t#)]!t!u#4]!u;'S#)];'S;=`#*`<%lO#)]V#4`VOz#)]z{#)o{!{#)]!{!|#/V!|;'S#)];'S;=`#*`<%lO#)]V#4xXOz#)]z{#)o{!c#)]!c!d#5e!d!g#)]!g!h#6g!h;'S#)];'S;=`#*`<%lO#)]V#5hVOz#)]z{#)o{!d#)]!d!e#5}!e;'S#)];'S;=`#*`<%lO#)]V#6QVOz#)]z{#)o{!n#)]!n!o#2X!o;'S#)];'S;=`#*`<%lO#)]V#6jVOz#)]z{#)o{!u#)]!u!v#7P!v;'S#)];'S;=`#*`<%lO#)]V#7SVOz#)]z{#)o{!v#)]!v!w#7i!w;'S#)];'S;=`#*`<%lO#)]V#7lVOz#)]z{#)o{!f#)]!f!g#8R!g;'S#)];'S;=`#*`<%lO#)]V#8UVOz#)]z{#)o{!c#)]!c!d#8k!d;'S#)];'S;=`#*`<%lO#)]V#8nVOz#)]z{#)o{!v#)]!v!w#9T!w;'S#)];'S;=`#*`<%lO#)]V#9WVOz#)]z{#)o{!c#)]!c!d#/V!d;'S#)];'S;=`#*`<%lO#)]U#9t]mS[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#:r][QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#;nTOz#;kz{#;}{;'S#;k;'S;=`#<l<%lO#;kQ#<QVOz#;kz{#;}{!P#;k!P!Q#<g!Q;'S#;k;'S;=`#<l<%lO#;kQ#<lO^QQ#<oP;=`<%l#;kQ#<w_[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{!P#:m!P!Q#=v!Q;'S#:m;'S;=`#>d<%lO#:mQ#=}V^Q[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ#>gP;=`<%l#:mV#>o_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!z#(_!z!{#?n!{;'S#(_;'S;=`#,Y<%lO#(_V#?s_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#@r!h;'S#(_;'S;=`#,Y<%lO#(_V#@w_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!e#(_!e!f#Av!f;'S#(_;'S;=`#,Y<%lO#(_V#A}]RR[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_V#B{_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!k#(_!k!l#Cz!l;'S#(_;'S;=`#,Y<%lO#(_V#DP_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!i#(_!i!j#EO!j;'S#(_;'S;=`#,Y<%lO#(_V#ET_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#FS!u;'S#(_;'S;=`#,Y<%lO#(_V#FX_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#GW!d;'S#(_;'S;=`#,Y<%lO#(_V#G]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w#H[!w;'S#(_;'S;=`#,Y<%lO#(_V#Ha_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Av!h;'S#(_;'S;=`#,Y<%lO#(_V#Ie_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!w#(_!w!x#Jd!x;'S#(_;'S;=`#,Y<%lO#(_V#Ji_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Kh!h;'S#(_;'S;=`#,Y<%lO#(_V#Km_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#Ll!u;'S#(_;'S;=`#,Y<%lO#(_V#Lq_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!{#(_!{!|#Av!|;'S#(_;'S;=`#,Y<%lO#(_V#Mua[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Nz!d!g#(_!g!h$#S!h;'S#(_;'S;=`#,Y<%lO#(_V$ P_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!d#(_!d!e$!O!e;'S#(_;'S;=`#,Y<%lO#(_V$!T_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!n#(_!n!o#H[!o;'S#(_;'S;=`#,Y<%lO#(_V$#X_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!u#(_!u!v$$W!v;'S#(_;'S;=`#,Y<%lO#(_V$$]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$%[!w;'S#(_;'S;=`#,Y<%lO#(_V$%a_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!f#(_!f!g$&`!g;'S#(_;'S;=`#,Y<%lO#(_V$&e_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d$'d!d;'S#(_;'S;=`#,Y<%lO#(_V$'i_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$(h!w;'S#(_;'S;=`#,Y<%lO#(_V$(m_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Av!d;'S#(_;'S;=`#,Y<%lO#(_V$)seSP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$)l![!c#Y!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#o;'S#Y;'S;=`'q<%lO#Y~$+Ze[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#Y~$,seT~[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#YV$.]]pP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU$/Z_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#g#Y#g#h$0Y#h;'S#Y;'S;=`'q<%lO#YU$0__[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#X#Y#X#Y$1^#Y;'S#Y;'S;=`'q<%lO#YU$1c_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#h#Y#h#i$2b#i;'S#Y;'S;=`'q<%lO#YU$2i]oQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#Y",
|
|
295
|
-
tokenizers: [
|
|
296
|
-
0,
|
|
297
|
-
1,
|
|
298
|
-
2
|
|
299
|
-
],
|
|
300
|
-
topRules: { "File": [0, 1] },
|
|
301
|
-
tokenPrec: 245
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
//#endregion
|
|
305
|
-
//#region src/sql-query.ts
|
|
306
|
-
var ListType = class {
|
|
307
|
-
constructor(baseType) {
|
|
308
|
-
this.baseType = baseType;
|
|
309
|
-
}
|
|
310
|
-
toString() {
|
|
311
|
-
return `${this.baseType.toString()}[]`;
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
var StructType = class {
|
|
315
|
-
constructor(fields) {
|
|
316
|
-
this.fields = fields;
|
|
317
|
-
}
|
|
318
|
-
toString() {
|
|
319
|
-
return `STRUCT(${this.fields.map((f) => `"${f.name}" ${f.type.toString()}`).join(", ")})`;
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
var MapType = class {
|
|
323
|
-
constructor(keyType, valueType) {
|
|
324
|
-
this.keyType = keyType;
|
|
325
|
-
this.valueType = valueType;
|
|
326
|
-
}
|
|
327
|
-
toString() {
|
|
328
|
-
return `MAP(${this.keyType.toString()}, ${this.valueType.toString()})`;
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
var EnumType = class {
|
|
332
|
-
constructor(values) {
|
|
333
|
-
this.values = values;
|
|
334
|
-
}
|
|
335
|
-
toString() {
|
|
336
|
-
return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
var SQLQuery = class {
|
|
340
|
-
columns;
|
|
341
|
-
allColumns;
|
|
342
|
-
constructor(filename, id, rawQuery, queryAnonymous, queryNamed, queryPositional, type, isOne, isPluck, variables, config) {
|
|
343
|
-
this.filename = filename;
|
|
344
|
-
this.id = id;
|
|
345
|
-
this.rawQuery = rawQuery;
|
|
346
|
-
this.queryAnonymous = queryAnonymous;
|
|
347
|
-
this.queryNamed = queryNamed;
|
|
348
|
-
this.queryPositional = queryPositional;
|
|
349
|
-
this.type = type;
|
|
350
|
-
this.isOne = isOne;
|
|
351
|
-
this.isPluck = isPluck;
|
|
352
|
-
this.variables = variables;
|
|
353
|
-
this.config = config;
|
|
354
|
-
this.columns = [];
|
|
355
|
-
}
|
|
356
|
-
get isQuery() {
|
|
357
|
-
return this.type === "QUERY";
|
|
358
|
-
}
|
|
359
|
-
get isExec() {
|
|
360
|
-
return this.type === "EXEC";
|
|
361
|
-
}
|
|
362
|
-
get isMigrate() {
|
|
363
|
-
return this.type === "MIGRATE";
|
|
364
|
-
}
|
|
365
|
-
get isTestdata() {
|
|
366
|
-
return this.type === "TESTDATA";
|
|
367
|
-
}
|
|
368
|
-
get skipGenerateFunction() {
|
|
369
|
-
return this.isTestdata || this.isMigrate || this.id.startsWith("_");
|
|
370
|
-
}
|
|
371
|
-
validateVariables() {
|
|
372
|
-
const missingVars = [];
|
|
373
|
-
const varRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
374
|
-
let match;
|
|
375
|
-
while (true) {
|
|
376
|
-
match = varRegex.exec(this.rawQuery);
|
|
377
|
-
if (match === null) break;
|
|
378
|
-
const varName = match[1];
|
|
379
|
-
if (!this.variables.has(varName)) missingVars.push(varName);
|
|
380
|
-
}
|
|
381
|
-
return missingVars;
|
|
382
|
-
}
|
|
383
|
-
};
|
|
342
|
+
//#region src/init.ts
|
|
384
343
|
/**
|
|
385
|
-
*
|
|
386
|
-
* TABLE annotations specify a table name for which to generate bulk insert appenders.
|
|
344
|
+
* SQG Project Initialization - Creates new SQG projects with example files
|
|
387
345
|
*/
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Get the default generator for a language preference
|
|
348
|
+
*/
|
|
349
|
+
function getDefaultGenerator() {
|
|
350
|
+
return "typescript/sqlite";
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Generate example SQL content based on engine
|
|
354
|
+
*/
|
|
355
|
+
function getExampleSql(engine) {
|
|
356
|
+
return {
|
|
357
|
+
sqlite: `-- MIGRATE 1
|
|
358
|
+
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
359
|
+
CREATE TABLE users (
|
|
360
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
361
|
+
name TEXT NOT NULL,
|
|
362
|
+
email TEXT UNIQUE NOT NULL,
|
|
363
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
-- MIGRATE 2
|
|
367
|
+
CREATE TABLE posts (
|
|
368
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
369
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
370
|
+
title TEXT NOT NULL,
|
|
371
|
+
content TEXT,
|
|
372
|
+
published INTEGER DEFAULT 0,
|
|
373
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
-- TESTDATA seed_data
|
|
377
|
+
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
|
|
378
|
+
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
|
|
379
|
+
INSERT INTO posts (user_id, title, content, published) VALUES (1, 'Hello World', 'My first post!', 1);
|
|
380
|
+
`,
|
|
381
|
+
duckdb: `-- MIGRATE 1
|
|
382
|
+
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
383
|
+
CREATE TABLE users (
|
|
384
|
+
id INTEGER PRIMARY KEY,
|
|
385
|
+
name VARCHAR NOT NULL,
|
|
386
|
+
email VARCHAR UNIQUE NOT NULL,
|
|
387
|
+
metadata STRUCT(role VARCHAR, active BOOLEAN),
|
|
388
|
+
tags VARCHAR[],
|
|
389
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
-- MIGRATE 2
|
|
393
|
+
CREATE TABLE posts (
|
|
394
|
+
id INTEGER PRIMARY KEY,
|
|
395
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
396
|
+
title VARCHAR NOT NULL,
|
|
397
|
+
content VARCHAR,
|
|
398
|
+
published BOOLEAN DEFAULT FALSE,
|
|
399
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
-- TESTDATA seed_data
|
|
403
|
+
INSERT INTO users (id, name, email, metadata, tags)
|
|
404
|
+
VALUES (1, 'Alice', 'alice@example.com', {'role': 'admin', 'active': true}, ['developer', 'lead']);
|
|
405
|
+
INSERT INTO users (id, name, email, metadata, tags)
|
|
406
|
+
VALUES (2, 'Bob', 'bob@example.com', {'role': 'user', 'active': true}, ['developer']);
|
|
407
|
+
INSERT INTO posts (id, user_id, title, content, published)
|
|
408
|
+
VALUES (1, 1, 'Hello World', 'My first post!', TRUE);
|
|
409
|
+
`,
|
|
410
|
+
postgres: `-- MIGRATE 1
|
|
411
|
+
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
412
|
+
CREATE TABLE users (
|
|
413
|
+
id SERIAL PRIMARY KEY,
|
|
414
|
+
name TEXT NOT NULL,
|
|
415
|
+
email TEXT UNIQUE NOT NULL,
|
|
416
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
-- MIGRATE 2
|
|
420
|
+
CREATE TABLE posts (
|
|
421
|
+
id SERIAL PRIMARY KEY,
|
|
422
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
423
|
+
title TEXT NOT NULL,
|
|
424
|
+
content TEXT,
|
|
425
|
+
published BOOLEAN DEFAULT FALSE,
|
|
426
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
-- TESTDATA seed_data
|
|
430
|
+
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
|
|
431
|
+
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
|
|
432
|
+
INSERT INTO posts (user_id, title, content, published) VALUES (1, 'Hello World', 'My first post!', TRUE);
|
|
433
|
+
`
|
|
434
|
+
}[engine] + `
|
|
435
|
+
-- QUERY list_users
|
|
436
|
+
SELECT id, name, email, created_at
|
|
437
|
+
FROM users
|
|
438
|
+
ORDER BY created_at DESC;
|
|
439
|
+
|
|
440
|
+
-- QUERY get_user_by_id :one
|
|
441
|
+
@set id = 1
|
|
442
|
+
SELECT id, name, email, created_at
|
|
443
|
+
FROM users
|
|
444
|
+
WHERE id = \${id};
|
|
445
|
+
|
|
446
|
+
-- QUERY get_user_by_email :one
|
|
447
|
+
@set email = 'alice@example.com'
|
|
448
|
+
SELECT id, name, email, created_at
|
|
449
|
+
FROM users
|
|
450
|
+
WHERE email = \${email};
|
|
451
|
+
|
|
452
|
+
-- QUERY count_users :one :pluck
|
|
453
|
+
SELECT COUNT(*) FROM users;
|
|
454
|
+
|
|
455
|
+
-- QUERY list_posts_by_user
|
|
456
|
+
@set user_id = 1
|
|
457
|
+
SELECT p.id, p.title, p.content, p.published, p.created_at
|
|
458
|
+
FROM posts p
|
|
459
|
+
WHERE p.user_id = \${user_id}
|
|
460
|
+
ORDER BY p.created_at DESC;
|
|
461
|
+
|
|
462
|
+
-- QUERY list_published_posts
|
|
463
|
+
SELECT
|
|
464
|
+
p.id,
|
|
465
|
+
p.title,
|
|
466
|
+
p.content,
|
|
467
|
+
p.created_at,
|
|
468
|
+
u.name as author_name,
|
|
469
|
+
u.email as author_email
|
|
470
|
+
FROM posts p
|
|
471
|
+
JOIN users u ON p.user_id = u.id
|
|
472
|
+
WHERE p.published = 1
|
|
473
|
+
ORDER BY p.created_at DESC;
|
|
474
|
+
` + {
|
|
475
|
+
sqlite: `
|
|
476
|
+
-- EXEC create_user
|
|
477
|
+
@set name = 'New User'
|
|
478
|
+
@set email = 'new@example.com'
|
|
479
|
+
INSERT INTO users (name, email)
|
|
480
|
+
VALUES (\${name}, \${email});
|
|
481
|
+
|
|
482
|
+
-- EXEC create_post
|
|
483
|
+
@set user_id = 1
|
|
484
|
+
@set title = 'New Post'
|
|
485
|
+
@set content = 'Post content here'
|
|
486
|
+
INSERT INTO posts (user_id, title, content)
|
|
487
|
+
VALUES (\${user_id}, \${title}, \${content});
|
|
488
|
+
|
|
489
|
+
-- EXEC publish_post
|
|
490
|
+
@set id = 1
|
|
491
|
+
UPDATE posts SET published = 1 WHERE id = \${id};
|
|
492
|
+
|
|
493
|
+
-- EXEC delete_post
|
|
494
|
+
@set id = 1
|
|
495
|
+
DELETE FROM posts WHERE id = \${id};
|
|
496
|
+
`,
|
|
497
|
+
duckdb: `
|
|
498
|
+
-- EXEC create_user
|
|
499
|
+
@set id = 100
|
|
500
|
+
@set name = 'New User'
|
|
501
|
+
@set email = 'new@example.com'
|
|
502
|
+
INSERT INTO users (id, name, email)
|
|
503
|
+
VALUES (\${id}, \${name}, \${email});
|
|
504
|
+
|
|
505
|
+
-- EXEC create_post
|
|
506
|
+
@set id = 100
|
|
507
|
+
@set user_id = 1
|
|
508
|
+
@set title = 'New Post'
|
|
509
|
+
@set content = 'Post content here'
|
|
510
|
+
INSERT INTO posts (id, user_id, title, content)
|
|
511
|
+
VALUES (\${id}, \${user_id}, \${title}, \${content});
|
|
512
|
+
|
|
513
|
+
-- EXEC publish_post
|
|
514
|
+
@set id = 1
|
|
515
|
+
UPDATE posts SET published = TRUE WHERE id = \${id};
|
|
516
|
+
|
|
517
|
+
-- EXEC delete_post
|
|
518
|
+
@set id = 1
|
|
519
|
+
DELETE FROM posts WHERE id = \${id};
|
|
520
|
+
`,
|
|
521
|
+
postgres: `
|
|
522
|
+
-- EXEC create_user
|
|
523
|
+
@set name = 'New User'
|
|
524
|
+
@set email = 'new@example.com'
|
|
525
|
+
INSERT INTO users (name, email)
|
|
526
|
+
VALUES (\${name}, \${email});
|
|
527
|
+
|
|
528
|
+
-- EXEC create_post
|
|
529
|
+
@set user_id = 1
|
|
530
|
+
@set title = 'New Post'
|
|
531
|
+
@set content = 'Post content here'
|
|
532
|
+
INSERT INTO posts (user_id, title, content)
|
|
533
|
+
VALUES (\${user_id}, \${title}, \${content});
|
|
534
|
+
|
|
535
|
+
-- EXEC publish_post
|
|
536
|
+
@set id = 1
|
|
537
|
+
UPDATE posts SET published = TRUE WHERE id = \${id};
|
|
538
|
+
|
|
539
|
+
-- EXEC delete_post
|
|
540
|
+
@set id = 1
|
|
541
|
+
DELETE FROM posts WHERE id = \${id};
|
|
542
|
+
`
|
|
543
|
+
}[engine];
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Generate sqg.yaml configuration
|
|
547
|
+
*/
|
|
548
|
+
function getConfigYaml(generator, output) {
|
|
549
|
+
const isJava = parseGenerator(generator).language === "java";
|
|
550
|
+
return `# SQG Configuration
|
|
551
|
+
# Generated by: sqg init
|
|
552
|
+
# Documentation: https://sqg.dev
|
|
553
|
+
|
|
554
|
+
version: 1
|
|
555
|
+
name: my-project
|
|
556
|
+
|
|
557
|
+
sql:
|
|
558
|
+
- files:
|
|
559
|
+
- queries.sql
|
|
560
|
+
gen:
|
|
561
|
+
- generator: ${generator}
|
|
562
|
+
output: ${output.endsWith("/") ? output : `${output}/`}${isJava ? `
|
|
563
|
+
config:
|
|
564
|
+
package: generated` : ""}
|
|
565
|
+
`;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Initialize a new SQG project
|
|
569
|
+
*/
|
|
570
|
+
async function initProject(options) {
|
|
571
|
+
const generator = options.generator || getDefaultGenerator();
|
|
572
|
+
const output = options.output || "./generated";
|
|
573
|
+
if (!isValidGenerator(generator)) {
|
|
574
|
+
const similar = findSimilarGenerators(generator);
|
|
575
|
+
throw new InvalidGeneratorError(generator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
|
|
576
|
+
}
|
|
577
|
+
const engine = parseGenerator(generator).engine;
|
|
578
|
+
const configPath = "sqg.yaml";
|
|
579
|
+
const sqlPath = "queries.sql";
|
|
580
|
+
if (!options.force) {
|
|
581
|
+
if (existsSync(configPath)) throw new SqgError(`File already exists: ${configPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
|
|
582
|
+
if (existsSync(sqlPath)) throw new SqgError(`File already exists: ${sqlPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
|
|
583
|
+
}
|
|
584
|
+
if (!existsSync(output)) {
|
|
585
|
+
mkdirSync(output, { recursive: true });
|
|
586
|
+
consola.success(`Created output directory: ${output}`);
|
|
587
|
+
}
|
|
588
|
+
writeFileSync(configPath, getConfigYaml(generator, output));
|
|
589
|
+
consola.success(`Created ${configPath}`);
|
|
590
|
+
writeFileSync(sqlPath, getExampleSql(engine));
|
|
591
|
+
consola.success(`Created ${sqlPath}`);
|
|
592
|
+
consola.box(`
|
|
593
|
+
SQG project initialized!
|
|
594
|
+
|
|
595
|
+
Generator: ${generator}
|
|
596
|
+
Engine: ${engine}
|
|
597
|
+
Output: ${output}
|
|
598
|
+
|
|
599
|
+
Next steps:
|
|
600
|
+
1. Edit queries.sql to add your SQL queries
|
|
601
|
+
2. Run: sqg sqg.yaml
|
|
602
|
+
3. Import the generated code from ${output}
|
|
603
|
+
|
|
604
|
+
Documentation: https://sqg.dev
|
|
605
|
+
`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/parser/sql-parser.ts
|
|
610
|
+
const parser = LRParser.deserialize({
|
|
611
|
+
version: 14,
|
|
612
|
+
states: "&SOVQPOOO_QPO'#CwOdQPO'#CzOiQPO'#CvO!SQQO'#C^OOQO'#Cn'#CnQVQPOOO!aQSO,59cO!lQPO,59fOOQO'#Cp'#CpO!tQQO,59bOOQO'#C}'#C}O#iQQO'#CiOOQO,58x,58xOOQO-E6l-E6lOOQO'#Co'#CoO!aQSO1G.}O!dQSO1G.}OOQO'#Cb'#CbOOQO1G.}1G.}O#yQPO1G/QOOQO-E6n-E6nO$RQPO'#CdOOQO'#Cq'#CqO$WQQO1G.|OOQO'#Cm'#CmOOQO'#Cr'#CrO$xQQO,59TOOQO-E6m-E6mO!dQSO7+$iOOQO7+$i7+$iO%YQPO,59OOOQO-E6o-E6oOOQO-E6p-E6pOOQO<<HT<<HTO%_QQO1G.jOOQO'#Ce'#CeOiQPO7+$UO%jQQO<<Gp",
|
|
613
|
+
stateData: "&l~OiOS~ORPOVQO~OSVO~OSWO~OlXO~OYZOZZO[ZO^ZO_ZO`ZO~OR]PV]Pg]P~PnOT_OlXOmbO~OT_Olna~OlXOofORjaVjaYjaZja[ja^ja_ja`jagja~OliOR]XV]Xg]X~PnOT_Olni~OSoO~OofORjiVjiYjiZji[ji^ji_ji`jigji~OliOR]aV]ag]a~PnOpsO~OYtOZtO[tO~OlXORWyVWyYWyZWy[Wy^Wy_Wy`WygWyoWy~OR`o^iZTmYV_[~",
|
|
614
|
+
goto: "#prPPsPPPwP!R!VPPP!YPPP!]!a!g!q#T#ZPPP#a#ePP#ePP#iTTOUQcVSn`aRrmTgYhRusR]STj[kQUOR^UQ`VQdWTl`dQYRQaVWeYamvQm`RvuQhYRphQk[RqkTSOUTROUQ[STj[k",
|
|
615
|
+
nodeNames: "⚠ File QueryBlock BlockCommentStartSpecial Name Modifiers Config LineCommentStartSpecial SetVarLine Value StringLiteral StringLiteralSingle SQLText SQLBlock BlockComment LineComment VarRef BR",
|
|
616
|
+
maxTerm: 33,
|
|
617
|
+
skippedNodes: [0],
|
|
618
|
+
repeatNodeCount: 5,
|
|
619
|
+
tokenData: "$3b~RqOX#YXY'wYZ(iZ]#Y]^$W^p#Ypq'wqr#Yrs(}st#Ytu6^uw#Ywx9[xz#Yz{%_{}#Y}!OKi!O!P#Y!P!Q#%p!Q![$)l![!]$+U!]!_#Y!_!`$.U!`!b#Y!b!c$/U!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#o;'S#Y;'S;=`'q<%lO#YU#_][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YS$ZTOz$Wz{$j{;'S$W;'S;=`%X<%lO$WS$mVOz$Wz{$j{!P$W!P!Q%S!Q;'S$W;'S;=`%X<%lO$WS%XOmSS%[P;=`<%l$WU%d_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!P#Y!P!Q&c!Q;'S#Y;'S;=`'q<%lO#YU&jVmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'UV[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ'nP;=`<%l'PU'tP;=`<%l#Y~'|Xi~OX$WXY'wYp$Wpq'wqz$Wz{$j{;'S$W;'S;=`%X<%lO$W~(nTl~Oz$Wz{$j{;'S$W;'S;=`%X<%lO$WU)Sb[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U*_ZOY*[YZ$WZr*[rs+Qsz*[z{+f{#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U+VTYQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU+i]OY*[YZ$WZr*[rs+Qsz*[z{+f{!P*[!P!Q,b!Q#O*[#O#P.Z#P;'S*[;'S;=`.s<%lO*[U,gWmSOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-SWOY-PZr-Prs-ls#O-P#O#P-q#P;'S-P;'S;=`.T<%lO-PQ-qOYQQ-tTOY-PYZ-PZ;'S-P;'S;=`.T<%lO-PQ.WP;=`<%l-PU.^VOY*[YZ*[Zz*[z{+f{;'S*[;'S;=`.s<%lO*[U.vP;=`<%l*[U/Q]YQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU0Od[QOX(}XY*[YZ$WZ](}]^*[^p(}pq*[qr(}rs.yst(}tu*[uz(}z{/y{!P(}!P!Q1^!Q#O(}#O#P5V#P;'S(};'S;=`6W<%lO(}U1e_mS[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ2i_[QOX2dXY-PZ]2d]^-P^p2dpq-Pqr2drs3hst2dtu-Pu#O2d#O#P4U#P;'S2d;'S;=`5P<%lO2dQ3oVYQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ4Z[[QOX2dXY-PYZ-PZ]2d]^-P^p2dpq-Pqt2dtu-Pu;'S2d;'S;=`5P<%lO2dQ5SP;=`<%l2dU5[^[QOX(}XY*[YZ*[Z](}]^*[^p(}pq*[qt(}tu*[uz(}z{/y{;'S(};'S;=`6W<%lO(}U6ZP;=`<%l(}U6cV[QOz$Wz{$j{#o$W#o#p6x#p;'S$W;'S;=`%X<%lO$WU6{]Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o;'S$W;'S;=`%X<%lO$WU7w_Oz$Wz{$j{!Q$W!Q![7t![!c$W!c!}7t!}#R$W#R#S7t#S#T$W#T#o7t#o#q$W#q#r8v#r;'S$W;'S;=`%X<%lO$WU8{T`QOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU9ab[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[U:lZOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU;dTZQOz$Wz{$j{;'S$W;'S;=`%X<%lO$WU;v]OY:iYZ$WZw:iwx;_xz:iz{;s{!P:i!P!Q<o!Q#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU<tWmSOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q=aWOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>OOZQQ>RXOY=^YZ=^Zw=^wx>nx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q>sWZQOY=^Zw=^wx=yx#O=^#O#P>O#P;'S=^;'S;=`?]<%lO=^Q?`P;=`<%l=^U?fZOY:iYZ:iZw:iwx@Xxz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iU@^ZZQOY:iYZ$WZw:iwx;_xz:iz{;s{#O:i#O#P?c#P;'S:i;'S;=`AP<%lO:iUASP;=`<%l:iUA^]ZQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YUB[d[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{!P9[!P!QCj!Q#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UCq_mS[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQDu_[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQE{VZQ[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQFg`[QOXDpXY=^YZ=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxGix#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQGp_ZQ[QOXDpXY=^Z]Dp]^=^^pDppq=^qtDptu=^uwDpwxEtx#ODp#O#PFb#P;'SDp;'S;=`Ho<%lODpQHrP;=`<%lDpUHzb[QOX9[XY:iYZ:iZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxJSxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UJZbZQ[QOX9[XY:iYZ$WZ]9[]^:i^p9[pq:iqt9[tu:iuw9[wxAVxz9[z{BV{#O9[#O#PHu#P;'S9[;'S;=`Kc<%lO9[UKfP;=`<%l9[VKn_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{}#Y}!OLm!O;'S#Y;'S;=`'q<%lO#YVLtf_Q[QOXNYXY!&vYZ$WZ]NY]^! ]^pNYpq!&vqtNYtu! ]uzNYz{!#k{!gNY!g!h!8V!h!oNY!o!p!<w!p!sNY!s!t!DP!t!vNY!v!w!Hu!w;'SNY;'S;=`!&p<%lONYUNa^_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYU! bV_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]U! |X_QOY! ]YZ$WZz! ]z{! w{!P! ]!P!Q!!i!Q;'S! ];'S;=`!#e<%lO! ]U!!pSmS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#RS_QOY!!|Z;'S!!|;'S;=`!#_<%lO!!|Q!#bP;=`<%l!!|U!#hP;=`<%l! ]U!#r`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!PNY!P!Q!$t!Q;'SNY;'S;=`!&p<%lONYU!$}ZmS_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!%wZ_Q[QOX!%pXY!!|Z]!%p]^!!|^p!%ppq!!|qt!%ptu!!|u;'S!%p;'S;=`!&j<%lO!%pQ!&mP;=`<%l!%pU!&sP;=`<%lNYV!&{b_QOX! ]XY!&vYZ$WZp! ]pq!&vqz! ]z{! w{!g! ]!g!h!(T!h!o! ]!o!p!*u!p!s! ]!s!t!.}!t!v! ]!v!w!1s!w;'S! ];'S;=`!#e<%lO! ]V!(YX_QOY! ]YZ$WZz! ]z{! w{!z! ]!z!{!(u!{;'S! ];'S;=`!#e<%lO! ]V!(zX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!)g!h;'S! ];'S;=`!#e<%lO! ]V!)lX_QOY! ]YZ$WZz! ]z{! w{!e! ]!e!f!*X!f;'S! ];'S;=`!#e<%lO! ]V!*`VVR_QOY! ]YZ$WZz! ]z{! w{;'S! ];'S;=`!#e<%lO! ]V!*zX_QOY! ]YZ$WZz! ]z{! w{!k! ]!k!l!+g!l;'S! ];'S;=`!#e<%lO! ]V!+lX_QOY! ]YZ$WZz! ]z{! w{!i! ]!i!j!,X!j;'S! ];'S;=`!#e<%lO! ]V!,^X_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!,y!u;'S! ];'S;=`!#e<%lO! ]V!-OX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!-k!d;'S! ];'S;=`!#e<%lO! ]V!-pX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!.]!w;'S! ];'S;=`!#e<%lO! ]V!.bX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!*X!h;'S! ];'S;=`!#e<%lO! ]V!/SX_QOY! ]YZ$WZz! ]z{! w{!w! ]!w!x!/o!x;'S! ];'S;=`!#e<%lO! ]V!/tX_QOY! ]YZ$WZz! ]z{! w{!g! ]!g!h!0a!h;'S! ];'S;=`!#e<%lO! ]V!0fX_QOY! ]YZ$WZz! ]z{! w{!t! ]!t!u!1R!u;'S! ];'S;=`!#e<%lO! ]V!1WX_QOY! ]YZ$WZz! ]z{! w{!{! ]!{!|!*X!|;'S! ];'S;=`!#e<%lO! ]V!1xZ_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!2k!d!g! ]!g!h!3}!h;'S! ];'S;=`!#e<%lO! ]V!2pX_QOY! ]YZ$WZz! ]z{! w{!d! ]!d!e!3]!e;'S! ];'S;=`!#e<%lO! ]V!3bX_QOY! ]YZ$WZz! ]z{! w{!n! ]!n!o!.]!o;'S! ];'S;=`!#e<%lO! ]V!4SX_QOY! ]YZ$WZz! ]z{! w{!u! ]!u!v!4o!v;'S! ];'S;=`!#e<%lO! ]V!4tX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!5a!w;'S! ];'S;=`!#e<%lO! ]V!5fX_QOY! ]YZ$WZz! ]z{! w{!f! ]!f!g!6R!g;'S! ];'S;=`!#e<%lO! ]V!6WX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!6s!d;'S! ];'S;=`!#e<%lO! ]V!6xX_QOY! ]YZ$WZz! ]z{! w{!v! ]!v!w!7e!w;'S! ];'S;=`!#e<%lO! ]V!7jX_QOY! ]YZ$WZz! ]z{! w{!c! ]!c!d!*X!d;'S! ];'S;=`!#e<%lO! ]V!8^`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!zNY!z!{!9`!{;'SNY;'S;=`!&p<%lONYV!9g`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!:i!h;'SNY;'S;=`!&p<%lONYV!:p`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!eNY!e!f!;r!f;'SNY;'S;=`!&p<%lONYV!;{^VR_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{;'SNY;'S;=`!&p<%lONYV!=O`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!kNY!k!l!>Q!l;'SNY;'S;=`!&p<%lONYV!>X`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!iNY!i!j!?Z!j;'SNY;'S;=`!&p<%lONYV!?b`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!@d!u;'SNY;'S;=`!&p<%lONYV!@k`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!Am!d;'SNY;'S;=`!&p<%lONYV!At`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Bv!w;'SNY;'S;=`!&p<%lONYV!B}`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!;r!h;'SNY;'S;=`!&p<%lONYV!DW`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!wNY!w!x!EY!x;'SNY;'S;=`!&p<%lONYV!Ea`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!gNY!g!h!Fc!h;'SNY;'S;=`!&p<%lONYV!Fj`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!tNY!t!u!Gl!u;'SNY;'S;=`!&p<%lONYV!Gs`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!{NY!{!|!;r!|;'SNY;'S;=`!&p<%lONYV!H|b_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!JU!d!gNY!g!h!Lh!h;'SNY;'S;=`!&p<%lONYV!J]`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!dNY!d!e!K_!e;'SNY;'S;=`!&p<%lONYV!Kf`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!nNY!n!o!Bv!o;'SNY;'S;=`!&p<%lONYV!Lo`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!uNY!u!v!Mq!v;'SNY;'S;=`!&p<%lONYV!Mx`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w!Nz!w;'SNY;'S;=`!&p<%lONYV# R`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!fNY!f!g#!T!g;'SNY;'S;=`!&p<%lONYV#![`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d##^!d;'SNY;'S;=`!&p<%lONYV##e`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!vNY!v!w#$g!w;'SNY;'S;=`!&p<%lONYV#$n`_Q[QOXNYXY! ]YZ$WZ]NY]^! ]^pNYpq! ]qtNYtu! ]uzNYz{!#k{!cNY!c!d!;r!d;'SNY;'S;=`!&p<%lONYV#%u][QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{#&n{;'S#Y;'S;=`'q<%lO#YV#&sh[QOX#(_XY#,`YZ#,`Z]#(_]^#)]^p#(_pq#,`qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#9m!Q!g#(_!g!h#>j!h!o#(_!o!p#Bv!p!s#(_!s!t#I`!t!v#(_!v!w#Mp!w;'S#(_;'S;=`#,Y<%lO#(_U#(d][QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_U#)`TOz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]U#)rVOz#)]z{#)o{!P#)]!P!Q#*X!Q;'S#)];'S;=`#*`<%lO#)]U#*`O^QmSU#*cP;=`<%l#)]U#*k_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!P#(_!P!Q#+j!Q;'S#(_;'S;=`#,Y<%lO#(_U#+sV^QmS[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PU#,]P;=`<%l#(_V#,cbOX#)]XY#,`YZ#,`Zp#)]pq#,`qz#)]z{#)o{!g#)]!g!h#-k!h!o#)]!o!p#/k!p!s#)]!s!t#2q!t!v#)]!v!w#4u!w;'S#)];'S;=`#*`<%lO#)]V#-nVOz#)]z{#)o{!z#)]!z!{#.T!{;'S#)];'S;=`#*`<%lO#)]V#.WVOz#)]z{#)o{!g#)]!g!h#.m!h;'S#)];'S;=`#*`<%lO#)]V#.pVOz#)]z{#)o{!e#)]!e!f#/V!f;'S#)];'S;=`#*`<%lO#)]V#/[TRROz#)]z{#)o{;'S#)];'S;=`#*`<%lO#)]V#/nVOz#)]z{#)o{!k#)]!k!l#0T!l;'S#)];'S;=`#*`<%lO#)]V#0WVOz#)]z{#)o{!i#)]!i!j#0m!j;'S#)];'S;=`#*`<%lO#)]V#0pVOz#)]z{#)o{!t#)]!t!u#1V!u;'S#)];'S;=`#*`<%lO#)]V#1YVOz#)]z{#)o{!c#)]!c!d#1o!d;'S#)];'S;=`#*`<%lO#)]V#1rVOz#)]z{#)o{!v#)]!v!w#2X!w;'S#)];'S;=`#*`<%lO#)]V#2[VOz#)]z{#)o{!g#)]!g!h#/V!h;'S#)];'S;=`#*`<%lO#)]V#2tVOz#)]z{#)o{!w#)]!w!x#3Z!x;'S#)];'S;=`#*`<%lO#)]V#3^VOz#)]z{#)o{!g#)]!g!h#3s!h;'S#)];'S;=`#*`<%lO#)]V#3vVOz#)]z{#)o{!t#)]!t!u#4]!u;'S#)];'S;=`#*`<%lO#)]V#4`VOz#)]z{#)o{!{#)]!{!|#/V!|;'S#)];'S;=`#*`<%lO#)]V#4xXOz#)]z{#)o{!c#)]!c!d#5e!d!g#)]!g!h#6g!h;'S#)];'S;=`#*`<%lO#)]V#5hVOz#)]z{#)o{!d#)]!d!e#5}!e;'S#)];'S;=`#*`<%lO#)]V#6QVOz#)]z{#)o{!n#)]!n!o#2X!o;'S#)];'S;=`#*`<%lO#)]V#6jVOz#)]z{#)o{!u#)]!u!v#7P!v;'S#)];'S;=`#*`<%lO#)]V#7SVOz#)]z{#)o{!v#)]!v!w#7i!w;'S#)];'S;=`#*`<%lO#)]V#7lVOz#)]z{#)o{!f#)]!f!g#8R!g;'S#)];'S;=`#*`<%lO#)]V#8UVOz#)]z{#)o{!c#)]!c!d#8k!d;'S#)];'S;=`#*`<%lO#)]V#8nVOz#)]z{#)o{!v#)]!v!w#9T!w;'S#)];'S;=`#*`<%lO#)]V#9WVOz#)]z{#)o{!c#)]!c!d#/V!d;'S#)];'S;=`#*`<%lO#)]U#9t]mS[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#:r][QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{;'S#:m;'S;=`#>d<%lO#:mQ#;nTOz#;kz{#;}{;'S#;k;'S;=`#<l<%lO#;kQ#<QVOz#;kz{#;}{!P#;k!P!Q#<g!Q;'S#;k;'S;=`#<l<%lO#;kQ#<lO^QQ#<oP;=`<%l#;kQ#<w_[QOX#:mXZ#;kZ]#:m]^#;k^p#:mpq#;kqt#:mtu#;kuz#:mz{#<r{!P#:m!P!Q#=v!Q;'S#:m;'S;=`#>d<%lO#:mQ#=}V^Q[QOX'PZ]'P^p'Pqt'Pu;'S'P;'S;=`'k<%lO'PQ#>gP;=`<%l#:mV#>o_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!z#(_!z!{#?n!{;'S#(_;'S;=`#,Y<%lO#(_V#?s_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#@r!h;'S#(_;'S;=`#,Y<%lO#(_V#@w_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!e#(_!e!f#Av!f;'S#(_;'S;=`#,Y<%lO#(_V#A}]RR[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{;'S#(_;'S;=`#,Y<%lO#(_V#B{_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!k#(_!k!l#Cz!l;'S#(_;'S;=`#,Y<%lO#(_V#DP_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!i#(_!i!j#EO!j;'S#(_;'S;=`#,Y<%lO#(_V#ET_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#FS!u;'S#(_;'S;=`#,Y<%lO#(_V#FX_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#GW!d;'S#(_;'S;=`#,Y<%lO#(_V#G]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w#H[!w;'S#(_;'S;=`#,Y<%lO#(_V#Ha_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Av!h;'S#(_;'S;=`#,Y<%lO#(_V#Ie_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!w#(_!w!x#Jd!x;'S#(_;'S;=`#,Y<%lO#(_V#Ji_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!g#(_!g!h#Kh!h;'S#(_;'S;=`#,Y<%lO#(_V#Km_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!t#(_!t!u#Ll!u;'S#(_;'S;=`#,Y<%lO#(_V#Lq_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!{#(_!{!|#Av!|;'S#(_;'S;=`#,Y<%lO#(_V#Mua[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Nz!d!g#(_!g!h$#S!h;'S#(_;'S;=`#,Y<%lO#(_V$ P_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!d#(_!d!e$!O!e;'S#(_;'S;=`#,Y<%lO#(_V$!T_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!n#(_!n!o#H[!o;'S#(_;'S;=`#,Y<%lO#(_V$#X_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!u#(_!u!v$$W!v;'S#(_;'S;=`#,Y<%lO#(_V$$]_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$%[!w;'S#(_;'S;=`#,Y<%lO#(_V$%a_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!f#(_!f!g$&`!g;'S#(_;'S;=`#,Y<%lO#(_V$&e_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d$'d!d;'S#(_;'S;=`#,Y<%lO#(_V$'i_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!v#(_!v!w$(h!w;'S#(_;'S;=`#,Y<%lO#(_V$(m_[QOX#(_XZ#)]Z]#(_]^#)]^p#(_pq#)]qt#(_tu#)]uz#(_z{#*f{!c#(_!c!d#Av!d;'S#(_;'S;=`#,Y<%lO#(_V$)seSP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$)l![!c#Y!c!}$)l!}#R#Y#R#S$)l#S#T#Y#T#o$)l#o;'S#Y;'S;=`'q<%lO#Y~$+Ze[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#Y~$,seT~[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{!Q#Y!Q![$,l![!c#Y!c!}$,l!}#R#Y#R#S$,l#S#T#Y#T#o$,l#o;'S#Y;'S;=`'q<%lO#YV$.]]pP[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#YU$/Z_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#g#Y#g#h$0Y#h;'S#Y;'S;=`'q<%lO#YU$0__[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#X#Y#X#Y$1^#Y;'S#Y;'S;=`'q<%lO#YU$1c_[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{#h#Y#h#i$2b#i;'S#Y;'S;=`'q<%lO#YU$2i]oQ[QOX#YXZ$WZ]#Y]^$W^p#Ypq$Wqt#Ytu$Wuz#Yz{%_{;'S#Y;'S;=`'q<%lO#Y",
|
|
620
|
+
tokenizers: [
|
|
621
|
+
0,
|
|
622
|
+
1,
|
|
623
|
+
2
|
|
624
|
+
],
|
|
625
|
+
topRules: { "File": [0, 1] },
|
|
626
|
+
tokenPrec: 245
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region src/sql-query.ts
|
|
631
|
+
var ListType = class {
|
|
632
|
+
constructor(baseType) {
|
|
633
|
+
this.baseType = baseType;
|
|
634
|
+
}
|
|
635
|
+
toString() {
|
|
636
|
+
return `${this.baseType.toString()}[]`;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
var StructType = class {
|
|
640
|
+
constructor(fields) {
|
|
641
|
+
this.fields = fields;
|
|
642
|
+
}
|
|
643
|
+
toString() {
|
|
644
|
+
return `STRUCT(${this.fields.map((f) => `"${f.name}" ${f.type.toString()}`).join(", ")})`;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
var MapType = class {
|
|
648
|
+
constructor(keyType, valueType) {
|
|
649
|
+
this.keyType = keyType;
|
|
650
|
+
this.valueType = valueType;
|
|
651
|
+
}
|
|
652
|
+
toString() {
|
|
653
|
+
return `MAP(${this.keyType.toString()}, ${this.valueType.toString()})`;
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
var EnumType = class {
|
|
657
|
+
constructor(values) {
|
|
658
|
+
this.values = values;
|
|
659
|
+
}
|
|
660
|
+
toString() {
|
|
661
|
+
return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
var SQLQuery = class {
|
|
665
|
+
columns;
|
|
666
|
+
allColumns;
|
|
667
|
+
constructor(filename, id, rawQuery, queryAnonymous, queryNamed, queryPositional, type, isOne, isPluck, variables, config) {
|
|
668
|
+
this.filename = filename;
|
|
669
|
+
this.id = id;
|
|
670
|
+
this.rawQuery = rawQuery;
|
|
671
|
+
this.queryAnonymous = queryAnonymous;
|
|
672
|
+
this.queryNamed = queryNamed;
|
|
673
|
+
this.queryPositional = queryPositional;
|
|
674
|
+
this.type = type;
|
|
675
|
+
this.isOne = isOne;
|
|
676
|
+
this.isPluck = isPluck;
|
|
677
|
+
this.variables = variables;
|
|
678
|
+
this.config = config;
|
|
679
|
+
this.columns = [];
|
|
680
|
+
}
|
|
681
|
+
get isQuery() {
|
|
682
|
+
return this.type === "QUERY";
|
|
683
|
+
}
|
|
684
|
+
get isExec() {
|
|
685
|
+
return this.type === "EXEC";
|
|
686
|
+
}
|
|
687
|
+
get isMigrate() {
|
|
688
|
+
return this.type === "MIGRATE";
|
|
689
|
+
}
|
|
690
|
+
get isTestdata() {
|
|
691
|
+
return this.type === "TESTDATA";
|
|
692
|
+
}
|
|
693
|
+
get skipGenerateFunction() {
|
|
694
|
+
return this.isTestdata || this.isMigrate || this.id.startsWith("_");
|
|
695
|
+
}
|
|
696
|
+
validateVariables() {
|
|
697
|
+
const missingVars = [];
|
|
698
|
+
const varRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
699
|
+
let match;
|
|
700
|
+
while (true) {
|
|
701
|
+
match = varRegex.exec(this.rawQuery);
|
|
702
|
+
if (match === null) break;
|
|
703
|
+
const varName = match[1];
|
|
704
|
+
if (!this.variables.has(varName)) missingVars.push(varName);
|
|
705
|
+
}
|
|
706
|
+
return missingVars;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
/**
|
|
710
|
+
* Represents a TABLE annotation for generating appenders.
|
|
711
|
+
* TABLE annotations specify a table name for which to generate bulk insert appenders.
|
|
712
|
+
*/
|
|
713
|
+
var TableInfo = class {
|
|
714
|
+
/** Columns introspected from the database table schema */
|
|
715
|
+
columns = [];
|
|
716
|
+
constructor(filename, id, tableName, includeColumns, hasAppender) {
|
|
717
|
+
this.filename = filename;
|
|
718
|
+
this.id = id;
|
|
719
|
+
this.tableName = tableName;
|
|
720
|
+
this.includeColumns = includeColumns;
|
|
721
|
+
this.hasAppender = hasAppender;
|
|
722
|
+
}
|
|
723
|
+
get skipGenerateFunction() {
|
|
724
|
+
return !this.hasAppender;
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
function parseSQLQueries(filePath, extraVariables) {
|
|
728
|
+
const content = readFileSync(filePath, "utf-8");
|
|
729
|
+
consola.info(`Parsing SQL file: ${filePath}`);
|
|
730
|
+
consola.debug(`File start: ${content.slice(0, 200)}`);
|
|
731
|
+
const queries = [];
|
|
732
|
+
const tables = [];
|
|
733
|
+
const cursor = parser.parse(content).cursor();
|
|
734
|
+
function getLineNumber(position) {
|
|
735
|
+
return content.slice(0, position).split("\n").length;
|
|
736
|
+
}
|
|
737
|
+
function getStr(nodeName, optional = false) {
|
|
738
|
+
const node = cursor.node.getChild(nodeName);
|
|
739
|
+
if (!node) {
|
|
740
|
+
if (optional) return;
|
|
741
|
+
const lineNumber = getLineNumber(cursor.node.from);
|
|
742
|
+
throw new Error(`Node '${nodeName}' not found at line ${lineNumber}`);
|
|
743
|
+
}
|
|
744
|
+
return nodeStr(node);
|
|
745
|
+
}
|
|
746
|
+
function nodeStr(node) {
|
|
747
|
+
return content.slice(node.from, node.to);
|
|
748
|
+
}
|
|
749
|
+
const queryNames = /* @__PURE__ */ new Set();
|
|
750
|
+
do
|
|
751
|
+
if (cursor.name === "QueryBlock") {
|
|
752
|
+
const queryType = (getStr("LineCommentStartSpecial", true) ?? getStr("BlockCommentStartSpecial")).replace("--", "").replace("/*", "").trim();
|
|
753
|
+
const name = getStr("Name").trim();
|
|
754
|
+
const modifiers = cursor.node.getChildren("Modifiers").map((node) => nodeStr(node));
|
|
755
|
+
const isOne = modifiers.includes(":one");
|
|
756
|
+
const isPluck = modifiers.includes(":pluck");
|
|
757
|
+
let configStr = getStr("Config", true);
|
|
758
|
+
if (configStr?.endsWith("*/")) configStr = configStr.slice(0, -2);
|
|
759
|
+
let config = null;
|
|
760
|
+
if (configStr) config = Config.fromYaml(name, filePath, configStr);
|
|
761
|
+
const setVars = cursor.node.getChildren("SetVarLine");
|
|
762
|
+
const variables = /* @__PURE__ */ new Map();
|
|
763
|
+
for (const setVar of setVars) {
|
|
764
|
+
const varName = nodeStr(setVar.getChild("Name"));
|
|
765
|
+
const value = nodeStr(setVar.getChild("Value"));
|
|
766
|
+
variables.set(varName, value.trim());
|
|
767
|
+
}
|
|
768
|
+
function getVariable(varName) {
|
|
769
|
+
if (variables.has(varName)) return variables.get(varName);
|
|
770
|
+
for (const extraVariable of extraVariables) if (extraVariable.name === varName) {
|
|
771
|
+
variables.set(varName, extraVariable.value);
|
|
772
|
+
return extraVariable.value;
|
|
773
|
+
}
|
|
774
|
+
const definedVars = Array.from(variables.keys());
|
|
775
|
+
const suggestion = definedVars.length > 0 ? `Add '@set ${varName} = <value>' before the query. Defined variables: ${definedVars.join(", ")}` : `Add '@set ${varName} = <value>' before the query`;
|
|
776
|
+
throw SqgError.inQuery(`Variable '\${${varName}}' is referenced but not defined`, "MISSING_VARIABLE", name, filePath, { suggestion });
|
|
777
|
+
}
|
|
778
|
+
const sqlNode = cursor.node.getChild("SQLBlock");
|
|
779
|
+
if (!sqlNode) throw SqgError.inQuery("SQL block not found", "SQL_PARSE_ERROR", name, filePath, { suggestion: "Ensure the query has valid SQL content after the annotation comment" });
|
|
780
|
+
const sqlContentStr = nodeStr(sqlNode).trim();
|
|
781
|
+
const sqlCursor = sqlNode.cursor();
|
|
782
|
+
let from = -1;
|
|
783
|
+
let to = -1;
|
|
784
|
+
class SQLQueryBuilder {
|
|
785
|
+
sqlParts = [];
|
|
786
|
+
appendSql(sql$1) {
|
|
787
|
+
this.sqlParts.push(sql$1);
|
|
788
|
+
}
|
|
789
|
+
appendVariable(varName, value) {
|
|
790
|
+
this.sqlParts.push({
|
|
791
|
+
name: varName,
|
|
792
|
+
value
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
trim() {
|
|
796
|
+
const lastPart = this.sqlParts.length > 0 ? this.sqlParts[this.sqlParts.length - 1] : null;
|
|
797
|
+
if (lastPart && typeof lastPart === "string") this.sqlParts[this.sqlParts.length - 1] = lastPart.trimEnd();
|
|
798
|
+
}
|
|
799
|
+
parameters() {
|
|
800
|
+
return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
|
|
801
|
+
}
|
|
802
|
+
toSqlWithAnonymousPlaceholders() {
|
|
803
|
+
let sql$1 = "";
|
|
479
804
|
const sqlParts = [];
|
|
480
805
|
for (const part of this.sqlParts) if (typeof part === "string") {
|
|
481
806
|
sql$1 += part;
|
|
@@ -568,7 +893,7 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
568
893
|
if (match) includeColumns.push(...match[1].split(",").map((c) => c.trim()));
|
|
569
894
|
}
|
|
570
895
|
const table = new TableInfo(filePath, name, tableName, includeColumns, hasAppender);
|
|
571
|
-
if (queryNames.has(name)) throw SqgError.inFile(`Duplicate name '${name}'`, "DUPLICATE_QUERY", filePath, { suggestion:
|
|
896
|
+
if (queryNames.has(name)) throw SqgError.inFile(`Duplicate name '${name}'`, "DUPLICATE_QUERY", filePath, { suggestion: "Rename one of the tables/queries to have a unique name" });
|
|
572
897
|
queryNames.add(name);
|
|
573
898
|
tables.push(table);
|
|
574
899
|
consola.debug(`Added table: ${name} -> ${tableName} (appender: ${hasAppender})`);
|
|
@@ -584,7 +909,7 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
584
909
|
config
|
|
585
910
|
});
|
|
586
911
|
const query = new SQLQuery(filePath, name, sqlContentStr, sql.toSqlWithAnonymousPlaceholders(), sql.toSqlWithNamedPlaceholders(), sql.toSqlWithPositionalPlaceholders(), queryType, isOne, isPluck, variables, config);
|
|
587
|
-
if (queryNames.has(name)) throw SqgError.inFile(`Duplicate query name '${name}'`, "DUPLICATE_QUERY", filePath, { suggestion:
|
|
912
|
+
if (queryNames.has(name)) throw SqgError.inFile(`Duplicate query name '${name}'`, "DUPLICATE_QUERY", filePath, { suggestion: "Rename one of the queries to have a unique name" });
|
|
588
913
|
queryNames.add(name);
|
|
589
914
|
queries.push(query);
|
|
590
915
|
consola.debug(`Added query: ${name} (${queryType})`);
|
|
@@ -986,7 +1311,7 @@ var TypeMapper = class {
|
|
|
986
1311
|
}
|
|
987
1312
|
if (column.type instanceof StructType) return path + this.formatStructTypeName(column.name);
|
|
988
1313
|
if (column.type instanceof MapType) return path + this.formatMapTypeName(column.name);
|
|
989
|
-
if (!column.type) throw new TypeMappingError(
|
|
1314
|
+
if (!column.type) throw new TypeMappingError("Missing type information", column.name);
|
|
990
1315
|
return this.mapPrimitiveType(column.type.toString(), column.nullable);
|
|
991
1316
|
}
|
|
992
1317
|
/**
|
|
@@ -1457,7 +1782,7 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
1457
1782
|
const name = `${gen.name}-jdbc`;
|
|
1458
1783
|
writeGeneratedFile(projectDir, {
|
|
1459
1784
|
name,
|
|
1460
|
-
generator: "java/jdbc",
|
|
1785
|
+
generator: "java/duckdb/jdbc",
|
|
1461
1786
|
output: gen.output,
|
|
1462
1787
|
config: gen.config
|
|
1463
1788
|
}, this.javaGenerator, name, q, tables, "duckdb");
|
|
@@ -1754,16 +2079,26 @@ var TsDuckDBGenerator = class extends TsGenerator {
|
|
|
1754
2079
|
|
|
1755
2080
|
//#endregion
|
|
1756
2081
|
//#region src/generators/index.ts
|
|
2082
|
+
/**
|
|
2083
|
+
* Get a generator instance for the given generator.
|
|
2084
|
+
* Accepts both short (language/engine) and full (language/engine/driver) formats.
|
|
2085
|
+
*/
|
|
1757
2086
|
function getGenerator(generator) {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
2087
|
+
const fullGenerator = resolveGenerator(generator);
|
|
2088
|
+
try {
|
|
2089
|
+
const info = parseGenerator(generator);
|
|
2090
|
+
const key = `${info.language}/${info.driver}`;
|
|
2091
|
+
switch (key) {
|
|
2092
|
+
case "typescript/better-sqlite3":
|
|
2093
|
+
case "typescript/node": return new TsGenerator(`templates/${info.template}`);
|
|
2094
|
+
case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
|
|
2095
|
+
case "java/jdbc": return new JavaGenerator(`templates/${info.template}`);
|
|
2096
|
+
case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
|
|
2097
|
+
default: throw new Error(`No generator class for ${key}`);
|
|
1766
2098
|
}
|
|
2099
|
+
} catch {
|
|
2100
|
+
const similar = findSimilarGenerators(generator);
|
|
2101
|
+
throw new InvalidGeneratorError(fullGenerator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
|
|
1767
2102
|
}
|
|
1768
2103
|
}
|
|
1769
2104
|
|
|
@@ -1932,6 +2267,8 @@ function generateSourceFile(name, queries, tables, templatePath, generator, engi
|
|
|
1932
2267
|
allowProtoMethodsByDefault: true
|
|
1933
2268
|
});
|
|
1934
2269
|
}
|
|
2270
|
+
/** All valid generator strings for schema validation */
|
|
2271
|
+
const validGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
1935
2272
|
/**
|
|
1936
2273
|
* Project configuration schema with descriptions for validation messages
|
|
1937
2274
|
*/
|
|
@@ -1939,10 +2276,9 @@ const ProjectSchema = z.object({
|
|
|
1939
2276
|
version: z.number().describe("Configuration version (currently 1)"),
|
|
1940
2277
|
name: z.string().min(1, "Project name is required").describe("Project name used for generated class names"),
|
|
1941
2278
|
sql: z.array(z.object({
|
|
1942
|
-
engine: z.enum(DB_ENGINES).describe(`Database engine: ${DB_ENGINES.join(", ")}`),
|
|
1943
2279
|
files: z.array(z.string().min(1)).min(1, "At least one SQL file is required").describe("SQL files to process"),
|
|
1944
2280
|
gen: z.array(z.object({
|
|
1945
|
-
generator: z.
|
|
2281
|
+
generator: z.string().refine((val) => isValidGenerator(val), { message: `Invalid generator. Valid generators: ${validGenerators.join(", ")}` }).describe(`Code generation generator: ${SHORT_GENERATOR_NAMES.join(", ")}`),
|
|
1946
2282
|
name: z.string().optional().describe("Override the generated class/module name"),
|
|
1947
2283
|
template: z.string().optional().describe("Custom Handlebars template path"),
|
|
1948
2284
|
output: z.string().min(1, "Output path is required").describe("Output file or directory path"),
|
|
@@ -1960,15 +2296,36 @@ var ExtraVariable = class {
|
|
|
1960
2296
|
this.value = value;
|
|
1961
2297
|
}
|
|
1962
2298
|
};
|
|
1963
|
-
function createExtraVariables(sources) {
|
|
2299
|
+
function createExtraVariables(sources, suppressLogging = false) {
|
|
1964
2300
|
return sources.map((source) => {
|
|
1965
2301
|
const path = source.path;
|
|
1966
2302
|
const resolvedPath = path.replace("$HOME", homedir());
|
|
1967
2303
|
const varName = `sources_${(source.name ?? basename(path, extname(resolvedPath))).replace(/\s+/g, "_")}`;
|
|
1968
|
-
consola.info("Extra variable:", varName, resolvedPath);
|
|
2304
|
+
if (!suppressLogging) consola.info("Extra variable:", varName, resolvedPath);
|
|
1969
2305
|
return new ExtraVariable(varName, `'${resolvedPath}'`);
|
|
1970
2306
|
});
|
|
1971
2307
|
}
|
|
2308
|
+
function buildProjectFromCliOptions(options) {
|
|
2309
|
+
if (!isValidGenerator(options.generator)) {
|
|
2310
|
+
const similar = findSimilarGenerators(options.generator);
|
|
2311
|
+
const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
2312
|
+
throw new InvalidGeneratorError(options.generator, allGenerators, similar.length > 0 ? similar[0] : void 0);
|
|
2313
|
+
}
|
|
2314
|
+
const generatorInfo = parseGenerator(options.generator);
|
|
2315
|
+
const genConfig = {
|
|
2316
|
+
generator: options.generator,
|
|
2317
|
+
output: options.output || "."
|
|
2318
|
+
};
|
|
2319
|
+
if (generatorInfo.language === "java") genConfig.config = { package: "generated" };
|
|
2320
|
+
return {
|
|
2321
|
+
version: 1,
|
|
2322
|
+
name: options.name || "generated",
|
|
2323
|
+
sql: [{
|
|
2324
|
+
files: options.files,
|
|
2325
|
+
gen: [genConfig]
|
|
2326
|
+
}]
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
1972
2329
|
/**
|
|
1973
2330
|
* Parse and validate project configuration with helpful error messages
|
|
1974
2331
|
*/
|
|
@@ -1990,12 +2347,12 @@ function parseProjectConfig(filePath) {
|
|
|
1990
2347
|
const obj = parsed;
|
|
1991
2348
|
if (obj.sql && Array.isArray(obj.sql)) for (let i = 0; i < obj.sql.length; i++) {
|
|
1992
2349
|
const sqlConfig = obj.sql[i];
|
|
1993
|
-
if (sqlConfig.engine && !DB_ENGINES.includes(sqlConfig.engine)) throw new InvalidEngineError(String(sqlConfig.engine), [...DB_ENGINES]);
|
|
1994
2350
|
if (sqlConfig.gen && Array.isArray(sqlConfig.gen)) for (let j = 0; j < sqlConfig.gen.length; j++) {
|
|
1995
2351
|
const genConfig = sqlConfig.gen[j];
|
|
1996
|
-
if (genConfig.generator && !
|
|
2352
|
+
if (genConfig.generator && !isValidGenerator(String(genConfig.generator))) {
|
|
1997
2353
|
const similar = findSimilarGenerators(String(genConfig.generator));
|
|
1998
|
-
|
|
2354
|
+
const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
|
|
2355
|
+
throw new InvalidGeneratorError(String(genConfig.generator), allGenerators, similar.length > 0 ? similar[0] : void 0);
|
|
1999
2356
|
}
|
|
2000
2357
|
}
|
|
2001
2358
|
}
|
|
@@ -2039,11 +2396,16 @@ function validateQueries(queries) {
|
|
|
2039
2396
|
};
|
|
2040
2397
|
}
|
|
2041
2398
|
}
|
|
2042
|
-
async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables, engine) {
|
|
2399
|
+
async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables, engine, writeToStdout = false) {
|
|
2043
2400
|
await generator.beforeGenerate(projectDir, gen, queries, tables);
|
|
2044
2401
|
const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
|
|
2045
2402
|
const name = gen.name ?? basename(file, extname(file));
|
|
2046
2403
|
const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.config);
|
|
2404
|
+
if (writeToStdout) {
|
|
2405
|
+
process.stdout.write(sourceFile);
|
|
2406
|
+
if (!sourceFile.endsWith("\n")) process.stdout.write("\n");
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2047
2409
|
const outputPath = getOutputPath(projectDir, name, gen, generator);
|
|
2048
2410
|
writeFileSync(outputPath, sourceFile);
|
|
2049
2411
|
consola.success(`Generated ${outputPath}`);
|
|
@@ -2051,33 +2413,11 @@ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tab
|
|
|
2051
2413
|
return outputPath;
|
|
2052
2414
|
}
|
|
2053
2415
|
/**
|
|
2054
|
-
* Validate project configuration without executing queries
|
|
2416
|
+
* Validate project configuration from a Project object without executing queries
|
|
2055
2417
|
* Use this for pre-flight checks before generation
|
|
2056
2418
|
*/
|
|
2057
|
-
async function
|
|
2419
|
+
async function validateProjectFromConfig(project, projectDir) {
|
|
2058
2420
|
const errors = [];
|
|
2059
|
-
const projectDir = resolve(dirname(projectPath));
|
|
2060
|
-
let project;
|
|
2061
|
-
try {
|
|
2062
|
-
project = parseProjectConfig(projectPath);
|
|
2063
|
-
} catch (e) {
|
|
2064
|
-
if (e instanceof SqgError) return {
|
|
2065
|
-
valid: false,
|
|
2066
|
-
errors: [{
|
|
2067
|
-
code: e.code,
|
|
2068
|
-
message: e.message,
|
|
2069
|
-
suggestion: e.suggestion,
|
|
2070
|
-
context: e.context
|
|
2071
|
-
}]
|
|
2072
|
-
};
|
|
2073
|
-
return {
|
|
2074
|
-
valid: false,
|
|
2075
|
-
errors: [{
|
|
2076
|
-
code: "UNKNOWN_ERROR",
|
|
2077
|
-
message: String(e)
|
|
2078
|
-
}]
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
2421
|
const sqlFiles = [];
|
|
2082
2422
|
const generators = [];
|
|
2083
2423
|
for (const sql of project.sql) for (const sqlFile of sql.files) {
|
|
@@ -2091,15 +2431,15 @@ async function validateProject(projectPath) {
|
|
|
2091
2431
|
});
|
|
2092
2432
|
for (const gen of sql.gen) {
|
|
2093
2433
|
generators.push(gen.generator);
|
|
2094
|
-
if (!
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2434
|
+
if (!isValidGenerator(gen.generator)) {
|
|
2435
|
+
const similar = findSimilarGenerators(gen.generator);
|
|
2436
|
+
errors.push({
|
|
2437
|
+
code: "INVALID_GENERATOR",
|
|
2438
|
+
message: `Invalid generator '${gen.generator}'`,
|
|
2439
|
+
suggestion: similar.length > 0 ? `Did you mean '${similar[0]}'?` : `Valid generators: ${SHORT_GENERATOR_NAMES.join(", ")}`,
|
|
2440
|
+
context: { generator: gen.generator }
|
|
2441
|
+
});
|
|
2442
|
+
}
|
|
2103
2443
|
}
|
|
2104
2444
|
}
|
|
2105
2445
|
return {
|
|
@@ -2114,387 +2454,506 @@ async function validateProject(projectPath) {
|
|
|
2114
2454
|
};
|
|
2115
2455
|
}
|
|
2116
2456
|
/**
|
|
2117
|
-
*
|
|
2457
|
+
* Validate project configuration from a YAML file without executing queries
|
|
2458
|
+
* Use this for pre-flight checks before generation
|
|
2118
2459
|
*/
|
|
2119
|
-
async function
|
|
2460
|
+
async function validateProject(projectPath) {
|
|
2120
2461
|
const projectDir = resolve(dirname(projectPath));
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
});
|
|
2156
|
-
}
|
|
2462
|
+
let project;
|
|
2463
|
+
try {
|
|
2464
|
+
project = parseProjectConfig(projectPath);
|
|
2465
|
+
} catch (e) {
|
|
2466
|
+
if (e instanceof SqgError) return {
|
|
2467
|
+
valid: false,
|
|
2468
|
+
errors: [{
|
|
2469
|
+
code: e.code,
|
|
2470
|
+
message: e.message,
|
|
2471
|
+
suggestion: e.suggestion,
|
|
2472
|
+
context: e.context
|
|
2473
|
+
}]
|
|
2474
|
+
};
|
|
2475
|
+
return {
|
|
2476
|
+
valid: false,
|
|
2477
|
+
errors: [{
|
|
2478
|
+
code: "UNKNOWN_ERROR",
|
|
2479
|
+
message: String(e)
|
|
2480
|
+
}]
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
return await validateProjectFromConfig(project, projectDir);
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Process a project configuration and generate code from a Project object
|
|
2487
|
+
*/
|
|
2488
|
+
async function processProjectFromConfig(project, projectDir, writeToStdout = false) {
|
|
2489
|
+
const originalLevel = consola.level;
|
|
2490
|
+
if (writeToStdout) consola.level = LogLevels.silent;
|
|
2491
|
+
try {
|
|
2492
|
+
const extraVariables = createExtraVariables(project.sources ?? [], writeToStdout);
|
|
2493
|
+
const files = [];
|
|
2494
|
+
for (const sql of project.sql) {
|
|
2495
|
+
const gensByEngine = /* @__PURE__ */ new Map();
|
|
2157
2496
|
for (const gen of sql.gen) {
|
|
2158
|
-
const
|
|
2159
|
-
|
|
2497
|
+
const engine = getGeneratorEngine(gen.generator);
|
|
2498
|
+
if (!gensByEngine.has(engine)) gensByEngine.set(engine, []);
|
|
2499
|
+
gensByEngine.get(engine).push(gen);
|
|
2500
|
+
}
|
|
2501
|
+
for (const sqlFile of sql.files) {
|
|
2502
|
+
const fullPath = join(projectDir, sqlFile);
|
|
2503
|
+
if (!existsSync(fullPath)) throw new FileNotFoundError(fullPath, projectDir);
|
|
2504
|
+
let queries;
|
|
2505
|
+
let tables;
|
|
2506
|
+
try {
|
|
2507
|
+
const parseResult = parseSQLQueries(fullPath, extraVariables);
|
|
2508
|
+
queries = parseResult.queries;
|
|
2509
|
+
tables = parseResult.tables;
|
|
2510
|
+
} catch (e) {
|
|
2511
|
+
if (e instanceof SqgError) throw e;
|
|
2512
|
+
throw SqgError.inFile(`Failed to parse SQL file: ${e.message}`, "SQL_PARSE_ERROR", sqlFile, { suggestion: "Check SQL syntax and annotation format" });
|
|
2513
|
+
}
|
|
2514
|
+
for (const [engine, gens] of gensByEngine) {
|
|
2515
|
+
try {
|
|
2516
|
+
const dbEngine = getDatabaseEngine(engine);
|
|
2517
|
+
await dbEngine.initializeDatabase(queries);
|
|
2518
|
+
await dbEngine.executeQueries(queries);
|
|
2519
|
+
if (tables.length > 0) await dbEngine.introspectTables(tables);
|
|
2520
|
+
validateQueries(queries);
|
|
2521
|
+
await dbEngine.close();
|
|
2522
|
+
} catch (e) {
|
|
2523
|
+
if (e instanceof SqgError) throw e;
|
|
2524
|
+
throw new SqgError(`Database error processing ${sqlFile}: ${e.message}`, "DATABASE_ERROR", `Check that the SQL is valid for engine '${engine}'`, {
|
|
2525
|
+
file: sqlFile,
|
|
2526
|
+
engine
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
for (const gen of gens) {
|
|
2530
|
+
const outputPath = await writeGeneratedFile(projectDir, gen, getGenerator(gen.generator), sqlFile, queries, tables, engine, writeToStdout);
|
|
2531
|
+
if (outputPath !== null) files.push(outputPath);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2160
2534
|
}
|
|
2161
2535
|
}
|
|
2536
|
+
return files;
|
|
2537
|
+
} finally {
|
|
2538
|
+
if (writeToStdout) consola.level = originalLevel;
|
|
2162
2539
|
}
|
|
2163
|
-
return files;
|
|
2164
2540
|
}
|
|
2165
|
-
|
|
2166
|
-
//#endregion
|
|
2167
|
-
//#region src/init.ts
|
|
2168
|
-
/**
|
|
2169
|
-
* SQG Project Initialization - Creates new SQG projects with example files
|
|
2170
|
-
*/
|
|
2171
2541
|
/**
|
|
2172
|
-
*
|
|
2542
|
+
* Process a project configuration and generate code from a YAML file
|
|
2173
2543
|
*/
|
|
2174
|
-
function
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
duckdb: "typescript/duckdb",
|
|
2178
|
-
postgres: "java/jdbc"
|
|
2179
|
-
}[engine];
|
|
2544
|
+
async function processProject(projectPath) {
|
|
2545
|
+
const projectDir = resolve(dirname(projectPath));
|
|
2546
|
+
return await processProjectFromConfig(parseProjectConfig(projectPath), projectDir, false);
|
|
2180
2547
|
}
|
|
2181
|
-
/**
|
|
2182
|
-
* Generate example SQL content based on engine
|
|
2183
|
-
*/
|
|
2184
|
-
function getExampleSql(engine) {
|
|
2185
|
-
return {
|
|
2186
|
-
sqlite: `-- MIGRATE 1
|
|
2187
|
-
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
2188
|
-
CREATE TABLE users (
|
|
2189
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2190
|
-
name TEXT NOT NULL,
|
|
2191
|
-
email TEXT UNIQUE NOT NULL,
|
|
2192
|
-
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
2193
|
-
);
|
|
2194
2548
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
);
|
|
2549
|
+
//#endregion
|
|
2550
|
+
//#region src/mcp-server.ts
|
|
2551
|
+
const server = new Server({
|
|
2552
|
+
name: "sqg-mcp",
|
|
2553
|
+
version: process.env.npm_package_version ?? "0.6.0"
|
|
2554
|
+
}, { capabilities: {
|
|
2555
|
+
tools: {},
|
|
2556
|
+
resources: {}
|
|
2557
|
+
} });
|
|
2558
|
+
function formatGeneratorListWithDescriptions() {
|
|
2559
|
+
const lines = [];
|
|
2560
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2561
|
+
for (const shortName of SHORT_GENERATOR_NAMES) {
|
|
2562
|
+
const fullName = Object.keys(GENERATORS).find((g) => g.startsWith(`${shortName}/`));
|
|
2563
|
+
if (fullName) {
|
|
2564
|
+
const info = GENERATORS[fullName];
|
|
2565
|
+
lines.push(`- ${shortName} - ${info.description}`);
|
|
2566
|
+
seen.add(fullName);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
for (const [fullName, info] of Object.entries(GENERATORS)) if (!seen.has(fullName)) lines.push(`- ${fullName} - ${info.description}`);
|
|
2570
|
+
return lines.join("\n");
|
|
2571
|
+
}
|
|
2572
|
+
function formatGeneratorListSimple() {
|
|
2573
|
+
const generators = [...SHORT_GENERATOR_NAMES];
|
|
2574
|
+
const seen = new Set(SHORT_GENERATOR_NAMES.map((s) => Object.keys(GENERATORS).find((g) => g.startsWith(`${s}/`))));
|
|
2575
|
+
for (const fullName of Object.keys(GENERATORS)) if (!seen.has(fullName)) generators.push(fullName);
|
|
2576
|
+
return generators.join(", ");
|
|
2577
|
+
}
|
|
2578
|
+
async function generateCode(sql, generator) {
|
|
2579
|
+
const engine = getGeneratorEngine(generator);
|
|
2580
|
+
const tempDir = join(tmpdir(), `sqg-mcp-${randomUUID()}`);
|
|
2581
|
+
const sqlFile = join(tempDir, "queries.sql");
|
|
2582
|
+
const configFile = join(tempDir, "sqg.yaml");
|
|
2583
|
+
try {
|
|
2584
|
+
mkdirSync(tempDir, { recursive: true });
|
|
2585
|
+
writeFileSync(sqlFile, sql, "utf-8");
|
|
2586
|
+
const genConfig = {
|
|
2587
|
+
generator,
|
|
2588
|
+
output: "./generated/"
|
|
2589
|
+
};
|
|
2590
|
+
if (generator.startsWith("java/")) genConfig.config = { package: "sqg.generated" };
|
|
2591
|
+
const projectYaml = {
|
|
2592
|
+
version: 1,
|
|
2593
|
+
name: "generated",
|
|
2594
|
+
sql: [{
|
|
2595
|
+
engine,
|
|
2596
|
+
files: ["queries.sql"],
|
|
2597
|
+
gen: [genConfig]
|
|
2598
|
+
}]
|
|
2599
|
+
};
|
|
2600
|
+
writeFileSync(configFile, YAML.stringify(projectYaml), "utf-8");
|
|
2601
|
+
const files = await processProject(configFile);
|
|
2602
|
+
if (files.length === 0) return {
|
|
2603
|
+
code: "",
|
|
2604
|
+
error: "No files were generated"
|
|
2605
|
+
};
|
|
2606
|
+
return { code: readFileSync(files[0], "utf-8") };
|
|
2607
|
+
} catch (error) {
|
|
2608
|
+
return {
|
|
2609
|
+
code: "",
|
|
2610
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2611
|
+
};
|
|
2612
|
+
} finally {
|
|
2613
|
+
try {
|
|
2614
|
+
rmSync(tempDir, {
|
|
2615
|
+
recursive: true,
|
|
2616
|
+
force: true
|
|
2617
|
+
});
|
|
2618
|
+
} catch (error) {}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
const GENERATE_CODE_DESCRIPTION = `Generate type-safe database access code from annotated SQL queries.
|
|
2622
|
+
|
|
2623
|
+
CRITICAL REQUIREMENTS:
|
|
2624
|
+
1. MIGRATE statements MUST come BEFORE any QUERY/EXEC that references those tables
|
|
2625
|
+
2. Each query block needs a unique name
|
|
2626
|
+
3. Parameters require @set declarations with sample values
|
|
2204
2627
|
|
|
2205
|
-
|
|
2206
|
-
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
|
|
2207
|
-
INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
|
|
2208
|
-
INSERT INTO posts (user_id, title, content, published) VALUES (1, 'Hello World', 'My first post!', 1);
|
|
2209
|
-
`,
|
|
2210
|
-
duckdb: `-- MIGRATE 1
|
|
2211
|
-
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
2212
|
-
CREATE TABLE users (
|
|
2213
|
-
id INTEGER PRIMARY KEY,
|
|
2214
|
-
name VARCHAR NOT NULL,
|
|
2215
|
-
email VARCHAR UNIQUE NOT NULL,
|
|
2216
|
-
metadata STRUCT(role VARCHAR, active BOOLEAN),
|
|
2217
|
-
tags VARCHAR[],
|
|
2218
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
2219
|
-
);
|
|
2628
|
+
${SQL_SYNTAX_REFERENCE}
|
|
2220
2629
|
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
id INTEGER PRIMARY KEY,
|
|
2224
|
-
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
2225
|
-
title VARCHAR NOT NULL,
|
|
2226
|
-
content VARCHAR,
|
|
2227
|
-
published BOOLEAN DEFAULT FALSE,
|
|
2228
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
2229
|
-
);
|
|
2630
|
+
VALID GENERATORS (use short form):
|
|
2631
|
+
${formatGeneratorListWithDescriptions()}
|
|
2230
2632
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
VALUES (1, 1, 'Hello World', 'My first post!', TRUE);
|
|
2238
|
-
`,
|
|
2239
|
-
postgres: `-- MIGRATE 1
|
|
2240
|
-
-- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
|
|
2241
|
-
CREATE TABLE users (
|
|
2242
|
-
id SERIAL PRIMARY KEY,
|
|
2243
|
-
name TEXT NOT NULL,
|
|
2244
|
-
email TEXT UNIQUE NOT NULL,
|
|
2245
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
2246
|
-
);
|
|
2633
|
+
COMMON MISTAKES TO AVOID:
|
|
2634
|
+
- Missing MIGRATE before QUERY (causes "no such table" error)
|
|
2635
|
+
- Missing @set for parameters (causes "undefined variable" error)
|
|
2636
|
+
- Duplicate query names (causes "duplicate query" error)
|
|
2637
|
+
- Using :pluck with multiple columns (only works with 1 column)`;
|
|
2638
|
+
const VALIDATE_SQL_DESCRIPTION = `Validate SQL queries with SQG annotations without generating code. Use this to check for errors before generating.
|
|
2247
2639
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
published BOOLEAN DEFAULT FALSE,
|
|
2255
|
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
2256
|
-
);
|
|
2640
|
+
Returns JSON with validation results including:
|
|
2641
|
+
- valid: boolean indicating success
|
|
2642
|
+
- project: project metadata if valid
|
|
2643
|
+
- sqlFiles: list of SQL files processed
|
|
2644
|
+
- generators: list of generators used
|
|
2645
|
+
- errors: array of error messages if invalid
|
|
2257
2646
|
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2647
|
+
See generate_code tool description for complete syntax reference.`;
|
|
2648
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
2649
|
+
return { tools: [{
|
|
2650
|
+
name: "generate_code",
|
|
2651
|
+
description: GENERATE_CODE_DESCRIPTION,
|
|
2652
|
+
inputSchema: {
|
|
2653
|
+
type: "object",
|
|
2654
|
+
properties: {
|
|
2655
|
+
sql: {
|
|
2656
|
+
type: "string",
|
|
2657
|
+
description: "Complete SQL file content with SQG annotations. IMPORTANT: Include MIGRATE statements first to create tables before QUERY statements that use them."
|
|
2658
|
+
},
|
|
2659
|
+
generator: {
|
|
2660
|
+
type: "string",
|
|
2661
|
+
description: `Code generator to use. Valid options: ${formatGeneratorListSimple()}`
|
|
2662
|
+
}
|
|
2663
|
+
},
|
|
2664
|
+
required: ["sql", "generator"]
|
|
2665
|
+
}
|
|
2666
|
+
}, {
|
|
2667
|
+
name: "validate_sql",
|
|
2668
|
+
description: VALIDATE_SQL_DESCRIPTION,
|
|
2669
|
+
inputSchema: {
|
|
2670
|
+
type: "object",
|
|
2671
|
+
properties: {
|
|
2672
|
+
sql: {
|
|
2673
|
+
type: "string",
|
|
2674
|
+
description: "Complete SQL file content with SQG annotations to validate. Include MIGRATE statements before QUERY statements."
|
|
2675
|
+
},
|
|
2676
|
+
generator: {
|
|
2677
|
+
type: "string",
|
|
2678
|
+
description: `Code generator for validation context. Valid options: ${formatGeneratorListSimple()}`
|
|
2679
|
+
}
|
|
2680
|
+
},
|
|
2681
|
+
required: ["sql", "generator"]
|
|
2682
|
+
}
|
|
2683
|
+
}] };
|
|
2684
|
+
});
|
|
2685
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2686
|
+
return { resources: [{
|
|
2687
|
+
uri: "sqg://documentation",
|
|
2688
|
+
name: "SQG Documentation",
|
|
2689
|
+
description: "Complete documentation for SQG (SQL Query Generator) including syntax, generators, and usage examples",
|
|
2690
|
+
mimeType: "text/markdown"
|
|
2691
|
+
}] };
|
|
2692
|
+
});
|
|
2693
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
2694
|
+
const { uri } = request.params;
|
|
2695
|
+
if (uri === "sqg://documentation") {
|
|
2696
|
+
const generatorListMarkdown = Object.entries(GENERATORS).map(([name, info]) => {
|
|
2697
|
+
const shortName = `${info.language}/${info.engine}`;
|
|
2698
|
+
return `- \`${Object.keys(GENERATORS).find((g) => g.startsWith(`${shortName}/`)) === name ? shortName : name}\` - ${info.description}`;
|
|
2699
|
+
}).filter((line, index, arr) => arr.indexOf(line) === index).join("\n");
|
|
2700
|
+
return { contents: [{
|
|
2701
|
+
uri,
|
|
2702
|
+
mimeType: "text/markdown",
|
|
2703
|
+
text: `# SQG - SQL Query Generator
|
|
2268
2704
|
|
|
2269
|
-
|
|
2270
|
-
@set id = 1
|
|
2271
|
-
SELECT id, name, email, created_at
|
|
2272
|
-
FROM users
|
|
2273
|
-
WHERE id = \${id};
|
|
2705
|
+
SQG is a type-safe SQL code generator that reads SQL queries from \`.sql\` files with special annotations and generates type-safe database access code in multiple target languages (TypeScript and Java).
|
|
2274
2706
|
|
|
2275
|
-
|
|
2276
|
-
@set email = 'alice@example.com'
|
|
2277
|
-
SELECT id, name, email, created_at
|
|
2278
|
-
FROM users
|
|
2279
|
-
WHERE email = \${email};
|
|
2707
|
+
## Overview
|
|
2280
2708
|
|
|
2281
|
-
|
|
2282
|
-
SELECT COUNT(*) FROM users;
|
|
2709
|
+
SQG introspects SQL queries at build time against real database engines to determine column types and generates strongly-typed wrapper functions.
|
|
2283
2710
|
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
SELECT p.id, p.title, p.content, p.published, p.created_at
|
|
2287
|
-
FROM posts p
|
|
2288
|
-
WHERE p.user_id = \${user_id}
|
|
2289
|
-
ORDER BY p.created_at DESC;
|
|
2711
|
+
**Website:** https://sqg.dev
|
|
2712
|
+
**Repository:** https://github.com/sqg-dev/sqg
|
|
2290
2713
|
|
|
2291
|
-
|
|
2292
|
-
SELECT
|
|
2293
|
-
p.id,
|
|
2294
|
-
p.title,
|
|
2295
|
-
p.content,
|
|
2296
|
-
p.created_at,
|
|
2297
|
-
u.name as author_name,
|
|
2298
|
-
u.email as author_email
|
|
2299
|
-
FROM posts p
|
|
2300
|
-
JOIN users u ON p.user_id = u.id
|
|
2301
|
-
WHERE p.published = 1
|
|
2302
|
-
ORDER BY p.created_at DESC;
|
|
2303
|
-
` + {
|
|
2304
|
-
sqlite: `
|
|
2305
|
-
-- EXEC create_user
|
|
2306
|
-
@set name = 'New User'
|
|
2307
|
-
@set email = 'new@example.com'
|
|
2308
|
-
INSERT INTO users (name, email)
|
|
2309
|
-
VALUES (\${name}, \${email});
|
|
2714
|
+
## Key Features
|
|
2310
2715
|
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2716
|
+
- **Type-safe by design** - Generates fully-typed code with accurate column types inferred from your database
|
|
2717
|
+
- **Multiple database engines** - Supports ${DB_ENGINES.join(", ")}
|
|
2718
|
+
- **Multiple language targets** - Generate TypeScript or Java code from the same SQL files
|
|
2719
|
+
- **Arrow API support** - Can generate Apache Arrow API bindings for DuckDB (Java)
|
|
2720
|
+
- **DBeaver compatible** - Works seamlessly with DBeaver for database development and testing
|
|
2721
|
+
- **Complex type support** - DuckDB: Handles structs, lists, and maps
|
|
2722
|
+
- **Migration management** - Built-in support for schema migrations and test data
|
|
2317
2723
|
|
|
2318
|
-
|
|
2319
|
-
@set id = 1
|
|
2320
|
-
UPDATE posts SET published = 1 WHERE id = \${id};
|
|
2724
|
+
## SQL Annotations
|
|
2321
2725
|
|
|
2322
|
-
|
|
2323
|
-
@set id = 1
|
|
2324
|
-
DELETE FROM posts WHERE id = \${id};
|
|
2325
|
-
`,
|
|
2326
|
-
duckdb: `
|
|
2327
|
-
-- EXEC create_user
|
|
2328
|
-
@set id = 100
|
|
2329
|
-
@set name = 'New User'
|
|
2330
|
-
@set email = 'new@example.com'
|
|
2331
|
-
INSERT INTO users (id, name, email)
|
|
2332
|
-
VALUES (\${id}, \${name}, \${email});
|
|
2726
|
+
${SQL_SYNTAX_REFERENCE}
|
|
2333
2727
|
|
|
2334
|
-
|
|
2335
|
-
@set id = 100
|
|
2336
|
-
@set user_id = 1
|
|
2337
|
-
@set title = 'New Post'
|
|
2338
|
-
@set content = 'Post content here'
|
|
2339
|
-
INSERT INTO posts (id, user_id, title, content)
|
|
2340
|
-
VALUES (\${id}, \${user_id}, \${title}, \${content});
|
|
2728
|
+
## Supported Generators
|
|
2341
2729
|
|
|
2342
|
-
|
|
2343
|
-
@set id = 1
|
|
2344
|
-
UPDATE posts SET published = TRUE WHERE id = \${id};
|
|
2730
|
+
Valid generator strings:
|
|
2345
2731
|
|
|
2346
|
-
|
|
2347
|
-
@set id = 1
|
|
2348
|
-
DELETE FROM posts WHERE id = \${id};
|
|
2349
|
-
`,
|
|
2350
|
-
postgres: `
|
|
2351
|
-
-- EXEC create_user
|
|
2352
|
-
@set name = 'New User'
|
|
2353
|
-
@set email = 'new@example.com'
|
|
2354
|
-
INSERT INTO users (name, email)
|
|
2355
|
-
VALUES (\${name}, \${email});
|
|
2732
|
+
${generatorListMarkdown}
|
|
2356
2733
|
|
|
2357
|
-
|
|
2358
|
-
@set user_id = 1
|
|
2359
|
-
@set title = 'New Post'
|
|
2360
|
-
@set content = 'Post content here'
|
|
2361
|
-
INSERT INTO posts (user_id, title, content)
|
|
2362
|
-
VALUES (\${user_id}, \${title}, \${content});
|
|
2734
|
+
## MCP Tools
|
|
2363
2735
|
|
|
2364
|
-
|
|
2365
|
-
@set id = 1
|
|
2366
|
-
UPDATE posts SET published = TRUE WHERE id = \${id};
|
|
2736
|
+
### generate_code
|
|
2367
2737
|
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2738
|
+
Generate type-safe database access code from SQL queries with SQG annotations.
|
|
2739
|
+
|
|
2740
|
+
**Parameters:**
|
|
2741
|
+
- \`sql\` (string, required): SQL queries with SQG annotations
|
|
2742
|
+
- \`generator\` (string, required): Code generation generator (see supported generators above)
|
|
2743
|
+
|
|
2744
|
+
**Example:**
|
|
2745
|
+
\`\`\`json
|
|
2746
|
+
{
|
|
2747
|
+
"sql": "-- MIGRATE 1\\nCREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);\\n\\n-- QUERY getUsers\\nSELECT * FROM users;",
|
|
2748
|
+
"generator": "typescript/sqlite"
|
|
2373
2749
|
}
|
|
2374
|
-
|
|
2375
|
-
* Generate sqg.yaml configuration
|
|
2376
|
-
*/
|
|
2377
|
-
function getConfigYaml(engine, generator, output) {
|
|
2378
|
-
SUPPORTED_GENERATORS[generator];
|
|
2379
|
-
const config = {
|
|
2380
|
-
version: 1,
|
|
2381
|
-
name: "my-project",
|
|
2382
|
-
sql: [{
|
|
2383
|
-
engine,
|
|
2384
|
-
files: ["queries.sql"],
|
|
2385
|
-
gen: [{
|
|
2386
|
-
generator,
|
|
2387
|
-
output: output.endsWith("/") ? output : `${output}/`
|
|
2388
|
-
}]
|
|
2389
|
-
}]
|
|
2390
|
-
};
|
|
2391
|
-
if (generator.startsWith("java/")) config.sql[0].gen[0].config = { package: "generated" };
|
|
2392
|
-
return `# SQG Configuration
|
|
2393
|
-
# Generated by: sqg init
|
|
2394
|
-
# Documentation: https://sqg.dev
|
|
2750
|
+
\`\`\`
|
|
2395
2751
|
|
|
2396
|
-
|
|
2397
|
-
name: my-project
|
|
2752
|
+
### validate_sql
|
|
2398
2753
|
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2754
|
+
Validate SQL queries with SQG annotations without generating code.
|
|
2755
|
+
|
|
2756
|
+
**Parameters:**
|
|
2757
|
+
- \`sql\` (string, required): SQL queries with SQG annotations to validate
|
|
2758
|
+
- \`generator\` (string, required): Code generation generator to use for validation
|
|
2759
|
+
|
|
2760
|
+
**Example:**
|
|
2761
|
+
\`\`\`json
|
|
2762
|
+
{
|
|
2763
|
+
"sql": "-- QUERY getUsers\\nSELECT * FROM users;",
|
|
2764
|
+
"generator": "typescript/sqlite"
|
|
2409
2765
|
}
|
|
2410
|
-
|
|
2411
|
-
* Initialize a new SQG project
|
|
2412
|
-
*/
|
|
2413
|
-
async function initProject(options) {
|
|
2414
|
-
const engine = options.engine || "sqlite";
|
|
2415
|
-
const output = options.output || "./generated";
|
|
2416
|
-
if (!DB_ENGINES.includes(engine)) throw new InvalidEngineError(engine, [...DB_ENGINES]);
|
|
2417
|
-
let generator;
|
|
2418
|
-
if (options.generator) {
|
|
2419
|
-
if (!(options.generator in SUPPORTED_GENERATORS)) {
|
|
2420
|
-
const similar = findSimilarGenerators(options.generator);
|
|
2421
|
-
throw new InvalidGeneratorError(options.generator, Object.keys(SUPPORTED_GENERATORS), similar.length > 0 ? similar[0] : void 0);
|
|
2422
|
-
}
|
|
2423
|
-
generator = options.generator;
|
|
2424
|
-
if (!SUPPORTED_GENERATORS[generator].compatibleEngines.includes(engine)) throw new SqgError(`Generator '${generator}' is not compatible with engine '${engine}'`, "GENERATOR_ENGINE_MISMATCH", `For '${engine}', use one of: ${Object.entries(SUPPORTED_GENERATORS).filter(([_, info]) => info.compatibleEngines.includes(engine)).map(([name]) => name).join(", ")}`);
|
|
2425
|
-
} else generator = getDefaultGenerator(engine);
|
|
2426
|
-
const configPath = "sqg.yaml";
|
|
2427
|
-
const sqlPath = "queries.sql";
|
|
2428
|
-
if (!options.force) {
|
|
2429
|
-
if (existsSync(configPath)) throw new SqgError(`File already exists: ${configPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
|
|
2430
|
-
if (existsSync(sqlPath)) throw new SqgError(`File already exists: ${sqlPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
|
|
2431
|
-
}
|
|
2432
|
-
if (!existsSync(output)) {
|
|
2433
|
-
mkdirSync(output, { recursive: true });
|
|
2434
|
-
consola.success(`Created output directory: ${output}`);
|
|
2435
|
-
}
|
|
2436
|
-
writeFileSync(configPath, getConfigYaml(engine, generator, output));
|
|
2437
|
-
consola.success(`Created ${configPath}`);
|
|
2438
|
-
writeFileSync(sqlPath, getExampleSql(engine));
|
|
2439
|
-
consola.success(`Created ${sqlPath}`);
|
|
2440
|
-
consola.box(`
|
|
2441
|
-
SQG project initialized!
|
|
2766
|
+
\`\`\`
|
|
2442
2767
|
|
|
2443
|
-
|
|
2444
|
-
Generator: ${generator}
|
|
2445
|
-
Output: ${output}
|
|
2768
|
+
## Generator Format
|
|
2446
2769
|
|
|
2447
|
-
|
|
2448
|
-
1. Edit queries.sql to add your SQL queries
|
|
2449
|
-
2. Run: sqg sqg.yaml
|
|
2450
|
-
3. Import the generated code from ${output}
|
|
2770
|
+
Generators follow the pattern \`<language>/<engine>[/<driver>]\`:
|
|
2451
2771
|
|
|
2452
|
-
|
|
2453
|
-
|
|
2772
|
+
- **Short form**: \`typescript/sqlite\`, \`java/duckdb\` (uses default driver)
|
|
2773
|
+
- **Full form**: \`typescript/sqlite/better-sqlite3\`, \`java/duckdb/arrow\` (specifies driver)
|
|
2774
|
+
|
|
2775
|
+
The MCP server accepts both short and full forms, but short forms are recommended.
|
|
2776
|
+
|
|
2777
|
+
## More Information
|
|
2778
|
+
|
|
2779
|
+
- Full documentation: https://sqg.dev
|
|
2780
|
+
- GitHub: https://github.com/sqg-dev/sqg
|
|
2781
|
+
- SQL Syntax Reference: Run \`sqg syntax\` command
|
|
2782
|
+
`
|
|
2783
|
+
}] };
|
|
2784
|
+
}
|
|
2785
|
+
return {
|
|
2786
|
+
contents: [],
|
|
2787
|
+
isError: true
|
|
2788
|
+
};
|
|
2789
|
+
});
|
|
2790
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2791
|
+
const { name, arguments: args } = request.params;
|
|
2792
|
+
if (name === "generate_code") {
|
|
2793
|
+
const { sql, generator } = args;
|
|
2794
|
+
const result = await generateCode(sql, generator);
|
|
2795
|
+
if (result.error) return {
|
|
2796
|
+
content: [{
|
|
2797
|
+
type: "text",
|
|
2798
|
+
text: `Error generating code: ${result.error}`
|
|
2799
|
+
}],
|
|
2800
|
+
isError: true
|
|
2801
|
+
};
|
|
2802
|
+
return { content: [{
|
|
2803
|
+
type: "text",
|
|
2804
|
+
text: result.code
|
|
2805
|
+
}] };
|
|
2806
|
+
}
|
|
2807
|
+
if (name === "validate_sql") {
|
|
2808
|
+
const { sql, generator } = args;
|
|
2809
|
+
const engine = getGeneratorEngine(generator);
|
|
2810
|
+
const tempDir = join(tmpdir(), `sqg-mcp-validate-${randomUUID()}`);
|
|
2811
|
+
const sqlFile = join(tempDir, "queries.sql");
|
|
2812
|
+
const configFile = join(tempDir, "sqg.yaml");
|
|
2813
|
+
try {
|
|
2814
|
+
mkdirSync(tempDir, { recursive: true });
|
|
2815
|
+
writeFileSync(sqlFile, sql, "utf-8");
|
|
2816
|
+
const projectYaml = {
|
|
2817
|
+
version: 1,
|
|
2818
|
+
name: "validation",
|
|
2819
|
+
sql: [{
|
|
2820
|
+
engine,
|
|
2821
|
+
files: ["queries.sql"],
|
|
2822
|
+
gen: [{
|
|
2823
|
+
generator,
|
|
2824
|
+
output: "./generated/"
|
|
2825
|
+
}]
|
|
2826
|
+
}]
|
|
2827
|
+
};
|
|
2828
|
+
writeFileSync(configFile, YAML.stringify(projectYaml), "utf-8");
|
|
2829
|
+
const validation = await validateProject(configFile);
|
|
2830
|
+
if (validation.valid) return { content: [{
|
|
2831
|
+
type: "text",
|
|
2832
|
+
text: JSON.stringify({
|
|
2833
|
+
valid: true,
|
|
2834
|
+
project: validation.project,
|
|
2835
|
+
sqlFiles: validation.sqlFiles,
|
|
2836
|
+
generators: validation.generators
|
|
2837
|
+
}, null, 2)
|
|
2838
|
+
}] };
|
|
2839
|
+
return {
|
|
2840
|
+
content: [{
|
|
2841
|
+
type: "text",
|
|
2842
|
+
text: JSON.stringify({
|
|
2843
|
+
valid: false,
|
|
2844
|
+
errors: validation.errors
|
|
2845
|
+
}, null, 2)
|
|
2846
|
+
}],
|
|
2847
|
+
isError: true
|
|
2848
|
+
};
|
|
2849
|
+
} catch (error) {
|
|
2850
|
+
return {
|
|
2851
|
+
content: [{
|
|
2852
|
+
type: "text",
|
|
2853
|
+
text: `Error validating SQL: ${error instanceof Error ? error.message : String(error)}`
|
|
2854
|
+
}],
|
|
2855
|
+
isError: true
|
|
2856
|
+
};
|
|
2857
|
+
} finally {
|
|
2858
|
+
try {
|
|
2859
|
+
rmSync(tempDir, {
|
|
2860
|
+
recursive: true,
|
|
2861
|
+
force: true
|
|
2862
|
+
});
|
|
2863
|
+
} catch (error) {}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
return {
|
|
2867
|
+
content: [{
|
|
2868
|
+
type: "text",
|
|
2869
|
+
text: `Unknown tool: ${name}`
|
|
2870
|
+
}],
|
|
2871
|
+
isError: true
|
|
2872
|
+
};
|
|
2873
|
+
});
|
|
2874
|
+
async function startMcpServer() {
|
|
2875
|
+
const transport = new StdioServerTransport();
|
|
2876
|
+
await server.connect(transport);
|
|
2877
|
+
console.error("SQG MCP server running on stdio");
|
|
2454
2878
|
}
|
|
2455
2879
|
|
|
2456
2880
|
//#endregion
|
|
2457
2881
|
//#region src/sqg.ts
|
|
2458
|
-
const version = process.env.npm_package_version ?? "0.
|
|
2882
|
+
const version = process.env.npm_package_version ?? "0.6.0";
|
|
2459
2883
|
const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
|
|
2460
2884
|
consola.level = LogLevels.info;
|
|
2461
2885
|
const program = new Command().name("sqg").description(`${description}
|
|
2462
2886
|
|
|
2463
2887
|
Generate type-safe database access code from annotated SQL files.
|
|
2464
2888
|
|
|
2465
|
-
Supported Engines:
|
|
2466
|
-
${formatEnginesHelp()}
|
|
2467
|
-
|
|
2468
2889
|
Supported Generators:
|
|
2469
|
-
${formatGeneratorsHelp()}`).version(version, "-v, --version", "output the version number").option("--verbose", "Enable debug logging (shows SQL execution details)").option("--format <format>", "Output format: text (default) or json", "text").option("--validate", "Validate configuration without generating code").
|
|
2470
|
-
|
|
2890
|
+
${formatGeneratorsHelp()}`).version(version, "-v, --version", "output the version number").option("--verbose", "Enable debug logging (shows SQL execution details)").option("--format <format>", "Output format: text (default) or json", "text").option("--validate", "Validate configuration without generating code").option("--generator <generator>", `Code generation generator (${SHORT_GENERATOR_NAMES.join(", ")})`).option("--file <file>", "SQL file path (can be repeated)", (val, prev = []) => {
|
|
2891
|
+
prev.push(val);
|
|
2892
|
+
return prev;
|
|
2893
|
+
}).option("--output <path>", "Output file or directory path (optional, if omitted writes to stdout)").option("--name <name>", "Project name (optional, defaults to 'generated')").showHelpAfterError().showSuggestionAfterError();
|
|
2894
|
+
program.argument("[project]", "Path to the project YAML config (sqg.yaml) or omit to use CLI options").hook("preAction", (thisCommand) => {
|
|
2471
2895
|
const opts = thisCommand.opts();
|
|
2472
2896
|
if (opts.verbose) consola.level = LogLevels.debug;
|
|
2473
2897
|
if (opts.format === "json") consola.level = LogLevels.silent;
|
|
2474
2898
|
}).action(async (projectPath, options) => {
|
|
2475
2899
|
try {
|
|
2476
|
-
if (
|
|
2477
|
-
|
|
2478
|
-
if (options.
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2900
|
+
if (!projectPath) {
|
|
2901
|
+
if (!options.generator) throw new SqgError("Missing required option: --generator", "CONFIG_VALIDATION_ERROR", `Specify a code generation generator: ${SHORT_GENERATOR_NAMES.join(", ")}`);
|
|
2902
|
+
if (!options.file || options.file.length === 0) throw new SqgError("Missing required option: --file", "CONFIG_VALIDATION_ERROR", "Specify at least one SQL file with --file <path>");
|
|
2903
|
+
const project = buildProjectFromCliOptions({
|
|
2904
|
+
generator: options.generator,
|
|
2905
|
+
files: options.file,
|
|
2906
|
+
output: options.output,
|
|
2907
|
+
name: options.name
|
|
2908
|
+
});
|
|
2909
|
+
const projectDir = process.cwd();
|
|
2910
|
+
const writeToStdout = !options.output;
|
|
2911
|
+
if (options.validate) {
|
|
2912
|
+
const result = await validateProjectFromConfig(project, projectDir);
|
|
2913
|
+
if (options.format === "json") console.log(JSON.stringify(result, null, 2));
|
|
2914
|
+
else if (result.valid) {
|
|
2915
|
+
consola.success("Configuration is valid");
|
|
2916
|
+
consola.info(`Project: ${result.project?.name}`);
|
|
2917
|
+
consola.info(`SQL files: ${result.sqlFiles?.join(", ")}`);
|
|
2918
|
+
consola.info(`Generators: ${result.generators?.join(", ")}`);
|
|
2919
|
+
} else {
|
|
2920
|
+
consola.error("Validation failed");
|
|
2921
|
+
for (const error of result.errors || []) {
|
|
2922
|
+
consola.error(` ${error.message}`);
|
|
2923
|
+
if (error.suggestion) consola.info(` Suggestion: ${error.suggestion}`);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
exit(result.valid ? 0 : 1);
|
|
2927
|
+
}
|
|
2928
|
+
const files = await processProjectFromConfig(project, projectDir, writeToStdout);
|
|
2929
|
+
if (options.format === "json" && !writeToStdout) console.log(JSON.stringify({
|
|
2930
|
+
status: "success",
|
|
2931
|
+
generatedFiles: files
|
|
2932
|
+
}));
|
|
2933
|
+
} else {
|
|
2934
|
+
if (options.validate) {
|
|
2935
|
+
const result = await validateProject(projectPath);
|
|
2936
|
+
if (options.format === "json") console.log(JSON.stringify(result, null, 2));
|
|
2937
|
+
else if (result.valid) {
|
|
2938
|
+
consola.success("Configuration is valid");
|
|
2939
|
+
consola.info(`Project: ${result.project?.name}`);
|
|
2940
|
+
consola.info(`SQL files: ${result.sqlFiles?.join(", ")}`);
|
|
2941
|
+
consola.info(`Generators: ${result.generators?.join(", ")}`);
|
|
2942
|
+
} else {
|
|
2943
|
+
consola.error("Validation failed");
|
|
2944
|
+
for (const error of result.errors || []) {
|
|
2945
|
+
consola.error(` ${error.message}`);
|
|
2946
|
+
if (error.suggestion) consola.info(` Suggestion: ${error.suggestion}`);
|
|
2947
|
+
}
|
|
2489
2948
|
}
|
|
2949
|
+
exit(result.valid ? 0 : 1);
|
|
2490
2950
|
}
|
|
2491
|
-
|
|
2951
|
+
const files = await processProject(projectPath);
|
|
2952
|
+
if (options.format === "json") console.log(JSON.stringify({
|
|
2953
|
+
status: "success",
|
|
2954
|
+
generatedFiles: files
|
|
2955
|
+
}));
|
|
2492
2956
|
}
|
|
2493
|
-
const files = await processProject(projectPath);
|
|
2494
|
-
if (options.format === "json") console.log(JSON.stringify({
|
|
2495
|
-
status: "success",
|
|
2496
|
-
generatedFiles: files
|
|
2497
|
-
}));
|
|
2498
2957
|
} catch (err) {
|
|
2499
2958
|
if (options.format === "json") console.log(JSON.stringify(formatErrorForOutput(err)));
|
|
2500
2959
|
else if (err instanceof SqgError) {
|
|
@@ -2505,7 +2964,7 @@ program.argument("<project>", "Path to the project YAML config (sqg.yaml)").hook
|
|
|
2505
2964
|
exit(1);
|
|
2506
2965
|
}
|
|
2507
2966
|
});
|
|
2508
|
-
program.command("init").description("Initialize a new SQG project with example configuration").option("-
|
|
2967
|
+
program.command("init").description("Initialize a new SQG project with example configuration").option("-t, --generator <generator>", `Code generation generator (${SHORT_GENERATOR_NAMES.join(", ")})`, "typescript/sqlite").option("-o, --output <dir>", "Output directory for generated files", "./generated").option("-f, --force", "Overwrite existing files").action(async (options) => {
|
|
2509
2968
|
const parentOpts = program.opts();
|
|
2510
2969
|
try {
|
|
2511
2970
|
await initProject(options);
|
|
@@ -2525,6 +2984,14 @@ program.command("init").description("Initialize a new SQG project with example c
|
|
|
2525
2984
|
program.command("syntax").description("Show SQL annotation syntax reference").action(() => {
|
|
2526
2985
|
console.log(SQL_SYNTAX_REFERENCE);
|
|
2527
2986
|
});
|
|
2987
|
+
program.command("mcp").description("Start MCP (Model Context Protocol) server for AI assistants").action(async () => {
|
|
2988
|
+
try {
|
|
2989
|
+
await startMcpServer();
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
consola.error("Fatal error in MCP server:", error);
|
|
2992
|
+
exit(1);
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2528
2995
|
if (process.argv.length <= 2) {
|
|
2529
2996
|
program.outputHelp();
|
|
2530
2997
|
exit(1);
|