@sqg/sqg 0.15.0 → 0.16.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/dist/index.mjs ADDED
@@ -0,0 +1,3369 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, dirname, extname, join, resolve } from "node:path";
4
+ import consola, { LogLevels } from "consola";
5
+ import { camelCase, pascalCase, snakeCase } from "es-toolkit/string";
6
+ import Handlebars from "handlebars";
7
+ import YAML from "yaml";
8
+ import { z } from "zod";
9
+ import { DuckDBEnumType, DuckDBInstance, DuckDBListType, DuckDBMapType, DuckDBStructType } from "@duckdb/node-api";
10
+ import { LRParser } from "@lezer/lr";
11
+ import { isNotNil, sortBy } from "es-toolkit";
12
+ import { PostgreSqlContainer } from "@testcontainers/postgresql";
13
+ import { Client } from "pg";
14
+ import types from "pg-types";
15
+ import BetterSqlite3 from "better-sqlite3";
16
+ import prettier from "prettier/standalone";
17
+ import prettierPluginJava from "prettier-plugin-java";
18
+ import { execSync } from "node:child_process";
19
+ import typescriptPlugin from "prettier/parser-typescript";
20
+ import estree from "prettier/plugins/estree";
21
+ import * as clack from "@clack/prompts";
22
+ import pc from "picocolors";
23
+ //#region src/constants.ts
24
+ /**
25
+ * SQG Constants - Centralized definitions for supported generators
26
+ *
27
+ * Generator format: <language>/<engine>/<driver>
28
+ * Short format: <language>/<engine> (uses default driver)
29
+ */
30
+ /** Supported database engines */
31
+ const DB_ENGINES = [
32
+ "sqlite",
33
+ "duckdb",
34
+ "postgres"
35
+ ];
36
+ /** Supported languages */
37
+ const LANGUAGES = [
38
+ "typescript",
39
+ "java",
40
+ "python"
41
+ ];
42
+ /** All supported generators with their full specification */
43
+ const GENERATORS = {
44
+ "typescript/sqlite/better-sqlite3": {
45
+ language: "typescript",
46
+ engine: "sqlite",
47
+ driver: "better-sqlite3",
48
+ description: "TypeScript with better-sqlite3 driver",
49
+ extension: ".ts",
50
+ template: "better-sqlite3.hbs"
51
+ },
52
+ "typescript/sqlite/node": {
53
+ language: "typescript",
54
+ engine: "sqlite",
55
+ driver: "node",
56
+ description: "TypeScript with Node.js built-in SQLite",
57
+ extension: ".ts",
58
+ template: "node-sqlite.hbs"
59
+ },
60
+ "typescript/sqlite/libsql": {
61
+ language: "typescript",
62
+ engine: "sqlite",
63
+ driver: "libsql",
64
+ description: "TypeScript with @libsql/client (Turso)",
65
+ extension: ".ts",
66
+ template: "libsql.hbs"
67
+ },
68
+ "typescript/sqlite/turso": {
69
+ language: "typescript",
70
+ engine: "sqlite",
71
+ driver: "turso",
72
+ description: "TypeScript with Turso (limbo) native driver",
73
+ extension: ".ts",
74
+ template: "turso.hbs"
75
+ },
76
+ "typescript/duckdb/node-api": {
77
+ language: "typescript",
78
+ engine: "duckdb",
79
+ driver: "node-api",
80
+ description: "TypeScript with @duckdb/node-api driver",
81
+ extension: ".ts",
82
+ template: "typescript-duckdb.hbs"
83
+ },
84
+ "java/sqlite/jdbc": {
85
+ language: "java",
86
+ engine: "sqlite",
87
+ driver: "jdbc",
88
+ description: "Java with JDBC for SQLite",
89
+ extension: ".java",
90
+ template: "java-jdbc.hbs"
91
+ },
92
+ "java/duckdb/jdbc": {
93
+ language: "java",
94
+ engine: "duckdb",
95
+ driver: "jdbc",
96
+ description: "Java with JDBC for DuckDB",
97
+ extension: ".java",
98
+ template: "java-jdbc.hbs"
99
+ },
100
+ "java/duckdb/arrow": {
101
+ language: "java",
102
+ engine: "duckdb",
103
+ driver: "arrow",
104
+ description: "Java with DuckDB Arrow API",
105
+ extension: ".java",
106
+ template: "java-duckdb-arrow.hbs"
107
+ },
108
+ "java/postgres/jdbc": {
109
+ language: "java",
110
+ engine: "postgres",
111
+ driver: "jdbc",
112
+ description: "Java with JDBC for PostgreSQL",
113
+ extension: ".java",
114
+ template: "java-jdbc.hbs"
115
+ },
116
+ "python/sqlite/sqlite3": {
117
+ language: "python",
118
+ engine: "sqlite",
119
+ driver: "sqlite3",
120
+ description: "Python with sqlite3 standard library",
121
+ extension: ".py",
122
+ template: "python.hbs"
123
+ },
124
+ "python/duckdb/duckdb": {
125
+ language: "python",
126
+ engine: "duckdb",
127
+ driver: "duckdb",
128
+ description: "Python with duckdb driver",
129
+ extension: ".py",
130
+ template: "python.hbs"
131
+ },
132
+ "python/postgres/psycopg": {
133
+ language: "python",
134
+ engine: "postgres",
135
+ driver: "psycopg",
136
+ description: "Python with psycopg3 driver",
137
+ extension: ".py",
138
+ template: "python.hbs"
139
+ }
140
+ };
141
+ /** Default drivers for language/engine combinations */
142
+ const DEFAULT_DRIVERS = {
143
+ "typescript/sqlite": "better-sqlite3",
144
+ "typescript/duckdb": "node-api",
145
+ "java/sqlite": "jdbc",
146
+ "java/duckdb": "jdbc",
147
+ "java/postgres": "jdbc",
148
+ "python/sqlite": "sqlite3",
149
+ "python/duckdb": "duckdb",
150
+ "python/postgres": "psycopg"
151
+ };
152
+ /** List of all full generator names */
153
+ const GENERATOR_NAMES = Object.keys(GENERATORS);
154
+ /** List of short generator names (language/engine) */
155
+ const SHORT_GENERATOR_NAMES = Object.keys(DEFAULT_DRIVERS);
156
+ /**
157
+ * Resolve a generator string to its full form.
158
+ * Accepts both short (language/engine) and full (language/engine/driver) formats.
159
+ */
160
+ function resolveGenerator(generator) {
161
+ if (generator in GENERATORS) return generator;
162
+ if (generator in DEFAULT_DRIVERS) return `${generator}/${DEFAULT_DRIVERS[generator]}`;
163
+ return generator;
164
+ }
165
+ /**
166
+ * Parse a generator string and return its info.
167
+ * Throws if the generator is invalid.
168
+ */
169
+ function parseGenerator(generator) {
170
+ const info = GENERATORS[resolveGenerator(generator)];
171
+ if (!info) throw new Error(`Invalid generator: ${generator}`);
172
+ return info;
173
+ }
174
+ /**
175
+ * Check if a generator string is valid (either short or full form).
176
+ */
177
+ function isValidGenerator(generator) {
178
+ return resolveGenerator(generator) in GENERATORS;
179
+ }
180
+ /**
181
+ * Get the database engine for a generator.
182
+ */
183
+ function getGeneratorEngine(generator) {
184
+ return parseGenerator(generator).engine;
185
+ }
186
+ /**
187
+ * Get the language for a generator.
188
+ */
189
+ function getGeneratorLanguage(generator) {
190
+ return parseGenerator(generator).language;
191
+ }
192
+ /**
193
+ * Find similar generator names for typo suggestions.
194
+ */
195
+ function findSimilarGenerators(input) {
196
+ const normalized = input.toLowerCase();
197
+ return [...GENERATOR_NAMES, ...SHORT_GENERATOR_NAMES].filter((name) => {
198
+ const nameLower = name.toLowerCase();
199
+ if (nameLower.includes(normalized) || normalized.includes(nameLower)) return true;
200
+ return [
201
+ normalized.replace(/\//g, "-"),
202
+ normalized.replace(/-/g, "/"),
203
+ normalized.replace(/_/g, "/"),
204
+ normalized.replace(/_/g, "-")
205
+ ].some((v) => nameLower.includes(v) || v.includes(nameLower));
206
+ });
207
+ }
208
+ /**
209
+ * Format generators for CLI help output.
210
+ */
211
+ function formatGeneratorsHelp() {
212
+ const lines = [];
213
+ for (const shortName of SHORT_GENERATOR_NAMES) {
214
+ const fullName = `${shortName}/${DEFAULT_DRIVERS[shortName]}`;
215
+ const info = GENERATORS[fullName];
216
+ lines.push(` ${shortName.padEnd(24)} ${info.description} (default)`);
217
+ for (const [generatorName, generatorInfo] of Object.entries(GENERATORS)) if (generatorName.startsWith(`${shortName}/`) && generatorName !== fullName) lines.push(` ${generatorName.padEnd(24)} ${generatorInfo.description}`);
218
+ }
219
+ return lines.join("\n");
220
+ }
221
+ /**
222
+ * Format a simple list of valid generators.
223
+ */
224
+ function formatGeneratorsList() {
225
+ return [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES.filter((t) => !SHORT_GENERATOR_NAMES.some((s) => t === `${s}/${DEFAULT_DRIVERS[s]}`))].join(", ");
226
+ }
227
+ `
228
+ SQL Annotation Syntax:
229
+ -- QUERY <name> [:one] [:pluck] Select query (returns rows)
230
+ -- EXEC <name> Execute statement (INSERT/UPDATE/DELETE)
231
+ -- MIGRATE <number> Schema migration (run in order)
232
+ -- TESTDATA <name> Test data setup (not generated)
233
+ -- TABLE <name> :appender Table for bulk insert appender (DuckDB, PostgreSQL)
234
+
235
+ @set <varName> = <value> Define a variable
236
+ \${varName} Reference a variable in SQL
237
+
238
+ Modifiers:
239
+ :one Return single row (or null) instead of array
240
+ :pluck Return single column value (requires exactly 1 column)
241
+ :all Return all rows (default)
242
+ :appender Generate bulk insert appender for TABLE annotation
243
+
244
+ Example:
245
+ -- MIGRATE 1
246
+ CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
247
+
248
+ -- QUERY get_user :one
249
+ @set id = 1
250
+ SELECT * FROM users WHERE id = \${id};
251
+
252
+ -- TABLE users :appender
253
+ `.trim();
254
+ //#endregion
255
+ //#region src/errors.ts
256
+ /**
257
+ * Base error class for SQG with structured information
258
+ */
259
+ var SqgError = class SqgError extends Error {
260
+ constructor(message, code, suggestion, context) {
261
+ super(message);
262
+ this.code = code;
263
+ this.suggestion = suggestion;
264
+ this.context = context;
265
+ this.name = "SqgError";
266
+ }
267
+ /**
268
+ * Create error with file context
269
+ */
270
+ static inFile(message, code, file, options) {
271
+ return new SqgError(`${message} in ${options?.line ? `${file}:${options.line}` : file}`, code, options?.suggestion, {
272
+ file,
273
+ line: options?.line,
274
+ ...options?.context
275
+ });
276
+ }
277
+ /**
278
+ * Create error with query context
279
+ */
280
+ static inQuery(message, code, queryName, file, options) {
281
+ return new SqgError(`${message} in query '${queryName}' (${file})`, code, options?.suggestion, {
282
+ file,
283
+ query: queryName,
284
+ sql: options?.sql,
285
+ ...options?.context
286
+ });
287
+ }
288
+ toJSON() {
289
+ return {
290
+ name: this.name,
291
+ code: this.code,
292
+ message: this.message,
293
+ suggestion: this.suggestion,
294
+ context: this.context
295
+ };
296
+ }
297
+ };
298
+ /**
299
+ * Error for configuration issues
300
+ */
301
+ var ConfigError = class extends SqgError {
302
+ constructor(message, suggestion, context) {
303
+ super(message, "CONFIG_VALIDATION_ERROR", suggestion, context);
304
+ this.name = "ConfigError";
305
+ }
306
+ };
307
+ /**
308
+ * Error for invalid generator names
309
+ */
310
+ var InvalidGeneratorError = class extends SqgError {
311
+ constructor(generatorName, validGenerators, suggestion) {
312
+ const similarMsg = suggestion ? ` Did you mean '${suggestion}'?` : "";
313
+ super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators.join(", ")}`, { generator: generatorName });
314
+ this.name = "InvalidGeneratorError";
315
+ }
316
+ };
317
+ /**
318
+ * Error for database initialization/connection issues
319
+ */
320
+ var DatabaseError = class extends SqgError {
321
+ constructor(message, engine, suggestion, context) {
322
+ super(message, "DATABASE_ERROR", suggestion, {
323
+ ...context,
324
+ engine
325
+ });
326
+ this.name = "DatabaseError";
327
+ }
328
+ };
329
+ /**
330
+ * Error for SQL execution issues
331
+ */
332
+ var SqlExecutionError = class extends SqgError {
333
+ constructor(message, queryName, file, sql, originalError) {
334
+ super(`Failed to execute query '${queryName}' in ${file}: ${message}`, "SQL_EXECUTION_ERROR", void 0, {
335
+ query: queryName,
336
+ file,
337
+ sql,
338
+ originalError: originalError?.message
339
+ });
340
+ this.name = "SqlExecutionError";
341
+ }
342
+ };
343
+ /**
344
+ * Error for type mapping issues
345
+ */
346
+ var TypeMappingError = class extends SqgError {
347
+ constructor(message, columnName, queryName, file) {
348
+ const location = queryName && file ? ` in query '${queryName}' (${file})` : "";
349
+ super(`Type mapping error for column '${columnName}'${location}: ${message}`, "TYPE_MAPPING_ERROR", void 0, {
350
+ columnName,
351
+ query: queryName,
352
+ file
353
+ });
354
+ this.name = "TypeMappingError";
355
+ }
356
+ };
357
+ /**
358
+ * Error for file not found
359
+ */
360
+ var FileNotFoundError = class extends SqgError {
361
+ constructor(filePath, searchedFrom) {
362
+ const suggestion = searchedFrom ? `Check that the path is relative to ${searchedFrom}` : "Check that the file path is correct";
363
+ super(`File not found: ${filePath}`, "FILE_NOT_FOUND", suggestion, { file: filePath });
364
+ this.name = "FileNotFoundError";
365
+ }
366
+ };
367
+ /**
368
+ * Format any error for JSON output
369
+ */
370
+ function formatErrorForOutput(err) {
371
+ if (err instanceof SqgError) return {
372
+ status: "error",
373
+ error: {
374
+ code: err.code,
375
+ message: err.message,
376
+ suggestion: err.suggestion,
377
+ context: err.context
378
+ }
379
+ };
380
+ if (err instanceof Error) return {
381
+ status: "error",
382
+ error: {
383
+ code: "UNKNOWN_ERROR",
384
+ message: err.message
385
+ }
386
+ };
387
+ return {
388
+ status: "error",
389
+ error: {
390
+ code: "UNKNOWN_ERROR",
391
+ message: String(err)
392
+ }
393
+ };
394
+ }
395
+ //#endregion
396
+ //#region src/parser/sql-parser.ts
397
+ const parser = LRParser.deserialize({
398
+ version: 14,
399
+ 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",
400
+ 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_[~",
401
+ goto: "#prPPsPPPwP!R!VPPP!YPPP!]!a!g!q#T#ZPPP#a#ePP#ePP#iTTOUQcVSn`aRrmTgYhRusR]STj[kQUOR^UQ`VQdWTl`dQYRQaVWeYamvQm`RvuQhYRphQk[RqkTSOUTROUQ[STj[k",
402
+ nodeNames: "⚠ File QueryBlock BlockCommentStartSpecial Name Modifiers Config LineCommentStartSpecial SetVarLine Value StringLiteral StringLiteralSingle SQLText SQLBlock BlockComment LineComment VarRef BR",
403
+ maxTerm: 33,
404
+ skippedNodes: [0],
405
+ repeatNodeCount: 5,
406
+ 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",
407
+ tokenizers: [
408
+ 0,
409
+ 1,
410
+ 2
411
+ ],
412
+ topRules: { "File": [0, 1] },
413
+ tokenPrec: 245
414
+ });
415
+ //#endregion
416
+ //#region src/sql-query.ts
417
+ var ListType = class {
418
+ constructor(baseType) {
419
+ this.baseType = baseType;
420
+ }
421
+ toString() {
422
+ return `${this.baseType.toString()}[]`;
423
+ }
424
+ };
425
+ var StructType = class {
426
+ constructor(fields) {
427
+ this.fields = fields;
428
+ }
429
+ toString() {
430
+ return `STRUCT(${this.fields.map((f) => `"${f.name}" ${f.type.toString()}`).join(", ")})`;
431
+ }
432
+ };
433
+ var MapType = class {
434
+ constructor(keyType, valueType) {
435
+ this.keyType = keyType;
436
+ this.valueType = valueType;
437
+ }
438
+ toString() {
439
+ return `MAP(${this.keyType.toString()}, ${this.valueType.toString()})`;
440
+ }
441
+ };
442
+ var EnumType = class {
443
+ constructor(values, name) {
444
+ this.values = values;
445
+ this.name = name;
446
+ }
447
+ toString() {
448
+ return `ENUM(${this.values.map((v) => `'${v}'`).join(", ")})`;
449
+ }
450
+ };
451
+ var SQLQuery = class {
452
+ columns;
453
+ allColumns;
454
+ /** Database-reported parameter types (variable name → SQL type), set by database adapters */
455
+ parameterTypes;
456
+ constructor(filename, id, rawQuery, queryAnonymous, queryNamed, queryPositional, type, isOne, isPluck, variables, config) {
457
+ this.filename = filename;
458
+ this.id = id;
459
+ this.rawQuery = rawQuery;
460
+ this.queryAnonymous = queryAnonymous;
461
+ this.queryNamed = queryNamed;
462
+ this.queryPositional = queryPositional;
463
+ this.type = type;
464
+ this.isOne = isOne;
465
+ this.isPluck = isPluck;
466
+ this.variables = variables;
467
+ this.config = config;
468
+ this.columns = [];
469
+ }
470
+ get isQuery() {
471
+ return this.type === "QUERY";
472
+ }
473
+ get isExec() {
474
+ return this.type === "EXEC";
475
+ }
476
+ get isMigrate() {
477
+ return this.type === "MIGRATE";
478
+ }
479
+ get isTestdata() {
480
+ return this.type === "TESTDATA";
481
+ }
482
+ get skipGenerateFunction() {
483
+ return this.isTestdata || this.isMigrate || this.id.startsWith("_");
484
+ }
485
+ validateVariables() {
486
+ const missingVars = [];
487
+ const varRegex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
488
+ let match;
489
+ while (true) {
490
+ match = varRegex.exec(this.rawQuery);
491
+ if (match === null) break;
492
+ const varName = match[1];
493
+ if (!this.variables.has(varName)) missingVars.push(varName);
494
+ }
495
+ return missingVars;
496
+ }
497
+ };
498
+ /**
499
+ * Represents a TABLE annotation for generating appenders.
500
+ * TABLE annotations specify a table name for which to generate bulk insert appenders.
501
+ */
502
+ var TableInfo = class {
503
+ /** Columns introspected from the database table schema */
504
+ columns = [];
505
+ constructor(filename, id, tableName, includeColumns, hasAppender) {
506
+ this.filename = filename;
507
+ this.id = id;
508
+ this.tableName = tableName;
509
+ this.includeColumns = includeColumns;
510
+ this.hasAppender = hasAppender;
511
+ }
512
+ get skipGenerateFunction() {
513
+ return !this.hasAppender;
514
+ }
515
+ };
516
+ function parseSQLQueries(filePath, extraVariables) {
517
+ const content = readFileSync(filePath, "utf-8");
518
+ consola.debug(`Parsing SQL file: ${filePath}`);
519
+ consola.debug(`File start: ${content.slice(0, 200)}`);
520
+ const queries = [];
521
+ const tables = [];
522
+ const cursor = parser.parse(content).cursor();
523
+ function getLineNumber(position) {
524
+ return content.slice(0, position).split("\n").length;
525
+ }
526
+ function getStr(nodeName, optional = false) {
527
+ const node = cursor.node.getChild(nodeName);
528
+ if (!node) {
529
+ if (optional) return;
530
+ const lineNumber = getLineNumber(cursor.node.from);
531
+ throw new Error(`Node '${nodeName}' not found at line ${lineNumber}`);
532
+ }
533
+ return nodeStr(node);
534
+ }
535
+ function nodeStr(node) {
536
+ return content.slice(node.from, node.to);
537
+ }
538
+ const queryNames = /* @__PURE__ */ new Set();
539
+ do
540
+ if (cursor.name === "QueryBlock") {
541
+ const queryType = (getStr("LineCommentStartSpecial", true) ?? getStr("BlockCommentStartSpecial")).replace("--", "").replace("/*", "").trim();
542
+ const name = getStr("Name").trim();
543
+ const modifiers = cursor.node.getChildren("Modifiers").map((node) => nodeStr(node));
544
+ const isOne = modifiers.includes(":one");
545
+ const isPluck = modifiers.includes(":pluck");
546
+ let configStr = getStr("Config", true);
547
+ if (configStr?.endsWith("*/")) configStr = configStr.slice(0, -2);
548
+ let config = null;
549
+ if (configStr) config = Config.fromYaml(name, filePath, configStr);
550
+ const setVars = cursor.node.getChildren("SetVarLine");
551
+ const variables = /* @__PURE__ */ new Map();
552
+ for (const setVar of setVars) {
553
+ const varName = nodeStr(setVar.getChild("Name"));
554
+ const value = nodeStr(setVar.getChild("Value"));
555
+ variables.set(varName, value.trim());
556
+ }
557
+ function getVariable(varName) {
558
+ if (variables.has(varName)) return variables.get(varName);
559
+ for (const extraVariable of extraVariables) if (extraVariable.name === varName) {
560
+ variables.set(varName, extraVariable.value);
561
+ return extraVariable.value;
562
+ }
563
+ const definedVars = Array.from(variables.keys());
564
+ const suggestion = definedVars.length > 0 ? `Add '@set ${varName} = <value>' before the query. Defined variables: ${definedVars.join(", ")}` : `Add '@set ${varName} = <value>' before the query`;
565
+ throw SqgError.inQuery(`Variable '\${${varName}}' is referenced but not defined`, "MISSING_VARIABLE", name, filePath, { suggestion });
566
+ }
567
+ const sqlNode = cursor.node.getChild("SQLBlock");
568
+ 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" });
569
+ const sqlContentStr = nodeStr(sqlNode).trim();
570
+ const sqlCursor = sqlNode.cursor();
571
+ let from = -1;
572
+ let to = -1;
573
+ class SQLQueryBuilder {
574
+ sqlParts = [];
575
+ appendSql(sql) {
576
+ this.sqlParts.push(sql);
577
+ }
578
+ appendVariable(varName, value) {
579
+ this.sqlParts.push({
580
+ name: varName,
581
+ value
582
+ });
583
+ }
584
+ trim() {
585
+ const lastPart = this.sqlParts.length > 0 ? this.sqlParts[this.sqlParts.length - 1] : null;
586
+ if (lastPart && typeof lastPart === "string") this.sqlParts[this.sqlParts.length - 1] = lastPart.trimEnd();
587
+ }
588
+ parameters() {
589
+ return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
590
+ }
591
+ toSqlWithAnonymousPlaceholders() {
592
+ let sql = "";
593
+ const sqlParts = [];
594
+ for (const part of this.sqlParts) if (typeof part === "string") {
595
+ sql += part;
596
+ sqlParts.push(part);
597
+ } else {
598
+ if (sql.length > 0) {
599
+ const last = sql[sql.length - 1];
600
+ if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql += " ";
601
+ }
602
+ sql += "?";
603
+ if (part.name.startsWith("sources_")) sqlParts.push(part);
604
+ else sqlParts.push("?");
605
+ }
606
+ return {
607
+ parameters: this.parameters(),
608
+ sql,
609
+ sqlParts
610
+ };
611
+ }
612
+ toSqlWithPositionalPlaceholders() {
613
+ const parameters = [];
614
+ const sqlParts = [];
615
+ for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
616
+ else {
617
+ const varName = part.name;
618
+ const value = part.value;
619
+ if (varName.startsWith("sources_")) sqlParts.push(part);
620
+ else {
621
+ let pos = parameters.findIndex((p) => p.name === varName);
622
+ if (pos < 0) {
623
+ parameters.push({
624
+ name: varName,
625
+ value
626
+ });
627
+ pos = parameters.length;
628
+ } else pos = pos + 1;
629
+ sqlParts.push(`$${pos}`);
630
+ }
631
+ }
632
+ return {
633
+ parameters,
634
+ sqlParts,
635
+ sql: sqlParts.map((part) => typeof part === "string" ? part : ` ${part.value} `).join("").trim()
636
+ };
637
+ }
638
+ toSqlWithNamedPlaceholders() {
639
+ const sqlParts = [];
640
+ for (const part of this.sqlParts) if (typeof part === "string") sqlParts.push(part);
641
+ else if (part.name.startsWith("sources_")) sqlParts.push(part);
642
+ else sqlParts.push(`$${part.name}`);
643
+ return {
644
+ parameters: this.parameters(),
645
+ sqlParts,
646
+ sql: sqlParts.map((part) => typeof part === "string" ? part : `$${part.name}`).join("").trim()
647
+ };
648
+ }
649
+ }
650
+ const sql = new SQLQueryBuilder();
651
+ if (sqlCursor.firstChild()) {
652
+ do {
653
+ const child = sqlCursor.node;
654
+ if (child.name === "BlockComment" || child.name === "LineComment") {
655
+ if (to > from) sql.appendSql(content.slice(from, to));
656
+ from = child.to;
657
+ sql.appendSql(" ");
658
+ continue;
659
+ }
660
+ if (child.name === "VarRef") {
661
+ const varRef = nodeStr(child);
662
+ if (!varRef.startsWith("${") || !varRef.endsWith("}")) throw SqgError.inQuery(`Invalid variable reference: ${varRef}`, "SQL_PARSE_ERROR", name, filePath, { suggestion: "Variables should be in the format ${varName}" });
663
+ const varName = varRef.replace("${", "").replace("}", "");
664
+ const value = getVariable(varName);
665
+ if (to > from) sql.appendSql(content.slice(from, to));
666
+ from = child.to;
667
+ sql.appendVariable(varName, value);
668
+ } else {
669
+ if (from < 0) from = child.from;
670
+ to = child.to;
671
+ }
672
+ } while (sqlCursor.nextSibling());
673
+ if (to > from) sql.appendSql(content.slice(from, to));
674
+ sql.trim();
675
+ }
676
+ if (queryType === "TABLE") {
677
+ const hasAppender = modifiers.includes(":appender");
678
+ const tableName = sqlContentStr.split("\n").map((l) => l.trim()).find((l) => l.length > 0) || name;
679
+ const includeColumns = [];
680
+ for (const mod of modifiers) {
681
+ const match = mod.match(/:appender\(([^)]+)\)/);
682
+ if (match) includeColumns.push(...match[1].split(",").map((c) => c.trim()));
683
+ }
684
+ const table = new TableInfo(filePath, name, tableName, includeColumns, hasAppender);
685
+ 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" });
686
+ queryNames.add(name);
687
+ tables.push(table);
688
+ consola.debug(`Added table: ${name} -> ${tableName} (appender: ${hasAppender})`);
689
+ continue;
690
+ }
691
+ consola.debug("Parsed query:", {
692
+ type: queryType,
693
+ name,
694
+ modifiers,
695
+ variables: Object.fromEntries(variables),
696
+ sqlContent: sqlContentStr,
697
+ sql,
698
+ config
699
+ });
700
+ const query = new SQLQuery(filePath, name, sqlContentStr, sql.toSqlWithAnonymousPlaceholders(), sql.toSqlWithNamedPlaceholders(), sql.toSqlWithPositionalPlaceholders(), queryType, isOne, isPluck, variables, config);
701
+ 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" });
702
+ queryNames.add(name);
703
+ queries.push(query);
704
+ consola.debug(`Added query: ${name} (${queryType})`);
705
+ }
706
+ while (cursor.next());
707
+ consola.debug(`Total queries parsed: ${queries.length}, tables: ${tables.length}`);
708
+ consola.debug(`Query names: ${queries.map((q) => q.id).join(", ")}`);
709
+ if (tables.length > 0) consola.debug(`Table names: ${tables.map((t) => t.id).join(", ")}`);
710
+ return {
711
+ queries,
712
+ tables
713
+ };
714
+ }
715
+ //#endregion
716
+ //#region src/db/types.ts
717
+ async function initializeDatabase(queries, execQueries, reporter) {
718
+ const migrationQueries = queries.filter((q) => q.isMigrate);
719
+ sortBy(migrationQueries, [(q) => Number(q.id.split("_")[1])]);
720
+ for (const query of migrationQueries) try {
721
+ await execQueries(query);
722
+ } catch (error) {
723
+ consola.error("Failed to initialize database:" + error.message + " when running query:\n\n " + query.rawQuery);
724
+ throw error;
725
+ }
726
+ const testdataQueries = queries.filter((q) => q.isTestdata);
727
+ for (const query of testdataQueries) try {
728
+ await execQueries(query);
729
+ } catch (error) {
730
+ consola.error("Failed to initialize testdata:" + error.message + " when running query:\n\n " + query.rawQuery);
731
+ throw error;
732
+ }
733
+ if (migrationQueries.length + testdataQueries.length === 0) consola.warn("No migration or testdata queries found");
734
+ reporter?.onDatabaseInitialized?.();
735
+ }
736
+ //#endregion
737
+ //#region src/db/duckdb.ts
738
+ /** Cache of enum type names, keyed by stringified sorted values for lookup */
739
+ let enumNameCache = /* @__PURE__ */ new Map();
740
+ function enumCacheKey(values) {
741
+ return values.join("\0");
742
+ }
743
+ function convertType(type) {
744
+ if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
745
+ if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
746
+ name: type.entryNames[index],
747
+ type: convertType(t),
748
+ nullable: true
749
+ })));
750
+ if (type instanceof DuckDBMapType) return new MapType({
751
+ name: "key",
752
+ type: convertType(type.keyType),
753
+ nullable: true
754
+ }, {
755
+ name: "value",
756
+ type: convertType(type.valueType),
757
+ nullable: true
758
+ });
759
+ if (type instanceof DuckDBEnumType) {
760
+ const name = type.alias ?? enumNameCache.get(enumCacheKey(type.values));
761
+ return new EnumType(type.values, name);
762
+ }
763
+ return type.toString();
764
+ }
765
+ const duckdb = new class {
766
+ db;
767
+ connection;
768
+ async initializeDatabase(queries, reporter) {
769
+ this.db = await DuckDBInstance.create(":memory:");
770
+ this.connection = await this.db.connect();
771
+ await initializeDatabase(queries, async (query) => {
772
+ try {
773
+ await this.connection.run(query.rawQuery);
774
+ } catch (e) {
775
+ throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
776
+ }
777
+ }, reporter);
778
+ await this.loadEnumCache();
779
+ }
780
+ async loadEnumCache() {
781
+ enumNameCache = /* @__PURE__ */ new Map();
782
+ try {
783
+ const result = await this.connection.runAndReadAll("SELECT type_name, labels FROM duckdb_types() WHERE logical_type = 'ENUM' AND internal = false");
784
+ for (const row of result.getRows()) {
785
+ const typeName = row[0];
786
+ const labels = row[1];
787
+ if (typeName && labels?.items) enumNameCache.set(enumCacheKey(labels.items), typeName);
788
+ }
789
+ consola.debug("DuckDB enum types:", Object.fromEntries(enumNameCache));
790
+ } catch (e) {
791
+ consola.debug("Failed to load DuckDB enum types:", e.message);
792
+ }
793
+ }
794
+ async executeQueries(queries, reporter) {
795
+ const connection = this.connection;
796
+ if (!connection) throw new DatabaseError("DuckDB connection not initialized", "duckdb", "This is an internal error. Check that migrations completed successfully.");
797
+ try {
798
+ const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
799
+ for (const query of executableQueries) {
800
+ reporter?.onQueryStart?.(query.id);
801
+ await this.executeQuery(connection, query);
802
+ reporter?.onQueryComplete?.(query.id);
803
+ }
804
+ } catch (error) {
805
+ consola.error("Error executing queries:", error.message);
806
+ throw error;
807
+ }
808
+ }
809
+ async executeQuery(connection, query) {
810
+ const statement = query.queryAnonymous;
811
+ try {
812
+ consola.debug("Query:", statement.sql, statement.sqlParts);
813
+ const sql = statement.sqlParts.map((part) => {
814
+ if (typeof part === "string") return part;
815
+ return ` ${part.value} `;
816
+ }).join("");
817
+ const stmt = await connection.prepare(sql);
818
+ if (stmt.parameterCount > 0) {
819
+ const paramTypes = /* @__PURE__ */ new Map();
820
+ for (let i = 0; i < stmt.parameterCount; i++) {
821
+ const paramType = stmt.parameterType(i + 1);
822
+ paramTypes.set(statement.parameters[i].name, convertType(paramType));
823
+ }
824
+ query.parameterTypes = paramTypes;
825
+ consola.debug("Parameter types:", Object.fromEntries(paramTypes));
826
+ }
827
+ for (let i = 0; i < stmt.parameterCount; i++) {
828
+ let value = statement.parameters[i].value;
829
+ if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
830
+ stmt.bindValue(i + 1, value);
831
+ }
832
+ if (query.isQuery) {
833
+ const result = await stmt.stream();
834
+ const columnNames = result.columnNames();
835
+ const columnTypes = result.columnTypes();
836
+ consola.debug("Columns:", columnNames);
837
+ consola.debug("Types:", columnTypes.map((t) => `${t.toString()} / ${t.constructor.name}`));
838
+ query.columns = columnNames.map((name, index) => ({
839
+ name,
840
+ type: convertType(columnTypes[index]),
841
+ nullable: true
842
+ }));
843
+ }
844
+ if (query.isQuery) {
845
+ if (query.isOne) return await stmt.runAndRead();
846
+ return await stmt.runAndReadAll();
847
+ }
848
+ return await stmt.run();
849
+ } catch (error) {
850
+ consola.error(`Failed to execute query '${query.id}':`, error);
851
+ throw error;
852
+ }
853
+ }
854
+ async introspectTables(tables, reporter) {
855
+ const connection = this.connection;
856
+ if (!connection) throw new DatabaseError("DuckDB connection not initialized", "duckdb", "This is an internal error. Check that migrations completed successfully.");
857
+ for (const table of tables) {
858
+ reporter?.onTableStart?.(table.tableName);
859
+ try {
860
+ const descRows = (await connection.runAndReadAll(`DESCRIBE ${table.tableName}`)).getRows();
861
+ const nullabilityMap = /* @__PURE__ */ new Map();
862
+ for (const row of descRows) nullabilityMap.set(row[0], row[2] !== "NO");
863
+ const stream = await (await connection.prepare(`SELECT * FROM ${table.tableName} LIMIT 0`)).stream();
864
+ const columnNames = stream.columnNames();
865
+ const columnTypes = stream.columnTypes();
866
+ table.columns = columnNames.map((name, index) => ({
867
+ name,
868
+ type: convertType(columnTypes[index]),
869
+ nullable: nullabilityMap.get(name) ?? true
870
+ }));
871
+ consola.debug(`Table ${table.tableName} columns:`, table.columns);
872
+ reporter?.onTableComplete?.(table.tableName, table.columns.length);
873
+ } catch (error) {
874
+ consola.error(`Failed to introspect table '${table.tableName}':`, error);
875
+ throw error;
876
+ }
877
+ }
878
+ }
879
+ close() {
880
+ this.connection.closeSync();
881
+ enumNameCache = /* @__PURE__ */ new Map();
882
+ }
883
+ }();
884
+ //#endregion
885
+ //#region src/db/postgres.ts
886
+ const tempDatabaseName = "sqg-db-temp";
887
+ const typeIdToName = /* @__PURE__ */ new Map();
888
+ for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
889
+ /**
890
+ * External database mode: connects directly to the user's database.
891
+ * Uses a single transaction for the entire session and rolls back on close,
892
+ * so the database is never modified. Individual queries use savepoints.
893
+ */
894
+ var ExternalDbMode = class {
895
+ constructor(connectionString) {
896
+ this.connectionString = connectionString;
897
+ }
898
+ async connect() {
899
+ const db = new Client({ connectionString: this.connectionString });
900
+ try {
901
+ await db.connect();
902
+ } catch (e) {
903
+ throw new DatabaseError(`Failed to connect to PostgreSQL: ${e.message}`, "postgres", `Check that PostgreSQL is running and accessible at ${this.connectionString}.`);
904
+ }
905
+ await db.query("BEGIN");
906
+ return db;
907
+ }
908
+ async wrapQuery(db, fn) {
909
+ try {
910
+ await db.query("SAVEPOINT sqg_query");
911
+ return await fn();
912
+ } finally {
913
+ await db.query("ROLLBACK TO SAVEPOINT sqg_query");
914
+ }
915
+ }
916
+ async close(db) {
917
+ await db.query("ROLLBACK");
918
+ await db.end();
919
+ }
920
+ };
921
+ /**
922
+ * Temp database mode: creates a temporary database for SQG to work in.
923
+ * Connects to the provided server first (dbInitial) to CREATE the temp DB,
924
+ * then connects to the temp DB for all operations.
925
+ * On close, drops the temp DB and optionally stops the testcontainer.
926
+ */
927
+ var TempDbMode = class {
928
+ dbInitial;
929
+ container = null;
930
+ constructor(connectionString, container) {
931
+ this.connectionString = connectionString;
932
+ this.container = container;
933
+ }
934
+ async connect() {
935
+ this.dbInitial = new Client({ connectionString: this.connectionString });
936
+ try {
937
+ await this.dbInitial.connect();
938
+ } catch (e) {
939
+ throw new DatabaseError(`Failed to connect to PostgreSQL: ${e.message}`, "postgres", `Check that PostgreSQL is running and accessible at ${this.connectionString}. Set SQG_POSTGRES_URL environment variable to use a different connection string.`);
940
+ }
941
+ try {
942
+ await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}";`);
943
+ } catch (error) {}
944
+ try {
945
+ await this.dbInitial.query(`CREATE DATABASE "${tempDatabaseName}";`);
946
+ } catch (error) {
947
+ throw new DatabaseError(`Failed to create temporary database: ${error.message}`, "postgres", "Check PostgreSQL user permissions to create databases");
948
+ }
949
+ const db = new Client({ connectionString: this.connectionString.replace(/\/[^/]+$/, `/${tempDatabaseName}`) });
950
+ try {
951
+ await db.connect();
952
+ } catch (e) {
953
+ throw new DatabaseError(`Failed to connect to temporary database: ${e.message}`, "postgres");
954
+ }
955
+ return db;
956
+ }
957
+ async wrapQuery(db, fn) {
958
+ try {
959
+ await db.query("BEGIN");
960
+ return await fn();
961
+ } finally {
962
+ await db.query("ROLLBACK");
963
+ }
964
+ }
965
+ async close(db) {
966
+ await db.end();
967
+ await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}"`);
968
+ await this.dbInitial.end();
969
+ if (this.container) {
970
+ await this.container.stop();
971
+ this.container = null;
972
+ }
973
+ }
974
+ };
975
+ const postgres = new class {
976
+ db;
977
+ mode;
978
+ dynamicTypeCache = /* @__PURE__ */ new Map();
979
+ enumTypeCache = /* @__PURE__ */ new Map();
980
+ async startContainer(reporter) {
981
+ reporter?.onContainerStarting?.();
982
+ const container = await new PostgreSqlContainer("postgres:16-alpine").withDatabase("sqg-db").withUsername("sqg").withPassword("secret").start();
983
+ const connectionUri = container.getConnectionUri();
984
+ reporter?.onContainerStarted?.(connectionUri);
985
+ return {
986
+ connectionUri,
987
+ container
988
+ };
989
+ }
990
+ async loadTypeCache(db) {
991
+ const result = await db.query(`
992
+ SELECT t.oid, t.typname, t.typtype, t.typelem, et.typname AS elemtype,
993
+ e.enumlabel
994
+ FROM pg_type t
995
+ LEFT JOIN pg_type et ON t.typelem = et.oid
996
+ LEFT JOIN pg_enum e ON t.oid = e.enumtypid AND t.typtype = 'e'
997
+ WHERE t.typtype IN ('b', 'e', 'r', 'c') -- base, enum, range, composite
998
+ OR t.typelem != 0 -- array types
999
+ ORDER BY t.oid, e.enumsortorder
1000
+ `);
1001
+ this.dynamicTypeCache = /* @__PURE__ */ new Map();
1002
+ this.enumTypeCache = /* @__PURE__ */ new Map();
1003
+ const enumsByOid = /* @__PURE__ */ new Map();
1004
+ for (const row of result.rows) {
1005
+ const oid = row.oid;
1006
+ let typeName = row.typname;
1007
+ if (typeName.startsWith("_") && row.elemtype) typeName = `_${row.elemtype.toUpperCase()}`;
1008
+ else typeName = typeName.toUpperCase();
1009
+ this.dynamicTypeCache.set(oid, typeName);
1010
+ if (row.enumlabel) {
1011
+ if (!enumsByOid.has(oid)) enumsByOid.set(oid, {
1012
+ name: row.typname,
1013
+ values: []
1014
+ });
1015
+ enumsByOid.get(oid).values.push(row.enumlabel);
1016
+ }
1017
+ }
1018
+ for (const [oid, { name, values }] of enumsByOid) this.enumTypeCache.set(oid, new EnumType(values, name));
1019
+ }
1020
+ getTypeName(dataTypeID) {
1021
+ const cached = this.dynamicTypeCache.get(dataTypeID);
1022
+ if (cached) return cached;
1023
+ return typeIdToName.get(dataTypeID) || `type_${dataTypeID}`;
1024
+ }
1025
+ getColumnType(dataTypeID) {
1026
+ const enumType = this.enumTypeCache.get(dataTypeID);
1027
+ if (enumType) return enumType;
1028
+ return this.getTypeName(dataTypeID);
1029
+ }
1030
+ async initializeDatabase(queries, reporter) {
1031
+ const externalUrl = process.env.SQG_POSTGRES_URL;
1032
+ this.dynamicTypeCache = /* @__PURE__ */ new Map();
1033
+ this.enumTypeCache = /* @__PURE__ */ new Map();
1034
+ if (externalUrl) this.mode = new ExternalDbMode(externalUrl);
1035
+ else {
1036
+ const { connectionUri, container } = await this.startContainer(reporter);
1037
+ this.mode = new TempDbMode(connectionUri, container);
1038
+ }
1039
+ this.db = await this.mode.connect();
1040
+ await initializeDatabase(queries, async (query) => {
1041
+ try {
1042
+ await this.db.query(query.rawQuery);
1043
+ } catch (e) {
1044
+ throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
1045
+ }
1046
+ }, reporter);
1047
+ await this.loadTypeCache(this.db);
1048
+ }
1049
+ async executeQueries(queries, reporter) {
1050
+ const db = this.db;
1051
+ if (!db) throw new DatabaseError("PostgreSQL database not initialized", "postgres", "This is an internal error. Check that migrations completed successfully.");
1052
+ try {
1053
+ const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
1054
+ for (const query of executableQueries) {
1055
+ reporter?.onQueryStart?.(query.id);
1056
+ await this.executeQuery(db, query);
1057
+ reporter?.onQueryComplete?.(query.id);
1058
+ }
1059
+ } catch (error) {
1060
+ consola.error("Error executing queries:", error.message);
1061
+ throw error;
1062
+ }
1063
+ }
1064
+ async executeQuery(db, query) {
1065
+ const statement = query.queryPositional;
1066
+ try {
1067
+ consola.debug("Query:", statement.sql);
1068
+ const parameterValues = statement.parameters.map((p) => {
1069
+ const value = p.value;
1070
+ if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
1071
+ return value;
1072
+ });
1073
+ if (statement.parameters.length > 0) try {
1074
+ await db.query("DEALLOCATE ALL");
1075
+ await db.query(`PREPARE sqg_param_check AS ${statement.sql}`);
1076
+ const paramTypeResult = await db.query(`SELECT unnest(parameter_types)::oid AS oid FROM pg_prepared_statements WHERE name = 'sqg_param_check'`);
1077
+ await db.query("DEALLOCATE sqg_param_check");
1078
+ if (paramTypeResult.rows.length === statement.parameters.length) {
1079
+ const paramTypes = /* @__PURE__ */ new Map();
1080
+ for (let i = 0; i < statement.parameters.length; i++) {
1081
+ const oid = Number(paramTypeResult.rows[i].oid);
1082
+ const colType = this.getColumnType(oid);
1083
+ paramTypes.set(statement.parameters[i].name, colType);
1084
+ }
1085
+ query.parameterTypes = paramTypes;
1086
+ consola.debug("Parameter types:", Object.fromEntries(paramTypes));
1087
+ }
1088
+ } catch (e) {
1089
+ consola.debug(`Parameter type introspection failed for ${query.id}, using heuristic:`, e.message);
1090
+ }
1091
+ const result = await this.mode.wrapQuery(db, () => db.query(statement.sql, parameterValues));
1092
+ if (query.isQuery) {
1093
+ const columnNames = result.fields.map((field) => field.name);
1094
+ const columnTypes = result.fields.map((field) => {
1095
+ return this.getColumnType(field.dataTypeID);
1096
+ });
1097
+ consola.debug("Columns:", columnNames);
1098
+ consola.debug("Types:", columnTypes.map((t) => t.toString()));
1099
+ query.columns = columnNames.map((name, index) => ({
1100
+ name,
1101
+ type: columnTypes[index],
1102
+ nullable: true
1103
+ }));
1104
+ }
1105
+ if (query.isQuery) {
1106
+ if (query.isOne) return result.rows[0] || null;
1107
+ return result.rows;
1108
+ }
1109
+ return result;
1110
+ } catch (error) {
1111
+ consola.error(`Failed to execute query '${query.id}':`, error);
1112
+ throw error;
1113
+ }
1114
+ }
1115
+ async introspectTables(tables, reporter) {
1116
+ const db = this.db;
1117
+ if (!db) throw new DatabaseError("PostgreSQL database not initialized", "postgres", "This is an internal error. Check that migrations completed successfully.");
1118
+ for (const table of tables) {
1119
+ reporter?.onTableStart?.(table.tableName);
1120
+ try {
1121
+ table.columns = (await db.query(`SELECT column_name, data_type, udt_name, is_nullable, column_default, is_identity
1122
+ FROM information_schema.columns
1123
+ WHERE table_name = $1
1124
+ ORDER BY ordinal_position`, [table.tableName])).rows.map((row) => ({
1125
+ name: row.column_name,
1126
+ type: row.udt_name.toUpperCase(),
1127
+ nullable: row.is_nullable === "YES",
1128
+ generated: row.is_identity === "YES" || (row.column_default?.startsWith("nextval") ?? false)
1129
+ }));
1130
+ reporter?.onTableComplete?.(table.tableName, table.columns.length);
1131
+ } catch (error) {
1132
+ consola.error(`Failed to introspect table '${table.tableName}':`, error);
1133
+ throw error;
1134
+ }
1135
+ }
1136
+ }
1137
+ async close() {
1138
+ await this.mode.close(this.db);
1139
+ this.dynamicTypeCache = /* @__PURE__ */ new Map();
1140
+ this.enumTypeCache = /* @__PURE__ */ new Map();
1141
+ }
1142
+ }();
1143
+ //#endregion
1144
+ //#region src/db/sqlite.ts
1145
+ const sqlite = new class {
1146
+ db;
1147
+ async initializeDatabase(queries, reporter) {
1148
+ const db = new BetterSqlite3(":memory:");
1149
+ await initializeDatabase(queries, (query) => {
1150
+ try {
1151
+ db.exec(query.rawQuery);
1152
+ } catch (e) {
1153
+ throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
1154
+ }
1155
+ return Promise.resolve();
1156
+ }, reporter);
1157
+ this.db = db;
1158
+ }
1159
+ executeQueries(queries, reporter) {
1160
+ const db = this.db;
1161
+ if (!db) throw new DatabaseError("SQLite database not initialized", "sqlite", "This is an internal error. Migrations may have failed silently.");
1162
+ try {
1163
+ const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
1164
+ for (const query of executableQueries) {
1165
+ reporter?.onQueryStart?.(query.id);
1166
+ this.executeQuery(db, query);
1167
+ reporter?.onQueryComplete?.(query.id);
1168
+ }
1169
+ } catch (error) {
1170
+ consola.error("Error executing queries:", error.message);
1171
+ throw error;
1172
+ }
1173
+ }
1174
+ introspectTables(tables, reporter) {
1175
+ const db = this.db;
1176
+ if (!db) throw new DatabaseError("SQLite database not initialized", "sqlite", "This is an internal error. Migrations may have failed silently.");
1177
+ for (const table of tables) {
1178
+ reporter?.onTableStart?.(table.tableName);
1179
+ const info = this.getTableInfo(db, table.tableName);
1180
+ table.columns = Array.from(info.values()).map((col) => ({
1181
+ name: col.name,
1182
+ type: col.type || "TEXT",
1183
+ nullable: col.notnull === 0 && col.pk === 0
1184
+ }));
1185
+ reporter?.onTableComplete?.(table.tableName, table.columns.length);
1186
+ }
1187
+ }
1188
+ close() {
1189
+ this.db.close();
1190
+ }
1191
+ getTableInfo(db, table) {
1192
+ const info = db.pragma(`table_info('${table}')`);
1193
+ return new Map(info.map((col) => [col.name, col]));
1194
+ }
1195
+ executeQuery(db, query) {
1196
+ const statement = query.queryAnonymous;
1197
+ try {
1198
+ consola.debug("Query:", statement.sql);
1199
+ const stmt = db.prepare(statement.sql);
1200
+ if (query.isQuery) {
1201
+ const info = stmt.columns();
1202
+ const tables = new Set(info.map((col) => col.table).filter(isNotNil));
1203
+ const data = new Map(Array.from(tables).map((table) => [table, this.getTableInfo(db, table)]));
1204
+ query.columns = info.map((col) => {
1205
+ const colInfo = col.table ? data.get(col.table)?.get(col.name) : null;
1206
+ return {
1207
+ name: col.name,
1208
+ type: col.type || "unknown",
1209
+ nullable: colInfo?.pk === 0 && colInfo?.notnull === 0
1210
+ };
1211
+ });
1212
+ }
1213
+ if (query.isQuery) {
1214
+ if (query.isOne) return stmt.get(...statement.parameters.map((p) => p.value));
1215
+ return stmt.all(...statement.parameters.map((p) => p.value));
1216
+ }
1217
+ return stmt.run(...statement.parameters.map((p) => p.value));
1218
+ } catch (error) {
1219
+ consola.error(`Failed to execute query '${query.id}' in ${query.filename}:\n ${statement.sql} \n ${statement.parameters.map((p) => p.value).join(", ")}`, error);
1220
+ throw error;
1221
+ }
1222
+ }
1223
+ }();
1224
+ //#endregion
1225
+ //#region src/db/index.ts
1226
+ function getDatabaseEngine(engine) {
1227
+ switch (engine) {
1228
+ case "sqlite": return sqlite;
1229
+ case "duckdb": return duckdb;
1230
+ case "postgres": return postgres;
1231
+ default: throw new Error(`Unsupported database engine: ${engine}`);
1232
+ }
1233
+ }
1234
+ //#endregion
1235
+ //#region src/type-mapping.ts
1236
+ /**
1237
+ * Abstract base class for mapping SQL column types to target language types.
1238
+ * Subclasses implement language-specific type mappings (e.g., Java, TypeScript).
1239
+ */
1240
+ var TypeMapper = class {
1241
+ /**
1242
+ * Returns the target language type name for a given SQL column.
1243
+ * Handles complex types (lists, structs, maps) by recursively resolving nested types.
1244
+ * @param column - The column information including name, type, and nullability
1245
+ * @param path - Optional prefix path for nested type references (e.g., "OuterStruct.")
1246
+ * @returns The fully qualified type name in the target language
1247
+ */
1248
+ getTypeName(column, path = "") {
1249
+ if (column.type instanceof ListType) {
1250
+ const elementType = this.getTypeName({
1251
+ name: column.name,
1252
+ type: column.type.baseType,
1253
+ nullable: true
1254
+ });
1255
+ return path + this.formatListType(elementType);
1256
+ }
1257
+ if (column.type instanceof StructType) return path + this.formatStructTypeName(column.name);
1258
+ if (column.type instanceof MapType) return path + this.formatMapTypeName(column.name);
1259
+ if (!column.type) throw new TypeMappingError("Missing type information", column.name);
1260
+ return this.mapPrimitiveType(column.type.toString(), column.nullable);
1261
+ }
1262
+ /**
1263
+ * Wraps a column's type in the target language's list/array type.
1264
+ * @param column - The column whose type should be wrapped in a list
1265
+ * @returns The list type representation (e.g., "List<String>" for Java)
1266
+ */
1267
+ listType(column) {
1268
+ const baseType = this.getTypeName(column);
1269
+ return this.formatListType(baseType);
1270
+ }
1271
+ /**
1272
+ * Generates type declarations for complex types (structs, nested lists).
1273
+ * For structs, generates a full type/interface declaration.
1274
+ * For lists, recursively processes the element type.
1275
+ * @param column - The column containing a complex type
1276
+ * @param path - Optional prefix path for nested type references
1277
+ * @returns Generated type declaration code, or empty string for primitive types
1278
+ */
1279
+ getDeclarations(column, path = "") {
1280
+ if (column.type instanceof StructType) return this.generateStructDeclaration(column, path);
1281
+ if (column.type instanceof ListType) return this.getDeclarations({
1282
+ name: column.name,
1283
+ type: column.type.baseType,
1284
+ nullable: true
1285
+ }, path);
1286
+ return "";
1287
+ }
1288
+ };
1289
+ /**
1290
+ * Type mapper for generating Java types from SQL column types.
1291
+ * Maps SQL types to Java types (e.g., INTEGER -> Integer, VARCHAR -> String).
1292
+ * Generates Java records for struct types and handles Java reserved keywords.
1293
+ */
1294
+ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
1295
+ typeMap = {
1296
+ INTEGER: "Integer",
1297
+ REAL: "Double",
1298
+ TEXT: "String",
1299
+ BLOB: "byte[]",
1300
+ BOOLEAN: "Boolean",
1301
+ DATE: "LocalDate",
1302
+ DATETIME: "LocalDateTime",
1303
+ TIMESTAMP: "LocalDateTime",
1304
+ NULL: "null",
1305
+ UNKNOWN: "Object",
1306
+ DOUBLE: "Double",
1307
+ FLOAT: "Float",
1308
+ VARCHAR: "String",
1309
+ TINYINT: "Byte",
1310
+ SMALLINT: "Short",
1311
+ BIGINT: "Long",
1312
+ HUGEINT: "BigInteger",
1313
+ UHUGEINT: "BigInteger",
1314
+ UTINYINT: "Short",
1315
+ USMALLINT: "Integer",
1316
+ UINTEGER: "Long",
1317
+ UBIGINT: "BigInteger",
1318
+ TIME: "LocalTime",
1319
+ "TIME WITH TIME ZONE": "OffsetTime",
1320
+ TIMESTAMP_S: "Instant",
1321
+ TIMESTAMP_MS: "Instant",
1322
+ TIMESTAMP_NS: "Instant",
1323
+ "TIMESTAMP WITH TIME ZONE": "OffsetDateTime",
1324
+ UUID: "UUID",
1325
+ INTERVAL: "String",
1326
+ BIT: "String",
1327
+ BIGNUM: "BigDecimal",
1328
+ INT2: "Short",
1329
+ INT4: "Integer",
1330
+ INT8: "Long",
1331
+ FLOAT4: "Float",
1332
+ FLOAT8: "Double",
1333
+ NUMERIC: "BigDecimal",
1334
+ BOOL: "Boolean",
1335
+ BYTEA: "byte[]",
1336
+ TIMESTAMPTZ: "OffsetDateTime",
1337
+ JSON: "String",
1338
+ JSONB: "String",
1339
+ OID: "Long",
1340
+ SERIAL: "Integer",
1341
+ BIGSERIAL: "Long"
1342
+ };
1343
+ static javaReservedKeywords = new Set([
1344
+ "abstract",
1345
+ "assert",
1346
+ "boolean",
1347
+ "break",
1348
+ "byte",
1349
+ "case",
1350
+ "catch",
1351
+ "char",
1352
+ "class",
1353
+ "const",
1354
+ "continue",
1355
+ "default",
1356
+ "do",
1357
+ "double",
1358
+ "else",
1359
+ "enum",
1360
+ "extends",
1361
+ "final",
1362
+ "finally",
1363
+ "float",
1364
+ "for",
1365
+ "goto",
1366
+ "if",
1367
+ "implements",
1368
+ "import",
1369
+ "instanceof",
1370
+ "int",
1371
+ "interface",
1372
+ "long",
1373
+ "native",
1374
+ "new",
1375
+ "package",
1376
+ "private",
1377
+ "protected",
1378
+ "public",
1379
+ "return",
1380
+ "short",
1381
+ "static",
1382
+ "strictfp",
1383
+ "super",
1384
+ "switch",
1385
+ "synchronized",
1386
+ "this",
1387
+ "throw",
1388
+ "throws",
1389
+ "transient",
1390
+ "try",
1391
+ "void",
1392
+ "volatile",
1393
+ "while",
1394
+ "true",
1395
+ "false",
1396
+ "null"
1397
+ ]);
1398
+ getTypeName(column, path = "") {
1399
+ if (column.type instanceof EnumType && column.type.name) return path + pascalCase(column.type.name);
1400
+ return super.getTypeName(column, path);
1401
+ }
1402
+ mapPrimitiveType(type, _nullable) {
1403
+ const upperType = type.toString().toUpperCase();
1404
+ const mappedType = this.typeMap[upperType];
1405
+ if (mappedType) return mappedType;
1406
+ if (upperType.startsWith("_")) {
1407
+ const baseType = upperType.substring(1);
1408
+ return `List<${this.typeMap[baseType] || "Object"}>`;
1409
+ }
1410
+ if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) return "BigDecimal";
1411
+ if (upperType.startsWith("ENUM(")) return "String";
1412
+ if (upperType.startsWith("UNION(")) return "Object";
1413
+ if (/\[\d+\]/.test(upperType)) return "Object";
1414
+ return "String";
1415
+ }
1416
+ formatListType(elementType) {
1417
+ return `List<${elementType}>`;
1418
+ }
1419
+ formatStructTypeName(fieldName) {
1420
+ return `${pascalCase(fieldName)}Result`;
1421
+ }
1422
+ generateStructDeclaration(column, path = "") {
1423
+ if (!(column.type instanceof StructType)) throw new Error(`Expected StructType ${column}`);
1424
+ const structName = this.formatStructTypeName(column.name);
1425
+ const newPath = `${path}${structName}.`;
1426
+ const children = column.type.fields.map((field) => {
1427
+ return this.getDeclarations({
1428
+ name: field.name,
1429
+ type: field.type,
1430
+ nullable: true
1431
+ }, newPath);
1432
+ }).join("\n");
1433
+ const fields = column.type.fields.map((field) => {
1434
+ return `${this.getTypeName(field)} ${this.varName(field.name)}`;
1435
+ }).join(", ");
1436
+ const fromAttributes = ` private static ${structName} fromAttributes(Object[] v) {
1437
+ return new ${structName}(${column.type.fields.map((f, i) => `${this.parseValue(f, `v[${i}]`, "")}`).join(",\n")});
1438
+ }`;
1439
+ return `public record ${structName}(${fields}) {
1440
+ ${path.length > 0 ? fromAttributes : ""}
1441
+ ${children}
1442
+ }`;
1443
+ }
1444
+ formatMapTypeName(fieldName) {
1445
+ return "HashMap";
1446
+ }
1447
+ varName(str) {
1448
+ const name = camelCase(str);
1449
+ if (JavaTypeMapper.javaReservedKeywords.has(name)) return `${name}_`;
1450
+ return name;
1451
+ }
1452
+ parseValue(column, value, path) {
1453
+ if (column.type instanceof EnumType && column.type.name) return `${pascalCase(column.type.name)}.fromValue((String)${value})`;
1454
+ if (column.type instanceof ListType) {
1455
+ const elementType = this.getTypeName({
1456
+ name: column.name,
1457
+ type: column.type.baseType,
1458
+ nullable: true
1459
+ }, path);
1460
+ if (column.type.baseType instanceof StructType) return `arrayOfStructToList((Array)${value}, ${elementType}::fromAttributes)`;
1461
+ if (column.type.baseType instanceof ListType) return `multiDimArrayToList((Array)${value}, ${this.getInnermostType(column.type)}[].class)`;
1462
+ return `arrayToList((Array)${value}, ${elementType}[].class)`;
1463
+ }
1464
+ if (column.type instanceof StructType) return `${path}${this.formatStructTypeName(column.name)}.fromAttributes(getAttr((Struct)${value}))`;
1465
+ const fieldType = this.getTypeName(column);
1466
+ const upperType = column.type?.toString().toUpperCase() ?? "";
1467
+ if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime(${value})`;
1468
+ if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime(${value})`;
1469
+ if (upperType === "TIMESTAMP WITH TIME ZONE") return `(OffsetDateTime)${value}`;
1470
+ if (upperType === "DATE") return `toLocalDate(${value})`;
1471
+ if (upperType === "TIME") return `toLocalTime(${value})`;
1472
+ if (upperType.startsWith("_")) {
1473
+ const baseType = upperType.substring(1);
1474
+ return `arrayToList((Array)${value}, ${this.typeMap[baseType] || "Object"}[].class)`;
1475
+ }
1476
+ if (fieldType === "Short") return `${value} != null ? ((Number)${value}).shortValue() : null`;
1477
+ if (fieldType === "Byte") return `${value} != null ? ((Number)${value}).byteValue() : null`;
1478
+ if (upperType === "JSON" || upperType === "JSONB") return `${value} != null ? ${value}.toString() : null`;
1479
+ return `(${fieldType})${value}`;
1480
+ }
1481
+ getInnermostType(type) {
1482
+ let current = type.baseType;
1483
+ while (current instanceof ListType) current = current.baseType;
1484
+ return this.getTypeName({
1485
+ name: "",
1486
+ type: current,
1487
+ nullable: true
1488
+ });
1489
+ }
1490
+ };
1491
+ /**
1492
+ * Type mapper for generating TypeScript types from SQL column types.
1493
+ * Maps SQL types to TypeScript types (e.g., INTEGER -> number, VARCHAR -> string).
1494
+ * Generates TypeScript interfaces for struct types and handles DuckDB's complex types.
1495
+ */
1496
+ var TypeScriptTypeMapper = class extends TypeMapper {
1497
+ /**
1498
+ * Returns the TypeScript type name for a given SQL column.
1499
+ * Overrides base to handle DuckDB's map type with key-value entry arrays.
1500
+ */
1501
+ getTypeName(column, path = "") {
1502
+ if (column.type instanceof MapType) return `{ entries: { key: ${this.getTypeName({
1503
+ name: "key",
1504
+ type: column.type.keyType.type,
1505
+ nullable: true
1506
+ })}; value: ${this.getTypeName({
1507
+ name: "value",
1508
+ type: column.type.valueType.type,
1509
+ nullable: true
1510
+ })} }[] }`;
1511
+ return super.getTypeName(column, path);
1512
+ }
1513
+ typeMap = {
1514
+ INTEGER: "number",
1515
+ REAL: "number",
1516
+ TEXT: "string",
1517
+ BLOB: "{ bytes: Uint8Array }",
1518
+ BOOLEAN: "boolean",
1519
+ DATE: "{ days: number }",
1520
+ DATETIME: "{ micros: bigint }",
1521
+ TIMESTAMP: "{ micros: bigint }",
1522
+ NULL: "null",
1523
+ UNKNOWN: "unknown",
1524
+ DOUBLE: "number",
1525
+ FLOAT: "number",
1526
+ VARCHAR: "string",
1527
+ TINYINT: "number",
1528
+ SMALLINT: "number",
1529
+ BIGINT: "bigint",
1530
+ HUGEINT: "bigint",
1531
+ UHUGEINT: "bigint",
1532
+ UTINYINT: "number",
1533
+ USMALLINT: "number",
1534
+ UINTEGER: "number",
1535
+ UBIGINT: "bigint",
1536
+ TIME: "{ micros: bigint }",
1537
+ "TIME WITH TIME ZONE": "{ micros: bigint; offset: number }",
1538
+ TIMESTAMP_S: "{ seconds: bigint }",
1539
+ TIMESTAMP_MS: "{ millis: bigint }",
1540
+ TIMESTAMP_NS: "{ nanos: bigint }",
1541
+ "TIMESTAMP WITH TIME ZONE": "{ micros: bigint }",
1542
+ UUID: "{ hugeint: bigint }",
1543
+ INTERVAL: "{ months: number; days: number; micros: bigint }",
1544
+ BIT: "{ data: Uint8Array }",
1545
+ BIGNUM: "bigint",
1546
+ INT2: "number",
1547
+ INT4: "number",
1548
+ INT8: "bigint",
1549
+ FLOAT4: "number",
1550
+ FLOAT8: "number",
1551
+ NUMERIC: "string",
1552
+ BOOL: "boolean",
1553
+ BYTEA: "Buffer",
1554
+ TIMESTAMPTZ: "Date",
1555
+ JSON: "unknown",
1556
+ JSONB: "unknown",
1557
+ OID: "number",
1558
+ SERIAL: "number",
1559
+ BIGSERIAL: "bigint"
1560
+ };
1561
+ mapPrimitiveType(type, nullable) {
1562
+ const upperType = type.toUpperCase();
1563
+ const mappedType = this.typeMap[upperType];
1564
+ if (mappedType) return nullable ? `${mappedType} | null` : mappedType;
1565
+ if (upperType.startsWith("_")) {
1566
+ const baseType = upperType.substring(1);
1567
+ const arrayType = `${this.typeMap[baseType] || "unknown"}[]`;
1568
+ return nullable ? `${arrayType} | null` : arrayType;
1569
+ }
1570
+ if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) {
1571
+ const baseType = "{ width: number; scale: number; value: bigint }";
1572
+ return nullable ? `${baseType} | null` : baseType;
1573
+ }
1574
+ if (upperType.startsWith("ENUM(")) {
1575
+ const baseType = "string";
1576
+ return nullable ? `${baseType} | null` : baseType;
1577
+ }
1578
+ if (upperType.startsWith("UNION(")) {
1579
+ const baseType = "{ tag: string; value: unknown }";
1580
+ return nullable ? `${baseType} | null` : baseType;
1581
+ }
1582
+ const fixedArrayMatch = upperType.match(/^([A-Z_]+)\[(\d+)\](\[\d+\])*$/);
1583
+ if (fixedArrayMatch) {
1584
+ const baseTypeName = fixedArrayMatch[1];
1585
+ const baseType = this.typeMap[baseTypeName];
1586
+ if (baseType) {
1587
+ const dimensions = (upperType.match(/\[\d+\]/g) || []).length;
1588
+ let result = baseType;
1589
+ for (let i = 0; i < dimensions; i++) result = `{ items: (${result} | null)[] }`;
1590
+ return nullable ? `${result} | null` : result;
1591
+ }
1592
+ }
1593
+ if (/\[\d+\]/.test(upperType)) return "{ items: unknown[] }";
1594
+ return nullable ? "string | null" : "string";
1595
+ }
1596
+ formatListType(elementType) {
1597
+ return `{ items: (${elementType})[] }`;
1598
+ }
1599
+ formatStructTypeName(fieldName) {
1600
+ return `${pascalCase(fieldName)}Struct`;
1601
+ }
1602
+ formatMapTypeName(fieldName) {
1603
+ return "Map";
1604
+ }
1605
+ generateStructDeclaration(column) {
1606
+ if (!(column.type instanceof StructType)) throw new Error("Expected StructType");
1607
+ return `interface ${this.formatStructTypeName(column.name)} {\n entries: {\n${column.type.fields.map((field) => {
1608
+ const fieldType = this.getTypeName({
1609
+ name: field.name,
1610
+ type: field.type,
1611
+ nullable: true
1612
+ });
1613
+ return ` ${field.name}: ${fieldType};`;
1614
+ }).join("\n")}\n };\n}`;
1615
+ }
1616
+ /**
1617
+ * Generates code to parse/convert a raw DuckDB value to the target TypeScript type.
1618
+ * DuckDB returns complex types with specific wrapper structures that need to be preserved.
1619
+ */
1620
+ parseValue(column, value, path = "") {
1621
+ if (column.type instanceof ListType) return value;
1622
+ if (column.type instanceof StructType) return value;
1623
+ if (column.type instanceof MapType) return value;
1624
+ return value;
1625
+ }
1626
+ };
1627
+ /**
1628
+ * Type mapper for generating Python types from SQL column types.
1629
+ * Maps SQL types to Python types (e.g., INTEGER -> int, VARCHAR -> str).
1630
+ * Generates frozen dataclasses for struct types and handles Python reserved keywords.
1631
+ */
1632
+ var PythonTypeMapper = class PythonTypeMapper extends TypeMapper {
1633
+ constructor() {
1634
+ super();
1635
+ }
1636
+ typeMap = {
1637
+ INTEGER: "int",
1638
+ INT: "int",
1639
+ INT2: "int",
1640
+ INT4: "int",
1641
+ TINYINT: "int",
1642
+ SMALLINT: "int",
1643
+ BIGINT: "int",
1644
+ INT8: "int",
1645
+ HUGEINT: "int",
1646
+ UHUGEINT: "int",
1647
+ UTINYINT: "int",
1648
+ USMALLINT: "int",
1649
+ UINTEGER: "int",
1650
+ UBIGINT: "int",
1651
+ SERIAL: "int",
1652
+ BIGSERIAL: "int",
1653
+ REAL: "float",
1654
+ DOUBLE: "float",
1655
+ FLOAT: "float",
1656
+ FLOAT4: "float",
1657
+ FLOAT8: "float",
1658
+ TEXT: "str",
1659
+ VARCHAR: "str",
1660
+ INTERVAL: "str",
1661
+ BIT: "str",
1662
+ UUID: "str",
1663
+ BOOLEAN: "bool",
1664
+ BOOL: "bool",
1665
+ BLOB: "bytes",
1666
+ BYTEA: "bytes",
1667
+ DATE: "datetime.date",
1668
+ TIMESTAMP: "datetime.datetime",
1669
+ DATETIME: "datetime.datetime",
1670
+ TIMESTAMPTZ: "datetime.datetime",
1671
+ "TIMESTAMP WITH TIME ZONE": "datetime.datetime",
1672
+ TIMESTAMP_S: "datetime.datetime",
1673
+ TIMESTAMP_MS: "datetime.datetime",
1674
+ TIMESTAMP_NS: "datetime.datetime",
1675
+ TIME: "datetime.time",
1676
+ "TIME WITH TIME ZONE": "datetime.time",
1677
+ NUMERIC: "Decimal",
1678
+ DECIMAL: "Decimal",
1679
+ BIGNUM: "Decimal",
1680
+ JSON: "Any",
1681
+ JSONB: "Any",
1682
+ NULL: "None",
1683
+ UNKNOWN: "Any"
1684
+ };
1685
+ static pythonReservedKeywords = new Set([
1686
+ "False",
1687
+ "None",
1688
+ "True",
1689
+ "and",
1690
+ "as",
1691
+ "assert",
1692
+ "async",
1693
+ "await",
1694
+ "break",
1695
+ "class",
1696
+ "continue",
1697
+ "def",
1698
+ "del",
1699
+ "elif",
1700
+ "else",
1701
+ "except",
1702
+ "finally",
1703
+ "for",
1704
+ "from",
1705
+ "global",
1706
+ "if",
1707
+ "import",
1708
+ "in",
1709
+ "is",
1710
+ "lambda",
1711
+ "nonlocal",
1712
+ "not",
1713
+ "or",
1714
+ "pass",
1715
+ "raise",
1716
+ "return",
1717
+ "try",
1718
+ "while",
1719
+ "with",
1720
+ "yield"
1721
+ ]);
1722
+ mapPrimitiveType(type, nullable) {
1723
+ const upperType = type.toString().toUpperCase();
1724
+ const mappedType = this.typeMap[upperType];
1725
+ if (mappedType) return nullable ? `${mappedType} | None` : mappedType;
1726
+ if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) return nullable ? "Decimal | None" : "Decimal";
1727
+ if (upperType.startsWith("ENUM(")) return nullable ? "str | None" : "str";
1728
+ if (upperType.startsWith("UNION(")) return nullable ? "Any | None" : "Any";
1729
+ return nullable ? "Any | None" : "Any";
1730
+ }
1731
+ formatListType(elementType) {
1732
+ return `list[${elementType}]`;
1733
+ }
1734
+ formatStructTypeName(fieldName) {
1735
+ return `${pascalCase(fieldName)}Struct`;
1736
+ }
1737
+ formatMapTypeName(_fieldName) {
1738
+ return "dict";
1739
+ }
1740
+ generateStructDeclaration(column, path = "") {
1741
+ if (!(column.type instanceof StructType)) throw new Error(`Expected StructType ${column}`);
1742
+ const structName = this.formatStructTypeName(column.name);
1743
+ const newPath = `${path}${structName}.`;
1744
+ const children = column.type.fields.map((field) => {
1745
+ return this.getDeclarations({
1746
+ name: field.name,
1747
+ type: field.type,
1748
+ nullable: true
1749
+ }, newPath);
1750
+ }).filter(Boolean).join("\n\n");
1751
+ const fields = column.type.fields.map((field) => {
1752
+ const fieldType = this.getTypeName(field);
1753
+ return ` ${this.varName(field.name)}: ${fieldType}`;
1754
+ }).join("\n");
1755
+ let result = "";
1756
+ if (children) result += `${children}\n\n`;
1757
+ result += `@dataclass(frozen=True)\nclass ${structName}:\n${fields}`;
1758
+ return result;
1759
+ }
1760
+ varName(str) {
1761
+ const name = snakeCase(str);
1762
+ if (PythonTypeMapper.pythonReservedKeywords.has(name)) return `${name}_`;
1763
+ return name;
1764
+ }
1765
+ parseValue(column, value, _path) {
1766
+ if (column.type instanceof StructType) return `${this.formatStructTypeName(column.name)}(${column.type.fields.map((field) => {
1767
+ const pyName = this.varName(field.name);
1768
+ const dictAccess = `${value}["${field.name}"]`;
1769
+ if (field.type instanceof StructType) return `${pyName}=${this.parseValue(field, dictAccess, _path)}`;
1770
+ return `${pyName}=${dictAccess}`;
1771
+ }).join(", ")})`;
1772
+ return value;
1773
+ }
1774
+ };
1775
+ //#endregion
1776
+ //#region src/generators/base-generator.ts
1777
+ var BaseGenerator = class {
1778
+ constructor(template, typeMapper) {
1779
+ this.template = template;
1780
+ this.typeMapper = typeMapper;
1781
+ }
1782
+ mapType(column) {
1783
+ return this.typeMapper.getTypeName(column);
1784
+ }
1785
+ mapParameterType(type, nullable) {
1786
+ return this.mapType({
1787
+ name: "",
1788
+ type,
1789
+ nullable
1790
+ });
1791
+ }
1792
+ listType(column) {
1793
+ return this.typeMapper.listType(column);
1794
+ }
1795
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {}
1796
+ isCompatibleWith(_engine) {
1797
+ return true;
1798
+ }
1799
+ supportsAppenders(_engine) {
1800
+ return false;
1801
+ }
1802
+ functionReturnType(query) {
1803
+ if (query.isOne) return this.rowType(query);
1804
+ return this.typeMapper.formatListType(this.rowType(query));
1805
+ }
1806
+ rowType(query) {
1807
+ if (query.isPluck) return this.mapType({
1808
+ ...query.columns[0],
1809
+ name: query.id
1810
+ });
1811
+ return this.getClassName(`${query.id}_Result`);
1812
+ }
1813
+ getStatement(q) {
1814
+ return q.queryAnonymous;
1815
+ }
1816
+ };
1817
+ //#endregion
1818
+ //#region src/generators/java-generator.ts
1819
+ var JavaGenerator = class extends BaseGenerator {
1820
+ engine;
1821
+ constructor(template, engine = "duckdb") {
1822
+ super(template, new JavaTypeMapper());
1823
+ this.template = template;
1824
+ this.engine = engine;
1825
+ }
1826
+ supportsAppenders(engine) {
1827
+ return engine === "duckdb" || engine === "postgres";
1828
+ }
1829
+ getFunctionName(id) {
1830
+ return camelCase(id);
1831
+ }
1832
+ getFilename(sqlFileName) {
1833
+ return `${pascalCase(sqlFileName)}.java`;
1834
+ }
1835
+ getClassName(name) {
1836
+ return pascalCase(name);
1837
+ }
1838
+ partsToString(parts) {
1839
+ const stringParts = [];
1840
+ function addPart(str, quote) {
1841
+ if (quote && stringParts.length > 0) {
1842
+ const last = stringParts[stringParts.length - 1];
1843
+ if (last.quote) {
1844
+ last.str += str;
1845
+ return;
1846
+ }
1847
+ }
1848
+ stringParts.push({
1849
+ str,
1850
+ quote
1851
+ });
1852
+ }
1853
+ for (const part of parts) if (typeof part === "string") addPart(part, true);
1854
+ else {
1855
+ addPart(" '", true);
1856
+ addPart(part.name, false);
1857
+ addPart("'", true);
1858
+ }
1859
+ return stringParts.map((part) => {
1860
+ if (part.quote && (part.str.includes("\n") || part.str.includes("\""))) return `"""\n${part.str}"""`;
1861
+ if (part.quote) return `"${part.str}"`;
1862
+ return part.str;
1863
+ }).join(" + ");
1864
+ }
1865
+ readColumn(column, index, path) {
1866
+ return this.typeMapper.parseValue(column, `rs.getObject(${index + 1})`, path);
1867
+ }
1868
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {
1869
+ Handlebars.registerHelper("isDuckDB", () => this.engine === "duckdb");
1870
+ Handlebars.registerHelper("isPostgres", () => this.engine === "postgres");
1871
+ Handlebars.registerHelper("pgBulkType", (column) => {
1872
+ return pgBulkInsertType(column.type.toString().toUpperCase());
1873
+ });
1874
+ Handlebars.registerHelper("pgBulkAccessor", (column) => {
1875
+ return pgBulkInsertAccessor(column.type.toString().toUpperCase());
1876
+ });
1877
+ Handlebars.registerHelper("javaVarName", (name) => {
1878
+ const n = camelCase(name);
1879
+ return JavaTypeMapper.javaReservedKeywords.has(n) ? `${n}_` : n;
1880
+ });
1881
+ Handlebars.registerHelper("partsToString", (parts) => this.partsToString(parts));
1882
+ Handlebars.registerHelper("declareTypes", (queryHelper) => {
1883
+ const query = queryHelper.query;
1884
+ if (queryHelper.isPluck) return queryHelper.typeMapper.getDeclarations({
1885
+ ...query.columns[0],
1886
+ name: query.id
1887
+ }, " ");
1888
+ return queryHelper.typeMapper.getDeclarations(query.allColumns);
1889
+ });
1890
+ Handlebars.registerHelper("appenderType", (column) => {
1891
+ return this.mapType(column);
1892
+ });
1893
+ Handlebars.registerHelper("declareEnums", (queryHelpers) => {
1894
+ const enumTypes = /* @__PURE__ */ new Map();
1895
+ for (const qh of queryHelpers) {
1896
+ for (const col of qh.query.columns) if (col.type instanceof EnumType && col.type.name) enumTypes.set(col.type.name, col.type);
1897
+ if (qh.query.parameterTypes) {
1898
+ for (const colType of qh.query.parameterTypes.values()) if (colType instanceof EnumType && colType.name) enumTypes.set(colType.name, colType);
1899
+ }
1900
+ }
1901
+ if (enumTypes.size === 0) return "";
1902
+ const parts = [];
1903
+ for (const [, enumType] of enumTypes) {
1904
+ const enumName = pascalCase(enumType.name);
1905
+ const sanitized = enumType.values.map((v) => {
1906
+ let ident = v.toUpperCase().replace(/[^A-Za-z0-9_]/g, "_");
1907
+ if (ident.length > 0 && /^[0-9]/.test(ident)) ident = `_${ident}`;
1908
+ if (ident.length === 0) ident = "_EMPTY";
1909
+ return ident;
1910
+ });
1911
+ const usedIdents = /* @__PURE__ */ new Set();
1912
+ const finalIdents = sanitized.map((base) => {
1913
+ if (!usedIdents.has(base)) {
1914
+ usedIdents.add(base);
1915
+ return base;
1916
+ }
1917
+ let counter = 2;
1918
+ let candidate = `${base}_${counter}`;
1919
+ while (usedIdents.has(candidate)) {
1920
+ counter++;
1921
+ candidate = `${base}_${counter}`;
1922
+ }
1923
+ usedIdents.add(candidate);
1924
+ return candidate;
1925
+ });
1926
+ const entries = enumType.values.map((v, i) => `${finalIdents[i]}("${v}")`);
1927
+ parts.push(`public enum ${enumName} {
1928
+ ${entries.join(", ")};
1929
+ private final String value;
1930
+ private static final java.util.Map<String, ${enumName}> BY_VALUE =
1931
+ java.util.Map.ofEntries(java.util.Arrays.stream(values()).map(v -> java.util.Map.entry(v.value, v)).toArray(java.util.Map.Entry[]::new));
1932
+ ${enumName}(String value) { this.value = value; }
1933
+ public String getValue() { return value; }
1934
+ public static ${enumName} fromValue(String value) {
1935
+ ${enumName} result = BY_VALUE.get(value);
1936
+ if (result == null) throw new IllegalArgumentException("Unknown value: " + value);
1937
+ return result;
1938
+ }
1939
+ }`);
1940
+ }
1941
+ return new Handlebars.SafeString(parts.join("\n\n "));
1942
+ });
1943
+ Handlebars.registerHelper("readColumns", (queryHelper) => {
1944
+ const query = queryHelper.query;
1945
+ if (queryHelper.isPluck) return this.readColumn({
1946
+ ...query.columns[0],
1947
+ name: query.id
1948
+ }, 0, "");
1949
+ const result = [];
1950
+ const rowType = this.rowType(queryHelper.query);
1951
+ for (let i = 0; i < queryHelper.columns.length; i++) {
1952
+ const column = queryHelper.columns[i];
1953
+ result.push(this.readColumn(column, i, `${rowType}.`));
1954
+ }
1955
+ return `new ${rowType}(${result.join(",\n")})`;
1956
+ });
1957
+ }
1958
+ async afterGenerate(outputPath) {
1959
+ try {
1960
+ consola.debug("Formatting file:", outputPath);
1961
+ const code = readFileSync(outputPath, "utf-8");
1962
+ writeFileSync(outputPath, await prettier.format(code, {
1963
+ parser: "java",
1964
+ plugins: [prettierPluginJava],
1965
+ tabWidth: 4
1966
+ }));
1967
+ } catch (error) {
1968
+ consola.error("Failed to format Java file:", error);
1969
+ }
1970
+ }
1971
+ };
1972
+ const PG_BULK_TYPE_MAP = {
1973
+ SMALLINT: "INT2",
1974
+ INTEGER: "INT4",
1975
+ BIGINT: "INT8",
1976
+ REAL: "FLOAT4",
1977
+ "DOUBLE PRECISION": "FLOAT8",
1978
+ BOOLEAN: "BOOLEAN",
1979
+ TEXT: "TEXT",
1980
+ "CHARACTER VARYING": "TEXT",
1981
+ CHARACTER: "TEXT",
1982
+ NUMERIC: "NUMERIC",
1983
+ DECIMAL: "NUMERIC",
1984
+ DATE: "DATE",
1985
+ "TIME WITHOUT TIME ZONE": "TIME",
1986
+ "TIMESTAMP WITHOUT TIME ZONE": "TIMESTAMP",
1987
+ "TIMESTAMP WITH TIME ZONE": "TIMESTAMPTZ",
1988
+ UUID: "UUID",
1989
+ BYTEA: "BYTEA",
1990
+ JSONB: "JSONB",
1991
+ JSON: "JSONB",
1992
+ INT2: "INT2",
1993
+ INT4: "INT4",
1994
+ INT8: "INT8",
1995
+ FLOAT4: "FLOAT4",
1996
+ FLOAT8: "FLOAT8",
1997
+ BOOL: "BOOLEAN",
1998
+ VARCHAR: "TEXT",
1999
+ TIMESTAMP: "TIMESTAMP",
2000
+ TIMESTAMPTZ: "TIMESTAMPTZ"
2001
+ };
2002
+ function pgBulkInsertType(sqlType) {
2003
+ if (sqlType.startsWith("_")) return `array(PgBulkInsert.PostgresTypes.${PG_BULK_TYPE_MAP[sqlType.substring(1)] || "TEXT"})`;
2004
+ return PG_BULK_TYPE_MAP[sqlType] || "TEXT";
2005
+ }
2006
+ function pgBulkInsertAccessor(sqlType) {
2007
+ if (sqlType === "TIMESTAMPTZ") return "offsetDateTime";
2008
+ return "from";
2009
+ }
2010
+ //#endregion
2011
+ //#region src/generators/java-duckdb-arrow-generator.ts
2012
+ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
2013
+ javaGenerator;
2014
+ constructor(template) {
2015
+ super(template, new JavaTypeMapper());
2016
+ this.template = template;
2017
+ this.javaGenerator = new JavaGenerator("templates/java-jdbc.hbs");
2018
+ }
2019
+ getFunctionName(id) {
2020
+ return this.javaGenerator.getFunctionName(id);
2021
+ }
2022
+ async beforeGenerate(projectDir, gen, queries, tables) {
2023
+ const q = queries.filter((q) => q.isQuery && q.isOne || q.isMigrate);
2024
+ const name = `${gen.name}-jdbc`;
2025
+ writeGeneratedFile(projectDir, {
2026
+ name,
2027
+ generator: "java/duckdb/jdbc",
2028
+ output: gen.output,
2029
+ config: gen.config,
2030
+ projectName: gen.projectName
2031
+ }, this.javaGenerator, name, q, tables, "duckdb");
2032
+ }
2033
+ isCompatibleWith(engine) {
2034
+ return engine === "duckdb";
2035
+ }
2036
+ supportsAppenders(_engine) {
2037
+ return true;
2038
+ }
2039
+ getFilename(sqlFileName) {
2040
+ return this.javaGenerator.getFilename(sqlFileName);
2041
+ }
2042
+ getClassName(name) {
2043
+ return this.javaGenerator.getClassName(name);
2044
+ }
2045
+ mapType(column) {
2046
+ const { type } = column;
2047
+ if (typeof type === "string") {
2048
+ const mappedType = {
2049
+ INTEGER: "IntVector",
2050
+ BIGINT: "BigIntVector",
2051
+ BOOLEAN: "BitVector",
2052
+ DOUBLE: "Float8Vector",
2053
+ FLOAT: "Float4Vector",
2054
+ VARCHAR: "VarCharVector",
2055
+ TEXT: "VarCharVector",
2056
+ TIMESTAMP: "TimeStampVector",
2057
+ DATE: "DateDayVector",
2058
+ TIME: "TimeMicroVector"
2059
+ }[type.toUpperCase()];
2060
+ if (!mappedType) consola.warn("(duckdb-arrow) Mapped type is unknown:", type);
2061
+ return mappedType ?? "Object";
2062
+ }
2063
+ if (type instanceof ListType) return "ListVector";
2064
+ if (type instanceof StructType) return "StructVector";
2065
+ if (type instanceof MapType) return "MapVector";
2066
+ consola.warn("(duckdb-arrow) Unknown complex type:", type);
2067
+ return "Object";
2068
+ }
2069
+ mapParameterType(type, nullable) {
2070
+ return this.typeMapper.getTypeName({
2071
+ name: "",
2072
+ type,
2073
+ nullable
2074
+ });
2075
+ }
2076
+ listType(type) {
2077
+ const mockColumn = {
2078
+ name: "",
2079
+ type,
2080
+ nullable: false
2081
+ };
2082
+ return this.typeMapper.listType(mockColumn);
2083
+ }
2084
+ async afterGenerate(outputPath) {
2085
+ return this.javaGenerator.afterGenerate(outputPath);
2086
+ }
2087
+ functionReturnType(query) {
2088
+ if (query.isOne) return this.javaGenerator.rowType(query);
2089
+ return this.rowType(query);
2090
+ }
2091
+ rowType(query) {
2092
+ if (query.isOne) return this.javaGenerator.rowType(query);
2093
+ return this.getClassName(`${query.id}_Result`);
2094
+ }
2095
+ };
2096
+ //#endregion
2097
+ //#region src/generators/python-generator.ts
2098
+ var PythonGenerator = class extends BaseGenerator {
2099
+ engine;
2100
+ constructor(template, engine) {
2101
+ super(template, new PythonTypeMapper());
2102
+ this.engine = engine;
2103
+ }
2104
+ get isDuckDB() {
2105
+ return this.engine === "duckdb";
2106
+ }
2107
+ get isPostgres() {
2108
+ return this.engine === "postgres";
2109
+ }
2110
+ getFunctionName(id) {
2111
+ return snakeCase(id);
2112
+ }
2113
+ getFilename(sqlFileName) {
2114
+ return `${snakeCase(sqlFileName)}.py`;
2115
+ }
2116
+ getClassName(name) {
2117
+ return pascalCase(name);
2118
+ }
2119
+ rowType(query) {
2120
+ if (query.isPluck) return this.mapType({
2121
+ ...query.columns[0],
2122
+ name: query.id
2123
+ });
2124
+ return this.getClassName(`${query.id}_row`);
2125
+ }
2126
+ getStatement(q) {
2127
+ if (this.isDuckDB) return q.queryPositional;
2128
+ if (this.isPostgres) {
2129
+ const anon = q.queryAnonymous;
2130
+ return {
2131
+ ...anon,
2132
+ sql: anon.sql.replace(/\?/g, "%s")
2133
+ };
2134
+ }
2135
+ return q.queryAnonymous;
2136
+ }
2137
+ supportsAppenders(_engine) {
2138
+ return this.engine === "duckdb" || this.engine === "postgres";
2139
+ }
2140
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {
2141
+ const pyMapper = this.typeMapper;
2142
+ Handlebars.registerHelper("isDuckDB", () => this.isDuckDB);
2143
+ Handlebars.registerHelper("isPostgres", () => this.isPostgres);
2144
+ Handlebars.registerHelper("connType", () => {
2145
+ if (this.isDuckDB) return "duckdb.DuckDBPyConnection";
2146
+ if (this.isPostgres) return "psycopg.Connection";
2147
+ return "sqlite3.Connection";
2148
+ });
2149
+ Handlebars.registerHelper("quote", (value) => {
2150
+ if (value.includes("\n") || value.includes("'") || value.includes("\"")) return `"""\\\n${value}"""`;
2151
+ return `"${value}"`;
2152
+ });
2153
+ Handlebars.registerHelper("pyType", (column) => {
2154
+ return this.getPyType(column);
2155
+ });
2156
+ Handlebars.registerHelper("declareTypes", (queryHelper) => {
2157
+ const query = queryHelper.query;
2158
+ if (queryHelper.isPluck) return "";
2159
+ const columns = queryHelper.columns;
2160
+ const rowTypeName = this.getClassName(`${query.id}_row`);
2161
+ const nestedDecls = [];
2162
+ for (const col of columns) {
2163
+ const decl = this.typeMapper.getDeclarations(col);
2164
+ if (decl) nestedDecls.push(decl);
2165
+ }
2166
+ const fields = columns.map((col) => {
2167
+ const pyType = this.getPyType(col);
2168
+ return ` ${pyMapper.varName(col.name)}: ${pyType}`;
2169
+ }).join("\n");
2170
+ let result = "";
2171
+ if (nestedDecls.length > 0) result += `${nestedDecls.join("\n\n")}\n\n`;
2172
+ result += `@dataclass(frozen=True)\nclass ${rowTypeName}:\n${fields}`;
2173
+ return new Handlebars.SafeString(result);
2174
+ });
2175
+ Handlebars.registerHelper("constructRow", (queryHelper) => {
2176
+ const query = queryHelper.query;
2177
+ const columns = queryHelper.columns;
2178
+ return `${this.getClassName(`${query.id}_row`)}(${columns.map((col, i) => {
2179
+ const pyName = pyMapper.varName(col.name);
2180
+ const value = `row[${i}]`;
2181
+ return `${pyName}=${this.typeMapper.parseValue(col, value, "")}`;
2182
+ }).join(", ")})`;
2183
+ });
2184
+ Handlebars.registerHelper("pyTypeOrNone", (column) => {
2185
+ const t = this.getPyType(column);
2186
+ if (t.endsWith(" | None")) return t;
2187
+ return `${t} | None`;
2188
+ });
2189
+ Handlebars.registerHelper("pyVarName", (name) => {
2190
+ return pyMapper.varName(name);
2191
+ });
2192
+ Handlebars.registerHelper("needsDatetime", (queries) => this.queryColumnsContainType(queries, "datetime."));
2193
+ Handlebars.registerHelper("needsDecimal", (queries) => this.queryColumnsContainType(queries, "Decimal"));
2194
+ }
2195
+ queryColumnsContainType(queries, substring) {
2196
+ for (const qh of queries) {
2197
+ if (!qh.isQuery || qh.skipGenerateFunction) continue;
2198
+ for (const col of qh.columns) if (this.getPyType(col).includes(substring)) return true;
2199
+ }
2200
+ return false;
2201
+ }
2202
+ getPyType(column) {
2203
+ const t = column.type;
2204
+ if (t instanceof ListType) {
2205
+ const base = `list[${this.getPyType({
2206
+ name: column.name,
2207
+ type: t.baseType,
2208
+ nullable: true
2209
+ })}]`;
2210
+ return column.nullable ? `${base} | None` : base;
2211
+ }
2212
+ if (t instanceof MapType) {
2213
+ const base = `dict[${this.getPyType({
2214
+ name: "key",
2215
+ type: t.keyType.type,
2216
+ nullable: false
2217
+ })}, ${this.getPyType({
2218
+ name: "value",
2219
+ type: t.valueType.type,
2220
+ nullable: true
2221
+ })}]`;
2222
+ return column.nullable ? `${base} | None` : base;
2223
+ }
2224
+ if (t instanceof StructType) {
2225
+ const structName = `${pascalCase(column.name)}Struct`;
2226
+ return column.nullable ? `${structName} | None` : structName;
2227
+ }
2228
+ return this.typeMapper.getTypeName(column);
2229
+ }
2230
+ async afterGenerate(outputPath) {
2231
+ try {
2232
+ execSync(`ruff format "${outputPath}"`, { stdio: "ignore" });
2233
+ } catch {
2234
+ consola.debug("ruff not available, skipping format for:", outputPath);
2235
+ }
2236
+ }
2237
+ };
2238
+ //#endregion
2239
+ //#region src/generators/typescript-generator.ts
2240
+ /** Resolve a ColumnType to its DuckDB type constant name (e.g. "VARCHAR", "INTEGER") for use in generated code. */
2241
+ function resolveElementType(baseType) {
2242
+ if (baseType instanceof ListType) return `new DuckDBListType(${resolveElementType(baseType.baseType)})`;
2243
+ return {
2244
+ VARCHAR: "VARCHAR",
2245
+ TEXT: "VARCHAR",
2246
+ INTEGER: "INTEGER",
2247
+ INT: "INTEGER",
2248
+ BIGINT: "BIGINT",
2249
+ DOUBLE: "DOUBLE",
2250
+ FLOAT: "FLOAT",
2251
+ BOOLEAN: "BOOLEAN",
2252
+ DATE: "DATE",
2253
+ TIMESTAMP: "TIMESTAMP",
2254
+ SMALLINT: "SMALLINT",
2255
+ TINYINT: "TINYINT"
2256
+ }[baseType.toString().toUpperCase()] || "VARCHAR";
2257
+ }
2258
+ var TsGenerator = class extends BaseGenerator {
2259
+ constructor(template) {
2260
+ super(template, new TypeScriptTypeMapper());
2261
+ }
2262
+ getFunctionName(id) {
2263
+ return camelCase(id);
2264
+ }
2265
+ isCompatibleWith(_engine) {
2266
+ return true;
2267
+ }
2268
+ getFilename(sqlFileName) {
2269
+ return `${sqlFileName}.ts`;
2270
+ }
2271
+ getClassName(name) {
2272
+ return pascalCase(name);
2273
+ }
2274
+ async beforeGenerate(_projectDir, _gen, _queries, _tables) {
2275
+ Handlebars.registerHelper("quote", (value) => this.quote(value));
2276
+ Handlebars.registerHelper("appendMethod", (column) => {
2277
+ if (column.type instanceof ListType) return "List";
2278
+ const typeStr = column.type?.toString().toUpperCase() || "";
2279
+ if (typeStr === "INTEGER" || typeStr === "INT" || typeStr === "INT4" || typeStr === "SIGNED") return "Integer";
2280
+ if (typeStr === "SMALLINT" || typeStr === "INT2" || typeStr === "SHORT") return "SmallInt";
2281
+ if (typeStr === "TINYINT" || typeStr === "INT1") return "TinyInt";
2282
+ if (typeStr === "BIGINT" || typeStr === "INT8" || typeStr === "LONG") return "BigInt";
2283
+ if (typeStr === "HUGEINT" || typeStr === "INT128") return "HugeInt";
2284
+ if (typeStr === "UINTEGER" || typeStr === "UINT4") return "UInteger";
2285
+ if (typeStr === "USMALLINT" || typeStr === "UINT2") return "USmallInt";
2286
+ if (typeStr === "UTINYINT" || typeStr === "UINT1") return "UTinyInt";
2287
+ if (typeStr === "UBIGINT" || typeStr === "UINT8") return "UBigInt";
2288
+ if (typeStr === "DOUBLE" || typeStr === "FLOAT8" || typeStr === "NUMERIC" || typeStr === "DECIMAL") return "Double";
2289
+ if (typeStr === "FLOAT" || typeStr === "FLOAT4" || typeStr === "REAL") return "Float";
2290
+ if (typeStr === "BOOLEAN" || typeStr === "BOOL" || typeStr === "LOGICAL") return "Boolean";
2291
+ if (typeStr === "DATE") return "Date";
2292
+ if (typeStr === "TIMESTAMP" || typeStr.includes("TIMESTAMP")) return "Timestamp";
2293
+ if (typeStr === "TIME" || typeStr.includes("TIME")) return "Time";
2294
+ if (typeStr === "BLOB" || typeStr === "BYTEA" || typeStr === "BINARY" || typeStr === "VARBINARY") return "Blob";
2295
+ if (typeStr === "UUID") return "Uuid";
2296
+ if (typeStr === "INTERVAL") return "Interval";
2297
+ return "Varchar";
2298
+ });
2299
+ Handlebars.registerHelper("appendListTypeArg", (column) => {
2300
+ if (!(column.type instanceof ListType)) return "";
2301
+ return `, new DuckDBListType(${resolveElementType(column.type.baseType)})`;
2302
+ });
2303
+ Handlebars.registerHelper("tsTypeForAppender", (column) => {
2304
+ if (column.type instanceof ListType) {
2305
+ const baseType = "readonly DuckDBValue[]";
2306
+ return column.nullable ? `${baseType} | null` : baseType;
2307
+ }
2308
+ const typeStr = column.type?.toString().toUpperCase() || "";
2309
+ let baseType;
2310
+ if (typeStr === "INTEGER" || typeStr === "INT" || typeStr === "INT4" || typeStr === "SMALLINT" || typeStr === "INT2" || typeStr === "TINYINT" || typeStr === "INT1" || typeStr === "UINTEGER" || typeStr === "UINT4" || typeStr === "USMALLINT" || typeStr === "UINT2" || typeStr === "UTINYINT" || typeStr === "UINT1" || typeStr === "DOUBLE" || typeStr === "FLOAT8" || typeStr === "FLOAT" || typeStr === "FLOAT4" || typeStr === "REAL") baseType = "number";
2311
+ else if (typeStr === "BIGINT" || typeStr === "INT8" || typeStr === "HUGEINT" || typeStr === "INT128" || typeStr === "UBIGINT" || typeStr === "UINT8") baseType = "bigint";
2312
+ else if (typeStr === "BOOLEAN" || typeStr === "BOOL") baseType = "boolean";
2313
+ else if (typeStr === "DATE") baseType = "DuckDBDateValue";
2314
+ else if (typeStr === "TIMESTAMP" || typeStr.includes("TIMESTAMP")) baseType = "DuckDBTimestampValue";
2315
+ else if (typeStr === "TIME" || typeStr.includes("TIME")) baseType = "DuckDBTimeValue";
2316
+ else if (typeStr === "BLOB" || typeStr === "BYTEA") baseType = "DuckDBBlobValue";
2317
+ else if (typeStr === "UUID") baseType = "string";
2318
+ else baseType = "string";
2319
+ if (column.nullable) return `${baseType} | null`;
2320
+ return baseType;
2321
+ });
2322
+ Handlebars.registerHelper("tsType", (column) => {
2323
+ const inlineType = (col) => {
2324
+ const t = col.type;
2325
+ const withNullability = (base) => {
2326
+ if (!col.nullable) return base;
2327
+ if (/\bnull\b/.test(base)) return base;
2328
+ return `${base} | null`;
2329
+ };
2330
+ if (t instanceof ListType) {
2331
+ const element = inlineType({
2332
+ name: col.name,
2333
+ type: t.baseType,
2334
+ nullable: true
2335
+ });
2336
+ return withNullability(`${element.includes(" | ") ? `(${element})` : element}[]`);
2337
+ }
2338
+ if (t instanceof MapType) return withNullability(`Map<${inlineType({
2339
+ name: "key",
2340
+ type: t.keyType.type,
2341
+ nullable: false
2342
+ })}, ${inlineType({
2343
+ name: "value",
2344
+ type: t.valueType.type,
2345
+ nullable: true
2346
+ })}>`);
2347
+ if (t instanceof StructType) {
2348
+ const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
2349
+ return withNullability(`{ ${t.fields.map((f) => {
2350
+ return `${isValidIdent(f.name) ? f.name : JSON.stringify(f.name)}: ${inlineType({
2351
+ name: f.name,
2352
+ type: f.type,
2353
+ nullable: true
2354
+ })}`;
2355
+ }).join("; ")} }`);
2356
+ }
2357
+ if (t instanceof EnumType) return withNullability(t.values.map((v) => JSON.stringify(v)).join(" | "));
2358
+ return this.typeMapper.getTypeName(col);
2359
+ };
2360
+ return inlineType(column);
2361
+ });
2362
+ Handlebars.registerHelper("declareTypes", (queryHelper) => {
2363
+ const typeMapper = queryHelper.typeMapper;
2364
+ const declarations = /* @__PURE__ */ new Map();
2365
+ const visit = (column) => {
2366
+ const t = column.type;
2367
+ if (t instanceof ListType) {
2368
+ visit({
2369
+ name: column.name,
2370
+ type: t.baseType,
2371
+ nullable: true
2372
+ });
2373
+ return;
2374
+ }
2375
+ if (t instanceof StructType) {
2376
+ const typeName = typeMapper.getTypeName(column);
2377
+ if (!declarations.has(typeName)) declarations.set(typeName, typeMapper.getDeclarations(column));
2378
+ for (const field of t.fields) visit({
2379
+ name: field.name,
2380
+ type: field.type,
2381
+ nullable: true
2382
+ });
2383
+ return;
2384
+ }
2385
+ if (t instanceof MapType) {
2386
+ visit({
2387
+ name: column.name,
2388
+ type: t.keyType.type,
2389
+ nullable: true
2390
+ });
2391
+ visit({
2392
+ name: column.name,
2393
+ type: t.valueType.type,
2394
+ nullable: true
2395
+ });
2396
+ }
2397
+ };
2398
+ if (queryHelper.isPluck) {
2399
+ const col = queryHelper.columns[0];
2400
+ visit({
2401
+ name: col.name,
2402
+ type: col.type,
2403
+ nullable: true
2404
+ });
2405
+ } else for (const col of queryHelper.columns) visit({
2406
+ name: col.name,
2407
+ type: col.type,
2408
+ nullable: true
2409
+ });
2410
+ return Array.from(declarations.values()).filter(Boolean).join("\n\n");
2411
+ });
2412
+ }
2413
+ async afterGenerate(outputPath) {
2414
+ try {
2415
+ consola.debug("Formatting file:", outputPath);
2416
+ const code = readFileSync(outputPath, "utf-8");
2417
+ writeFileSync(outputPath, await prettier.format(code, {
2418
+ parser: "typescript",
2419
+ plugins: [typescriptPlugin, estree]
2420
+ }));
2421
+ } catch (error) {
2422
+ consola.error("Failed to format file:", error);
2423
+ }
2424
+ }
2425
+ quote(value) {
2426
+ return value.includes("\n") || value.includes("'") ? `\`${value}\`` : `'${value}'`;
2427
+ }
2428
+ };
2429
+ //#endregion
2430
+ //#region src/generators/typescript-duckdb-generator.ts
2431
+ /**
2432
+ * TypeScript generator for DuckDB.
2433
+ * DuckDB's Node API returns complex types as wrapper objects:
2434
+ * - Lists as { items: T[] }
2435
+ * - Structs as { entries: { field1: T1, ... } }
2436
+ * - Maps as { entries: { key: K, value: V }[] }
2437
+ */
2438
+ var TsDuckDBGenerator = class extends TsGenerator {
2439
+ constructor(template) {
2440
+ super(template);
2441
+ }
2442
+ supportsAppenders(_engine) {
2443
+ return true;
2444
+ }
2445
+ async beforeGenerate(projectDir, gen, queries, tables) {
2446
+ await super.beforeGenerate(projectDir, gen, queries, tables);
2447
+ Handlebars.registerHelper("hasListParams", (queryHelper) => {
2448
+ const paramTypes = queryHelper.query.parameterTypes;
2449
+ if (!paramTypes) return false;
2450
+ for (const [, colType] of paramTypes) if (colType instanceof ListType) return true;
2451
+ return false;
2452
+ });
2453
+ Handlebars.registerHelper("bindStatements", (queryHelper) => {
2454
+ const paramNames = queryHelper.parameterNames;
2455
+ const paramTypes = queryHelper.query.parameterTypes;
2456
+ return paramNames.map((name, i) => {
2457
+ const colType = paramTypes?.get(name);
2458
+ if (colType instanceof ListType) return `stmt.bindList(${i + 1}, ${name}.items, new DuckDBListType(${resolveElementType(colType.baseType)}));`;
2459
+ return `stmt.bindValue(${i + 1}, ${name});`;
2460
+ }).join("\n ");
2461
+ });
2462
+ Handlebars.registerHelper("tsType", (column) => {
2463
+ const inlineType = (col) => {
2464
+ const t = col.type;
2465
+ const withNullability = (base) => {
2466
+ if (!col.nullable) return base;
2467
+ if (/\bnull\b/.test(base)) return base;
2468
+ return `${base} | null`;
2469
+ };
2470
+ if (t instanceof ListType) {
2471
+ const element = inlineType({
2472
+ name: col.name,
2473
+ type: t.baseType,
2474
+ nullable: true
2475
+ });
2476
+ return withNullability(`{ items: ${element.includes(" | ") ? `(${element})` : element}[] }`);
2477
+ }
2478
+ if (t instanceof MapType) return withNullability(`{ entries: { key: ${inlineType({
2479
+ name: "key",
2480
+ type: t.keyType.type,
2481
+ nullable: true
2482
+ })}; value: ${inlineType({
2483
+ name: "value",
2484
+ type: t.valueType.type,
2485
+ nullable: true
2486
+ })} }[] }`);
2487
+ if (t instanceof StructType) {
2488
+ const isValidIdent = (name) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
2489
+ return withNullability(`{ entries: { ${t.fields.map((f) => {
2490
+ return `${isValidIdent(f.name) ? f.name : JSON.stringify(f.name)}: ${inlineType({
2491
+ name: f.name,
2492
+ type: f.type,
2493
+ nullable: true
2494
+ })}`;
2495
+ }).join("; ")} } }`);
2496
+ }
2497
+ if (t instanceof EnumType) return withNullability(t.values.map((v) => JSON.stringify(v)).join(" | "));
2498
+ return this.typeMapper.getTypeName(col);
2499
+ };
2500
+ return inlineType(column);
2501
+ });
2502
+ }
2503
+ };
2504
+ //#endregion
2505
+ //#region src/generators/index.ts
2506
+ /**
2507
+ * Get a generator instance for the given generator.
2508
+ * Accepts both short (language/engine) and full (language/engine/driver) formats.
2509
+ */
2510
+ function getGenerator(generator) {
2511
+ const fullGenerator = resolveGenerator(generator);
2512
+ try {
2513
+ const info = parseGenerator(generator);
2514
+ const key = `${info.language}/${info.driver}`;
2515
+ switch (key) {
2516
+ case "typescript/better-sqlite3":
2517
+ case "typescript/node":
2518
+ case "typescript/libsql":
2519
+ case "typescript/turso": return new TsGenerator(`templates/${info.template}`);
2520
+ case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
2521
+ case "java/jdbc": return new JavaGenerator(`templates/${info.template}`, info.engine);
2522
+ case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
2523
+ case "python/sqlite3": return new PythonGenerator(`templates/${info.template}`, "sqlite");
2524
+ case "python/duckdb": return new PythonGenerator(`templates/${info.template}`, "duckdb");
2525
+ case "python/psycopg": return new PythonGenerator(`templates/${info.template}`, "postgres");
2526
+ default: throw new Error(`No generator class for ${key}`);
2527
+ }
2528
+ } catch {
2529
+ const similar = findSimilarGenerators(generator);
2530
+ throw new InvalidGeneratorError(fullGenerator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
2531
+ }
2532
+ }
2533
+ //#endregion
2534
+ //#region src/sqltool.ts
2535
+ const GENERATED_FILE_COMMENT = "This file is generated by SQG (https://sqg.dev). Do not edit manually.";
2536
+ const configSchema = z.object({ result: z.record(z.string(), z.string()).optional() });
2537
+ var Config = class Config {
2538
+ constructor(result) {
2539
+ this.result = result;
2540
+ }
2541
+ getColumnInfo(name) {
2542
+ return this.result.get(name);
2543
+ }
2544
+ static fromYaml(name, filePath, configStr) {
2545
+ let configObj;
2546
+ try {
2547
+ configObj = YAML.parse(configStr);
2548
+ } catch (e) {
2549
+ throw new Error(`Error parsing YAML config for query ${name} in ${filePath}: \n${configStr}\n ${e}`);
2550
+ }
2551
+ const result = configSchema.safeParse(configObj);
2552
+ if (!result.success) throw new Error(`Error parsing config for query ${name} in ${filePath}: \n${configStr}\n ${result.error}`);
2553
+ const columnMap = /* @__PURE__ */ new Map();
2554
+ for (const [name, info] of Object.entries(result.data.result ?? {})) {
2555
+ const parts = info.trim().split(" ").map((part) => part.trim());
2556
+ let type;
2557
+ let nullable = true;
2558
+ if (parts.length === 1) type = parts[0];
2559
+ else if (parts.length === 2) {
2560
+ type = parts[0];
2561
+ if (parts[1].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
2562
+ nullable = true;
2563
+ } else if (parts.length === 3) {
2564
+ type = parts[0];
2565
+ if (parts[1].toLocaleLowerCase() !== "not" || parts[2].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
2566
+ nullable = false;
2567
+ } else throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
2568
+ columnMap.set(name, {
2569
+ name,
2570
+ type,
2571
+ nullable
2572
+ });
2573
+ }
2574
+ return new Config(columnMap);
2575
+ }
2576
+ };
2577
+ /** Util class to help generating a query with a given generator */
2578
+ var SqlQueryHelper = class {
2579
+ constructor(query, generator, statement) {
2580
+ this.query = query;
2581
+ this.generator = generator;
2582
+ this.statement = statement;
2583
+ }
2584
+ get id() {
2585
+ return this.query.id;
2586
+ }
2587
+ get isQuery() {
2588
+ return this.query.isQuery;
2589
+ }
2590
+ get isExec() {
2591
+ return this.query.isExec;
2592
+ }
2593
+ get isMigrate() {
2594
+ return this.query.isMigrate;
2595
+ }
2596
+ get isPluck() {
2597
+ return this.query.isPluck;
2598
+ }
2599
+ get isOne() {
2600
+ return this.query.isOne;
2601
+ }
2602
+ get parameterNames() {
2603
+ return this.statement.parameters.map((param) => param.name);
2604
+ }
2605
+ get skipGenerateFunction() {
2606
+ return this.query.skipGenerateFunction;
2607
+ }
2608
+ get parameters() {
2609
+ const vars = new Map(this.variables.map((param) => [param.name, param.type]));
2610
+ return this.statement.parameters.map((param) => {
2611
+ const rawType = this.query.parameterTypes?.get(param.name);
2612
+ let isArray = false;
2613
+ let arrayBaseType = null;
2614
+ let isEnum = false;
2615
+ let enumClassName = null;
2616
+ if (rawType instanceof EnumType && rawType.name) {
2617
+ isEnum = true;
2618
+ enumClassName = pascalCase(rawType.name);
2619
+ } else if (rawType instanceof ListType) {
2620
+ isArray = true;
2621
+ arrayBaseType = rawType.baseType.toString();
2622
+ } else if (typeof rawType === "string" && rawType.startsWith("_")) {
2623
+ isArray = true;
2624
+ arrayBaseType = rawType.substring(1);
2625
+ }
2626
+ return {
2627
+ name: param.name,
2628
+ type: vars.get(param.name),
2629
+ isArray,
2630
+ arrayBaseType,
2631
+ isEnum,
2632
+ enumClassName
2633
+ };
2634
+ });
2635
+ }
2636
+ get columns() {
2637
+ if (!(this.query.allColumns.type instanceof StructType)) throw new Error(`Expected StructType ${this.query.allColumns.type}`);
2638
+ return this.query.allColumns.type.fields;
2639
+ }
2640
+ get variables() {
2641
+ return Array.from(this.query.variables.entries()).map(([name, value]) => ({
2642
+ name,
2643
+ type: this.generator.mapParameterType(this.query.parameterTypes?.get(name) ?? detectParameterType(value), false)
2644
+ }));
2645
+ }
2646
+ get sqlQuery() {
2647
+ return this.statement.sql;
2648
+ }
2649
+ get sqlQueryParts() {
2650
+ return this.statement.sqlParts;
2651
+ }
2652
+ get rowTypeStr() {
2653
+ return this.generator.rowType(this.query);
2654
+ }
2655
+ get functionReturnType() {
2656
+ return new Handlebars.SafeString(this.generator.functionReturnType(this.query));
2657
+ }
2658
+ get rowType() {
2659
+ return new Handlebars.SafeString(this.rowTypeStr);
2660
+ }
2661
+ get functionName() {
2662
+ return this.generator.getFunctionName(this.query.id);
2663
+ }
2664
+ get typeMapper() {
2665
+ return this.generator.typeMapper;
2666
+ }
2667
+ };
2668
+ /** Util class to help generating appenders for tables */
2669
+ var TableHelper = class {
2670
+ constructor(table, generator) {
2671
+ this.table = table;
2672
+ this.generator = generator;
2673
+ }
2674
+ get id() {
2675
+ return this.table.id;
2676
+ }
2677
+ get tableName() {
2678
+ return this.table.tableName;
2679
+ }
2680
+ get columns() {
2681
+ let cols = this.table.columns;
2682
+ if (this.table.includeColumns.length > 0) cols = cols.filter((c) => this.table.includeColumns.includes(c.name));
2683
+ return cols.filter((c) => !c.generated);
2684
+ }
2685
+ get skipGenerateFunction() {
2686
+ return this.table.skipGenerateFunction;
2687
+ }
2688
+ get functionName() {
2689
+ return this.generator.getFunctionName(`create_${this.table.id}_appender`);
2690
+ }
2691
+ get className() {
2692
+ return this.generator.getClassName(`${this.table.id}_appender`);
2693
+ }
2694
+ get rowTypeName() {
2695
+ return this.generator.getClassName(`${this.table.id}_row`);
2696
+ }
2697
+ get constantName() {
2698
+ return this.table.id.toUpperCase();
2699
+ }
2700
+ get typeMapper() {
2701
+ return this.generator.typeMapper;
2702
+ }
2703
+ };
2704
+ function generateSourceFile(name, queries, tables, templatePath, generator, engine, projectName, config) {
2705
+ const templateSrc = readFileSync(templatePath, "utf-8");
2706
+ const template = Handlebars.compile(templateSrc);
2707
+ Handlebars.registerHelper("mapType", (column) => generator.mapType(column));
2708
+ Handlebars.registerHelper("plusOne", (value) => value + 1);
2709
+ const migrations = queries.filter((q) => q.isMigrate).map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q)));
2710
+ const tableHelpers = generator.supportsAppenders(engine) ? tables.filter((t) => !t.skipGenerateFunction).map((t) => new TableHelper(t, generator)) : [];
2711
+ return template({
2712
+ generatedComment: GENERATED_FILE_COMMENT,
2713
+ migrations,
2714
+ queries: queries.map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
2715
+ tables: tableHelpers,
2716
+ className: generator.getClassName(name),
2717
+ projectName,
2718
+ config
2719
+ }, {
2720
+ allowProtoPropertiesByDefault: true,
2721
+ allowProtoMethodsByDefault: true
2722
+ });
2723
+ }
2724
+ /** All valid generator strings for schema validation */
2725
+ const validGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
2726
+ /**
2727
+ * Project configuration schema with descriptions for validation messages
2728
+ */
2729
+ const ProjectSchema = z.object({
2730
+ version: z.number().describe("Configuration version (currently 1)"),
2731
+ name: z.string().min(1, "Project name is required").describe("Project name used for generated class names"),
2732
+ sql: z.array(z.object({
2733
+ files: z.array(z.string().min(1)).min(1, "At least one SQL file is required").describe("SQL files to process"),
2734
+ gen: z.array(z.object({
2735
+ generator: z.string().refine((val) => isValidGenerator(val), { message: `Invalid generator. Valid generators: ${validGenerators.join(", ")}` }).describe(`Code generation generator: ${SHORT_GENERATOR_NAMES.join(", ")}`),
2736
+ name: z.string().optional().describe("Override the generated class/module name"),
2737
+ template: z.string().optional().describe("Custom Handlebars template path"),
2738
+ output: z.string().min(1, "Output path is required").describe("Output file or directory path"),
2739
+ config: z.any().optional().describe("Generator-specific configuration")
2740
+ })).min(1, "At least one generator is required").describe("Code generators to run")
2741
+ })).min(1, "At least one SQL configuration is required").describe("SQL file configurations"),
2742
+ sources: z.array(z.object({
2743
+ path: z.string().describe("Path to source file (supports $HOME)"),
2744
+ name: z.string().optional().describe("Variable name override")
2745
+ })).optional().describe("External source files to include as variables")
2746
+ });
2747
+ var ExtraVariable = class {
2748
+ constructor(name, value) {
2749
+ this.name = name;
2750
+ this.value = value;
2751
+ }
2752
+ };
2753
+ function createExtraVariables(sources, suppressLogging = false) {
2754
+ return sources.map((source) => {
2755
+ const path = source.path;
2756
+ const resolvedPath = path.replace("$HOME", homedir());
2757
+ const varName = `sources_${(source.name ?? basename(path, extname(resolvedPath))).replace(/\s+/g, "_")}`;
2758
+ if (!suppressLogging) consola.debug("Extra variable:", varName, resolvedPath);
2759
+ return new ExtraVariable(varName, `'${resolvedPath}'`);
2760
+ });
2761
+ }
2762
+ function buildProjectFromCliOptions(options) {
2763
+ if (!isValidGenerator(options.generator)) {
2764
+ const similar = findSimilarGenerators(options.generator);
2765
+ const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
2766
+ throw new InvalidGeneratorError(options.generator, allGenerators, similar.length > 0 ? similar[0] : void 0);
2767
+ }
2768
+ const generatorInfo = parseGenerator(options.generator);
2769
+ const genConfig = {
2770
+ generator: options.generator,
2771
+ output: options.output || "."
2772
+ };
2773
+ if (generatorInfo.language === "java") genConfig.config = { package: "generated" };
2774
+ return {
2775
+ version: 1,
2776
+ name: options.name || "generated",
2777
+ sql: [{
2778
+ files: options.files,
2779
+ gen: [genConfig]
2780
+ }]
2781
+ };
2782
+ }
2783
+ /**
2784
+ * Parse and validate project configuration with helpful error messages
2785
+ */
2786
+ function parseProjectConfig(filePath) {
2787
+ if (!existsSync(filePath)) throw new FileNotFoundError(filePath, process.cwd());
2788
+ let content;
2789
+ try {
2790
+ content = readFileSync(filePath, "utf-8");
2791
+ } catch (e) {
2792
+ throw new SqgError(`Cannot read config file: ${filePath}`, "CONFIG_PARSE_ERROR", "Check file permissions and that the path is correct");
2793
+ }
2794
+ let parsed;
2795
+ try {
2796
+ parsed = YAML.parse(content);
2797
+ } catch (e) {
2798
+ throw new SqgError(`Invalid YAML syntax in ${filePath}: ${e.message}`, "CONFIG_PARSE_ERROR", "Check YAML syntax - common issues: incorrect indentation, missing colons, unquoted special characters");
2799
+ }
2800
+ if (parsed && typeof parsed === "object") {
2801
+ const obj = parsed;
2802
+ if (obj.sql && Array.isArray(obj.sql)) for (let i = 0; i < obj.sql.length; i++) {
2803
+ const sqlConfig = obj.sql[i];
2804
+ if (sqlConfig.gen && Array.isArray(sqlConfig.gen)) for (let j = 0; j < sqlConfig.gen.length; j++) {
2805
+ const genConfig = sqlConfig.gen[j];
2806
+ if (genConfig.generator && !isValidGenerator(String(genConfig.generator))) {
2807
+ const similar = findSimilarGenerators(String(genConfig.generator));
2808
+ const allGenerators = [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES];
2809
+ throw new InvalidGeneratorError(String(genConfig.generator), allGenerators, similar.length > 0 ? similar[0] : void 0);
2810
+ }
2811
+ }
2812
+ }
2813
+ }
2814
+ const result = ProjectSchema.safeParse(parsed);
2815
+ if (!result.success) throw new ConfigError(`Configuration error in ${filePath}:\n${z.prettifyError(result.error)}`, "Check the configuration format against the documentation at https://sqg.dev", { file: filePath });
2816
+ return result.data;
2817
+ }
2818
+ function detectParameterType(value) {
2819
+ const num = Number(value);
2820
+ if (!Number.isNaN(num)) {
2821
+ if (Number.isInteger(num)) return "INTEGER";
2822
+ return "REAL";
2823
+ }
2824
+ if (value.toLowerCase() === "true" || value.toLowerCase() === "false") return "BOOLEAN";
2825
+ return "TEXT";
2826
+ }
2827
+ function getOutputPath(projectDir, sqlFileName, gen, generator) {
2828
+ const pathParts = [];
2829
+ if (!gen.output.startsWith("/")) pathParts.push(projectDir);
2830
+ if (gen.output.endsWith("/")) {
2831
+ const name = generator.getFilename(sqlFileName);
2832
+ pathParts.push(gen.output, name);
2833
+ } else pathParts.push(gen.output);
2834
+ const outputPath = join(...pathParts);
2835
+ mkdirSync(dirname(outputPath), { recursive: true });
2836
+ return outputPath;
2837
+ }
2838
+ function validateQueries(queries) {
2839
+ for (const query of queries) {
2840
+ if (query.isQuery && query.isPluck && query.columns.length !== 1) throw SqgError.inQuery(`':pluck' modifier requires exactly 1 column, but query has ${query.columns.length} columns`, "VALIDATION_ERROR", query.id, query.filename, { suggestion: query.columns.length === 0 ? "Ensure the query returns at least one column" : `Remove ':pluck' or select only one column. Current columns: ${query.columns.map((c) => c.name).join(", ")}` });
2841
+ const columns = query.columns.map((col) => {
2842
+ const configColumn = query.config?.getColumnInfo(col.name);
2843
+ if (configColumn) return configColumn;
2844
+ return col;
2845
+ });
2846
+ query.allColumns = {
2847
+ name: query.id,
2848
+ nullable: false,
2849
+ type: new StructType(columns)
2850
+ };
2851
+ }
2852
+ }
2853
+ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tables, engine, writeToStdout = false) {
2854
+ await generator.beforeGenerate(projectDir, gen, queries, tables);
2855
+ const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
2856
+ const name = gen.name ?? basename(file, extname(file));
2857
+ const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.projectName ?? name, {
2858
+ migrations: true,
2859
+ ...gen.config
2860
+ });
2861
+ if (writeToStdout) {
2862
+ process.stdout.write(sourceFile);
2863
+ if (!sourceFile.endsWith("\n")) process.stdout.write("\n");
2864
+ return null;
2865
+ }
2866
+ const outputPath = getOutputPath(projectDir, name, gen, generator);
2867
+ writeFileSync(outputPath, sourceFile);
2868
+ await generator.afterGenerate(outputPath);
2869
+ return outputPath;
2870
+ }
2871
+ /**
2872
+ * Validate project configuration from a Project object without executing queries
2873
+ * Use this for pre-flight checks before generation
2874
+ */
2875
+ async function validateProjectFromConfig(project, projectDir) {
2876
+ const errors = [];
2877
+ const sqlFiles = [];
2878
+ const generators = [];
2879
+ for (const sql of project.sql) for (const sqlFile of sql.files) {
2880
+ const fullPath = join(projectDir, sqlFile);
2881
+ sqlFiles.push(sqlFile);
2882
+ if (!existsSync(fullPath)) errors.push({
2883
+ code: "FILE_NOT_FOUND",
2884
+ message: `SQL file not found: ${sqlFile}`,
2885
+ suggestion: `Check that ${sqlFile} exists relative to ${projectDir}`,
2886
+ context: { file: fullPath }
2887
+ });
2888
+ for (const gen of sql.gen) {
2889
+ generators.push(gen.generator);
2890
+ if (!isValidGenerator(gen.generator)) {
2891
+ const similar = findSimilarGenerators(gen.generator);
2892
+ errors.push({
2893
+ code: "INVALID_GENERATOR",
2894
+ message: `Invalid generator '${gen.generator}'`,
2895
+ suggestion: similar.length > 0 ? `Did you mean '${similar[0]}'?` : `Valid generators: ${SHORT_GENERATOR_NAMES.join(", ")}`,
2896
+ context: { generator: gen.generator }
2897
+ });
2898
+ }
2899
+ }
2900
+ }
2901
+ return {
2902
+ valid: errors.length === 0,
2903
+ project: {
2904
+ name: project.name,
2905
+ version: project.version
2906
+ },
2907
+ sqlFiles: [...new Set(sqlFiles)],
2908
+ generators: [...new Set(generators)],
2909
+ errors: errors.length > 0 ? errors : void 0
2910
+ };
2911
+ }
2912
+ /**
2913
+ * Validate project configuration from a YAML file without executing queries
2914
+ * Use this for pre-flight checks before generation
2915
+ */
2916
+ async function validateProject(projectPath) {
2917
+ const projectDir = resolve(dirname(projectPath));
2918
+ let project;
2919
+ try {
2920
+ project = parseProjectConfig(projectPath);
2921
+ } catch (e) {
2922
+ if (e instanceof SqgError) return {
2923
+ valid: false,
2924
+ errors: [{
2925
+ code: e.code,
2926
+ message: e.message,
2927
+ suggestion: e.suggestion,
2928
+ context: e.context
2929
+ }]
2930
+ };
2931
+ return {
2932
+ valid: false,
2933
+ errors: [{
2934
+ code: "UNKNOWN_ERROR",
2935
+ message: String(e)
2936
+ }]
2937
+ };
2938
+ }
2939
+ return await validateProjectFromConfig(project, projectDir);
2940
+ }
2941
+ /** Count enum types across queries */
2942
+ function countEnums(queries) {
2943
+ const enumNames = /* @__PURE__ */ new Set();
2944
+ for (const query of queries) {
2945
+ if (!query.isQuery) continue;
2946
+ for (const col of query.columns) if (col.type instanceof EnumType && col.type.name) enumNames.add(col.type.name);
2947
+ }
2948
+ return enumNames.size;
2949
+ }
2950
+ /**
2951
+ * Process a project configuration and generate code from a Project object
2952
+ */
2953
+ async function processProjectFromConfig(project, projectDir, writeToStdout = false, ui) {
2954
+ const originalLevel = consola.level;
2955
+ if (writeToStdout) consola.level = LogLevels.silent;
2956
+ const totalStart = performance.now();
2957
+ const reporter = ui?.createReporter();
2958
+ const results = [];
2959
+ try {
2960
+ const extraVariables = createExtraVariables(project.sources ?? [], writeToStdout);
2961
+ const files = [];
2962
+ for (const sql of project.sql) {
2963
+ const gensByEngine = /* @__PURE__ */ new Map();
2964
+ for (const gen of sql.gen) {
2965
+ const engine = getGeneratorEngine(gen.generator);
2966
+ if (!gensByEngine.has(engine)) gensByEngine.set(engine, []);
2967
+ gensByEngine.get(engine).push(gen);
2968
+ }
2969
+ for (const sqlFile of sql.files) {
2970
+ const fullPath = join(projectDir, sqlFile);
2971
+ if (!existsSync(fullPath)) throw new FileNotFoundError(fullPath, projectDir);
2972
+ let queries;
2973
+ let tables;
2974
+ try {
2975
+ const parseResult = parseSQLQueries(fullPath, extraVariables);
2976
+ queries = parseResult.queries;
2977
+ tables = parseResult.tables;
2978
+ } catch (e) {
2979
+ if (e instanceof SqgError) throw e;
2980
+ throw SqgError.inFile(`Failed to parse SQL file: ${e.message}`, "SQL_PARSE_ERROR", sqlFile, { suggestion: "Check SQL syntax and annotation format" });
2981
+ }
2982
+ for (const [engine, gens] of gensByEngine) {
2983
+ const executableQueries = queries.filter((q) => !q.skipGenerateFunction);
2984
+ const reporterAny = reporter;
2985
+ if (reporterAny?.setQueryTotal) reporterAny.setQueryTotal(executableQueries.length);
2986
+ try {
2987
+ const dbEngine = getDatabaseEngine(engine);
2988
+ ui?.startPhase(`Initializing ${engine} database...`);
2989
+ await dbEngine.initializeDatabase(queries, reporter);
2990
+ ui?.startPhase(`Introspecting ${executableQueries.length} queries...`);
2991
+ await dbEngine.executeQueries(queries, reporter);
2992
+ if (tables.length > 0) await dbEngine.introspectTables(tables, reporter);
2993
+ ui?.succeedPhase(`Introspected ${executableQueries.length} queries`);
2994
+ validateQueries(queries);
2995
+ await dbEngine.close();
2996
+ } catch (e) {
2997
+ ui?.failPhase(`Failed to process ${sqlFile}`);
2998
+ if (e instanceof SqgError) throw e;
2999
+ throw new SqgError(`Database error processing ${sqlFile}: ${e.message}`, "DATABASE_ERROR", `Check that the SQL is valid for engine '${engine}'`, {
3000
+ file: sqlFile,
3001
+ engine
3002
+ });
3003
+ }
3004
+ ui?.startPhase("Generating code...");
3005
+ const genStart = performance.now();
3006
+ for (const gen of gens) {
3007
+ const generator = getGenerator(gen.generator);
3008
+ const outputPath = await writeGeneratedFile(projectDir, {
3009
+ ...gen,
3010
+ projectName: project.name
3011
+ }, generator, sqlFile, queries, tables, engine, writeToStdout);
3012
+ if (outputPath !== null) {
3013
+ files.push(outputPath);
3014
+ results.push({
3015
+ outputPath,
3016
+ queryCount: executableQueries.length,
3017
+ enumCount: countEnums(queries),
3018
+ sqlFile,
3019
+ generator: gen.generator,
3020
+ elapsedMs: performance.now() - genStart
3021
+ });
3022
+ }
3023
+ }
3024
+ ui?.succeedPhase(`Generated ${gens.length} ${gens.length === 1 ? "file" : "files"} from ${sqlFile}`);
3025
+ }
3026
+ }
3027
+ }
3028
+ ui?.summary(results, performance.now() - totalStart);
3029
+ return files;
3030
+ } finally {
3031
+ if (writeToStdout) consola.level = originalLevel;
3032
+ }
3033
+ }
3034
+ /**
3035
+ * Process a project configuration and generate code from a YAML file
3036
+ */
3037
+ async function processProject(projectPath, ui) {
3038
+ const projectDir = resolve(dirname(projectPath));
3039
+ return await processProjectFromConfig(parseProjectConfig(projectPath), projectDir, false, ui);
3040
+ }
3041
+ //#endregion
3042
+ //#region src/init.ts
3043
+ /**
3044
+ * SQG Project Initialization - Creates new SQG projects with example files
3045
+ */
3046
+ /**
3047
+ * Generate example SQL content based on engine
3048
+ */
3049
+ function getExampleSql(engine) {
3050
+ return {
3051
+ sqlite: `-- MIGRATE 1
3052
+ -- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
3053
+ CREATE TABLE users (
3054
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3055
+ name TEXT NOT NULL,
3056
+ email TEXT UNIQUE NOT NULL,
3057
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
3058
+ );
3059
+
3060
+ -- MIGRATE 2
3061
+ CREATE TABLE posts (
3062
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3063
+ user_id INTEGER NOT NULL REFERENCES users(id),
3064
+ title TEXT NOT NULL,
3065
+ content TEXT,
3066
+ published INTEGER DEFAULT 0,
3067
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP
3068
+ );
3069
+
3070
+ -- TESTDATA seed_data
3071
+ INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
3072
+ INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
3073
+ INSERT INTO posts (user_id, title, content, published) VALUES (1, 'Hello World', 'My first post!', 1);
3074
+ `,
3075
+ duckdb: `-- MIGRATE 1
3076
+ -- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
3077
+ CREATE TABLE users (
3078
+ id INTEGER PRIMARY KEY,
3079
+ name VARCHAR NOT NULL,
3080
+ email VARCHAR UNIQUE NOT NULL,
3081
+ metadata STRUCT(role VARCHAR, active BOOLEAN),
3082
+ tags VARCHAR[],
3083
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3084
+ );
3085
+
3086
+ -- MIGRATE 2
3087
+ CREATE TABLE posts (
3088
+ id INTEGER PRIMARY KEY,
3089
+ user_id INTEGER NOT NULL REFERENCES users(id),
3090
+ title VARCHAR NOT NULL,
3091
+ content VARCHAR,
3092
+ published BOOLEAN DEFAULT FALSE,
3093
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3094
+ );
3095
+
3096
+ -- TESTDATA seed_data
3097
+ INSERT INTO users (id, name, email, metadata, tags)
3098
+ VALUES (1, 'Alice', 'alice@example.com', {'role': 'admin', 'active': true}, ['developer', 'lead']);
3099
+ INSERT INTO users (id, name, email, metadata, tags)
3100
+ VALUES (2, 'Bob', 'bob@example.com', {'role': 'user', 'active': true}, ['developer']);
3101
+ INSERT INTO posts (id, user_id, title, content, published)
3102
+ VALUES (1, 1, 'Hello World', 'My first post!', TRUE);
3103
+ `,
3104
+ postgres: `-- MIGRATE 1
3105
+ -- Create the users table (SQG Example - https://sqg.dev/guides/sql-syntax/)
3106
+ CREATE TABLE users (
3107
+ id SERIAL PRIMARY KEY,
3108
+ name TEXT NOT NULL,
3109
+ email TEXT UNIQUE NOT NULL,
3110
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3111
+ );
3112
+
3113
+ -- MIGRATE 2
3114
+ CREATE TABLE posts (
3115
+ id SERIAL PRIMARY KEY,
3116
+ user_id INTEGER NOT NULL REFERENCES users(id),
3117
+ title TEXT NOT NULL,
3118
+ content TEXT,
3119
+ published BOOLEAN DEFAULT FALSE,
3120
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
3121
+ );
3122
+
3123
+ -- TESTDATA seed_data
3124
+ INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
3125
+ INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com');
3126
+ INSERT INTO posts (user_id, title, content, published) VALUES (1, 'Hello World', 'My first post!', TRUE);
3127
+ `
3128
+ }[engine] + `
3129
+ -- QUERY list_users
3130
+ SELECT id, name, email, created_at
3131
+ FROM users
3132
+ ORDER BY created_at DESC;
3133
+
3134
+ -- QUERY get_user_by_id :one
3135
+ @set id = 1
3136
+ SELECT id, name, email, created_at
3137
+ FROM users
3138
+ WHERE id = \${id};
3139
+
3140
+ -- QUERY get_user_by_email :one
3141
+ @set email = 'alice@example.com'
3142
+ SELECT id, name, email, created_at
3143
+ FROM users
3144
+ WHERE email = \${email};
3145
+
3146
+ -- QUERY count_users :one :pluck
3147
+ SELECT COUNT(*) FROM users;
3148
+
3149
+ -- QUERY list_posts_by_user
3150
+ @set user_id = 1
3151
+ SELECT p.id, p.title, p.content, p.published, p.created_at
3152
+ FROM posts p
3153
+ WHERE p.user_id = \${user_id}
3154
+ ORDER BY p.created_at DESC;
3155
+
3156
+ -- QUERY list_published_posts
3157
+ SELECT
3158
+ p.id,
3159
+ p.title,
3160
+ p.content,
3161
+ p.created_at,
3162
+ u.name as author_name,
3163
+ u.email as author_email
3164
+ FROM posts p
3165
+ JOIN users u ON p.user_id = u.id
3166
+ WHERE p.published = 1
3167
+ ORDER BY p.created_at DESC;
3168
+ ` + {
3169
+ sqlite: `
3170
+ -- EXEC create_user
3171
+ @set name = 'New User'
3172
+ @set email = 'new@example.com'
3173
+ INSERT INTO users (name, email)
3174
+ VALUES (\${name}, \${email});
3175
+
3176
+ -- EXEC create_post
3177
+ @set user_id = 1
3178
+ @set title = 'New Post'
3179
+ @set content = 'Post content here'
3180
+ INSERT INTO posts (user_id, title, content)
3181
+ VALUES (\${user_id}, \${title}, \${content});
3182
+
3183
+ -- EXEC publish_post
3184
+ @set id = 1
3185
+ UPDATE posts SET published = 1 WHERE id = \${id};
3186
+
3187
+ -- EXEC delete_post
3188
+ @set id = 1
3189
+ DELETE FROM posts WHERE id = \${id};
3190
+ `,
3191
+ duckdb: `
3192
+ -- EXEC create_user
3193
+ @set id = 100
3194
+ @set name = 'New User'
3195
+ @set email = 'new@example.com'
3196
+ INSERT INTO users (id, name, email)
3197
+ VALUES (\${id}, \${name}, \${email});
3198
+
3199
+ -- EXEC create_post
3200
+ @set id = 100
3201
+ @set user_id = 1
3202
+ @set title = 'New Post'
3203
+ @set content = 'Post content here'
3204
+ INSERT INTO posts (id, user_id, title, content)
3205
+ VALUES (\${id}, \${user_id}, \${title}, \${content});
3206
+
3207
+ -- EXEC publish_post
3208
+ @set id = 1
3209
+ UPDATE posts SET published = TRUE WHERE id = \${id};
3210
+
3211
+ -- EXEC delete_post
3212
+ @set id = 1
3213
+ DELETE FROM posts WHERE id = \${id};
3214
+ `,
3215
+ postgres: `
3216
+ -- EXEC create_user
3217
+ @set name = 'New User'
3218
+ @set email = 'new@example.com'
3219
+ INSERT INTO users (name, email)
3220
+ VALUES (\${name}, \${email});
3221
+
3222
+ -- EXEC create_post
3223
+ @set user_id = 1
3224
+ @set title = 'New Post'
3225
+ @set content = 'Post content here'
3226
+ INSERT INTO posts (user_id, title, content)
3227
+ VALUES (\${user_id}, \${title}, \${content});
3228
+
3229
+ -- EXEC publish_post
3230
+ @set id = 1
3231
+ UPDATE posts SET published = TRUE WHERE id = \${id};
3232
+
3233
+ -- EXEC delete_post
3234
+ @set id = 1
3235
+ DELETE FROM posts WHERE id = \${id};
3236
+ `
3237
+ }[engine];
3238
+ }
3239
+ /**
3240
+ * Generate sqg.yaml configuration
3241
+ */
3242
+ function getConfigYaml(generator, output, projectName) {
3243
+ const isJava = parseGenerator(generator).language === "java";
3244
+ return `# SQG Configuration
3245
+ # Generated by: sqg init
3246
+ # Documentation: https://sqg.dev
3247
+
3248
+ version: 1
3249
+ name: ${projectName}
3250
+
3251
+ sql:
3252
+ - files:
3253
+ - queries.sql
3254
+ gen:
3255
+ - generator: ${generator}
3256
+ output: ${output.endsWith("/") ? output : `${output}/`}${isJava ? `
3257
+ config:
3258
+ package: generated` : ""}
3259
+ `;
3260
+ }
3261
+ /**
3262
+ * Run interactive wizard when no --generator flag is provided
3263
+ */
3264
+ async function runInteractiveInit(options) {
3265
+ clack.intro(pc.bold("Create a new SQG project"));
3266
+ const projectName = await clack.text({
3267
+ message: "Project name",
3268
+ placeholder: "my-project",
3269
+ defaultValue: "my-project",
3270
+ validate: (value) => {
3271
+ if (!value?.trim()) return "Project name is required";
3272
+ }
3273
+ });
3274
+ if (clack.isCancel(projectName)) {
3275
+ clack.cancel("Operation cancelled");
3276
+ process.exit(0);
3277
+ }
3278
+ const generatorOptions = [];
3279
+ for (const shortName of SHORT_GENERATOR_NAMES) {
3280
+ const info = GENERATORS[`${shortName}/${Object.entries(GENERATORS).find(([k]) => k.startsWith(`${shortName}/`))?.[1]?.driver}`];
3281
+ if (info) generatorOptions.push({
3282
+ value: shortName,
3283
+ label: shortName,
3284
+ hint: info.description
3285
+ });
3286
+ }
3287
+ const generator = await clack.select({
3288
+ message: "Generator",
3289
+ options: generatorOptions
3290
+ });
3291
+ if (clack.isCancel(generator)) {
3292
+ clack.cancel("Operation cancelled");
3293
+ process.exit(0);
3294
+ }
3295
+ const output = await clack.text({
3296
+ message: "Output directory",
3297
+ placeholder: "./generated",
3298
+ defaultValue: options.output || "./generated"
3299
+ });
3300
+ if (clack.isCancel(output)) {
3301
+ clack.cancel("Operation cancelled");
3302
+ process.exit(0);
3303
+ }
3304
+ clack.log.step("Files to create:");
3305
+ clack.log.message(` ${pc.dim("sqg.yaml")} Project configuration`);
3306
+ clack.log.message(` ${pc.dim("queries.sql")} Example SQL queries`);
3307
+ clack.log.message(` ${pc.dim(`${output}/`)} Output directory`);
3308
+ const confirm = await clack.confirm({ message: "Create files?" });
3309
+ if (clack.isCancel(confirm) || !confirm) {
3310
+ clack.cancel("Operation cancelled");
3311
+ process.exit(0);
3312
+ }
3313
+ await createProjectFiles({
3314
+ generator,
3315
+ output,
3316
+ force: options.force,
3317
+ projectName
3318
+ });
3319
+ clack.outro(`Done! Run: ${pc.bold("sqg sqg.yaml")}`);
3320
+ }
3321
+ /**
3322
+ * Create project files (shared between interactive and non-interactive modes)
3323
+ */
3324
+ async function createProjectFiles(options) {
3325
+ const { generator, output, force, projectName } = options;
3326
+ if (!isValidGenerator(generator)) {
3327
+ const similar = findSimilarGenerators(generator);
3328
+ throw new InvalidGeneratorError(generator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
3329
+ }
3330
+ const engine = parseGenerator(generator).engine;
3331
+ const configPath = "sqg.yaml";
3332
+ const sqlPath = "queries.sql";
3333
+ if (!force) {
3334
+ if (existsSync(configPath)) throw new SqgError(`File already exists: ${configPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
3335
+ if (existsSync(sqlPath)) throw new SqgError(`File already exists: ${sqlPath}`, "VALIDATION_ERROR", "Use --force to overwrite existing files");
3336
+ }
3337
+ if (!existsSync(output)) mkdirSync(output, { recursive: true });
3338
+ writeFileSync(configPath, getConfigYaml(generator, output, projectName));
3339
+ writeFileSync(sqlPath, getExampleSql(engine));
3340
+ }
3341
+ /**
3342
+ * Initialize a new SQG project
3343
+ */
3344
+ async function initProject(options) {
3345
+ if (!options.generator && process.stdin.isTTY) {
3346
+ await runInteractiveInit(options);
3347
+ return;
3348
+ }
3349
+ const generator = options.generator || "typescript/sqlite";
3350
+ const output = options.output || "./generated";
3351
+ await createProjectFiles({
3352
+ generator,
3353
+ output,
3354
+ force: options.force,
3355
+ projectName: "my-project"
3356
+ });
3357
+ const generatorInfo = parseGenerator(generator);
3358
+ clack.intro(pc.bold("SQG project initialized!"));
3359
+ clack.log.info(`Generator: ${generator}`);
3360
+ clack.log.info(`Engine: ${generatorInfo.engine}`);
3361
+ clack.log.info(`Output: ${output}`);
3362
+ clack.log.step("Next steps:");
3363
+ clack.log.message(" 1. Edit queries.sql to add your SQL queries");
3364
+ clack.log.message(" 2. Run: sqg sqg.yaml");
3365
+ clack.log.message(` 3. Import the generated code from ${output}`);
3366
+ clack.outro("Documentation: https://sqg.dev");
3367
+ }
3368
+ //#endregion
3369
+ export { BaseGenerator, Config, ConfigError, DB_ENGINES, DEFAULT_DRIVERS, DatabaseError, EnumType, ExtraVariable, FileNotFoundError, GENERATED_FILE_COMMENT, GENERATORS, GENERATOR_NAMES, InvalidGeneratorError, JavaDuckDBArrowGenerator, JavaGenerator, JavaTypeMapper, LANGUAGES, ListType, MapType, PythonGenerator, PythonTypeMapper, SHORT_GENERATOR_NAMES, SQLQuery, SqgError, SqlExecutionError, SqlQueryHelper, StructType, TableHelper, TableInfo, TsDuckDBGenerator, TsGenerator, TypeMapper, TypeMappingError, TypeScriptTypeMapper, buildProjectFromCliOptions, createExtraVariables, findSimilarGenerators, formatErrorForOutput, formatGeneratorsHelp, formatGeneratorsList, getDatabaseEngine, getGenerator, getGeneratorEngine, getGeneratorLanguage, getOutputPath, initProject, initializeDatabase, isValidGenerator, parseGenerator, parseProjectConfig, parseSQLQueries, processProject, processProjectFromConfig, resolveGenerator, validateProject, validateProjectFromConfig, validateQueries, writeGeneratedFile };