@sqg/sqg 0.10.0 → 0.12.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/sqg.mjs +158 -85
- package/dist/templates/java-jdbc.hbs +8 -6
- package/package.json +6 -4
package/dist/sqg.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { exit } from "node:process";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import consola, { LogLevels } from "consola";
|
|
5
|
+
import updateNotifier from "update-notifier";
|
|
5
6
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -17,6 +18,7 @@ import { LRParser } from "@lezer/lr";
|
|
|
17
18
|
import { camelCase, isNotNil, pascalCase, sortBy } from "es-toolkit";
|
|
18
19
|
import { Client } from "pg";
|
|
19
20
|
import types from "pg-types";
|
|
21
|
+
import { PostgreSqlContainer } from "@testcontainers/postgresql";
|
|
20
22
|
import BetterSqlite3 from "better-sqlite3";
|
|
21
23
|
import { camelCase as camelCase$1, pascalCase as pascalCase$1 } from "es-toolkit/string";
|
|
22
24
|
import prettier from "prettier/standalone";
|
|
@@ -1088,84 +1090,141 @@ const duckdb = new class {
|
|
|
1088
1090
|
|
|
1089
1091
|
//#endregion
|
|
1090
1092
|
//#region src/db/postgres.ts
|
|
1091
|
-
const
|
|
1092
|
-
let containerInstance = null;
|
|
1093
|
-
async function startTestContainer() {
|
|
1094
|
-
if (containerInstance) return containerInstance.getConnectionUri();
|
|
1095
|
-
consola.info("Starting PostgreSQL container via testcontainers...");
|
|
1096
|
-
const { PostgreSqlContainer } = await import("@testcontainers/postgresql");
|
|
1097
|
-
containerInstance = await new PostgreSqlContainer("postgres:16-alpine").withDatabase("sqg-db").withUsername("sqg").withPassword("secret").start();
|
|
1098
|
-
const connectionUri = containerInstance.getConnectionUri();
|
|
1099
|
-
consola.success(`PostgreSQL container started at: ${connectionUri}`);
|
|
1100
|
-
return connectionUri;
|
|
1101
|
-
}
|
|
1102
|
-
async function stopTestContainer() {
|
|
1103
|
-
if (containerInstance) {
|
|
1104
|
-
consola.info("Stopping PostgreSQL container...");
|
|
1105
|
-
await containerInstance.stop();
|
|
1106
|
-
containerInstance = null;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
async function getConnectionString() {
|
|
1110
|
-
if (process.env.SQG_POSTGRES_URL) return process.env.SQG_POSTGRES_URL;
|
|
1111
|
-
return await startTestContainer();
|
|
1112
|
-
}
|
|
1113
|
-
function getTempConnectionString(baseUrl) {
|
|
1114
|
-
return baseUrl.replace(/\/[^/]+$/, `/${databaseName}`);
|
|
1115
|
-
}
|
|
1093
|
+
const tempDatabaseName = "sqg-db-temp";
|
|
1116
1094
|
const typeIdToName = /* @__PURE__ */ new Map();
|
|
1117
1095
|
for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
`);
|
|
1127
|
-
dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1128
|
-
for (const row of result.rows) {
|
|
1129
|
-
const oid = row.oid;
|
|
1130
|
-
let typeName = row.typname;
|
|
1131
|
-
if (typeName.startsWith("_") && row.elemtype) typeName = `_${row.elemtype.toUpperCase()}`;
|
|
1132
|
-
else typeName = typeName.toUpperCase();
|
|
1133
|
-
dynamicTypeCache.set(oid, typeName);
|
|
1096
|
+
/**
|
|
1097
|
+
* External database mode: connects directly to the user's database.
|
|
1098
|
+
* Uses a single transaction for the entire session and rolls back on close,
|
|
1099
|
+
* so the database is never modified. Individual queries use savepoints.
|
|
1100
|
+
*/
|
|
1101
|
+
var ExternalDbMode = class {
|
|
1102
|
+
constructor(connectionString) {
|
|
1103
|
+
this.connectionString = connectionString;
|
|
1134
1104
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1105
|
+
async connect() {
|
|
1106
|
+
const db = new Client({ connectionString: this.connectionString });
|
|
1107
|
+
try {
|
|
1108
|
+
await db.connect();
|
|
1109
|
+
} catch (e) {
|
|
1110
|
+
throw new DatabaseError(`Failed to connect to PostgreSQL: ${e.message}`, "postgres", `Check that PostgreSQL is running and accessible at ${this.connectionString}.`);
|
|
1111
|
+
}
|
|
1112
|
+
await db.query("BEGIN");
|
|
1113
|
+
return db;
|
|
1114
|
+
}
|
|
1115
|
+
async wrapQuery(db, fn) {
|
|
1116
|
+
try {
|
|
1117
|
+
await db.query("SAVEPOINT sqg_query");
|
|
1118
|
+
return await fn();
|
|
1119
|
+
} finally {
|
|
1120
|
+
await db.query("ROLLBACK TO SAVEPOINT sqg_query");
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
async close(db) {
|
|
1124
|
+
await db.query("ROLLBACK");
|
|
1125
|
+
await db.end();
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
/**
|
|
1129
|
+
* Temp database mode: creates a temporary database for SQG to work in.
|
|
1130
|
+
* Connects to the provided server first (dbInitial) to CREATE the temp DB,
|
|
1131
|
+
* then connects to the temp DB for all operations.
|
|
1132
|
+
* On close, drops the temp DB and optionally stops the testcontainer.
|
|
1133
|
+
*/
|
|
1134
|
+
var TempDbMode = class {
|
|
1142
1135
|
dbInitial;
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
this.dbInitial = new Client({ connectionString });
|
|
1150
|
-
this.db = new Client({ connectionString: connectionStringTemp });
|
|
1136
|
+
container = null;
|
|
1137
|
+
constructor(connectionString, container) {
|
|
1138
|
+
this.connectionString = connectionString;
|
|
1139
|
+
this.container = container;
|
|
1140
|
+
}
|
|
1141
|
+
async connect() {
|
|
1142
|
+
this.dbInitial = new Client({ connectionString: this.connectionString });
|
|
1151
1143
|
try {
|
|
1152
1144
|
await this.dbInitial.connect();
|
|
1153
1145
|
} catch (e) {
|
|
1154
|
-
throw new DatabaseError(`Failed to connect to PostgreSQL: ${e.message}`, "postgres", `Check that PostgreSQL is running and accessible at ${connectionString}. Set SQG_POSTGRES_URL environment variable to use a different connection string.`);
|
|
1146
|
+
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.`);
|
|
1155
1147
|
}
|
|
1156
1148
|
try {
|
|
1157
|
-
await this.dbInitial.query(`DROP DATABASE IF EXISTS "${
|
|
1149
|
+
await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}";`);
|
|
1158
1150
|
} catch (error) {}
|
|
1159
1151
|
try {
|
|
1160
|
-
await this.dbInitial.query(`CREATE DATABASE "${
|
|
1152
|
+
await this.dbInitial.query(`CREATE DATABASE "${tempDatabaseName}";`);
|
|
1161
1153
|
} catch (error) {
|
|
1162
1154
|
throw new DatabaseError(`Failed to create temporary database: ${error.message}`, "postgres", "Check PostgreSQL user permissions to create databases");
|
|
1163
1155
|
}
|
|
1156
|
+
const db = new Client({ connectionString: this.connectionString.replace(/\/[^/]+$/, `/${tempDatabaseName}`) });
|
|
1164
1157
|
try {
|
|
1165
|
-
await
|
|
1158
|
+
await db.connect();
|
|
1166
1159
|
} catch (e) {
|
|
1167
1160
|
throw new DatabaseError(`Failed to connect to temporary database: ${e.message}`, "postgres");
|
|
1168
1161
|
}
|
|
1162
|
+
return db;
|
|
1163
|
+
}
|
|
1164
|
+
async wrapQuery(db, fn) {
|
|
1165
|
+
try {
|
|
1166
|
+
await db.query("BEGIN");
|
|
1167
|
+
return await fn();
|
|
1168
|
+
} finally {
|
|
1169
|
+
await db.query("ROLLBACK");
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
async close(db) {
|
|
1173
|
+
await db.end();
|
|
1174
|
+
await this.dbInitial.query(`DROP DATABASE IF EXISTS "${tempDatabaseName}"`);
|
|
1175
|
+
await this.dbInitial.end();
|
|
1176
|
+
if (this.container) {
|
|
1177
|
+
consola.info("Stopping PostgreSQL container...");
|
|
1178
|
+
await this.container.stop();
|
|
1179
|
+
this.container = null;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
const postgres = new class {
|
|
1184
|
+
db;
|
|
1185
|
+
mode;
|
|
1186
|
+
dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1187
|
+
async startContainer() {
|
|
1188
|
+
consola.info("Starting PostgreSQL container via testcontainers...");
|
|
1189
|
+
const container = await new PostgreSqlContainer("postgres:16-alpine").withDatabase("sqg-db").withUsername("sqg").withPassword("secret").start();
|
|
1190
|
+
const connectionUri = container.getConnectionUri();
|
|
1191
|
+
consola.success(`PostgreSQL container started at: ${connectionUri}`);
|
|
1192
|
+
return {
|
|
1193
|
+
connectionUri,
|
|
1194
|
+
container
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
async loadTypeCache(db) {
|
|
1198
|
+
const result = await db.query(`
|
|
1199
|
+
SELECT t.oid, t.typname, t.typtype, t.typelem, et.typname AS elemtype
|
|
1200
|
+
FROM pg_type t
|
|
1201
|
+
LEFT JOIN pg_type et ON t.typelem = et.oid
|
|
1202
|
+
WHERE t.typtype IN ('b', 'e', 'r', 'c') -- base, enum, range, composite
|
|
1203
|
+
OR t.typelem != 0 -- array types
|
|
1204
|
+
`);
|
|
1205
|
+
this.dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const row of result.rows) {
|
|
1207
|
+
const oid = row.oid;
|
|
1208
|
+
let typeName = row.typname;
|
|
1209
|
+
if (typeName.startsWith("_") && row.elemtype) typeName = `_${row.elemtype.toUpperCase()}`;
|
|
1210
|
+
else typeName = typeName.toUpperCase();
|
|
1211
|
+
this.dynamicTypeCache.set(oid, typeName);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
getTypeName(dataTypeID) {
|
|
1215
|
+
const cached = this.dynamicTypeCache.get(dataTypeID);
|
|
1216
|
+
if (cached) return cached;
|
|
1217
|
+
return typeIdToName.get(dataTypeID) || `type_${dataTypeID}`;
|
|
1218
|
+
}
|
|
1219
|
+
async initializeDatabase(queries) {
|
|
1220
|
+
const externalUrl = process.env.SQG_POSTGRES_URL;
|
|
1221
|
+
this.dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1222
|
+
if (externalUrl) this.mode = new ExternalDbMode(externalUrl);
|
|
1223
|
+
else {
|
|
1224
|
+
const { connectionUri, container } = await this.startContainer();
|
|
1225
|
+
this.mode = new TempDbMode(connectionUri, container);
|
|
1226
|
+
}
|
|
1227
|
+
this.db = await this.mode.connect();
|
|
1169
1228
|
await initializeDatabase(queries, async (query) => {
|
|
1170
1229
|
try {
|
|
1171
1230
|
await this.db.query(query.rawQuery);
|
|
@@ -1173,7 +1232,7 @@ const postgres = new class {
|
|
|
1173
1232
|
throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
|
|
1174
1233
|
}
|
|
1175
1234
|
});
|
|
1176
|
-
await loadTypeCache(this.db);
|
|
1235
|
+
await this.loadTypeCache(this.db);
|
|
1177
1236
|
}
|
|
1178
1237
|
async executeQueries(queries) {
|
|
1179
1238
|
const db = this.db;
|
|
@@ -1183,7 +1242,6 @@ const postgres = new class {
|
|
|
1183
1242
|
for (const query of executableQueries) {
|
|
1184
1243
|
consola.debug(`Executing query: ${query.id}`);
|
|
1185
1244
|
await this.executeQuery(db, query);
|
|
1186
|
-
if (query.isQuery) {}
|
|
1187
1245
|
consola.success(`Query ${query.id} executed successfully`);
|
|
1188
1246
|
}
|
|
1189
1247
|
} catch (error) {
|
|
@@ -1194,21 +1252,22 @@ const postgres = new class {
|
|
|
1194
1252
|
async executeQuery(db, query) {
|
|
1195
1253
|
const statement = query.queryPositional;
|
|
1196
1254
|
try {
|
|
1197
|
-
consola.
|
|
1255
|
+
consola.debug("Query:", statement.sql);
|
|
1198
1256
|
const parameterValues = statement.parameters.map((p) => {
|
|
1199
1257
|
const value = p.value;
|
|
1200
1258
|
if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
|
|
1201
1259
|
return value;
|
|
1202
1260
|
});
|
|
1203
1261
|
if (statement.parameters.length > 0) try {
|
|
1204
|
-
await db.query(
|
|
1262
|
+
await db.query("DEALLOCATE ALL");
|
|
1205
1263
|
await db.query(`PREPARE sqg_param_check AS ${statement.sql}`);
|
|
1206
1264
|
const paramTypeResult = await db.query(`SELECT unnest(parameter_types)::oid AS oid FROM pg_prepared_statements WHERE name = 'sqg_param_check'`);
|
|
1207
|
-
await db.query(
|
|
1265
|
+
await db.query("DEALLOCATE sqg_param_check");
|
|
1208
1266
|
if (paramTypeResult.rows.length === statement.parameters.length) {
|
|
1209
1267
|
const paramTypes = /* @__PURE__ */ new Map();
|
|
1210
1268
|
for (let i = 0; i < statement.parameters.length; i++) {
|
|
1211
|
-
const
|
|
1269
|
+
const oid = Number(paramTypeResult.rows[i].oid);
|
|
1270
|
+
const typeName = this.getTypeName(oid);
|
|
1212
1271
|
paramTypes.set(statement.parameters[i].name, typeName);
|
|
1213
1272
|
}
|
|
1214
1273
|
query.parameterTypes = paramTypes;
|
|
@@ -1217,17 +1276,11 @@ const postgres = new class {
|
|
|
1217
1276
|
} catch (e) {
|
|
1218
1277
|
consola.debug(`Parameter type introspection failed for ${query.id}, using heuristic:`, e.message);
|
|
1219
1278
|
}
|
|
1220
|
-
|
|
1221
|
-
try {
|
|
1222
|
-
await db.query("BEGIN");
|
|
1223
|
-
result = await db.query(statement.sql, parameterValues);
|
|
1224
|
-
} finally {
|
|
1225
|
-
await db.query("ROLLBACK");
|
|
1226
|
-
}
|
|
1279
|
+
const result = await this.mode.wrapQuery(db, () => db.query(statement.sql, parameterValues));
|
|
1227
1280
|
if (query.isQuery) {
|
|
1228
1281
|
const columnNames = result.fields.map((field) => field.name);
|
|
1229
1282
|
const columnTypes = result.fields.map((field) => {
|
|
1230
|
-
return getTypeName(field.dataTypeID);
|
|
1283
|
+
return this.getTypeName(field.dataTypeID);
|
|
1231
1284
|
});
|
|
1232
1285
|
consola.debug("Columns:", columnNames);
|
|
1233
1286
|
consola.debug("Types:", columnTypes);
|
|
@@ -1269,10 +1322,8 @@ const postgres = new class {
|
|
|
1269
1322
|
}
|
|
1270
1323
|
}
|
|
1271
1324
|
async close() {
|
|
1272
|
-
await this.
|
|
1273
|
-
|
|
1274
|
-
await this.dbInitial.end();
|
|
1275
|
-
if (this.usingTestContainer) await stopTestContainer();
|
|
1325
|
+
await this.mode.close(this.db);
|
|
1326
|
+
this.dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1276
1327
|
}
|
|
1277
1328
|
}();
|
|
1278
1329
|
|
|
@@ -1459,7 +1510,7 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1459
1510
|
TIMESTAMP_S: "Instant",
|
|
1460
1511
|
TIMESTAMP_MS: "Instant",
|
|
1461
1512
|
TIMESTAMP_NS: "Instant",
|
|
1462
|
-
"TIMESTAMP WITH TIME ZONE": "
|
|
1513
|
+
"TIMESTAMP WITH TIME ZONE": "OffsetDateTime",
|
|
1463
1514
|
UUID: "UUID",
|
|
1464
1515
|
INTERVAL: "String",
|
|
1465
1516
|
BIT: "String",
|
|
@@ -1600,6 +1651,7 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1600
1651
|
const upperType = column.type?.toString().toUpperCase() ?? "";
|
|
1601
1652
|
if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime((java.sql.Timestamp)${value})`;
|
|
1602
1653
|
if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime((java.sql.Timestamp)${value})`;
|
|
1654
|
+
if (upperType === "TIMESTAMP WITH TIME ZONE") return `(OffsetDateTime)${value}`;
|
|
1603
1655
|
if (upperType === "DATE") return `toLocalDate((java.sql.Date)${value})`;
|
|
1604
1656
|
if (upperType === "TIME") return `toLocalTime((java.sql.Time)${value})`;
|
|
1605
1657
|
if (upperType.startsWith("_")) {
|
|
@@ -1857,6 +1909,9 @@ var JavaGenerator = class extends BaseGenerator {
|
|
|
1857
1909
|
}, " ");
|
|
1858
1910
|
return queryHelper.typeMapper.getDeclarations(query.allColumns);
|
|
1859
1911
|
});
|
|
1912
|
+
Handlebars.registerHelper("appenderType", (column) => {
|
|
1913
|
+
return this.mapType(column);
|
|
1914
|
+
});
|
|
1860
1915
|
Handlebars.registerHelper("readColumns", (queryHelper) => {
|
|
1861
1916
|
const query = queryHelper.query;
|
|
1862
1917
|
if (queryHelper.isPluck) return this.readColumn({
|
|
@@ -2346,10 +2401,24 @@ var SqlQueryHelper = class {
|
|
|
2346
2401
|
}
|
|
2347
2402
|
get parameters() {
|
|
2348
2403
|
const vars = new Map(this.variables.map((param) => [param.name, param.type]));
|
|
2349
|
-
return this.statement.parameters.map((param) =>
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2404
|
+
return this.statement.parameters.map((param) => {
|
|
2405
|
+
const rawType = this.query.parameterTypes?.get(param.name);
|
|
2406
|
+
let isArray = false;
|
|
2407
|
+
let arrayBaseType = null;
|
|
2408
|
+
if (rawType instanceof ListType) {
|
|
2409
|
+
isArray = true;
|
|
2410
|
+
arrayBaseType = rawType.baseType.toString();
|
|
2411
|
+
} else if (typeof rawType === "string" && rawType.startsWith("_")) {
|
|
2412
|
+
isArray = true;
|
|
2413
|
+
arrayBaseType = rawType.substring(1);
|
|
2414
|
+
}
|
|
2415
|
+
return {
|
|
2416
|
+
name: param.name,
|
|
2417
|
+
type: vars.get(param.name),
|
|
2418
|
+
isArray,
|
|
2419
|
+
arrayBaseType
|
|
2420
|
+
};
|
|
2421
|
+
});
|
|
2353
2422
|
}
|
|
2354
2423
|
get columns() {
|
|
2355
2424
|
if (!(this.query.allColumns.type instanceof StructType)) throw new Error(`Expected StructType ${this.query.allColumns.type}`);
|
|
@@ -2725,7 +2794,7 @@ async function processProject(projectPath) {
|
|
|
2725
2794
|
//#region src/mcp-server.ts
|
|
2726
2795
|
const server = new Server({
|
|
2727
2796
|
name: "sqg-mcp",
|
|
2728
|
-
version: process.env.npm_package_version ?? "0.
|
|
2797
|
+
version: process.env.npm_package_version ?? "0.12.0"
|
|
2729
2798
|
}, { capabilities: {
|
|
2730
2799
|
tools: {},
|
|
2731
2800
|
resources: {}
|
|
@@ -3054,7 +3123,11 @@ async function startMcpServer() {
|
|
|
3054
3123
|
|
|
3055
3124
|
//#endregion
|
|
3056
3125
|
//#region src/sqg.ts
|
|
3057
|
-
const version = process.env.npm_package_version ?? "0.
|
|
3126
|
+
const version = process.env.npm_package_version ?? "0.12.0";
|
|
3127
|
+
updateNotifier({ pkg: {
|
|
3128
|
+
name: "@sqg/sqg",
|
|
3129
|
+
version
|
|
3130
|
+
} }).notify({ message: "Update available {currentVersion} → {latestVersion}" });
|
|
3058
3131
|
const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
|
|
3059
3132
|
consola.level = LogLevels.info;
|
|
3060
3133
|
const program = new Command().name("sqg").description(`${description}
|
|
@@ -212,8 +212,9 @@ public class {{className}} {
|
|
|
212
212
|
{{#if isQuery}}{{#unless isOne}}
|
|
213
213
|
public Stream<{{rowType}}> {{functionName}}Stream({{#each variables}}{{{type}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
|
|
214
214
|
var stmt = connection.prepareStatement({{{partsToString sqlQueryParts}}});
|
|
215
|
-
{{#each
|
|
216
|
-
{{
|
|
215
|
+
{{#each parameters}}{{#if isArray}}stmt.setArray({{plusOne @index}}, connection.createArrayOf("{{arrayBaseType}}", {{name}}.toArray()));
|
|
216
|
+
{{else}}stmt.setObject({{plusOne @index}}, {{name}});
|
|
217
|
+
{{/if}}{{/each}}
|
|
217
218
|
var rs = stmt.executeQuery();
|
|
218
219
|
var iter = new Iterator<{{rowType}}>() {
|
|
219
220
|
private Boolean hasNext = null;
|
|
@@ -256,7 +257,7 @@ public class {{className}} {
|
|
|
256
257
|
|
|
257
258
|
{{#each tables}}
|
|
258
259
|
/** Row type for {{tableName}} appender */
|
|
259
|
-
public record {{rowTypeName}}({{#each columns}}{{{
|
|
260
|
+
public record {{rowTypeName}}({{#each columns}}{{{appenderType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) {}
|
|
260
261
|
|
|
261
262
|
/** Appender for bulk inserts into {{tableName}} */
|
|
262
263
|
public static class {{className}} implements AutoCloseable {
|
|
@@ -282,7 +283,7 @@ public static class {{className}} implements AutoCloseable {
|
|
|
282
283
|
}
|
|
283
284
|
|
|
284
285
|
/** Append a single row with individual values */
|
|
285
|
-
public {{className}} append({{#each columns}}{{{
|
|
286
|
+
public {{className}} append({{#each columns}}{{{appenderType this}}} {{name}}{{#unless @last}}, {{/unless}}{{/each}}) throws SQLException {
|
|
286
287
|
appender.beginRow();
|
|
287
288
|
{{#each columns}}
|
|
288
289
|
appender.append({{name}});
|
|
@@ -327,8 +328,9 @@ int
|
|
|
327
328
|
{{/inline~}}
|
|
328
329
|
|
|
329
330
|
{{#*inline "execute"}}
|
|
330
|
-
{{#each
|
|
331
|
-
{{
|
|
331
|
+
{{#each parameters}}{{#if isArray}}stmt.setArray({{plusOne @index}}, connection.createArrayOf("{{arrayBaseType}}", {{name}}.toArray()));
|
|
332
|
+
{{else}}stmt.setObject({{plusOne @index}}, {{name}});
|
|
333
|
+
{{/if}}{{/each}}
|
|
332
334
|
{{#if isQuery}}
|
|
333
335
|
try(var rs = stmt.executeQuery()) {
|
|
334
336
|
{{#if isOne}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqg/sqg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
"@lezer/common": "^1.5.0",
|
|
46
46
|
"@lezer/generator": "^1.8.0",
|
|
47
47
|
"@lezer/lr": "^1.4.6",
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
49
|
+
"@testcontainers/postgresql": "^10.21.0",
|
|
48
50
|
"better-sqlite3": "^12.5.0",
|
|
49
51
|
"commander": "^14.0.2",
|
|
50
52
|
"consola": "^3.4.2",
|
|
@@ -53,12 +55,11 @@
|
|
|
53
55
|
"handlebars": "^4.7.8",
|
|
54
56
|
"pg": "^8.16.3",
|
|
55
57
|
"pg-types": "^4.1.0",
|
|
56
|
-
"@testcontainers/postgresql": "^10.21.0",
|
|
57
58
|
"prettier": "^3.7.4",
|
|
58
59
|
"prettier-plugin-java": "^2.7.7",
|
|
60
|
+
"update-notifier": "^7.3.1",
|
|
59
61
|
"yaml": "^2.8.2",
|
|
60
|
-
"zod": "^4.3.5"
|
|
61
|
-
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
62
|
+
"zod": "^4.3.5"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@libsql/client": "^0.17.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@types/better-sqlite3": "^7.6.13",
|
|
67
68
|
"@types/node": "^25.0.3",
|
|
68
69
|
"@types/pg": "^8.16.0",
|
|
70
|
+
"@types/update-notifier": "^6.0.8",
|
|
69
71
|
"@vitest/ui": "^4.0.16",
|
|
70
72
|
"tsdown": "0.18.0",
|
|
71
73
|
"tsx": "^4.21.0",
|