@sqg/sqg 0.14.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/ide-BieFvmwk.mjs +82 -0
- package/dist/ide-public/assets/index-DHD4h34g.css +2 -0
- package/dist/ide-public/assets/index-DPVATxCE.js +45 -0
- package/dist/ide-public/index.html +16 -0
- package/dist/ide-server.mjs +41129 -0
- package/dist/index.mjs +3369 -0
- package/dist/sqg.mjs +105 -61
- package/dist/templates/java-jdbc.hbs +48 -12
- package/dist/templates/python.hbs +36 -4
- package/package.json +10 -5
package/dist/sqg.mjs
CHANGED
|
@@ -29,7 +29,6 @@ import { execSync } from "node:child_process";
|
|
|
29
29
|
import typescriptPlugin from "prettier/parser-typescript";
|
|
30
30
|
import estree from "prettier/plugins/estree";
|
|
31
31
|
import yoctoSpinner from "yocto-spinner";
|
|
32
|
-
|
|
33
32
|
//#region src/constants.ts
|
|
34
33
|
/**
|
|
35
34
|
* SQG Constants - Centralized definitions for supported generators
|
|
@@ -223,7 +222,7 @@ SQL Annotation Syntax:
|
|
|
223
222
|
-- EXEC <name> Execute statement (INSERT/UPDATE/DELETE)
|
|
224
223
|
-- MIGRATE <number> Schema migration (run in order)
|
|
225
224
|
-- TESTDATA <name> Test data setup (not generated)
|
|
226
|
-
-- TABLE <name> :appender Table for bulk insert appender (DuckDB
|
|
225
|
+
-- TABLE <name> :appender Table for bulk insert appender (DuckDB, PostgreSQL)
|
|
227
226
|
|
|
228
227
|
@set <varName> = <value> Define a variable
|
|
229
228
|
\${varName} Reference a variable in SQL
|
|
@@ -244,7 +243,6 @@ Example:
|
|
|
244
243
|
|
|
245
244
|
-- TABLE users :appender
|
|
246
245
|
`.trim();
|
|
247
|
-
|
|
248
246
|
//#endregion
|
|
249
247
|
//#region src/errors.ts
|
|
250
248
|
/**
|
|
@@ -302,9 +300,9 @@ var ConfigError = class extends SqgError {
|
|
|
302
300
|
* Error for invalid generator names
|
|
303
301
|
*/
|
|
304
302
|
var InvalidGeneratorError = class extends SqgError {
|
|
305
|
-
constructor(generatorName, validGenerators
|
|
303
|
+
constructor(generatorName, validGenerators, suggestion) {
|
|
306
304
|
const similarMsg = suggestion ? ` Did you mean '${suggestion}'?` : "";
|
|
307
|
-
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators
|
|
305
|
+
super(`Invalid generator '${generatorName}'.${similarMsg} Valid generators: ${validGenerators.join(", ")}`, "INVALID_GENERATOR", suggestion ? `Use '${suggestion}' instead` : `Choose from: ${validGenerators.join(", ")}`, { generator: generatorName });
|
|
308
306
|
this.name = "InvalidGeneratorError";
|
|
309
307
|
}
|
|
310
308
|
};
|
|
@@ -386,7 +384,6 @@ function formatErrorForOutput(err) {
|
|
|
386
384
|
}
|
|
387
385
|
};
|
|
388
386
|
}
|
|
389
|
-
|
|
390
387
|
//#endregion
|
|
391
388
|
//#region src/init.ts
|
|
392
389
|
/**
|
|
@@ -714,7 +711,6 @@ async function initProject(options) {
|
|
|
714
711
|
clack.log.message(` 3. Import the generated code from ${output}`);
|
|
715
712
|
clack.outro("Documentation: https://sqg.dev");
|
|
716
713
|
}
|
|
717
|
-
|
|
718
714
|
//#endregion
|
|
719
715
|
//#region src/parser/sql-parser.ts
|
|
720
716
|
const parser = LRParser.deserialize({
|
|
@@ -735,7 +731,6 @@ const parser = LRParser.deserialize({
|
|
|
735
731
|
topRules: { "File": [0, 1] },
|
|
736
732
|
tokenPrec: 245
|
|
737
733
|
});
|
|
738
|
-
|
|
739
734
|
//#endregion
|
|
740
735
|
//#region src/sql-query.ts
|
|
741
736
|
var ListType = class {
|
|
@@ -896,8 +891,8 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
896
891
|
let to = -1;
|
|
897
892
|
class SQLQueryBuilder {
|
|
898
893
|
sqlParts = [];
|
|
899
|
-
appendSql(sql
|
|
900
|
-
this.sqlParts.push(sql
|
|
894
|
+
appendSql(sql) {
|
|
895
|
+
this.sqlParts.push(sql);
|
|
901
896
|
}
|
|
902
897
|
appendVariable(varName, value) {
|
|
903
898
|
this.sqlParts.push({
|
|
@@ -913,23 +908,23 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
913
908
|
return this.sqlParts.filter((part) => typeof part !== "string" && !part.name.startsWith("sources_"));
|
|
914
909
|
}
|
|
915
910
|
toSqlWithAnonymousPlaceholders() {
|
|
916
|
-
let sql
|
|
911
|
+
let sql = "";
|
|
917
912
|
const sqlParts = [];
|
|
918
913
|
for (const part of this.sqlParts) if (typeof part === "string") {
|
|
919
|
-
sql
|
|
914
|
+
sql += part;
|
|
920
915
|
sqlParts.push(part);
|
|
921
916
|
} else {
|
|
922
|
-
if (sql
|
|
923
|
-
const last = sql
|
|
924
|
-
if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql
|
|
917
|
+
if (sql.length > 0) {
|
|
918
|
+
const last = sql[sql.length - 1];
|
|
919
|
+
if (last !== " " && last !== "=" && last !== ">" && last !== "<") sql += " ";
|
|
925
920
|
}
|
|
926
|
-
sql
|
|
921
|
+
sql += "?";
|
|
927
922
|
if (part.name.startsWith("sources_")) sqlParts.push(part);
|
|
928
923
|
else sqlParts.push("?");
|
|
929
924
|
}
|
|
930
925
|
return {
|
|
931
926
|
parameters: this.parameters(),
|
|
932
|
-
sql
|
|
927
|
+
sql,
|
|
933
928
|
sqlParts
|
|
934
929
|
};
|
|
935
930
|
}
|
|
@@ -999,7 +994,7 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
999
994
|
}
|
|
1000
995
|
if (queryType === "TABLE") {
|
|
1001
996
|
const hasAppender = modifiers.includes(":appender");
|
|
1002
|
-
const tableName = sqlContentStr.trim() || name;
|
|
997
|
+
const tableName = sqlContentStr.split("\n").map((l) => l.trim()).find((l) => l.length > 0) || name;
|
|
1003
998
|
const includeColumns = [];
|
|
1004
999
|
for (const mod of modifiers) {
|
|
1005
1000
|
const match = mod.match(/:appender\(([^)]+)\)/);
|
|
@@ -1036,7 +1031,6 @@ function parseSQLQueries(filePath, extraVariables) {
|
|
|
1036
1031
|
tables
|
|
1037
1032
|
};
|
|
1038
1033
|
}
|
|
1039
|
-
|
|
1040
1034
|
//#endregion
|
|
1041
1035
|
//#region src/db/types.ts
|
|
1042
1036
|
async function initializeDatabase(queries, execQueries, reporter) {
|
|
@@ -1058,7 +1052,6 @@ async function initializeDatabase(queries, execQueries, reporter) {
|
|
|
1058
1052
|
if (migrationQueries.length + testdataQueries.length === 0) consola.warn("No migration or testdata queries found");
|
|
1059
1053
|
reporter?.onDatabaseInitialized?.();
|
|
1060
1054
|
}
|
|
1061
|
-
|
|
1062
1055
|
//#endregion
|
|
1063
1056
|
//#region src/db/duckdb.ts
|
|
1064
1057
|
/** Cache of enum type names, keyed by stringified sorted values for lookup */
|
|
@@ -1207,7 +1200,6 @@ const duckdb = new class {
|
|
|
1207
1200
|
enumNameCache = /* @__PURE__ */ new Map();
|
|
1208
1201
|
}
|
|
1209
1202
|
}();
|
|
1210
|
-
|
|
1211
1203
|
//#endregion
|
|
1212
1204
|
//#region src/db/postgres.ts
|
|
1213
1205
|
const tempDatabaseName = "sqg-db-temp";
|
|
@@ -1445,13 +1437,14 @@ const postgres = new class {
|
|
|
1445
1437
|
for (const table of tables) {
|
|
1446
1438
|
reporter?.onTableStart?.(table.tableName);
|
|
1447
1439
|
try {
|
|
1448
|
-
table.columns = (await db.query(`SELECT column_name, data_type, is_nullable
|
|
1440
|
+
table.columns = (await db.query(`SELECT column_name, data_type, udt_name, is_nullable, column_default, is_identity
|
|
1449
1441
|
FROM information_schema.columns
|
|
1450
1442
|
WHERE table_name = $1
|
|
1451
1443
|
ORDER BY ordinal_position`, [table.tableName])).rows.map((row) => ({
|
|
1452
1444
|
name: row.column_name,
|
|
1453
|
-
type: row.
|
|
1454
|
-
nullable: row.is_nullable === "YES"
|
|
1445
|
+
type: row.udt_name.toUpperCase(),
|
|
1446
|
+
nullable: row.is_nullable === "YES",
|
|
1447
|
+
generated: row.is_identity === "YES" || (row.column_default?.startsWith("nextval") ?? false)
|
|
1455
1448
|
}));
|
|
1456
1449
|
reporter?.onTableComplete?.(table.tableName, table.columns.length);
|
|
1457
1450
|
} catch (error) {
|
|
@@ -1466,7 +1459,6 @@ const postgres = new class {
|
|
|
1466
1459
|
this.enumTypeCache = /* @__PURE__ */ new Map();
|
|
1467
1460
|
}
|
|
1468
1461
|
}();
|
|
1469
|
-
|
|
1470
1462
|
//#endregion
|
|
1471
1463
|
//#region src/db/sqlite.ts
|
|
1472
1464
|
const sqlite = new class {
|
|
@@ -1548,7 +1540,6 @@ const sqlite = new class {
|
|
|
1548
1540
|
}
|
|
1549
1541
|
}
|
|
1550
1542
|
}();
|
|
1551
|
-
|
|
1552
1543
|
//#endregion
|
|
1553
1544
|
//#region src/db/index.ts
|
|
1554
1545
|
function getDatabaseEngine(engine) {
|
|
@@ -1559,7 +1550,6 @@ function getDatabaseEngine(engine) {
|
|
|
1559
1550
|
default: throw new Error(`Unsupported database engine: ${engine}`);
|
|
1560
1551
|
}
|
|
1561
1552
|
}
|
|
1562
|
-
|
|
1563
1553
|
//#endregion
|
|
1564
1554
|
//#region src/type-mapping.ts
|
|
1565
1555
|
/**
|
|
@@ -1793,15 +1783,18 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1793
1783
|
if (column.type instanceof StructType) return `${path}${this.formatStructTypeName(column.name)}.fromAttributes(getAttr((Struct)${value}))`;
|
|
1794
1784
|
const fieldType = this.getTypeName(column);
|
|
1795
1785
|
const upperType = column.type?.toString().toUpperCase() ?? "";
|
|
1796
|
-
if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime(
|
|
1797
|
-
if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime(
|
|
1786
|
+
if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime(${value})`;
|
|
1787
|
+
if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime(${value})`;
|
|
1798
1788
|
if (upperType === "TIMESTAMP WITH TIME ZONE") return `(OffsetDateTime)${value}`;
|
|
1799
|
-
if (upperType === "DATE") return `toLocalDate(
|
|
1800
|
-
if (upperType === "TIME") return `toLocalTime(
|
|
1789
|
+
if (upperType === "DATE") return `toLocalDate(${value})`;
|
|
1790
|
+
if (upperType === "TIME") return `toLocalTime(${value})`;
|
|
1801
1791
|
if (upperType.startsWith("_")) {
|
|
1802
1792
|
const baseType = upperType.substring(1);
|
|
1803
1793
|
return `arrayToList((Array)${value}, ${this.typeMap[baseType] || "Object"}[].class)`;
|
|
1804
1794
|
}
|
|
1795
|
+
if (fieldType === "Short") return `${value} != null ? ((Number)${value}).shortValue() : null`;
|
|
1796
|
+
if (fieldType === "Byte") return `${value} != null ? ((Number)${value}).byteValue() : null`;
|
|
1797
|
+
if (upperType === "JSON" || upperType === "JSONB") return `${value} != null ? ${value}.toString() : null`;
|
|
1805
1798
|
return `(${fieldType})${value}`;
|
|
1806
1799
|
}
|
|
1807
1800
|
getInnermostType(type) {
|
|
@@ -2098,7 +2091,6 @@ var PythonTypeMapper = class PythonTypeMapper extends TypeMapper {
|
|
|
2098
2091
|
return value;
|
|
2099
2092
|
}
|
|
2100
2093
|
};
|
|
2101
|
-
|
|
2102
2094
|
//#endregion
|
|
2103
2095
|
//#region src/generators/base-generator.ts
|
|
2104
2096
|
var BaseGenerator = class {
|
|
@@ -2141,16 +2133,17 @@ var BaseGenerator = class {
|
|
|
2141
2133
|
return q.queryAnonymous;
|
|
2142
2134
|
}
|
|
2143
2135
|
};
|
|
2144
|
-
|
|
2145
2136
|
//#endregion
|
|
2146
2137
|
//#region src/generators/java-generator.ts
|
|
2147
2138
|
var JavaGenerator = class extends BaseGenerator {
|
|
2148
|
-
|
|
2139
|
+
engine;
|
|
2140
|
+
constructor(template, engine = "duckdb") {
|
|
2149
2141
|
super(template, new JavaTypeMapper());
|
|
2150
2142
|
this.template = template;
|
|
2143
|
+
this.engine = engine;
|
|
2151
2144
|
}
|
|
2152
2145
|
supportsAppenders(engine) {
|
|
2153
|
-
return engine === "duckdb";
|
|
2146
|
+
return engine === "duckdb" || engine === "postgres";
|
|
2154
2147
|
}
|
|
2155
2148
|
getFunctionName(id) {
|
|
2156
2149
|
return camelCase(id);
|
|
@@ -2192,6 +2185,18 @@ var JavaGenerator = class extends BaseGenerator {
|
|
|
2192
2185
|
return this.typeMapper.parseValue(column, `rs.getObject(${index + 1})`, path);
|
|
2193
2186
|
}
|
|
2194
2187
|
async beforeGenerate(_projectDir, _gen, _queries, _tables) {
|
|
2188
|
+
Handlebars.registerHelper("isDuckDB", () => this.engine === "duckdb");
|
|
2189
|
+
Handlebars.registerHelper("isPostgres", () => this.engine === "postgres");
|
|
2190
|
+
Handlebars.registerHelper("pgBulkType", (column) => {
|
|
2191
|
+
return pgBulkInsertType(column.type.toString().toUpperCase());
|
|
2192
|
+
});
|
|
2193
|
+
Handlebars.registerHelper("pgBulkAccessor", (column) => {
|
|
2194
|
+
return pgBulkInsertAccessor(column.type.toString().toUpperCase());
|
|
2195
|
+
});
|
|
2196
|
+
Handlebars.registerHelper("javaVarName", (name) => {
|
|
2197
|
+
const n = camelCase(name);
|
|
2198
|
+
return JavaTypeMapper.javaReservedKeywords.has(n) ? `${n}_` : n;
|
|
2199
|
+
});
|
|
2195
2200
|
Handlebars.registerHelper("partsToString", (parts) => this.partsToString(parts));
|
|
2196
2201
|
Handlebars.registerHelper("declareTypes", (queryHelper) => {
|
|
2197
2202
|
const query = queryHelper.query;
|
|
@@ -2283,7 +2288,44 @@ var JavaGenerator = class extends BaseGenerator {
|
|
|
2283
2288
|
}
|
|
2284
2289
|
}
|
|
2285
2290
|
};
|
|
2286
|
-
|
|
2291
|
+
const PG_BULK_TYPE_MAP = {
|
|
2292
|
+
SMALLINT: "INT2",
|
|
2293
|
+
INTEGER: "INT4",
|
|
2294
|
+
BIGINT: "INT8",
|
|
2295
|
+
REAL: "FLOAT4",
|
|
2296
|
+
"DOUBLE PRECISION": "FLOAT8",
|
|
2297
|
+
BOOLEAN: "BOOLEAN",
|
|
2298
|
+
TEXT: "TEXT",
|
|
2299
|
+
"CHARACTER VARYING": "TEXT",
|
|
2300
|
+
CHARACTER: "TEXT",
|
|
2301
|
+
NUMERIC: "NUMERIC",
|
|
2302
|
+
DECIMAL: "NUMERIC",
|
|
2303
|
+
DATE: "DATE",
|
|
2304
|
+
"TIME WITHOUT TIME ZONE": "TIME",
|
|
2305
|
+
"TIMESTAMP WITHOUT TIME ZONE": "TIMESTAMP",
|
|
2306
|
+
"TIMESTAMP WITH TIME ZONE": "TIMESTAMPTZ",
|
|
2307
|
+
UUID: "UUID",
|
|
2308
|
+
BYTEA: "BYTEA",
|
|
2309
|
+
JSONB: "JSONB",
|
|
2310
|
+
JSON: "JSONB",
|
|
2311
|
+
INT2: "INT2",
|
|
2312
|
+
INT4: "INT4",
|
|
2313
|
+
INT8: "INT8",
|
|
2314
|
+
FLOAT4: "FLOAT4",
|
|
2315
|
+
FLOAT8: "FLOAT8",
|
|
2316
|
+
BOOL: "BOOLEAN",
|
|
2317
|
+
VARCHAR: "TEXT",
|
|
2318
|
+
TIMESTAMP: "TIMESTAMP",
|
|
2319
|
+
TIMESTAMPTZ: "TIMESTAMPTZ"
|
|
2320
|
+
};
|
|
2321
|
+
function pgBulkInsertType(sqlType) {
|
|
2322
|
+
if (sqlType.startsWith("_")) return `array(PgBulkInsert.PostgresTypes.${PG_BULK_TYPE_MAP[sqlType.substring(1)] || "TEXT"})`;
|
|
2323
|
+
return PG_BULK_TYPE_MAP[sqlType] || "TEXT";
|
|
2324
|
+
}
|
|
2325
|
+
function pgBulkInsertAccessor(sqlType) {
|
|
2326
|
+
if (sqlType === "TIMESTAMPTZ") return "offsetDateTime";
|
|
2327
|
+
return "from";
|
|
2328
|
+
}
|
|
2287
2329
|
//#endregion
|
|
2288
2330
|
//#region src/generators/java-duckdb-arrow-generator.ts
|
|
2289
2331
|
var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
@@ -2297,7 +2339,7 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
2297
2339
|
return this.javaGenerator.getFunctionName(id);
|
|
2298
2340
|
}
|
|
2299
2341
|
async beforeGenerate(projectDir, gen, queries, tables) {
|
|
2300
|
-
const q = queries.filter((q
|
|
2342
|
+
const q = queries.filter((q) => q.isQuery && q.isOne || q.isMigrate);
|
|
2301
2343
|
const name = `${gen.name}-jdbc`;
|
|
2302
2344
|
writeGeneratedFile(projectDir, {
|
|
2303
2345
|
name,
|
|
@@ -2370,7 +2412,6 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
2370
2412
|
return this.getClassName(`${query.id}_Result`);
|
|
2371
2413
|
}
|
|
2372
2414
|
};
|
|
2373
|
-
|
|
2374
2415
|
//#endregion
|
|
2375
2416
|
//#region src/generators/python-generator.ts
|
|
2376
2417
|
var PythonGenerator = class extends BaseGenerator {
|
|
@@ -2413,7 +2454,7 @@ var PythonGenerator = class extends BaseGenerator {
|
|
|
2413
2454
|
return q.queryAnonymous;
|
|
2414
2455
|
}
|
|
2415
2456
|
supportsAppenders(_engine) {
|
|
2416
|
-
return this.engine === "duckdb";
|
|
2457
|
+
return this.engine === "duckdb" || this.engine === "postgres";
|
|
2417
2458
|
}
|
|
2418
2459
|
async beforeGenerate(_projectDir, _gen, _queries, _tables) {
|
|
2419
2460
|
const pyMapper = this.typeMapper;
|
|
@@ -2513,7 +2554,6 @@ var PythonGenerator = class extends BaseGenerator {
|
|
|
2513
2554
|
}
|
|
2514
2555
|
}
|
|
2515
2556
|
};
|
|
2516
|
-
|
|
2517
2557
|
//#endregion
|
|
2518
2558
|
//#region src/generators/typescript-generator.ts
|
|
2519
2559
|
/** Resolve a ColumnType to its DuckDB type constant name (e.g. "VARCHAR", "INTEGER") for use in generated code. */
|
|
@@ -2581,8 +2621,8 @@ var TsGenerator = class extends BaseGenerator {
|
|
|
2581
2621
|
});
|
|
2582
2622
|
Handlebars.registerHelper("tsTypeForAppender", (column) => {
|
|
2583
2623
|
if (column.type instanceof ListType) {
|
|
2584
|
-
const baseType
|
|
2585
|
-
return column.nullable ? `${baseType
|
|
2624
|
+
const baseType = "readonly DuckDBValue[]";
|
|
2625
|
+
return column.nullable ? `${baseType} | null` : baseType;
|
|
2586
2626
|
}
|
|
2587
2627
|
const typeStr = column.type?.toString().toUpperCase() || "";
|
|
2588
2628
|
let baseType;
|
|
@@ -2705,7 +2745,6 @@ var TsGenerator = class extends BaseGenerator {
|
|
|
2705
2745
|
return value.includes("\n") || value.includes("'") ? `\`${value}\`` : `'${value}'`;
|
|
2706
2746
|
}
|
|
2707
2747
|
};
|
|
2708
|
-
|
|
2709
2748
|
//#endregion
|
|
2710
2749
|
//#region src/generators/typescript-duckdb-generator.ts
|
|
2711
2750
|
/**
|
|
@@ -2781,7 +2820,6 @@ var TsDuckDBGenerator = class extends TsGenerator {
|
|
|
2781
2820
|
});
|
|
2782
2821
|
}
|
|
2783
2822
|
};
|
|
2784
|
-
|
|
2785
2823
|
//#endregion
|
|
2786
2824
|
//#region src/generators/index.ts
|
|
2787
2825
|
/**
|
|
@@ -2799,7 +2837,7 @@ function getGenerator(generator) {
|
|
|
2799
2837
|
case "typescript/libsql":
|
|
2800
2838
|
case "typescript/turso": return new TsGenerator(`templates/${info.template}`);
|
|
2801
2839
|
case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
|
|
2802
|
-
case "java/jdbc": return new JavaGenerator(`templates/${info.template}
|
|
2840
|
+
case "java/jdbc": return new JavaGenerator(`templates/${info.template}`, info.engine);
|
|
2803
2841
|
case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
|
|
2804
2842
|
case "python/sqlite3": return new PythonGenerator(`templates/${info.template}`, "sqlite");
|
|
2805
2843
|
case "python/duckdb": return new PythonGenerator(`templates/${info.template}`, "duckdb");
|
|
@@ -2811,7 +2849,6 @@ function getGenerator(generator) {
|
|
|
2811
2849
|
throw new InvalidGeneratorError(fullGenerator, [...SHORT_GENERATOR_NAMES, ...GENERATOR_NAMES], similar.length > 0 ? similar[0] : void 0);
|
|
2812
2850
|
}
|
|
2813
2851
|
}
|
|
2814
|
-
|
|
2815
2852
|
//#endregion
|
|
2816
2853
|
//#region src/sqltool.ts
|
|
2817
2854
|
const GENERATED_FILE_COMMENT = "This file is generated by SQG (https://sqg.dev). Do not edit manually.";
|
|
@@ -2833,22 +2870,22 @@ var Config = class Config {
|
|
|
2833
2870
|
const result = configSchema.safeParse(configObj);
|
|
2834
2871
|
if (!result.success) throw new Error(`Error parsing config for query ${name} in ${filePath}: \n${configStr}\n ${result.error}`);
|
|
2835
2872
|
const columnMap = /* @__PURE__ */ new Map();
|
|
2836
|
-
for (const [name
|
|
2873
|
+
for (const [name, info] of Object.entries(result.data.result ?? {})) {
|
|
2837
2874
|
const parts = info.trim().split(" ").map((part) => part.trim());
|
|
2838
2875
|
let type;
|
|
2839
2876
|
let nullable = true;
|
|
2840
2877
|
if (parts.length === 1) type = parts[0];
|
|
2841
2878
|
else if (parts.length === 2) {
|
|
2842
2879
|
type = parts[0];
|
|
2843
|
-
if (parts[1].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name
|
|
2880
|
+
if (parts[1].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
|
|
2844
2881
|
nullable = true;
|
|
2845
2882
|
} else if (parts.length === 3) {
|
|
2846
2883
|
type = parts[0];
|
|
2847
|
-
if (parts[1].toLocaleLowerCase() !== "not" || parts[2].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name
|
|
2884
|
+
if (parts[1].toLocaleLowerCase() !== "not" || parts[2].toLocaleLowerCase() !== "null") throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
|
|
2848
2885
|
nullable = false;
|
|
2849
|
-
} else throw new Error(`Invalid config for column ${name
|
|
2850
|
-
columnMap.set(name
|
|
2851
|
-
name
|
|
2886
|
+
} else throw new Error(`Invalid config for column ${name} in ${filePath}: \n${configStr}\n ${info}`);
|
|
2887
|
+
columnMap.set(name, {
|
|
2888
|
+
name,
|
|
2852
2889
|
type,
|
|
2853
2890
|
nullable
|
|
2854
2891
|
});
|
|
@@ -2960,8 +2997,9 @@ var TableHelper = class {
|
|
|
2960
2997
|
return this.table.tableName;
|
|
2961
2998
|
}
|
|
2962
2999
|
get columns() {
|
|
2963
|
-
|
|
2964
|
-
|
|
3000
|
+
let cols = this.table.columns;
|
|
3001
|
+
if (this.table.includeColumns.length > 0) cols = cols.filter((c) => this.table.includeColumns.includes(c.name));
|
|
3002
|
+
return cols.filter((c) => !c.generated);
|
|
2965
3003
|
}
|
|
2966
3004
|
get skipGenerateFunction() {
|
|
2967
3005
|
return this.table.skipGenerateFunction;
|
|
@@ -2975,6 +3013,9 @@ var TableHelper = class {
|
|
|
2975
3013
|
get rowTypeName() {
|
|
2976
3014
|
return this.generator.getClassName(`${this.table.id}_row`);
|
|
2977
3015
|
}
|
|
3016
|
+
get constantName() {
|
|
3017
|
+
return this.table.id.toUpperCase();
|
|
3018
|
+
}
|
|
2978
3019
|
get typeMapper() {
|
|
2979
3020
|
return this.generator.typeMapper;
|
|
2980
3021
|
}
|
|
@@ -3316,12 +3357,11 @@ async function processProject(projectPath, ui) {
|
|
|
3316
3357
|
const projectDir = resolve(dirname(projectPath));
|
|
3317
3358
|
return await processProjectFromConfig(parseProjectConfig(projectPath), projectDir, false, ui);
|
|
3318
3359
|
}
|
|
3319
|
-
|
|
3320
3360
|
//#endregion
|
|
3321
3361
|
//#region src/mcp-server.ts
|
|
3322
3362
|
const server = new Server({
|
|
3323
3363
|
name: "sqg-mcp",
|
|
3324
|
-
version: process.env.npm_package_version ?? "0.
|
|
3364
|
+
version: process.env.npm_package_version ?? "0.16.0"
|
|
3325
3365
|
}, { capabilities: {
|
|
3326
3366
|
tools: {},
|
|
3327
3367
|
resources: {}
|
|
@@ -3647,7 +3687,6 @@ async function startMcpServer() {
|
|
|
3647
3687
|
await server.connect(transport);
|
|
3648
3688
|
console.error("SQG MCP server running on stdio");
|
|
3649
3689
|
}
|
|
3650
|
-
|
|
3651
3690
|
//#endregion
|
|
3652
3691
|
//#region src/ui.ts
|
|
3653
3692
|
/**
|
|
@@ -3785,10 +3824,9 @@ function formatMs(ms) {
|
|
|
3785
3824
|
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
3786
3825
|
return `${(ms / 1e3).toFixed(1)}s`;
|
|
3787
3826
|
}
|
|
3788
|
-
|
|
3789
3827
|
//#endregion
|
|
3790
3828
|
//#region src/sqg.ts
|
|
3791
|
-
const version = process.env.npm_package_version ?? "0.
|
|
3829
|
+
const version = process.env.npm_package_version ?? "0.16.0";
|
|
3792
3830
|
updateNotifier({ pkg: {
|
|
3793
3831
|
name: "@sqg/sqg",
|
|
3794
3832
|
version
|
|
@@ -3908,6 +3946,13 @@ program.command("init").description("Initialize a new SQG project with example c
|
|
|
3908
3946
|
program.command("syntax").description("Show SQL annotation syntax reference").action(() => {
|
|
3909
3947
|
console.log(SQL_SYNTAX_REFERENCE);
|
|
3910
3948
|
});
|
|
3949
|
+
program.command("ide").description("Start the SQG IDE — interactive SQL development environment").argument("[project]", "Path to sqg.yaml project config").option("-p, --port <port>", "Server port", "3000").action(async (project, options) => {
|
|
3950
|
+
const { startIde } = await import("./ide-BieFvmwk.mjs");
|
|
3951
|
+
await startIde({
|
|
3952
|
+
project,
|
|
3953
|
+
port: parseInt(options.port, 10)
|
|
3954
|
+
});
|
|
3955
|
+
});
|
|
3911
3956
|
program.command("mcp").description("Start MCP (Model Context Protocol) server for AI assistants").action(async () => {
|
|
3912
3957
|
try {
|
|
3913
3958
|
await startMcpServer();
|
|
@@ -3926,6 +3971,5 @@ try {
|
|
|
3926
3971
|
consola.error(err);
|
|
3927
3972
|
exit(1);
|
|
3928
3973
|
}
|
|
3929
|
-
|
|
3930
3974
|
//#endregion
|
|
3931
|
-
export {
|
|
3975
|
+
export {};
|
|
@@ -29,8 +29,14 @@ import java.util.function.Function;
|
|
|
29
29
|
import java.util.stream.Stream;
|
|
30
30
|
import java.util.stream.StreamSupport;
|
|
31
31
|
{{#if tables.length}}
|
|
32
|
+
{{#if (isDuckDB)}}
|
|
32
33
|
import org.duckdb.DuckDBAppender;
|
|
33
34
|
import org.duckdb.DuckDBConnection;
|
|
35
|
+
{{else if (isPostgres)}}
|
|
36
|
+
// Requires dependency: de.bytefish:pgbulkinsert:9.0.0
|
|
37
|
+
import de.bytefish.pgbulkinsert.PgBulkInsert;
|
|
38
|
+
import java.io.IOException;
|
|
39
|
+
{{/if}}
|
|
34
40
|
{{/if}}
|
|
35
41
|
|
|
36
42
|
public class {{className}} {
|
|
@@ -61,20 +67,28 @@ public class {{className}} {
|
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
private static LocalDateTime toLocalDateTime(
|
|
65
|
-
|
|
70
|
+
private static LocalDateTime toLocalDateTime(Object ts) {
|
|
71
|
+
if (ts == null) return null;
|
|
72
|
+
if (ts instanceof LocalDateTime ldt) return ldt;
|
|
73
|
+
return ((java.sql.Timestamp) ts).toLocalDateTime();
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
private static LocalDate toLocalDate(
|
|
69
|
-
|
|
76
|
+
private static LocalDate toLocalDate(Object d) {
|
|
77
|
+
if (d == null) return null;
|
|
78
|
+
if (d instanceof LocalDate ld) return ld;
|
|
79
|
+
return ((java.sql.Date) d).toLocalDate();
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
private static LocalTime toLocalTime(
|
|
73
|
-
|
|
82
|
+
private static LocalTime toLocalTime(Object t) {
|
|
83
|
+
if (t == null) return null;
|
|
84
|
+
if (t instanceof LocalTime lt) return lt;
|
|
85
|
+
return ((java.sql.Time) t).toLocalTime();
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
private static OffsetDateTime toOffsetDateTime(
|
|
77
|
-
|
|
88
|
+
private static OffsetDateTime toOffsetDateTime(Object ts) {
|
|
89
|
+
if (ts == null) return null;
|
|
90
|
+
if (ts instanceof OffsetDateTime odt) return odt;
|
|
91
|
+
return ((java.sql.Timestamp) ts).toInstant().atOffset(java.time.ZoneOffset.UTC);
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
private static <K> List<K> arrayToList(
|
|
@@ -250,18 +264,30 @@ public class {{className}} {
|
|
|
250
264
|
// ==================== Appenders ====================
|
|
251
265
|
{{#each tables}}
|
|
252
266
|
|
|
267
|
+
{{#if (isDuckDB)}}
|
|
253
268
|
/** Create an appender for bulk inserts into {{tableName}} */
|
|
254
269
|
public {{className}} {{functionName}}() throws SQLException {
|
|
255
270
|
return new {{className}}(((DuckDBConnection) connection).createAppender(DuckDBConnection.DEFAULT_SCHEMA, "{{tableName}}"));
|
|
256
271
|
}
|
|
272
|
+
{{else if (isPostgres)}}
|
|
273
|
+
/** Bulk insert rows into {{tableName}} using PostgreSQL COPY BINARY (via PgBulkInsert). */
|
|
274
|
+
public void bulkInsert{{rowTypeName}}(Iterable<{{rowTypeName}}> rows) throws SQLException, IOException {
|
|
275
|
+
{{constantName}}_WRITER.saveAll(connection, "{{tableName}}", rows);
|
|
276
|
+
}
|
|
277
|
+
{{/if}}
|
|
257
278
|
{{/each}}
|
|
258
279
|
{{/if}}
|
|
259
280
|
|
|
260
281
|
|
|
261
282
|
{{#each tables}}
|
|
262
283
|
/** Row type for {{tableName}} appender */
|
|
263
|
-
|
|
284
|
+
{{#if (isPostgres)}}
|
|
285
|
+
public record {{rowTypeName}}({{#each columns}}{{{appenderType this}}} {{javaVarName name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
|
|
286
|
+
{{else}}
|
|
287
|
+
public record {{rowTypeName}}({{#each columns}}{{{appenderType this}}} {{javaVarName name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
|
|
288
|
+
{{/if}}
|
|
264
289
|
|
|
290
|
+
{{#if (isDuckDB)}}
|
|
265
291
|
/** Appender for bulk inserts into {{tableName}} */
|
|
266
292
|
public static class {{className}} implements AutoCloseable {
|
|
267
293
|
private final DuckDBAppender appender;
|
|
@@ -279,17 +305,17 @@ public static class {{className}} implements AutoCloseable {
|
|
|
279
305
|
public {{className}} append({{rowTypeName}} row) throws SQLException {
|
|
280
306
|
appender.beginRow();
|
|
281
307
|
{{#each columns}}
|
|
282
|
-
appender.append(row.{{name}}());
|
|
308
|
+
appender.append(row.{{javaVarName name}}());
|
|
283
309
|
{{/each}}
|
|
284
310
|
appender.endRow();
|
|
285
311
|
return this;
|
|
286
312
|
}
|
|
287
313
|
|
|
288
314
|
/** Append a single row with individual values */
|
|
289
|
-
public {{className}} append({{#each columns}}{{{appenderType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
|
|
315
|
+
public {{className}} append({{#each columns}}{{{appenderType this}}} {{javaVarName name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
|
|
290
316
|
appender.beginRow();
|
|
291
317
|
{{#each columns}}
|
|
292
|
-
appender.append({{name}});
|
|
318
|
+
appender.append({{javaVarName name}});
|
|
293
319
|
{{/each}}
|
|
294
320
|
appender.endRow();
|
|
295
321
|
return this;
|
|
@@ -309,6 +335,16 @@ public static class {{className}} implements AutoCloseable {
|
|
|
309
335
|
appender.close();
|
|
310
336
|
}
|
|
311
337
|
}
|
|
338
|
+
{{else if (isPostgres)}}
|
|
339
|
+
private static final PgBulkInsert.PgMapper<{{rowTypeName}}> {{constantName}}_MAPPER =
|
|
340
|
+
PgBulkInsert.PgMapper.forClass({{rowTypeName}}.class)
|
|
341
|
+
{{#each columns}}
|
|
342
|
+
.map("{{name}}", PgBulkInsert.PostgresTypes.{{{pgBulkType this}}}.{{{pgBulkAccessor this}}}({{../rowTypeName}}::{{javaVarName name}}))
|
|
343
|
+
{{/each}};
|
|
344
|
+
|
|
345
|
+
private static final PgBulkInsert.PgBulkWriter<{{rowTypeName}}> {{constantName}}_WRITER =
|
|
346
|
+
new PgBulkInsert.PgBulkWriter<>({{constantName}}_MAPPER);
|
|
347
|
+
{{/if}}
|
|
312
348
|
{{/each}}
|
|
313
349
|
}
|
|
314
350
|
|
|
@@ -204,19 +204,21 @@ class {{className}}:
|
|
|
204
204
|
{{/if}}
|
|
205
205
|
{{/unless}}
|
|
206
206
|
{{/each}}
|
|
207
|
-
{{#if (isDuckDB)}}
|
|
208
207
|
{{#if tables.length}}
|
|
209
208
|
# ==================== Appenders ====================
|
|
210
209
|
{{#each tables}}
|
|
211
210
|
|
|
211
|
+
{{#if (isDuckDB)}}
|
|
212
212
|
def {{functionName}}(self) -> {{className}}:
|
|
213
213
|
return {{className}}(self._conn.cursor())
|
|
214
|
+
{{else if (isPostgres)}}
|
|
215
|
+
def {{functionName}}(self) -> {{className}}:
|
|
216
|
+
return {{className}}(self._conn)
|
|
217
|
+
{{/if}}
|
|
214
218
|
|
|
215
219
|
{{/each}}
|
|
216
220
|
{{/if}}
|
|
217
|
-
{{/if}}
|
|
218
221
|
|
|
219
|
-
{{#if (isDuckDB)}}
|
|
220
222
|
{{#each tables}}
|
|
221
223
|
|
|
222
224
|
@dataclass(frozen=True)
|
|
@@ -226,6 +228,7 @@ class {{rowTypeName}}:
|
|
|
226
228
|
{{/each}}
|
|
227
229
|
|
|
228
230
|
|
|
231
|
+
{{#if (isDuckDB)}}
|
|
229
232
|
class {{className}}:
|
|
230
233
|
def __init__(self, cursor: duckdb.DuckDBPyConnection) -> None:
|
|
231
234
|
self._cursor = cursor
|
|
@@ -244,10 +247,39 @@ class {{className}}:
|
|
|
244
247
|
|
|
245
248
|
def close(self) -> None:
|
|
246
249
|
self._cursor.close()
|
|
250
|
+
{{else if (isPostgres)}}
|
|
251
|
+
class {{className}}:
|
|
252
|
+
"""COPY appender for high-performance bulk inserts into {{tableName}}."""
|
|
247
253
|
|
|
248
|
-
|
|
254
|
+
def __init__(self, conn: psycopg.Connection) -> None:
|
|
255
|
+
self._conn = conn
|
|
256
|
+
self._copy: psycopg.Copy | None = None
|
|
257
|
+
|
|
258
|
+
def __enter__(self) -> {{className}}:
|
|
259
|
+
cur = self._conn.cursor()
|
|
260
|
+
self._copy = cur.copy("COPY {{tableName}} ({{#each columns}}{{name}}{{#unless @last}}, {{/unless}}{{/each}}) FROM STDIN")
|
|
261
|
+
self._copy.__enter__()
|
|
262
|
+
return self
|
|
263
|
+
|
|
264
|
+
def __exit__(self, *args: object) -> None:
|
|
265
|
+
if self._copy is not None:
|
|
266
|
+
self._copy.__exit__(*args)
|
|
267
|
+
self._copy = None
|
|
268
|
+
|
|
269
|
+
def append(self, row: {{rowTypeName}}) -> {{className}}:
|
|
270
|
+
if self._copy is None:
|
|
271
|
+
raise RuntimeError("Use as context manager: with appender:")
|
|
272
|
+
self._copy.write_row(({{#each columns}}row.{{pyVarName name}}, {{/each}}))
|
|
273
|
+
return self
|
|
274
|
+
|
|
275
|
+
def append_many(self, rows: list[{{rowTypeName}}]) -> {{className}}:
|
|
276
|
+
for row in rows:
|
|
277
|
+
self.append(row)
|
|
278
|
+
return self
|
|
249
279
|
{{/if}}
|
|
250
280
|
|
|
281
|
+
{{/each}}
|
|
282
|
+
|
|
251
283
|
{{!-- ==================== Inline partials ==================== --}}
|
|
252
284
|
|
|
253
285
|
{{#*inline "params"}}{{#if (isDuckDB)}}[{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}]{{else}}({{#each parameterNames}}{{this}}, {{/each}}){{/if}}{{/inline}}
|