@sqg/sqg 0.6.0 → 0.8.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 +137 -19
- package/dist/templates/better-sqlite3.hbs +31 -1
- package/dist/templates/java-duckdb-arrow.hbs +11 -1
- package/dist/templates/java-jdbc.hbs +63 -1
- package/dist/templates/libsql.hbs +141 -0
- package/dist/templates/node-sqlite.hbs +33 -0
- package/dist/templates/turso.hbs +147 -0
- package/dist/templates/typescript-duckdb.hbs +37 -1
- package/package.json +4 -1
package/dist/sqg.mjs
CHANGED
|
@@ -55,6 +55,22 @@ const GENERATORS = {
|
|
|
55
55
|
extension: ".ts",
|
|
56
56
|
template: "node-sqlite.hbs"
|
|
57
57
|
},
|
|
58
|
+
"typescript/sqlite/libsql": {
|
|
59
|
+
language: "typescript",
|
|
60
|
+
engine: "sqlite",
|
|
61
|
+
driver: "libsql",
|
|
62
|
+
description: "TypeScript with @libsql/client (Turso)",
|
|
63
|
+
extension: ".ts",
|
|
64
|
+
template: "libsql.hbs"
|
|
65
|
+
},
|
|
66
|
+
"typescript/sqlite/turso": {
|
|
67
|
+
language: "typescript",
|
|
68
|
+
engine: "sqlite",
|
|
69
|
+
driver: "turso",
|
|
70
|
+
description: "TypeScript with Turso (limbo) native driver",
|
|
71
|
+
extension: ".ts",
|
|
72
|
+
template: "turso.hbs"
|
|
73
|
+
},
|
|
58
74
|
"typescript/duckdb/node-api": {
|
|
59
75
|
language: "typescript",
|
|
60
76
|
engine: "duckdb",
|
|
@@ -1078,14 +1094,63 @@ const duckdb = new class {
|
|
|
1078
1094
|
//#endregion
|
|
1079
1095
|
//#region src/db/postgres.ts
|
|
1080
1096
|
const databaseName = "sqg-db-temp";
|
|
1081
|
-
|
|
1082
|
-
|
|
1097
|
+
let containerInstance = null;
|
|
1098
|
+
async function startTestContainer() {
|
|
1099
|
+
if (containerInstance) return containerInstance.getConnectionUri();
|
|
1100
|
+
consola.info("Starting PostgreSQL container via testcontainers...");
|
|
1101
|
+
const { PostgreSqlContainer } = await import("@testcontainers/postgresql");
|
|
1102
|
+
containerInstance = await new PostgreSqlContainer("postgres:16-alpine").withDatabase("sqg-db").withUsername("sqg").withPassword("secret").start();
|
|
1103
|
+
const connectionUri = containerInstance.getConnectionUri();
|
|
1104
|
+
consola.success(`PostgreSQL container started at: ${connectionUri}`);
|
|
1105
|
+
return connectionUri;
|
|
1106
|
+
}
|
|
1107
|
+
async function stopTestContainer() {
|
|
1108
|
+
if (containerInstance) {
|
|
1109
|
+
consola.info("Stopping PostgreSQL container...");
|
|
1110
|
+
await containerInstance.stop();
|
|
1111
|
+
containerInstance = null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
async function getConnectionString() {
|
|
1115
|
+
if (process.env.SQG_POSTGRES_URL) return process.env.SQG_POSTGRES_URL;
|
|
1116
|
+
return await startTestContainer();
|
|
1117
|
+
}
|
|
1118
|
+
function getTempConnectionString(baseUrl) {
|
|
1119
|
+
return baseUrl.replace(/\/[^/]+$/, `/${databaseName}`);
|
|
1120
|
+
}
|
|
1083
1121
|
const typeIdToName = /* @__PURE__ */ new Map();
|
|
1084
1122
|
for (const [name, id] of Object.entries(types.builtins)) typeIdToName.set(Number(id), name);
|
|
1123
|
+
let dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1124
|
+
async function loadTypeCache(db) {
|
|
1125
|
+
const result = await db.query(`
|
|
1126
|
+
SELECT t.oid, t.typname, t.typtype, t.typelem, et.typname AS elemtype
|
|
1127
|
+
FROM pg_type t
|
|
1128
|
+
LEFT JOIN pg_type et ON t.typelem = et.oid
|
|
1129
|
+
WHERE t.typtype IN ('b', 'e', 'r', 'c') -- base, enum, range, composite
|
|
1130
|
+
OR t.typelem != 0 -- array types
|
|
1131
|
+
`);
|
|
1132
|
+
dynamicTypeCache = /* @__PURE__ */ new Map();
|
|
1133
|
+
for (const row of result.rows) {
|
|
1134
|
+
const oid = row.oid;
|
|
1135
|
+
let typeName = row.typname;
|
|
1136
|
+
if (typeName.startsWith("_") && row.elemtype) typeName = `_${row.elemtype.toUpperCase()}`;
|
|
1137
|
+
else typeName = typeName.toUpperCase();
|
|
1138
|
+
dynamicTypeCache.set(oid, typeName);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function getTypeName(dataTypeID) {
|
|
1142
|
+
const cached = dynamicTypeCache.get(dataTypeID);
|
|
1143
|
+
if (cached) return cached;
|
|
1144
|
+
return typeIdToName.get(dataTypeID) || `type_${dataTypeID}`;
|
|
1145
|
+
}
|
|
1085
1146
|
const postgres = new class {
|
|
1086
1147
|
dbInitial;
|
|
1087
1148
|
db;
|
|
1149
|
+
usingTestContainer = false;
|
|
1088
1150
|
async initializeDatabase(queries) {
|
|
1151
|
+
const connectionString = await getConnectionString();
|
|
1152
|
+
const connectionStringTemp = getTempConnectionString(connectionString);
|
|
1153
|
+
this.usingTestContainer = containerInstance !== null;
|
|
1089
1154
|
this.dbInitial = new Client({ connectionString });
|
|
1090
1155
|
this.db = new Client({ connectionString: connectionStringTemp });
|
|
1091
1156
|
try {
|
|
@@ -1094,7 +1159,7 @@ const postgres = new class {
|
|
|
1094
1159
|
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.`);
|
|
1095
1160
|
}
|
|
1096
1161
|
try {
|
|
1097
|
-
await this.dbInitial.query(`DROP DATABASE "${databaseName}";`);
|
|
1162
|
+
await this.dbInitial.query(`DROP DATABASE IF EXISTS "${databaseName}";`);
|
|
1098
1163
|
} catch (error) {}
|
|
1099
1164
|
try {
|
|
1100
1165
|
await this.dbInitial.query(`CREATE DATABASE "${databaseName}";`);
|
|
@@ -1113,6 +1178,7 @@ const postgres = new class {
|
|
|
1113
1178
|
throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
|
|
1114
1179
|
}
|
|
1115
1180
|
});
|
|
1181
|
+
await loadTypeCache(this.db);
|
|
1116
1182
|
}
|
|
1117
1183
|
async executeQueries(queries) {
|
|
1118
1184
|
const db = this.db;
|
|
@@ -1134,17 +1200,22 @@ const postgres = new class {
|
|
|
1134
1200
|
const statement = query.queryPositional;
|
|
1135
1201
|
try {
|
|
1136
1202
|
consola.info("Query:", statement.sql);
|
|
1203
|
+
const parameterValues = statement.parameters.map((p) => {
|
|
1204
|
+
const value = p.value;
|
|
1205
|
+
if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
|
|
1206
|
+
return value;
|
|
1207
|
+
});
|
|
1137
1208
|
let result;
|
|
1138
1209
|
try {
|
|
1139
1210
|
await db.query("BEGIN");
|
|
1140
|
-
result = await db.query(statement.sql,
|
|
1211
|
+
result = await db.query(statement.sql, parameterValues);
|
|
1141
1212
|
} finally {
|
|
1142
1213
|
await db.query("ROLLBACK");
|
|
1143
1214
|
}
|
|
1144
1215
|
if (query.isQuery) {
|
|
1145
1216
|
const columnNames = result.fields.map((field) => field.name);
|
|
1146
1217
|
const columnTypes = result.fields.map((field) => {
|
|
1147
|
-
return
|
|
1218
|
+
return getTypeName(field.dataTypeID);
|
|
1148
1219
|
});
|
|
1149
1220
|
consola.debug("Columns:", columnNames);
|
|
1150
1221
|
consola.debug("Types:", columnTypes);
|
|
@@ -1187,8 +1258,9 @@ const postgres = new class {
|
|
|
1187
1258
|
}
|
|
1188
1259
|
async close() {
|
|
1189
1260
|
await this.db.end();
|
|
1190
|
-
await this.dbInitial.query(`DROP DATABASE "${databaseName}"`);
|
|
1261
|
+
await this.dbInitial.query(`DROP DATABASE IF EXISTS "${databaseName}"`);
|
|
1191
1262
|
await this.dbInitial.end();
|
|
1263
|
+
if (this.usingTestContainer) await stopTestContainer();
|
|
1192
1264
|
}
|
|
1193
1265
|
}();
|
|
1194
1266
|
|
|
@@ -1380,7 +1452,20 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1380
1452
|
INTERVAL: "String",
|
|
1381
1453
|
BIT: "String",
|
|
1382
1454
|
BIGNUM: "BigDecimal",
|
|
1383
|
-
|
|
1455
|
+
INT2: "Short",
|
|
1456
|
+
INT4: "Integer",
|
|
1457
|
+
INT8: "Long",
|
|
1458
|
+
FLOAT4: "Float",
|
|
1459
|
+
FLOAT8: "Double",
|
|
1460
|
+
NUMERIC: "BigDecimal",
|
|
1461
|
+
BOOL: "Boolean",
|
|
1462
|
+
BYTEA: "byte[]",
|
|
1463
|
+
TIMESTAMPTZ: "OffsetDateTime",
|
|
1464
|
+
JSON: "String",
|
|
1465
|
+
JSONB: "String",
|
|
1466
|
+
OID: "Long",
|
|
1467
|
+
SERIAL: "Integer",
|
|
1468
|
+
BIGSERIAL: "Long"
|
|
1384
1469
|
};
|
|
1385
1470
|
static javaReservedKeywords = new Set([
|
|
1386
1471
|
"abstract",
|
|
@@ -1441,12 +1526,15 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1441
1526
|
const upperType = type.toString().toUpperCase();
|
|
1442
1527
|
const mappedType = this.typeMap[upperType];
|
|
1443
1528
|
if (mappedType) return mappedType;
|
|
1529
|
+
if (upperType.startsWith("_")) {
|
|
1530
|
+
const baseType = upperType.substring(1);
|
|
1531
|
+
return `List<${this.typeMap[baseType] || "Object"}>`;
|
|
1532
|
+
}
|
|
1444
1533
|
if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) return "BigDecimal";
|
|
1445
1534
|
if (upperType.startsWith("ENUM(")) return "String";
|
|
1446
1535
|
if (upperType.startsWith("UNION(")) return "Object";
|
|
1447
1536
|
if (/\[\d+\]/.test(upperType)) return "Object";
|
|
1448
|
-
|
|
1449
|
-
return "Object";
|
|
1537
|
+
return "String";
|
|
1450
1538
|
}
|
|
1451
1539
|
formatListType(elementType) {
|
|
1452
1540
|
return `List<${elementType}>`;
|
|
@@ -1499,8 +1587,13 @@ var JavaTypeMapper = class JavaTypeMapper extends TypeMapper {
|
|
|
1499
1587
|
const fieldType = this.getTypeName(column);
|
|
1500
1588
|
const upperType = column.type?.toString().toUpperCase() ?? "";
|
|
1501
1589
|
if (upperType === "TIMESTAMP" || upperType === "DATETIME") return `toLocalDateTime((java.sql.Timestamp)${value})`;
|
|
1590
|
+
if (upperType === "TIMESTAMPTZ") return `toOffsetDateTime((java.sql.Timestamp)${value})`;
|
|
1502
1591
|
if (upperType === "DATE") return `toLocalDate((java.sql.Date)${value})`;
|
|
1503
1592
|
if (upperType === "TIME") return `toLocalTime((java.sql.Time)${value})`;
|
|
1593
|
+
if (upperType.startsWith("_")) {
|
|
1594
|
+
const baseType = upperType.substring(1);
|
|
1595
|
+
return `arrayToList((Array)${value}, ${this.typeMap[baseType] || "Object"}[].class)`;
|
|
1596
|
+
}
|
|
1504
1597
|
return `(${fieldType})${value}`;
|
|
1505
1598
|
}
|
|
1506
1599
|
getInnermostType(type) {
|
|
@@ -1568,12 +1661,30 @@ var TypeScriptTypeMapper = class extends TypeMapper {
|
|
|
1568
1661
|
INTERVAL: "{ months: number; days: number; micros: bigint }",
|
|
1569
1662
|
BIT: "{ data: Uint8Array }",
|
|
1570
1663
|
BIGNUM: "bigint",
|
|
1571
|
-
|
|
1664
|
+
INT2: "number",
|
|
1665
|
+
INT4: "number",
|
|
1666
|
+
INT8: "bigint",
|
|
1667
|
+
FLOAT4: "number",
|
|
1668
|
+
FLOAT8: "number",
|
|
1669
|
+
NUMERIC: "string",
|
|
1670
|
+
BOOL: "boolean",
|
|
1671
|
+
BYTEA: "Buffer",
|
|
1672
|
+
TIMESTAMPTZ: "Date",
|
|
1673
|
+
JSON: "unknown",
|
|
1674
|
+
JSONB: "unknown",
|
|
1675
|
+
OID: "number",
|
|
1676
|
+
SERIAL: "number",
|
|
1677
|
+
BIGSERIAL: "bigint"
|
|
1572
1678
|
};
|
|
1573
1679
|
mapPrimitiveType(type, nullable) {
|
|
1574
1680
|
const upperType = type.toUpperCase();
|
|
1575
1681
|
const mappedType = this.typeMap[upperType];
|
|
1576
1682
|
if (mappedType) return nullable ? `${mappedType} | null` : mappedType;
|
|
1683
|
+
if (upperType.startsWith("_")) {
|
|
1684
|
+
const baseType = upperType.substring(1);
|
|
1685
|
+
const arrayType = `${this.typeMap[baseType] || "unknown"}[]`;
|
|
1686
|
+
return nullable ? `${arrayType} | null` : arrayType;
|
|
1687
|
+
}
|
|
1577
1688
|
if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) {
|
|
1578
1689
|
const baseType = "{ width: number; scale: number; value: bigint }";
|
|
1579
1690
|
return nullable ? `${baseType} | null` : baseType;
|
|
@@ -1598,8 +1709,7 @@ var TypeScriptTypeMapper = class extends TypeMapper {
|
|
|
1598
1709
|
}
|
|
1599
1710
|
}
|
|
1600
1711
|
if (/\[\d+\]/.test(upperType)) return "{ items: unknown[] }";
|
|
1601
|
-
|
|
1602
|
-
return "unknown";
|
|
1712
|
+
return nullable ? "string | null" : "string";
|
|
1603
1713
|
}
|
|
1604
1714
|
formatListType(elementType) {
|
|
1605
1715
|
return `{ items: (${elementType})[] }`;
|
|
@@ -1784,7 +1894,8 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
1784
1894
|
name,
|
|
1785
1895
|
generator: "java/duckdb/jdbc",
|
|
1786
1896
|
output: gen.output,
|
|
1787
|
-
config: gen.config
|
|
1897
|
+
config: gen.config,
|
|
1898
|
+
projectName: gen.projectName
|
|
1788
1899
|
}, this.javaGenerator, name, q, tables, "duckdb");
|
|
1789
1900
|
}
|
|
1790
1901
|
isCompatibleWith(engine) {
|
|
@@ -2090,7 +2201,9 @@ function getGenerator(generator) {
|
|
|
2090
2201
|
const key = `${info.language}/${info.driver}`;
|
|
2091
2202
|
switch (key) {
|
|
2092
2203
|
case "typescript/better-sqlite3":
|
|
2093
|
-
case "typescript/node":
|
|
2204
|
+
case "typescript/node":
|
|
2205
|
+
case "typescript/libsql":
|
|
2206
|
+
case "typescript/turso": return new TsGenerator(`templates/${info.template}`);
|
|
2094
2207
|
case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
|
|
2095
2208
|
case "java/jdbc": return new JavaGenerator(`templates/${info.template}`);
|
|
2096
2209
|
case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
|
|
@@ -2248,7 +2361,7 @@ var TableHelper = class {
|
|
|
2248
2361
|
return this.generator.typeMapper;
|
|
2249
2362
|
}
|
|
2250
2363
|
};
|
|
2251
|
-
function generateSourceFile(name, queries, tables, templatePath, generator, engine, config) {
|
|
2364
|
+
function generateSourceFile(name, queries, tables, templatePath, generator, engine, projectName, config) {
|
|
2252
2365
|
const templateSrc = readFileSync(templatePath, "utf-8");
|
|
2253
2366
|
const template = Handlebars.compile(templateSrc);
|
|
2254
2367
|
Handlebars.registerHelper("mapType", (column) => generator.mapType(column));
|
|
@@ -2261,6 +2374,7 @@ function generateSourceFile(name, queries, tables, templatePath, generator, engi
|
|
|
2261
2374
|
queries: queries.map((q) => new SqlQueryHelper(q, generator, generator.getStatement(q))),
|
|
2262
2375
|
tables: tableHelpers,
|
|
2263
2376
|
className: generator.getClassName(name),
|
|
2377
|
+
projectName,
|
|
2264
2378
|
config
|
|
2265
2379
|
}, {
|
|
2266
2380
|
allowProtoPropertiesByDefault: true,
|
|
@@ -2400,7 +2514,7 @@ async function writeGeneratedFile(projectDir, gen, generator, file, queries, tab
|
|
|
2400
2514
|
await generator.beforeGenerate(projectDir, gen, queries, tables);
|
|
2401
2515
|
const templatePath = join(dirname(new URL(import.meta.url).pathname), gen.template ?? generator.template);
|
|
2402
2516
|
const name = gen.name ?? basename(file, extname(file));
|
|
2403
|
-
const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.config);
|
|
2517
|
+
const sourceFile = generateSourceFile(name, queries, tables, templatePath, generator, engine, gen.projectName ?? name, gen.config);
|
|
2404
2518
|
if (writeToStdout) {
|
|
2405
2519
|
process.stdout.write(sourceFile);
|
|
2406
2520
|
if (!sourceFile.endsWith("\n")) process.stdout.write("\n");
|
|
@@ -2527,7 +2641,11 @@ async function processProjectFromConfig(project, projectDir, writeToStdout = fal
|
|
|
2527
2641
|
});
|
|
2528
2642
|
}
|
|
2529
2643
|
for (const gen of gens) {
|
|
2530
|
-
const
|
|
2644
|
+
const generator = getGenerator(gen.generator);
|
|
2645
|
+
const outputPath = await writeGeneratedFile(projectDir, {
|
|
2646
|
+
...gen,
|
|
2647
|
+
projectName: project.name
|
|
2648
|
+
}, generator, sqlFile, queries, tables, engine, writeToStdout);
|
|
2531
2649
|
if (outputPath !== null) files.push(outputPath);
|
|
2532
2650
|
}
|
|
2533
2651
|
}
|
|
@@ -2550,7 +2668,7 @@ async function processProject(projectPath) {
|
|
|
2550
2668
|
//#region src/mcp-server.ts
|
|
2551
2669
|
const server = new Server({
|
|
2552
2670
|
name: "sqg-mcp",
|
|
2553
|
-
version: process.env.npm_package_version ?? "0.
|
|
2671
|
+
version: process.env.npm_package_version ?? "0.8.0"
|
|
2554
2672
|
}, { capabilities: {
|
|
2555
2673
|
tools: {},
|
|
2556
2674
|
resources: {}
|
|
@@ -2879,7 +2997,7 @@ async function startMcpServer() {
|
|
|
2879
2997
|
|
|
2880
2998
|
//#endregion
|
|
2881
2999
|
//#region src/sqg.ts
|
|
2882
|
-
const version = process.env.npm_package_version ?? "0.
|
|
3000
|
+
const version = process.env.npm_package_version ?? "0.8.0";
|
|
2883
3001
|
const description = process.env.npm_package_description ?? "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)";
|
|
2884
3002
|
consola.level = LogLevels.info;
|
|
2885
3003
|
const program = new Command().name("sqg").description(`${description}
|
|
@@ -18,7 +18,7 @@ export class {{className}} {
|
|
|
18
18
|
return stmt as Statement<BindParameters, Result>;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
static getMigrations(): string[] {
|
|
21
|
+
static getMigrations(): string[] {
|
|
22
22
|
return [
|
|
23
23
|
{{#each migrations}}
|
|
24
24
|
{{{quote sqlQuery}}},
|
|
@@ -26,6 +26,36 @@ export class {{className}} {
|
|
|
26
26
|
];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
{{#if config.migrations}}
|
|
30
|
+
static applyMigrations(db: Database, projectName = '{{projectName}}'): void {
|
|
31
|
+
db.exec(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
32
|
+
project TEXT NOT NULL,
|
|
33
|
+
migration_id TEXT NOT NULL,
|
|
34
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
35
|
+
PRIMARY KEY (project, migration_id)
|
|
36
|
+
)`);
|
|
37
|
+
const runMigrations = db.transaction(() => {
|
|
38
|
+
const applied = new Set(
|
|
39
|
+
db.prepare('SELECT migration_id FROM _sqg_migrations WHERE project = ?')
|
|
40
|
+
.pluck().all(projectName) as string[]
|
|
41
|
+
);
|
|
42
|
+
const migrations: [string, string][] = [
|
|
43
|
+
{{#each migrations}}
|
|
44
|
+
['{{{id}}}', {{{quote sqlQuery}}}],
|
|
45
|
+
{{/each}}
|
|
46
|
+
];
|
|
47
|
+
for (const [id, sql] of migrations) {
|
|
48
|
+
if (!applied.has(id)) {
|
|
49
|
+
db.exec(sql);
|
|
50
|
+
db.prepare('INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)')
|
|
51
|
+
.run(projectName, id);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
runMigrations.immediate();
|
|
56
|
+
}
|
|
57
|
+
{{/if}}
|
|
58
|
+
|
|
29
59
|
static getQueryNames(): Map<string, keyof {{className}}> {
|
|
30
60
|
return new Map([
|
|
31
61
|
{{#each queries}} {{#unless skipGenerateFunction}}
|
|
@@ -29,10 +29,20 @@ public class {{className}} {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
public static List<String> getMigrations() {
|
|
32
|
+
public static List<String> getMigrations() {
|
|
33
33
|
return {{className}}Jdbc.getMigrations();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
{{#if config.migrations}}
|
|
37
|
+
public static void applyMigrations(Connection connection) throws SQLException {
|
|
38
|
+
{{className}}Jdbc.applyMigrations(connection);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static void applyMigrations(Connection connection, String projectName) throws SQLException {
|
|
42
|
+
{{className}}Jdbc.applyMigrations(connection, projectName);
|
|
43
|
+
}
|
|
44
|
+
{{/if}}
|
|
45
|
+
|
|
36
46
|
{{#each queries}}
|
|
37
47
|
{{#unless skipGenerateFunction}}
|
|
38
48
|
{{#if isOne}}
|
|
@@ -13,6 +13,7 @@ import java.time.Instant;
|
|
|
13
13
|
import java.time.LocalDate;
|
|
14
14
|
import java.time.LocalDateTime;
|
|
15
15
|
import java.time.LocalTime;
|
|
16
|
+
import java.time.OffsetDateTime;
|
|
16
17
|
import java.time.OffsetTime;
|
|
17
18
|
import java.util.ArrayList;
|
|
18
19
|
import java.util.Arrays;
|
|
@@ -65,6 +66,10 @@ public class {{className}} {
|
|
|
65
66
|
private static LocalTime toLocalTime(java.sql.Time t) {
|
|
66
67
|
return t != null ? t.toLocalTime() : null;
|
|
67
68
|
}
|
|
69
|
+
|
|
70
|
+
private static OffsetDateTime toOffsetDateTime(java.sql.Timestamp ts) {
|
|
71
|
+
return ts != null ? ts.toInstant().atOffset(java.time.ZoneOffset.UTC) : null;
|
|
72
|
+
}
|
|
68
73
|
|
|
69
74
|
private static <K> List<K> arrayToList(
|
|
70
75
|
Array array,
|
|
@@ -129,10 +134,67 @@ public class {{className}} {
|
|
|
129
134
|
{{/each}}
|
|
130
135
|
);
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
{{#if config.migrations}}
|
|
138
|
+
private static final List<String> migrationIds = List.of(
|
|
139
|
+
{{#each migrations}}"{{id}}"{{#unless @last}},{{/unless}}
|
|
140
|
+
{{/each}}
|
|
141
|
+
);
|
|
142
|
+
{{/if}}
|
|
143
|
+
|
|
144
|
+
public static List<String> getMigrations() {
|
|
133
145
|
return migrations;
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
{{#if config.migrations}}
|
|
149
|
+
public static void applyMigrations(Connection connection) throws SQLException {
|
|
150
|
+
applyMigrations(connection, "{{projectName}}");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public static void applyMigrations(Connection connection, String projectName) throws SQLException {
|
|
154
|
+
try (var stmt = connection.createStatement()) {
|
|
155
|
+
stmt.execute("""
|
|
156
|
+
CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
157
|
+
project TEXT NOT NULL,
|
|
158
|
+
migration_id TEXT NOT NULL,
|
|
159
|
+
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
160
|
+
PRIMARY KEY (project, migration_id)
|
|
161
|
+
)""");
|
|
162
|
+
}
|
|
163
|
+
boolean wasAutoCommit = connection.getAutoCommit();
|
|
164
|
+
connection.setAutoCommit(false);
|
|
165
|
+
try {
|
|
166
|
+
var applied = new java.util.HashSet<String>();
|
|
167
|
+
try (var stmt = connection.prepareStatement("SELECT migration_id FROM _sqg_migrations WHERE project = ?")) {
|
|
168
|
+
stmt.setString(1, projectName);
|
|
169
|
+
try (var rs = stmt.executeQuery()) {
|
|
170
|
+
while (rs.next()) {
|
|
171
|
+
applied.add(rs.getString(1));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (int i = 0; i < migrations.size(); i++) {
|
|
176
|
+
var id = migrationIds.get(i);
|
|
177
|
+
if (!applied.contains(id)) {
|
|
178
|
+
try (var stmt = connection.createStatement()) {
|
|
179
|
+
stmt.execute(migrations.get(i));
|
|
180
|
+
}
|
|
181
|
+
try (var stmt = connection.prepareStatement("INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)")) {
|
|
182
|
+
stmt.setString(1, projectName);
|
|
183
|
+
stmt.setString(2, id);
|
|
184
|
+
stmt.executeUpdate();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
connection.commit();
|
|
189
|
+
} catch (SQLException e) {
|
|
190
|
+
connection.rollback();
|
|
191
|
+
throw e;
|
|
192
|
+
} finally {
|
|
193
|
+
connection.setAutoCommit(wasAutoCommit);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
{{/if}}
|
|
197
|
+
|
|
136
198
|
{{#each queries}}
|
|
137
199
|
{{#unless skipGenerateFunction}}
|
|
138
200
|
{{>columnTypesRecord}}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// {{generatedComment}}
|
|
2
|
+
import type { Client, InArgs } from '@libsql/client';
|
|
3
|
+
|
|
4
|
+
interface RunResult {
|
|
5
|
+
changes: number;
|
|
6
|
+
lastInsertRowid: bigint;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class {{className}} {
|
|
10
|
+
constructor(private client: Client) {}
|
|
11
|
+
|
|
12
|
+
static getMigrations(): string[] {
|
|
13
|
+
return [
|
|
14
|
+
{{#each migrations}}
|
|
15
|
+
{{{quote sqlQuery}}},
|
|
16
|
+
{{/each}}
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
{{#if config.migrations}}
|
|
21
|
+
static async applyMigrations(client: Client, projectName = '{{projectName}}'): Promise<void> {
|
|
22
|
+
await client.execute({
|
|
23
|
+
sql: `CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
24
|
+
project TEXT NOT NULL,
|
|
25
|
+
migration_id TEXT NOT NULL,
|
|
26
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
27
|
+
PRIMARY KEY (project, migration_id)
|
|
28
|
+
)`,
|
|
29
|
+
args: [],
|
|
30
|
+
});
|
|
31
|
+
const tx = await client.transaction('write');
|
|
32
|
+
try {
|
|
33
|
+
const result = await tx.execute({
|
|
34
|
+
sql: 'SELECT migration_id FROM _sqg_migrations WHERE project = ?',
|
|
35
|
+
args: [projectName],
|
|
36
|
+
});
|
|
37
|
+
const applied = new Set(result.rows.map(r => r.migration_id as string));
|
|
38
|
+
const migrations: [string, string][] = [
|
|
39
|
+
{{#each migrations}}
|
|
40
|
+
['{{{id}}}', {{{quote sqlQuery}}}],
|
|
41
|
+
{{/each}}
|
|
42
|
+
];
|
|
43
|
+
for (const [id, sql] of migrations) {
|
|
44
|
+
if (!applied.has(id)) {
|
|
45
|
+
await tx.execute({ sql, args: [] });
|
|
46
|
+
await tx.execute({
|
|
47
|
+
sql: 'INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)',
|
|
48
|
+
args: [projectName, id],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await tx.commit();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
await tx.rollback();
|
|
55
|
+
throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
{{/if}}
|
|
59
|
+
|
|
60
|
+
static getQueryNames(): Map<string, keyof {{className}}> {
|
|
61
|
+
return new Map([
|
|
62
|
+
{{#each queries}} {{#unless skipGenerateFunction}}
|
|
63
|
+
["{{id}}", "{{functionName}}"],{{/unless}}{{/each}}]
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
{{#each queries}}
|
|
68
|
+
{{#unless skipGenerateFunction}}
|
|
69
|
+
async {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{> returnType }}> {
|
|
70
|
+
const result = await this.client.execute({
|
|
71
|
+
sql: {{{quote sqlQuery}}},
|
|
72
|
+
args: [{{> params}}] as InArgs,
|
|
73
|
+
});
|
|
74
|
+
{{> execute}}
|
|
75
|
+
}
|
|
76
|
+
{{/unless}}
|
|
77
|
+
{{/each}}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
{{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
81
|
+
|
|
82
|
+
{{#*inline "paramTypes"}}{{#each parameters}}{{type}} {{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
83
|
+
|
|
84
|
+
{{#*inline "columnTypes"}}{{#each columns}}{{name}}: {{mapType .}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
85
|
+
|
|
86
|
+
{{#*inline "rowType"}}
|
|
87
|
+
{{#if isQuery~}}
|
|
88
|
+
{{#if isPluck~}}
|
|
89
|
+
{{#if columns.length~}} {{mapType (lookup columns 0)}} {{else}}unknown{{/if~}}
|
|
90
|
+
{{~else~}}
|
|
91
|
+
{{#if columns.length}}{ {{> columnTypes}} }{{else}}unknown{{/if~}}
|
|
92
|
+
{{/if~}}
|
|
93
|
+
{{~else~}}
|
|
94
|
+
unknown
|
|
95
|
+
{{~/if~}}
|
|
96
|
+
{{/inline~}}
|
|
97
|
+
|
|
98
|
+
{{#*inline "resultType"}}
|
|
99
|
+
{{#if isQuery~}}
|
|
100
|
+
{{> rowType}}
|
|
101
|
+
{{~else~}}
|
|
102
|
+
unknown
|
|
103
|
+
{{~/if~}}
|
|
104
|
+
{{/inline~}}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
{{#*inline "returnType"}}
|
|
108
|
+
{{#if isQuery~}}
|
|
109
|
+
{{#if isOne~}}
|
|
110
|
+
{{> rowType}} | undefined
|
|
111
|
+
{{~else~}}
|
|
112
|
+
{{> rowType}}[]
|
|
113
|
+
{{/if~}}
|
|
114
|
+
{{~else~}}
|
|
115
|
+
RunResult
|
|
116
|
+
{{~/if~}}
|
|
117
|
+
{{/inline~}}
|
|
118
|
+
|
|
119
|
+
{{#*inline "execute"}}
|
|
120
|
+
{{#if isQuery}}
|
|
121
|
+
{{#if isOne}}
|
|
122
|
+
{{#if isPluck}}
|
|
123
|
+
const row = result.rows[0];
|
|
124
|
+
return row ? Object.values(row)[0] as {{> rowType}} : undefined;
|
|
125
|
+
{{else}}
|
|
126
|
+
return (result.rows as unknown[])[0] as {{> rowType}} | undefined;
|
|
127
|
+
{{/if}}
|
|
128
|
+
{{else}}
|
|
129
|
+
{{#if isPluck}}
|
|
130
|
+
return result.rows.map(row => Object.values(row)[0] as {{> rowType}});
|
|
131
|
+
{{else}}
|
|
132
|
+
return (result.rows as unknown[]) as {{> rowType}}[];
|
|
133
|
+
{{/if}}
|
|
134
|
+
{{/if}}
|
|
135
|
+
{{else}}
|
|
136
|
+
return {
|
|
137
|
+
changes: result.rowsAffected,
|
|
138
|
+
lastInsertRowid: result.lastInsertRowid ?? 0n,
|
|
139
|
+
};
|
|
140
|
+
{{/if}}
|
|
141
|
+
{{/inline}}
|
|
@@ -28,6 +28,39 @@ export class {{className}} {
|
|
|
28
28
|
];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
{{#if config.migrations}}
|
|
32
|
+
static applyMigrations(db: DatabaseSync, projectName = '{{projectName}}'): void {
|
|
33
|
+
db.exec(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
34
|
+
project TEXT NOT NULL,
|
|
35
|
+
migration_id TEXT NOT NULL,
|
|
36
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
37
|
+
PRIMARY KEY (project, migration_id)
|
|
38
|
+
)`);
|
|
39
|
+
db.exec('BEGIN IMMEDIATE');
|
|
40
|
+
try {
|
|
41
|
+
const rows = db.prepare('SELECT migration_id FROM _sqg_migrations WHERE project = ?')
|
|
42
|
+
.all(projectName) as { migration_id: string }[];
|
|
43
|
+
const applied = new Set(rows.map(r => r.migration_id));
|
|
44
|
+
const migrations: [string, string][] = [
|
|
45
|
+
{{#each migrations}}
|
|
46
|
+
['{{{id}}}', {{{quote sqlQuery}}}],
|
|
47
|
+
{{/each}}
|
|
48
|
+
];
|
|
49
|
+
for (const [id, sql] of migrations) {
|
|
50
|
+
if (!applied.has(id)) {
|
|
51
|
+
db.exec(sql);
|
|
52
|
+
db.prepare('INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)')
|
|
53
|
+
.run(projectName, id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
db.exec('COMMIT');
|
|
57
|
+
} catch (e) {
|
|
58
|
+
db.exec('ROLLBACK');
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
31
64
|
static getQueryNames(): Map<string, keyof {{className}}> {
|
|
32
65
|
return new Map([
|
|
33
66
|
{{#each queries}} {{#unless skipGenerateFunction}}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// {{generatedComment}}
|
|
2
|
+
import { connect, type Database } from '@tursodatabase/database';
|
|
3
|
+
|
|
4
|
+
interface RunResult {
|
|
5
|
+
changes: number;
|
|
6
|
+
lastInsertRowid: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Statement type inferred from Database.prepare()
|
|
10
|
+
type Statement = Awaited<ReturnType<Database['prepare']>>;
|
|
11
|
+
|
|
12
|
+
export class {{className}} {
|
|
13
|
+
private statements = new Map<string, Statement>();
|
|
14
|
+
|
|
15
|
+
constructor(private db: Database) {}
|
|
16
|
+
|
|
17
|
+
private async prepare(id: string, query: string): Promise<Statement> {
|
|
18
|
+
let stmt = this.statements.get(id);
|
|
19
|
+
if (!stmt) {
|
|
20
|
+
stmt = await this.db.prepare(query);
|
|
21
|
+
this.statements.set(id, stmt);
|
|
22
|
+
}
|
|
23
|
+
return stmt;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getMigrations(): string[] {
|
|
27
|
+
return [
|
|
28
|
+
{{#each migrations}}
|
|
29
|
+
{{{quote sqlQuery}}},
|
|
30
|
+
{{/each}}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
{{#if config.migrations}}
|
|
35
|
+
static async applyMigrations(db: Database, projectName = '{{projectName}}'): Promise<void> {
|
|
36
|
+
const createStmt = await db.prepare(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
37
|
+
project TEXT NOT NULL,
|
|
38
|
+
migration_id TEXT NOT NULL,
|
|
39
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
40
|
+
PRIMARY KEY (project, migration_id)
|
|
41
|
+
)`);
|
|
42
|
+
await createStmt.run();
|
|
43
|
+
const tx = await db.transaction('write');
|
|
44
|
+
try {
|
|
45
|
+
const selectStmt = await tx.prepare('SELECT migration_id FROM _sqg_migrations WHERE project = ?');
|
|
46
|
+
const rows = await selectStmt.all(projectName) as { migration_id: string }[];
|
|
47
|
+
const applied = new Set(rows.map(r => r.migration_id));
|
|
48
|
+
const migrations: [string, string][] = [
|
|
49
|
+
{{#each migrations}}
|
|
50
|
+
['{{{id}}}', {{{quote sqlQuery}}}],
|
|
51
|
+
{{/each}}
|
|
52
|
+
];
|
|
53
|
+
for (const [id, sql] of migrations) {
|
|
54
|
+
if (!applied.has(id)) {
|
|
55
|
+
const execStmt = await tx.prepare(sql);
|
|
56
|
+
await execStmt.run();
|
|
57
|
+
const insertStmt = await tx.prepare('INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)');
|
|
58
|
+
await insertStmt.run(projectName, id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
await tx.commit();
|
|
62
|
+
} catch (e) {
|
|
63
|
+
await tx.rollback();
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
{{/if}}
|
|
68
|
+
|
|
69
|
+
static getQueryNames(): Map<string, keyof {{className}}> {
|
|
70
|
+
return new Map([
|
|
71
|
+
{{#each queries}} {{#unless skipGenerateFunction}}
|
|
72
|
+
["{{id}}", "{{functionName}}"],{{/unless}}{{/each}}]
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
{{#each queries}}
|
|
77
|
+
{{#unless skipGenerateFunction}}
|
|
78
|
+
async {{functionName}}({{#each variables}}{{name}}: {{type}}{{#unless @last}}, {{/unless}}{{/each}}): Promise<{{> returnType }}> {
|
|
79
|
+
const stmt = await this.prepare('{{id}}',
|
|
80
|
+
{{{quote sqlQuery}}});
|
|
81
|
+
{{> execute}}
|
|
82
|
+
}
|
|
83
|
+
{{/unless}}
|
|
84
|
+
{{/each}}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
{{#*inline "params"}}{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
88
|
+
|
|
89
|
+
{{#*inline "paramTypes"}}{{#each parameters}}{{type}} {{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
90
|
+
|
|
91
|
+
{{#*inline "columnTypes"}}{{#each columns}}{{name}}: {{mapType .}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
|
92
|
+
|
|
93
|
+
{{#*inline "rowType"}}
|
|
94
|
+
{{#if isQuery~}}
|
|
95
|
+
{{#if isPluck~}}
|
|
96
|
+
{{#if columns.length~}} {{mapType (lookup columns 0)}} {{else}}unknown{{/if~}}
|
|
97
|
+
{{~else~}}
|
|
98
|
+
{{#if columns.length}}{ {{> columnTypes}} }{{else}}unknown{{/if~}}
|
|
99
|
+
{{/if~}}
|
|
100
|
+
{{~else~}}
|
|
101
|
+
unknown
|
|
102
|
+
{{~/if~}}
|
|
103
|
+
{{/inline~}}
|
|
104
|
+
|
|
105
|
+
{{#*inline "resultType"}}
|
|
106
|
+
{{#if isQuery~}}
|
|
107
|
+
{{> rowType}}
|
|
108
|
+
{{~else~}}
|
|
109
|
+
unknown
|
|
110
|
+
{{~/if~}}
|
|
111
|
+
{{/inline~}}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
{{#*inline "returnType"}}
|
|
115
|
+
{{#if isQuery~}}
|
|
116
|
+
{{#if isOne~}}
|
|
117
|
+
{{> rowType}} | undefined
|
|
118
|
+
{{~else~}}
|
|
119
|
+
{{> rowType}}[]
|
|
120
|
+
{{/if~}}
|
|
121
|
+
{{~else~}}
|
|
122
|
+
RunResult
|
|
123
|
+
{{~/if~}}
|
|
124
|
+
{{/inline~}}
|
|
125
|
+
|
|
126
|
+
{{#*inline "execute"}}
|
|
127
|
+
{{#if isQuery}}
|
|
128
|
+
{{#if isOne}}
|
|
129
|
+
{{#if isPluck}}
|
|
130
|
+
const row = await stmt.get({{> params}}) as Record<string, {{> rowType}}> | undefined;
|
|
131
|
+
return row ? Object.values(row)[0] : undefined;
|
|
132
|
+
{{else}}
|
|
133
|
+
return await stmt.get({{> params}}) as {{> rowType}} | undefined;
|
|
134
|
+
{{/if}}
|
|
135
|
+
{{else}}
|
|
136
|
+
{{#if isPluck}}
|
|
137
|
+
const rows = await stmt.all({{> params}}) as Record<string, {{> rowType}}>[];
|
|
138
|
+
return rows.map(row => Object.values(row)[0]);
|
|
139
|
+
{{else}}
|
|
140
|
+
return await stmt.all({{> params}}) as {{> rowType}}[];
|
|
141
|
+
{{/if}}
|
|
142
|
+
{{/if}}
|
|
143
|
+
{{else}}
|
|
144
|
+
const result = await stmt.run({{> params}});
|
|
145
|
+
return result as RunResult;
|
|
146
|
+
{{/if}}
|
|
147
|
+
{{/inline}}
|
|
@@ -5,7 +5,7 @@ export class {{className}} {
|
|
|
5
5
|
|
|
6
6
|
constructor(private conn: DuckDBConnection) {}
|
|
7
7
|
|
|
8
|
-
static getMigrations(): string[] {
|
|
8
|
+
static getMigrations(): string[] {
|
|
9
9
|
return [
|
|
10
10
|
{{#each migrations}}
|
|
11
11
|
{{{quote sqlQuery}}},
|
|
@@ -13,6 +13,42 @@ export class {{className}} {
|
|
|
13
13
|
];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
{{#if config.migrations}}
|
|
17
|
+
static async applyMigrations(conn: DuckDBConnection, projectName = '{{projectName}}'): Promise<void> {
|
|
18
|
+
await conn.run(`CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
19
|
+
project TEXT NOT NULL,
|
|
20
|
+
migration_id TEXT NOT NULL,
|
|
21
|
+
applied_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
22
|
+
PRIMARY KEY (project, migration_id)
|
|
23
|
+
)`);
|
|
24
|
+
await conn.run('BEGIN');
|
|
25
|
+
try {
|
|
26
|
+
const result = await conn.runAndReadAll(
|
|
27
|
+
'SELECT migration_id FROM _sqg_migrations WHERE project = $1', [projectName]
|
|
28
|
+
);
|
|
29
|
+
const applied = new Set(result.getRows().map((row) => row[0] as string));
|
|
30
|
+
const migrations: [string, string][] = [
|
|
31
|
+
{{#each migrations}}
|
|
32
|
+
['{{{id}}}', {{{quote sqlQuery}}}],
|
|
33
|
+
{{/each}}
|
|
34
|
+
];
|
|
35
|
+
for (const [id, sql] of migrations) {
|
|
36
|
+
if (!applied.has(id)) {
|
|
37
|
+
await conn.run(sql);
|
|
38
|
+
await conn.run(
|
|
39
|
+
'INSERT INTO _sqg_migrations (project, migration_id) VALUES ($1, $2)',
|
|
40
|
+
[projectName, id]
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
await conn.run('COMMIT');
|
|
45
|
+
} catch (e) {
|
|
46
|
+
await conn.run('ROLLBACK');
|
|
47
|
+
throw e;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
{{/if}}
|
|
51
|
+
|
|
16
52
|
static getQueryNames(): Map<string, keyof {{className}}> {
|
|
17
53
|
return new Map([
|
|
18
54
|
{{#each queries}} {{#unless skipGenerateFunction}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqg/sqg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"handlebars": "^4.7.8",
|
|
54
54
|
"pg": "^8.16.3",
|
|
55
55
|
"pg-types": "^4.1.0",
|
|
56
|
+
"@testcontainers/postgresql": "^10.21.0",
|
|
56
57
|
"prettier": "^3.7.4",
|
|
57
58
|
"prettier-plugin-java": "^2.7.7",
|
|
58
59
|
"yaml": "^2.8.2",
|
|
@@ -60,6 +61,8 @@
|
|
|
60
61
|
"@modelcontextprotocol/sdk": "^1.0.4"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
64
|
+
"@libsql/client": "^0.17.0",
|
|
65
|
+
"@tursodatabase/database": "^0.4.3",
|
|
63
66
|
"@types/better-sqlite3": "^7.6.13",
|
|
64
67
|
"@types/node": "^25.0.3",
|
|
65
68
|
"@types/pg": "^8.16.0",
|