yamchart 0.1.4 → 0.3.3
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/chunk-3CLMQNNR.js +64 -0
- package/dist/chunk-3CLMQNNR.js.map +1 -0
- package/dist/chunk-4P5UHWYK.js +247 -0
- package/dist/chunk-4P5UHWYK.js.map +1 -0
- package/dist/chunk-A24KVXJQ.js +922 -0
- package/dist/chunk-A24KVXJQ.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-HJVVHYVN.js +59 -0
- package/dist/chunk-HJVVHYVN.js.map +1 -0
- package/dist/chunk-LL3VKMZM.js +1265 -0
- package/dist/chunk-LL3VKMZM.js.map +1 -0
- package/dist/dev-PTVNJI7G.js +13557 -0
- package/dist/dev-PTVNJI7G.js.map +1 -0
- package/dist/dist-JH7OL7U4.js +16 -0
- package/dist/dist-JH7OL7U4.js.map +1 -0
- package/dist/dist-JKJLH3F6.js +56 -0
- package/dist/dist-JKJLH3F6.js.map +1 -0
- package/dist/{generate-RD3LCS73.js → generate-KNER36CB.js} +3 -1
- package/dist/{generate-RD3LCS73.js.map → generate-KNER36CB.js.map} +1 -1
- package/dist/index.js +89 -10
- package/dist/index.js.map +1 -1
- package/dist/{init-6D5VNGSP.js → init-FTSEOTAD.js} +3 -1
- package/dist/{init-6D5VNGSP.js.map → init-FTSEOTAD.js.map} +1 -1
- package/dist/reset-password-IZQTDTU7.js +60 -0
- package/dist/reset-password-IZQTDTU7.js.map +1 -0
- package/dist/{sync-dbt-IDDD4X2Z.js → sync-dbt-6WY7HKP7.js} +3 -1
- package/dist/{sync-dbt-IDDD4X2Z.js.map → sync-dbt-6WY7HKP7.js.map} +1 -1
- package/dist/templates/default/CLAUDE.md +9 -0
- package/dist/templates/default/docs/yamchart-reference.md +315 -0
- package/dist/test-WYNX4RYZ.js +222 -0
- package/dist/test-WYNX4RYZ.js.map +1 -0
- package/dist/update-GWPF5AS6.js +76 -0
- package/dist/update-GWPF5AS6.js.map +1 -0
- package/package.json +25 -6
- package/dist/chunk-TBILHUB3.js +0 -365
- package/dist/chunk-TBILHUB3.js.map +0 -1
- package/dist/dev-UHYN2RXH.js +0 -85
- package/dist/dev-UHYN2RXH.js.map +0 -1
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-DGUM43GV.js";
|
|
4
|
+
|
|
5
|
+
// ../../packages/query/dist/parser.js
|
|
6
|
+
function parseModelMetadata(sql) {
|
|
7
|
+
const lines = sql.split("\n");
|
|
8
|
+
const metadataLines = [];
|
|
9
|
+
const sqlLines = [];
|
|
10
|
+
let inMetadata = true;
|
|
11
|
+
let inMultiline = null;
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
const trimmed = line.trim();
|
|
14
|
+
if (trimmed.startsWith("-- @") || inMultiline && trimmed.startsWith("--")) {
|
|
15
|
+
metadataLines.push(trimmed);
|
|
16
|
+
inMetadata = true;
|
|
17
|
+
} else if (trimmed.startsWith("--") && inMetadata && metadataLines.length > 0) {
|
|
18
|
+
metadataLines.push(trimmed);
|
|
19
|
+
} else if (trimmed === "" && inMetadata) {
|
|
20
|
+
continue;
|
|
21
|
+
} else {
|
|
22
|
+
inMetadata = false;
|
|
23
|
+
sqlLines.push(line);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const metadata = parseMetadataLines(metadataLines);
|
|
27
|
+
if (!metadata.name) {
|
|
28
|
+
throw new Error("Model must have a @name");
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...metadata,
|
|
32
|
+
name: metadata.name,
|
|
33
|
+
sql: sqlLines.join("\n").trim()
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function parseMetadataLines(lines) {
|
|
37
|
+
const result = {};
|
|
38
|
+
const params = [];
|
|
39
|
+
const returns = [];
|
|
40
|
+
const tests = [];
|
|
41
|
+
let currentMultiline = null;
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const content = line.replace(/^--\s*/, "").trim();
|
|
44
|
+
if (currentMultiline && content.startsWith("- ")) {
|
|
45
|
+
const itemContent = content.slice(2).trim();
|
|
46
|
+
if (currentMultiline === "returns") {
|
|
47
|
+
const returnCol = parseReturnColumn(itemContent);
|
|
48
|
+
if (returnCol)
|
|
49
|
+
returns.push(returnCol);
|
|
50
|
+
} else if (currentMultiline === "tests") {
|
|
51
|
+
tests.push(itemContent);
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (content.startsWith("@")) {
|
|
56
|
+
currentMultiline = null;
|
|
57
|
+
if (content.startsWith("@name:")) {
|
|
58
|
+
result.name = content.slice(6).trim();
|
|
59
|
+
} else if (content.startsWith("@description:")) {
|
|
60
|
+
result.description = content.slice(13).trim();
|
|
61
|
+
} else if (content.startsWith("@owner:")) {
|
|
62
|
+
result.owner = content.slice(7).trim();
|
|
63
|
+
} else if (content.startsWith("@tags:")) {
|
|
64
|
+
result.tags = parseTags(content.slice(6).trim());
|
|
65
|
+
} else if (content.startsWith("@param")) {
|
|
66
|
+
const param = parseParam(content.slice(6).trim());
|
|
67
|
+
if (param)
|
|
68
|
+
params.push(param);
|
|
69
|
+
} else if (content.startsWith("@returns:")) {
|
|
70
|
+
currentMultiline = "returns";
|
|
71
|
+
} else if (content.startsWith("@tests:")) {
|
|
72
|
+
currentMultiline = "tests";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (params.length > 0)
|
|
77
|
+
result.params = params;
|
|
78
|
+
if (returns.length > 0)
|
|
79
|
+
result.returns = returns;
|
|
80
|
+
if (tests.length > 0)
|
|
81
|
+
result.tests = tests;
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function parseTags(input) {
|
|
85
|
+
const match = input.match(/\[(.*)\]/);
|
|
86
|
+
if (!match?.[1])
|
|
87
|
+
return [];
|
|
88
|
+
return match[1].split(",").map((t) => t.trim());
|
|
89
|
+
}
|
|
90
|
+
function parseParam(input) {
|
|
91
|
+
const match = input.match(/^(\w+):\s*(\w+(?:\[\])?)\s*(?:=\s*([^{]+))?\s*(?:\{([^}]+)\})?/);
|
|
92
|
+
if (!match)
|
|
93
|
+
return null;
|
|
94
|
+
const [, name, type, defaultValue, options] = match;
|
|
95
|
+
if (!name || !type)
|
|
96
|
+
return null;
|
|
97
|
+
const param = {
|
|
98
|
+
name: name.trim(),
|
|
99
|
+
type: type.trim()
|
|
100
|
+
};
|
|
101
|
+
if (defaultValue) {
|
|
102
|
+
param.default = defaultValue.trim();
|
|
103
|
+
}
|
|
104
|
+
if (options) {
|
|
105
|
+
param.options = options.split(",").map((o) => o.trim());
|
|
106
|
+
}
|
|
107
|
+
return param;
|
|
108
|
+
}
|
|
109
|
+
function parseReturnColumn(input) {
|
|
110
|
+
const match = input.match(/^(\w+):\s*(\w+)\s*(?:--\s*(.+))?/);
|
|
111
|
+
if (!match)
|
|
112
|
+
return null;
|
|
113
|
+
const [, name, type, description] = match;
|
|
114
|
+
if (!name || !type)
|
|
115
|
+
return null;
|
|
116
|
+
const col = {
|
|
117
|
+
name: name.trim(),
|
|
118
|
+
type: type.trim()
|
|
119
|
+
};
|
|
120
|
+
if (description) {
|
|
121
|
+
col.description = description.trim();
|
|
122
|
+
}
|
|
123
|
+
return col;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ../../packages/query/dist/presets.js
|
|
127
|
+
import { subDays, subMonths, subYears, startOfYear, startOfMonth, startOfQuarter, endOfMonth, endOfQuarter, endOfYear, format } from "date-fns";
|
|
128
|
+
function isCustomDateRange(value) {
|
|
129
|
+
return typeof value === "object" && value !== null && "type" in value && value.type === "custom" && "start" in value && "end" in value;
|
|
130
|
+
}
|
|
131
|
+
function expandCustomDateRange(range) {
|
|
132
|
+
return {
|
|
133
|
+
start_date: range.start,
|
|
134
|
+
end_date: range.end
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
var DATE_PRESETS = [
|
|
138
|
+
"last_7_days",
|
|
139
|
+
"last_30_days",
|
|
140
|
+
"last_90_days",
|
|
141
|
+
"last_12_months",
|
|
142
|
+
"year_to_date",
|
|
143
|
+
"month_to_date",
|
|
144
|
+
"quarter_to_date",
|
|
145
|
+
"previous_month",
|
|
146
|
+
"previous_quarter",
|
|
147
|
+
"previous_year"
|
|
148
|
+
];
|
|
149
|
+
var DATE_FORMAT = "yyyy-MM-dd";
|
|
150
|
+
function formatDate(date) {
|
|
151
|
+
return format(date, DATE_FORMAT);
|
|
152
|
+
}
|
|
153
|
+
function expandDatePreset(preset) {
|
|
154
|
+
const now = /* @__PURE__ */ new Date();
|
|
155
|
+
const today = formatDate(now);
|
|
156
|
+
switch (preset) {
|
|
157
|
+
case "last_7_days":
|
|
158
|
+
return {
|
|
159
|
+
start_date: formatDate(subDays(now, 7)),
|
|
160
|
+
end_date: today
|
|
161
|
+
};
|
|
162
|
+
case "last_30_days":
|
|
163
|
+
return {
|
|
164
|
+
start_date: formatDate(subDays(now, 30)),
|
|
165
|
+
end_date: today
|
|
166
|
+
};
|
|
167
|
+
case "last_90_days":
|
|
168
|
+
return {
|
|
169
|
+
start_date: formatDate(subDays(now, 90)),
|
|
170
|
+
end_date: today
|
|
171
|
+
};
|
|
172
|
+
case "last_12_months":
|
|
173
|
+
return {
|
|
174
|
+
start_date: formatDate(subMonths(now, 12)),
|
|
175
|
+
end_date: today
|
|
176
|
+
};
|
|
177
|
+
case "year_to_date":
|
|
178
|
+
return {
|
|
179
|
+
start_date: formatDate(startOfYear(now)),
|
|
180
|
+
end_date: today
|
|
181
|
+
};
|
|
182
|
+
case "month_to_date":
|
|
183
|
+
return {
|
|
184
|
+
start_date: formatDate(startOfMonth(now)),
|
|
185
|
+
end_date: today
|
|
186
|
+
};
|
|
187
|
+
case "quarter_to_date":
|
|
188
|
+
return {
|
|
189
|
+
start_date: formatDate(startOfQuarter(now)),
|
|
190
|
+
end_date: today
|
|
191
|
+
};
|
|
192
|
+
case "previous_month": {
|
|
193
|
+
const lastMonth = subMonths(now, 1);
|
|
194
|
+
return {
|
|
195
|
+
start_date: formatDate(startOfMonth(lastMonth)),
|
|
196
|
+
end_date: formatDate(endOfMonth(lastMonth))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
case "previous_quarter": {
|
|
200
|
+
const lastQuarter = subMonths(now, 3);
|
|
201
|
+
return {
|
|
202
|
+
start_date: formatDate(startOfQuarter(lastQuarter)),
|
|
203
|
+
end_date: formatDate(endOfQuarter(lastQuarter))
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
case "previous_year": {
|
|
207
|
+
const lastYear = subYears(now, 1);
|
|
208
|
+
return {
|
|
209
|
+
start_date: formatDate(startOfYear(lastYear)),
|
|
210
|
+
end_date: formatDate(endOfYear(lastYear))
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
default:
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function isDatePreset(value) {
|
|
218
|
+
return DATE_PRESETS.includes(value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ../../packages/query/dist/template.js
|
|
222
|
+
import nunjucks from "nunjucks";
|
|
223
|
+
var env = new nunjucks.Environment(null, {
|
|
224
|
+
autoescape: false,
|
|
225
|
+
// SQL doesn't need HTML escaping
|
|
226
|
+
throwOnUndefined: false
|
|
227
|
+
// user.x may be undefined for RLS
|
|
228
|
+
});
|
|
229
|
+
function createTemplateContext(params, refs = {}, userContext) {
|
|
230
|
+
return {
|
|
231
|
+
...params,
|
|
232
|
+
user: userContext ?? {},
|
|
233
|
+
ref: (name) => {
|
|
234
|
+
const resolved = refs[name];
|
|
235
|
+
if (resolved === void 0) {
|
|
236
|
+
throw new Error(`Unknown model reference: ${name}`);
|
|
237
|
+
}
|
|
238
|
+
return resolved;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function renderTemplate(template, context) {
|
|
243
|
+
const rendered = env.renderString(template, context);
|
|
244
|
+
return rendered.split("\n").map((line) => line.trimEnd()).filter((line, i, arr) => {
|
|
245
|
+
if (line === "" && arr[i - 1] === "")
|
|
246
|
+
return false;
|
|
247
|
+
return true;
|
|
248
|
+
}).join("\n").trim();
|
|
249
|
+
}
|
|
250
|
+
function templateHasVariable(template, varName) {
|
|
251
|
+
const regex = new RegExp(`\\{\\{\\s*${varName}(?:\\s*\\|[^}]+)?\\s*\\}\\}`, "g");
|
|
252
|
+
return regex.test(template);
|
|
253
|
+
}
|
|
254
|
+
function extractTemplateVariables(template) {
|
|
255
|
+
const variables = /* @__PURE__ */ new Set();
|
|
256
|
+
const regex = /\{\{\s*(\w+)(?:\s*\|[^}]+)?\s*\}\}/g;
|
|
257
|
+
let match;
|
|
258
|
+
while ((match = regex.exec(template)) !== null) {
|
|
259
|
+
const varName = match[1];
|
|
260
|
+
if (varName && varName !== "ref" && varName !== "loop") {
|
|
261
|
+
variables.add(varName);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return Array.from(variables);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ../../packages/query/dist/compiler.js
|
|
268
|
+
import { createHash } from "crypto";
|
|
269
|
+
import { format as format2 } from "date-fns";
|
|
270
|
+
function resolveDynamicDefault(value) {
|
|
271
|
+
if (typeof value !== "string")
|
|
272
|
+
return value;
|
|
273
|
+
const trimmed = value.trim().toLowerCase();
|
|
274
|
+
if (trimmed === "current_date()" || trimmed === "current_date" || trimmed === "now()") {
|
|
275
|
+
return format2(/* @__PURE__ */ new Date(), "yyyy-MM-dd");
|
|
276
|
+
}
|
|
277
|
+
if (trimmed.includes("current_date")) {
|
|
278
|
+
return format2(/* @__PURE__ */ new Date(), "yyyy-MM-dd");
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
var QueryCompiler = class {
|
|
283
|
+
models;
|
|
284
|
+
refs;
|
|
285
|
+
constructor(config) {
|
|
286
|
+
this.models = new Map(Object.entries(config.models));
|
|
287
|
+
this.refs = config.refs;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Compile a chart definition into an executable SQL query.
|
|
291
|
+
*/
|
|
292
|
+
compile(chart, inputParams, userContext) {
|
|
293
|
+
const { sql, modelParams } = this.getSQL(chart);
|
|
294
|
+
const params = this.resolveParams(chart, modelParams, inputParams);
|
|
295
|
+
const expandedParams = this.expandPresets(params);
|
|
296
|
+
const context = createTemplateContext(expandedParams, this.refs, userContext);
|
|
297
|
+
const renderedSQL = renderTemplate(sql, context);
|
|
298
|
+
const cacheKey = this.generateCacheKey(chart.name, renderedSQL, expandedParams, userContext);
|
|
299
|
+
return {
|
|
300
|
+
sql: renderedSQL,
|
|
301
|
+
params: expandedParams,
|
|
302
|
+
cacheKey,
|
|
303
|
+
chartName: chart.name
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
getSQL(chart) {
|
|
307
|
+
if (chart.source.sql) {
|
|
308
|
+
return { sql: chart.source.sql, modelParams: void 0 };
|
|
309
|
+
}
|
|
310
|
+
if (chart.source.model) {
|
|
311
|
+
const model = this.models.get(chart.source.model);
|
|
312
|
+
if (!model) {
|
|
313
|
+
throw new Error(`Unknown model: ${chart.source.model}`);
|
|
314
|
+
}
|
|
315
|
+
return { sql: model.sql, modelParams: model.metadata.params };
|
|
316
|
+
}
|
|
317
|
+
throw new Error("Chart source must specify either model or sql");
|
|
318
|
+
}
|
|
319
|
+
resolveParams(chart, modelParams, inputParams) {
|
|
320
|
+
const params = {};
|
|
321
|
+
if (modelParams) {
|
|
322
|
+
for (const param of modelParams) {
|
|
323
|
+
if (param.default !== void 0) {
|
|
324
|
+
params[param.name] = resolveDynamicDefault(param.default);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (chart.parameters) {
|
|
329
|
+
for (const param of chart.parameters) {
|
|
330
|
+
if (param.default !== void 0) {
|
|
331
|
+
params[param.name] = resolveDynamicDefault(param.default);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
Object.assign(params, inputParams);
|
|
336
|
+
return params;
|
|
337
|
+
}
|
|
338
|
+
expandPresets(params) {
|
|
339
|
+
const expanded = { ...params };
|
|
340
|
+
if (isCustomDateRange(params.date_range)) {
|
|
341
|
+
const dateRange = expandCustomDateRange(params.date_range);
|
|
342
|
+
expanded.start_date = dateRange.start_date;
|
|
343
|
+
expanded.end_date = dateRange.end_date;
|
|
344
|
+
} else if (typeof params.date_range === "string" && isDatePreset(params.date_range)) {
|
|
345
|
+
const dateRange = expandDatePreset(params.date_range);
|
|
346
|
+
if (dateRange) {
|
|
347
|
+
expanded.start_date = dateRange.start_date;
|
|
348
|
+
expanded.end_date = dateRange.end_date;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return expanded;
|
|
352
|
+
}
|
|
353
|
+
generateCacheKey(chartName, sql, params, userContext) {
|
|
354
|
+
const sqlHash = createHash("sha256").update(sql).digest("hex").slice(0, 8);
|
|
355
|
+
const paramsHash = createHash("sha256").update(JSON.stringify(params, Object.keys(params).sort())).digest("hex").slice(0, 8);
|
|
356
|
+
let key = `${chartName}:${sqlHash}:${paramsHash}`;
|
|
357
|
+
if (userContext && Object.keys(userContext).length > 0) {
|
|
358
|
+
const userHash = createHash("sha256").update(JSON.stringify(userContext, Object.keys(userContext).sort())).digest("hex").slice(0, 8);
|
|
359
|
+
key += `:${userHash}`;
|
|
360
|
+
}
|
|
361
|
+
return key;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Add or update a model in the compiler.
|
|
365
|
+
*/
|
|
366
|
+
addModel(name, metadata, sql) {
|
|
367
|
+
this.models.set(name, { metadata, sql });
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Add or update a ref mapping.
|
|
371
|
+
*/
|
|
372
|
+
addRef(name, target) {
|
|
373
|
+
this.refs[name] = target;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ../../packages/query/dist/connectors/duckdb.js
|
|
378
|
+
import duckdb from "duckdb";
|
|
379
|
+
import { performance as performance2 } from "perf_hooks";
|
|
380
|
+
var DuckDBConnector = class {
|
|
381
|
+
config;
|
|
382
|
+
db = null;
|
|
383
|
+
connection = null;
|
|
384
|
+
constructor(config) {
|
|
385
|
+
this.config = config;
|
|
386
|
+
}
|
|
387
|
+
async connect() {
|
|
388
|
+
return new Promise((resolve, reject) => {
|
|
389
|
+
this.db = new duckdb.Database(this.config.path, (err) => {
|
|
390
|
+
if (err) {
|
|
391
|
+
reject(err);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
this.connection = this.db.connect();
|
|
395
|
+
resolve();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async disconnect() {
|
|
400
|
+
return new Promise((resolve) => {
|
|
401
|
+
if (this.connection) {
|
|
402
|
+
this.connection.close(() => {
|
|
403
|
+
this.connection = null;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (this.db) {
|
|
407
|
+
this.db.close(() => {
|
|
408
|
+
this.db = null;
|
|
409
|
+
resolve();
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
resolve();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
async execute(sql) {
|
|
417
|
+
if (!this.connection) {
|
|
418
|
+
throw new Error("Not connected to database");
|
|
419
|
+
}
|
|
420
|
+
const startTime = performance2.now();
|
|
421
|
+
return new Promise((resolve, reject) => {
|
|
422
|
+
const stmt = this.connection.prepare(sql, (prepErr) => {
|
|
423
|
+
if (prepErr) {
|
|
424
|
+
reject(prepErr);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const columnInfo = stmt.columns();
|
|
428
|
+
stmt.all((err, rows) => {
|
|
429
|
+
const durationMs = performance2.now() - startTime;
|
|
430
|
+
if (err) {
|
|
431
|
+
reject(err);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const typedRows = rows;
|
|
435
|
+
const serializedRows = typedRows.map((row) => this.serializeRow(row));
|
|
436
|
+
const columns = columnInfo.map((col) => ({
|
|
437
|
+
name: col.name,
|
|
438
|
+
type: col.type.sql_type
|
|
439
|
+
}));
|
|
440
|
+
stmt.finalize();
|
|
441
|
+
resolve({
|
|
442
|
+
columns,
|
|
443
|
+
rows: serializedRows,
|
|
444
|
+
rowCount: serializedRows.length,
|
|
445
|
+
durationMs
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
isConnected() {
|
|
452
|
+
return this.connection !== null;
|
|
453
|
+
}
|
|
454
|
+
async explain(sql) {
|
|
455
|
+
if (!this.connection) {
|
|
456
|
+
throw new Error("Not connected to database");
|
|
457
|
+
}
|
|
458
|
+
return new Promise((resolve) => {
|
|
459
|
+
this.connection.all(`EXPLAIN ${sql}`, (err) => {
|
|
460
|
+
if (err) {
|
|
461
|
+
resolve({ valid: false, error: err.message });
|
|
462
|
+
} else {
|
|
463
|
+
resolve({ valid: true });
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
serializeRow(row) {
|
|
469
|
+
const result = {};
|
|
470
|
+
for (const [key, value] of Object.entries(row)) {
|
|
471
|
+
if (typeof value === "bigint") {
|
|
472
|
+
result[key] = Number(value);
|
|
473
|
+
} else {
|
|
474
|
+
result[key] = value;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// ../../packages/query/dist/connectors/postgres.js
|
|
482
|
+
import { Pool, types } from "pg";
|
|
483
|
+
import { performance as performance3 } from "perf_hooks";
|
|
484
|
+
types.setTypeParser(20, (val) => {
|
|
485
|
+
if (val === null)
|
|
486
|
+
return null;
|
|
487
|
+
return BigInt(val);
|
|
488
|
+
});
|
|
489
|
+
function escapeIdentifier(identifier) {
|
|
490
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
|
|
491
|
+
throw new Error(`Invalid identifier: ${identifier}`);
|
|
492
|
+
}
|
|
493
|
+
return `"${identifier}"`;
|
|
494
|
+
}
|
|
495
|
+
var PostgresConnector = class {
|
|
496
|
+
config;
|
|
497
|
+
pool = null;
|
|
498
|
+
constructor(config) {
|
|
499
|
+
this.config = config;
|
|
500
|
+
}
|
|
501
|
+
async connect() {
|
|
502
|
+
const poolConfig = {
|
|
503
|
+
host: this.config.host,
|
|
504
|
+
port: this.config.port,
|
|
505
|
+
database: this.config.database,
|
|
506
|
+
user: this.config.user,
|
|
507
|
+
password: this.config.password,
|
|
508
|
+
ssl: this.config.ssl,
|
|
509
|
+
min: this.config.min ?? 2,
|
|
510
|
+
max: this.config.max ?? 10,
|
|
511
|
+
idleTimeoutMillis: this.config.idleTimeoutMillis ?? 3e4,
|
|
512
|
+
connectionTimeoutMillis: this.config.connectTimeoutMillis ?? 1e4
|
|
513
|
+
};
|
|
514
|
+
if (this.config.statementTimeout) {
|
|
515
|
+
poolConfig.statement_timeout = this.config.statementTimeout;
|
|
516
|
+
}
|
|
517
|
+
this.pool = new Pool(poolConfig);
|
|
518
|
+
this.pool.on("error", (err) => {
|
|
519
|
+
console.error("Unexpected postgres pool error:", err.message);
|
|
520
|
+
});
|
|
521
|
+
if (this.config.schema) {
|
|
522
|
+
const schema = escapeIdentifier(this.config.schema);
|
|
523
|
+
this.pool.on("connect", (client2) => {
|
|
524
|
+
client2.query(`SET search_path TO ${schema}`).catch((err) => {
|
|
525
|
+
console.error("Failed to set search_path:", err.message);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
const client = await this.pool.connect();
|
|
530
|
+
client.release();
|
|
531
|
+
}
|
|
532
|
+
async disconnect() {
|
|
533
|
+
if (this.pool) {
|
|
534
|
+
await this.pool.end();
|
|
535
|
+
this.pool = null;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
isConnected() {
|
|
539
|
+
return this.pool !== null;
|
|
540
|
+
}
|
|
541
|
+
async execute(sql) {
|
|
542
|
+
if (!this.pool) {
|
|
543
|
+
throw new Error("Not connected to database");
|
|
544
|
+
}
|
|
545
|
+
const start = performance3.now();
|
|
546
|
+
const result = await this.pool.query(sql);
|
|
547
|
+
const durationMs = performance3.now() - start;
|
|
548
|
+
return {
|
|
549
|
+
columns: this.extractColumns(result),
|
|
550
|
+
rows: result.rows.map((row) => this.serializeRow(row)),
|
|
551
|
+
rowCount: result.rowCount ?? result.rows.length,
|
|
552
|
+
durationMs
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
async explain(sql) {
|
|
556
|
+
if (!this.pool) {
|
|
557
|
+
throw new Error("Not connected to database");
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
await this.pool.query(`EXPLAIN ${sql}`);
|
|
561
|
+
return { valid: true };
|
|
562
|
+
} catch (err) {
|
|
563
|
+
return {
|
|
564
|
+
valid: false,
|
|
565
|
+
error: err instanceof Error ? err.message : String(err)
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
extractColumns(result) {
|
|
570
|
+
return result.fields.map((field) => ({
|
|
571
|
+
name: field.name,
|
|
572
|
+
type: this.pgTypeToString(field.dataTypeID)
|
|
573
|
+
}));
|
|
574
|
+
}
|
|
575
|
+
pgTypeToString(oid) {
|
|
576
|
+
const typeMap = {
|
|
577
|
+
16: "boolean",
|
|
578
|
+
// bool
|
|
579
|
+
20: "integer",
|
|
580
|
+
// int8
|
|
581
|
+
21: "integer",
|
|
582
|
+
// int2
|
|
583
|
+
23: "integer",
|
|
584
|
+
// int4
|
|
585
|
+
700: "number",
|
|
586
|
+
// float4
|
|
587
|
+
701: "number",
|
|
588
|
+
// float8
|
|
589
|
+
1700: "number",
|
|
590
|
+
// numeric
|
|
591
|
+
25: "string",
|
|
592
|
+
// text
|
|
593
|
+
1043: "string",
|
|
594
|
+
// varchar
|
|
595
|
+
1082: "date",
|
|
596
|
+
// date
|
|
597
|
+
1114: "date",
|
|
598
|
+
// timestamp
|
|
599
|
+
1184: "date",
|
|
600
|
+
// timestamptz
|
|
601
|
+
114: "unknown",
|
|
602
|
+
// json
|
|
603
|
+
3802: "unknown"
|
|
604
|
+
// jsonb
|
|
605
|
+
};
|
|
606
|
+
return typeMap[oid] ?? "unknown";
|
|
607
|
+
}
|
|
608
|
+
serializeRow(row) {
|
|
609
|
+
const serialized = {};
|
|
610
|
+
for (const [key, value] of Object.entries(row)) {
|
|
611
|
+
serialized[key] = this.serializeValue(value);
|
|
612
|
+
}
|
|
613
|
+
return serialized;
|
|
614
|
+
}
|
|
615
|
+
serializeValue(value) {
|
|
616
|
+
if (value === null || value === void 0) {
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
if (typeof value === "bigint") {
|
|
620
|
+
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
|
|
621
|
+
return value.toString();
|
|
622
|
+
}
|
|
623
|
+
return Number(value);
|
|
624
|
+
}
|
|
625
|
+
if (value instanceof Date) {
|
|
626
|
+
return value.toISOString();
|
|
627
|
+
}
|
|
628
|
+
return value;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// ../../packages/query/dist/connectors/mysql.js
|
|
633
|
+
import { createPool } from "mysql2/promise";
|
|
634
|
+
import { performance as performance4 } from "perf_hooks";
|
|
635
|
+
var MySQLConnector = class {
|
|
636
|
+
config;
|
|
637
|
+
pool = null;
|
|
638
|
+
constructor(config) {
|
|
639
|
+
this.config = config;
|
|
640
|
+
}
|
|
641
|
+
async connect() {
|
|
642
|
+
const poolConfig = {
|
|
643
|
+
host: this.config.host,
|
|
644
|
+
port: this.config.port,
|
|
645
|
+
database: this.config.database,
|
|
646
|
+
user: this.config.user,
|
|
647
|
+
password: this.config.password,
|
|
648
|
+
ssl: this.config.ssl ? {} : void 0,
|
|
649
|
+
waitForConnections: true,
|
|
650
|
+
connectionLimit: this.config.max ?? 10,
|
|
651
|
+
queueLimit: 0,
|
|
652
|
+
connectTimeout: this.config.connectTimeoutMillis ?? 1e4
|
|
653
|
+
};
|
|
654
|
+
this.pool = createPool(poolConfig);
|
|
655
|
+
const connection = await this.pool.getConnection();
|
|
656
|
+
connection.release();
|
|
657
|
+
}
|
|
658
|
+
async disconnect() {
|
|
659
|
+
if (this.pool) {
|
|
660
|
+
await this.pool.end();
|
|
661
|
+
this.pool = null;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
isConnected() {
|
|
665
|
+
return this.pool !== null;
|
|
666
|
+
}
|
|
667
|
+
async execute(sql) {
|
|
668
|
+
if (!this.pool) {
|
|
669
|
+
throw new Error("Not connected to database");
|
|
670
|
+
}
|
|
671
|
+
const start = performance4.now();
|
|
672
|
+
const [rows, fields] = await this.pool.query(sql);
|
|
673
|
+
const durationMs = performance4.now() - start;
|
|
674
|
+
return {
|
|
675
|
+
columns: this.extractColumns(fields),
|
|
676
|
+
rows: rows.map((row) => this.serializeRow(row)),
|
|
677
|
+
rowCount: rows.length,
|
|
678
|
+
durationMs
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
async explain(sql) {
|
|
682
|
+
if (!this.pool) {
|
|
683
|
+
throw new Error("Not connected to database");
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
await this.pool.query(`EXPLAIN ${sql}`);
|
|
687
|
+
return { valid: true };
|
|
688
|
+
} catch (err) {
|
|
689
|
+
return {
|
|
690
|
+
valid: false,
|
|
691
|
+
error: err instanceof Error ? err.message : String(err)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
extractColumns(fields) {
|
|
696
|
+
return fields.map((field) => ({
|
|
697
|
+
name: field.name,
|
|
698
|
+
type: this.mysqlTypeToString(field.type)
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
mysqlTypeToString(type) {
|
|
702
|
+
const typeMap = {
|
|
703
|
+
0: "number",
|
|
704
|
+
// DECIMAL
|
|
705
|
+
1: "integer",
|
|
706
|
+
// TINY
|
|
707
|
+
2: "integer",
|
|
708
|
+
// SHORT
|
|
709
|
+
3: "integer",
|
|
710
|
+
// LONG
|
|
711
|
+
4: "number",
|
|
712
|
+
// FLOAT
|
|
713
|
+
5: "number",
|
|
714
|
+
// DOUBLE
|
|
715
|
+
7: "date",
|
|
716
|
+
// TIMESTAMP
|
|
717
|
+
8: "integer",
|
|
718
|
+
// LONGLONG
|
|
719
|
+
9: "integer",
|
|
720
|
+
// INT24
|
|
721
|
+
10: "date",
|
|
722
|
+
// DATE
|
|
723
|
+
11: "string",
|
|
724
|
+
// TIME
|
|
725
|
+
12: "date",
|
|
726
|
+
// DATETIME
|
|
727
|
+
13: "integer",
|
|
728
|
+
// YEAR
|
|
729
|
+
15: "string",
|
|
730
|
+
// VARCHAR
|
|
731
|
+
245: "unknown",
|
|
732
|
+
// JSON
|
|
733
|
+
246: "number",
|
|
734
|
+
// NEWDECIMAL
|
|
735
|
+
252: "string",
|
|
736
|
+
// BLOB
|
|
737
|
+
253: "string",
|
|
738
|
+
// VAR_STRING
|
|
739
|
+
254: "string"
|
|
740
|
+
// STRING
|
|
741
|
+
};
|
|
742
|
+
return typeMap[type ?? 0] ?? "unknown";
|
|
743
|
+
}
|
|
744
|
+
serializeRow(row) {
|
|
745
|
+
const serialized = {};
|
|
746
|
+
for (const [key, value] of Object.entries(row)) {
|
|
747
|
+
serialized[key] = this.serializeValue(value);
|
|
748
|
+
}
|
|
749
|
+
return serialized;
|
|
750
|
+
}
|
|
751
|
+
serializeValue(value) {
|
|
752
|
+
if (value === null || value === void 0) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
if (typeof value === "bigint") {
|
|
756
|
+
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
|
|
757
|
+
return value.toString();
|
|
758
|
+
}
|
|
759
|
+
return Number(value);
|
|
760
|
+
}
|
|
761
|
+
if (value instanceof Date) {
|
|
762
|
+
return value.toISOString();
|
|
763
|
+
}
|
|
764
|
+
if (Buffer.isBuffer(value)) {
|
|
765
|
+
return value.toString("utf-8");
|
|
766
|
+
}
|
|
767
|
+
return value;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// ../../packages/query/dist/connectors/sqlite.js
|
|
772
|
+
import Database from "better-sqlite3";
|
|
773
|
+
import { performance as performance5 } from "perf_hooks";
|
|
774
|
+
var SQLiteConnector = class {
|
|
775
|
+
config;
|
|
776
|
+
db = null;
|
|
777
|
+
constructor(config) {
|
|
778
|
+
this.config = config;
|
|
779
|
+
}
|
|
780
|
+
async connect() {
|
|
781
|
+
this.db = new Database(this.config.path, {
|
|
782
|
+
readonly: this.config.readonly ?? false
|
|
783
|
+
});
|
|
784
|
+
this.db.pragma("journal_mode = WAL");
|
|
785
|
+
this.db.pragma("foreign_keys = ON");
|
|
786
|
+
}
|
|
787
|
+
async disconnect() {
|
|
788
|
+
if (this.db) {
|
|
789
|
+
this.db.close();
|
|
790
|
+
this.db = null;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
isConnected() {
|
|
794
|
+
return this.db !== null;
|
|
795
|
+
}
|
|
796
|
+
async execute(sql) {
|
|
797
|
+
if (!this.db) {
|
|
798
|
+
throw new Error("Not connected to database");
|
|
799
|
+
}
|
|
800
|
+
const start = performance5.now();
|
|
801
|
+
const stmt = this.db.prepare(sql);
|
|
802
|
+
const rows = stmt.all();
|
|
803
|
+
const durationMs = performance5.now() - start;
|
|
804
|
+
const columns = stmt.columns().map((col) => ({
|
|
805
|
+
name: col.name,
|
|
806
|
+
type: this.sqliteTypeToString(col.type)
|
|
807
|
+
}));
|
|
808
|
+
return {
|
|
809
|
+
columns,
|
|
810
|
+
rows: rows.map((row) => this.serializeRow(row)),
|
|
811
|
+
rowCount: rows.length,
|
|
812
|
+
durationMs
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
async explain(sql) {
|
|
816
|
+
if (!this.db) {
|
|
817
|
+
throw new Error("Not connected to database");
|
|
818
|
+
}
|
|
819
|
+
try {
|
|
820
|
+
this.db.prepare(`EXPLAIN ${sql}`);
|
|
821
|
+
return { valid: true };
|
|
822
|
+
} catch (err) {
|
|
823
|
+
return {
|
|
824
|
+
valid: false,
|
|
825
|
+
error: err instanceof Error ? err.message : String(err)
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
sqliteTypeToString(type) {
|
|
830
|
+
if (!type)
|
|
831
|
+
return "unknown";
|
|
832
|
+
const upperType = type.toUpperCase();
|
|
833
|
+
if (upperType.includes("INT"))
|
|
834
|
+
return "integer";
|
|
835
|
+
if (upperType.includes("CHAR") || upperType.includes("TEXT") || upperType.includes("CLOB"))
|
|
836
|
+
return "string";
|
|
837
|
+
if (upperType.includes("BLOB"))
|
|
838
|
+
return "unknown";
|
|
839
|
+
if (upperType.includes("REAL") || upperType.includes("FLOA") || upperType.includes("DOUB"))
|
|
840
|
+
return "number";
|
|
841
|
+
if (upperType.includes("NUMERIC") || upperType.includes("DECIMAL"))
|
|
842
|
+
return "number";
|
|
843
|
+
if (upperType.includes("DATE") || upperType.includes("TIME"))
|
|
844
|
+
return "date";
|
|
845
|
+
if (upperType.includes("BOOL"))
|
|
846
|
+
return "boolean";
|
|
847
|
+
return "unknown";
|
|
848
|
+
}
|
|
849
|
+
serializeRow(row) {
|
|
850
|
+
const serialized = {};
|
|
851
|
+
for (const [key, value] of Object.entries(row)) {
|
|
852
|
+
serialized[key] = this.serializeValue(value);
|
|
853
|
+
}
|
|
854
|
+
return serialized;
|
|
855
|
+
}
|
|
856
|
+
serializeValue(value) {
|
|
857
|
+
if (value === null || value === void 0) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
if (typeof value === "bigint") {
|
|
861
|
+
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
|
|
862
|
+
return value.toString();
|
|
863
|
+
}
|
|
864
|
+
return Number(value);
|
|
865
|
+
}
|
|
866
|
+
if (Buffer.isBuffer(value)) {
|
|
867
|
+
return value.toString("base64");
|
|
868
|
+
}
|
|
869
|
+
return value;
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
// ../../packages/query/dist/connectors/snowflake.js
|
|
874
|
+
import snowflake from "snowflake-sdk";
|
|
875
|
+
import { performance as performance6 } from "perf_hooks";
|
|
876
|
+
var SnowflakeConnector = class {
|
|
877
|
+
config;
|
|
878
|
+
connection = null;
|
|
879
|
+
constructor(config) {
|
|
880
|
+
this.config = config;
|
|
881
|
+
}
|
|
882
|
+
async connect() {
|
|
883
|
+
const connectionOptions = {
|
|
884
|
+
account: this.config.account,
|
|
885
|
+
username: this.config.username,
|
|
886
|
+
password: this.config.password,
|
|
887
|
+
privateKey: this.config.privateKey,
|
|
888
|
+
warehouse: this.config.warehouse,
|
|
889
|
+
database: this.config.database,
|
|
890
|
+
schema: this.config.schema,
|
|
891
|
+
role: this.config.role,
|
|
892
|
+
timeout: this.config.connectTimeoutMillis ?? 6e4
|
|
893
|
+
};
|
|
894
|
+
this.connection = snowflake.createConnection(connectionOptions);
|
|
895
|
+
return new Promise((resolve, reject) => {
|
|
896
|
+
this.connection.connect((err) => {
|
|
897
|
+
if (err) {
|
|
898
|
+
this.connection = null;
|
|
899
|
+
reject(err);
|
|
900
|
+
} else {
|
|
901
|
+
resolve();
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
async disconnect() {
|
|
907
|
+
if (this.connection) {
|
|
908
|
+
return new Promise((resolve, reject) => {
|
|
909
|
+
this.connection.destroy((err) => {
|
|
910
|
+
this.connection = null;
|
|
911
|
+
if (err) {
|
|
912
|
+
reject(err);
|
|
913
|
+
} else {
|
|
914
|
+
resolve();
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
isConnected() {
|
|
921
|
+
return this.connection !== null && this.connection.isUp();
|
|
922
|
+
}
|
|
923
|
+
async execute(sql) {
|
|
924
|
+
if (!this.connection) {
|
|
925
|
+
throw new Error("Not connected to database");
|
|
926
|
+
}
|
|
927
|
+
const start = performance6.now();
|
|
928
|
+
return new Promise((resolve, reject) => {
|
|
929
|
+
this.connection.execute({
|
|
930
|
+
sqlText: sql,
|
|
931
|
+
complete: (err, stmt, rows) => {
|
|
932
|
+
const durationMs = performance6.now() - start;
|
|
933
|
+
if (err) {
|
|
934
|
+
reject(err);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const columns = stmt ? stmt.getColumns().map((col) => ({
|
|
938
|
+
name: col.getName(),
|
|
939
|
+
type: this.snowflakeTypeToString(col.getType())
|
|
940
|
+
})) : [];
|
|
941
|
+
resolve({
|
|
942
|
+
columns,
|
|
943
|
+
rows: (rows || []).map((row) => this.serializeRow(row)),
|
|
944
|
+
rowCount: rows?.length ?? 0,
|
|
945
|
+
durationMs
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
async explain(sql) {
|
|
952
|
+
if (!this.connection) {
|
|
953
|
+
throw new Error("Not connected to database");
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
await this.execute(`EXPLAIN ${sql}`);
|
|
957
|
+
return { valid: true };
|
|
958
|
+
} catch (err) {
|
|
959
|
+
return {
|
|
960
|
+
valid: false,
|
|
961
|
+
error: err instanceof Error ? err.message : String(err)
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
snowflakeTypeToString(type) {
|
|
966
|
+
const typeMap = {
|
|
967
|
+
"NUMBER": "number",
|
|
968
|
+
"DECIMAL": "number",
|
|
969
|
+
"NUMERIC": "number",
|
|
970
|
+
"INT": "integer",
|
|
971
|
+
"INTEGER": "integer",
|
|
972
|
+
"BIGINT": "integer",
|
|
973
|
+
"SMALLINT": "integer",
|
|
974
|
+
"TINYINT": "integer",
|
|
975
|
+
"BYTEINT": "integer",
|
|
976
|
+
"FLOAT": "number",
|
|
977
|
+
"FLOAT4": "number",
|
|
978
|
+
"FLOAT8": "number",
|
|
979
|
+
"DOUBLE": "number",
|
|
980
|
+
"DOUBLE PRECISION": "number",
|
|
981
|
+
"REAL": "number",
|
|
982
|
+
"VARCHAR": "string",
|
|
983
|
+
"CHAR": "string",
|
|
984
|
+
"CHARACTER": "string",
|
|
985
|
+
"STRING": "string",
|
|
986
|
+
"TEXT": "string",
|
|
987
|
+
"BINARY": "unknown",
|
|
988
|
+
"VARBINARY": "unknown",
|
|
989
|
+
"BOOLEAN": "boolean",
|
|
990
|
+
"DATE": "date",
|
|
991
|
+
"DATETIME": "date",
|
|
992
|
+
"TIME": "string",
|
|
993
|
+
"TIMESTAMP": "date",
|
|
994
|
+
"TIMESTAMP_LTZ": "date",
|
|
995
|
+
"TIMESTAMP_NTZ": "date",
|
|
996
|
+
"TIMESTAMP_TZ": "date",
|
|
997
|
+
"VARIANT": "unknown",
|
|
998
|
+
"OBJECT": "unknown",
|
|
999
|
+
"ARRAY": "unknown"
|
|
1000
|
+
};
|
|
1001
|
+
const upperType = type.toUpperCase();
|
|
1002
|
+
return typeMap[upperType] ?? "unknown";
|
|
1003
|
+
}
|
|
1004
|
+
serializeRow(row) {
|
|
1005
|
+
const serialized = {};
|
|
1006
|
+
for (const [key, value] of Object.entries(row)) {
|
|
1007
|
+
serialized[key] = this.serializeValue(value);
|
|
1008
|
+
}
|
|
1009
|
+
return serialized;
|
|
1010
|
+
}
|
|
1011
|
+
serializeValue(value) {
|
|
1012
|
+
if (value === null || value === void 0) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
if (typeof value === "bigint") {
|
|
1016
|
+
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) {
|
|
1017
|
+
return value.toString();
|
|
1018
|
+
}
|
|
1019
|
+
return Number(value);
|
|
1020
|
+
}
|
|
1021
|
+
if (value instanceof Date) {
|
|
1022
|
+
return value.toISOString();
|
|
1023
|
+
}
|
|
1024
|
+
return value;
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// ../../packages/query/dist/connectors/auth.js
|
|
1029
|
+
function resolvePostgresAuth(connection) {
|
|
1030
|
+
const auth = connection.auth;
|
|
1031
|
+
if (!auth) {
|
|
1032
|
+
return {
|
|
1033
|
+
user: process.env.PGUSER ?? "postgres",
|
|
1034
|
+
password: process.env.PGPASSWORD ?? ""
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
if (auth.type === "env") {
|
|
1038
|
+
const user = process.env[auth.user_var];
|
|
1039
|
+
const password = process.env[auth.password_var];
|
|
1040
|
+
if (!user) {
|
|
1041
|
+
throw new Error(`Missing environment variable: ${auth.user_var}`);
|
|
1042
|
+
}
|
|
1043
|
+
return { user, password: password ?? "" };
|
|
1044
|
+
}
|
|
1045
|
+
throw new Error(`Auth type "${auth.type}" not yet implemented`);
|
|
1046
|
+
}
|
|
1047
|
+
function resolveMySQLAuth(connection) {
|
|
1048
|
+
const auth = connection.auth;
|
|
1049
|
+
if (!auth) {
|
|
1050
|
+
return {
|
|
1051
|
+
user: process.env.MYSQL_USER ?? "root",
|
|
1052
|
+
password: process.env.MYSQL_PASSWORD ?? ""
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
if (auth.type === "env") {
|
|
1056
|
+
const user = process.env[auth.user_var];
|
|
1057
|
+
const password = process.env[auth.password_var];
|
|
1058
|
+
if (!user) {
|
|
1059
|
+
throw new Error(`Missing environment variable: ${auth.user_var}`);
|
|
1060
|
+
}
|
|
1061
|
+
return { user, password: password ?? "" };
|
|
1062
|
+
}
|
|
1063
|
+
throw new Error(`Auth type "${auth.type}" not yet implemented`);
|
|
1064
|
+
}
|
|
1065
|
+
function resolveSnowflakeAuth(connection) {
|
|
1066
|
+
const auth = connection.auth;
|
|
1067
|
+
if (auth.type === "env") {
|
|
1068
|
+
const username = process.env[auth.user_var];
|
|
1069
|
+
const password = process.env[auth.password_var];
|
|
1070
|
+
if (!username) {
|
|
1071
|
+
throw new Error(`Missing environment variable: ${auth.user_var}`);
|
|
1072
|
+
}
|
|
1073
|
+
return { username, password: password ?? void 0 };
|
|
1074
|
+
}
|
|
1075
|
+
if (auth.type === "key_pair") {
|
|
1076
|
+
const username = process.env[auth.user_var];
|
|
1077
|
+
if (!username) {
|
|
1078
|
+
throw new Error(`Missing environment variable: ${auth.user_var}`);
|
|
1079
|
+
}
|
|
1080
|
+
const fs = __require("fs");
|
|
1081
|
+
const privateKey = fs.readFileSync(auth.private_key_path, "utf-8");
|
|
1082
|
+
return { username, privateKey };
|
|
1083
|
+
}
|
|
1084
|
+
throw new Error(`Auth type "${auth.type}" not yet implemented for Snowflake`);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ../../packages/query/dist/test-runner.js
|
|
1088
|
+
var TYPE_MAP = {
|
|
1089
|
+
date: ["date", "timestamp", "datetime", "timestamptz", "timestamp with time zone"],
|
|
1090
|
+
number: ["integer", "int", "bigint", "float", "double", "decimal", "numeric", "real", "smallint", "tinyint", "hugeint", "int4", "int8", "float4", "float8"],
|
|
1091
|
+
integer: ["integer", "int", "bigint", "smallint", "tinyint", "hugeint", "int4", "int8"],
|
|
1092
|
+
string: ["varchar", "text", "char", "string", "character varying", "nvarchar"],
|
|
1093
|
+
boolean: ["boolean", "bool"]
|
|
1094
|
+
};
|
|
1095
|
+
function typesMatch(expectedType, actualType) {
|
|
1096
|
+
const expected = expectedType.toLowerCase();
|
|
1097
|
+
const actual = actualType.toLowerCase();
|
|
1098
|
+
if (expected === actual)
|
|
1099
|
+
return true;
|
|
1100
|
+
const acceptedTypes = TYPE_MAP[expected];
|
|
1101
|
+
if (acceptedTypes)
|
|
1102
|
+
return acceptedTypes.includes(actual);
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
function expandThis(assertionSql, compiledSql) {
|
|
1106
|
+
return assertionSql.replace(/\{\{\s*this\s*\}\}/g, compiledSql);
|
|
1107
|
+
}
|
|
1108
|
+
var MAX_SAMPLE_VIOLATIONS = 5;
|
|
1109
|
+
async function runAssertion(compiledSql, assertionSql, connector) {
|
|
1110
|
+
const warning = !/\{\{\s*this\s*\}\}/.test(assertionSql) ? "Test assertion does not reference {{this}} \u2014 did you mean to include it?" : void 0;
|
|
1111
|
+
const expandedSql = expandThis(assertionSql, compiledSql);
|
|
1112
|
+
try {
|
|
1113
|
+
const result = await connector.execute(expandedSql);
|
|
1114
|
+
return {
|
|
1115
|
+
sql: assertionSql,
|
|
1116
|
+
passed: result.rowCount === 0,
|
|
1117
|
+
violationCount: result.rowCount,
|
|
1118
|
+
sampleViolations: result.rows.slice(0, MAX_SAMPLE_VIOLATIONS),
|
|
1119
|
+
warning
|
|
1120
|
+
};
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
return {
|
|
1123
|
+
sql: assertionSql,
|
|
1124
|
+
passed: false,
|
|
1125
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1126
|
+
warning
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
async function runModel(compiledSql, metadata, connector) {
|
|
1131
|
+
const start = performance.now();
|
|
1132
|
+
const hasReturns = metadata.returns && metadata.returns.length > 0;
|
|
1133
|
+
const hasTests = metadata.tests && metadata.tests.length > 0;
|
|
1134
|
+
if (!hasReturns && !hasTests) {
|
|
1135
|
+
return {
|
|
1136
|
+
modelName: metadata.name,
|
|
1137
|
+
assertions: [],
|
|
1138
|
+
durationMs: performance.now() - start
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
let schemaCheckResult;
|
|
1142
|
+
if (hasReturns) {
|
|
1143
|
+
try {
|
|
1144
|
+
schemaCheckResult = await checkSchema(compiledSql, connector, metadata.returns);
|
|
1145
|
+
} catch (err) {
|
|
1146
|
+
return {
|
|
1147
|
+
modelName: metadata.name,
|
|
1148
|
+
assertions: [],
|
|
1149
|
+
durationMs: performance.now() - start,
|
|
1150
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const assertions = [];
|
|
1155
|
+
if (hasTests) {
|
|
1156
|
+
for (const testSql of metadata.tests) {
|
|
1157
|
+
const result = await runAssertion(compiledSql, testSql, connector);
|
|
1158
|
+
assertions.push(result);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
modelName: metadata.name,
|
|
1163
|
+
schemaCheck: schemaCheckResult,
|
|
1164
|
+
assertions,
|
|
1165
|
+
durationMs: performance.now() - start
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
async function runAll(models, connector) {
|
|
1169
|
+
const start = performance.now();
|
|
1170
|
+
const results = [];
|
|
1171
|
+
let passed = 0;
|
|
1172
|
+
let failed = 0;
|
|
1173
|
+
let skipped = 0;
|
|
1174
|
+
for (const model of models) {
|
|
1175
|
+
const result = await runModel(model.compiledSql, model.metadata, connector);
|
|
1176
|
+
results.push(result);
|
|
1177
|
+
const hasAnyCheck = result.schemaCheck || result.assertions.length > 0;
|
|
1178
|
+
if (!hasAnyCheck && !result.error) {
|
|
1179
|
+
skipped++;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (result.error) {
|
|
1183
|
+
failed++;
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
if (result.schemaCheck) {
|
|
1187
|
+
if (result.schemaCheck.passed)
|
|
1188
|
+
passed++;
|
|
1189
|
+
else
|
|
1190
|
+
failed++;
|
|
1191
|
+
}
|
|
1192
|
+
for (const assertion of result.assertions) {
|
|
1193
|
+
if (assertion.passed)
|
|
1194
|
+
passed++;
|
|
1195
|
+
else
|
|
1196
|
+
failed++;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
return {
|
|
1200
|
+
models: results,
|
|
1201
|
+
passed,
|
|
1202
|
+
failed,
|
|
1203
|
+
skipped,
|
|
1204
|
+
durationMs: performance.now() - start
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
async function checkSchema(compiledSql, connector, expectedReturns) {
|
|
1208
|
+
const wrappedSql = `SELECT * FROM (${compiledSql}) AS _model LIMIT 0`;
|
|
1209
|
+
const result = await connector.execute(wrappedSql);
|
|
1210
|
+
const actualColumns = result.columns.map((c) => c.name);
|
|
1211
|
+
const expectedColumns = expectedReturns.map((c) => c.name);
|
|
1212
|
+
const missingColumns = expectedColumns.filter((name) => !actualColumns.includes(name));
|
|
1213
|
+
const extraColumns = actualColumns.filter((name) => !expectedColumns.includes(name));
|
|
1214
|
+
const typeMismatches = [];
|
|
1215
|
+
for (const expected of expectedReturns) {
|
|
1216
|
+
const actual = result.columns.find((c) => c.name === expected.name);
|
|
1217
|
+
if (actual && !typesMatch(expected.type, actual.type)) {
|
|
1218
|
+
typeMismatches.push({
|
|
1219
|
+
column: expected.name,
|
|
1220
|
+
expected: expected.type,
|
|
1221
|
+
actual: actual.type
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
passed: missingColumns.length === 0 && typeMismatches.length === 0,
|
|
1227
|
+
expectedColumns,
|
|
1228
|
+
actualColumns,
|
|
1229
|
+
missingColumns,
|
|
1230
|
+
extraColumns,
|
|
1231
|
+
typeMismatches
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// ../../packages/query/dist/index.js
|
|
1236
|
+
var VERSION = "0.1.0";
|
|
1237
|
+
|
|
1238
|
+
export {
|
|
1239
|
+
parseModelMetadata,
|
|
1240
|
+
isCustomDateRange,
|
|
1241
|
+
expandCustomDateRange,
|
|
1242
|
+
DATE_PRESETS,
|
|
1243
|
+
expandDatePreset,
|
|
1244
|
+
isDatePreset,
|
|
1245
|
+
createTemplateContext,
|
|
1246
|
+
renderTemplate,
|
|
1247
|
+
templateHasVariable,
|
|
1248
|
+
extractTemplateVariables,
|
|
1249
|
+
QueryCompiler,
|
|
1250
|
+
DuckDBConnector,
|
|
1251
|
+
PostgresConnector,
|
|
1252
|
+
MySQLConnector,
|
|
1253
|
+
SQLiteConnector,
|
|
1254
|
+
SnowflakeConnector,
|
|
1255
|
+
resolvePostgresAuth,
|
|
1256
|
+
resolveMySQLAuth,
|
|
1257
|
+
resolveSnowflakeAuth,
|
|
1258
|
+
expandThis,
|
|
1259
|
+
runAssertion,
|
|
1260
|
+
runModel,
|
|
1261
|
+
runAll,
|
|
1262
|
+
checkSchema,
|
|
1263
|
+
VERSION
|
|
1264
|
+
};
|
|
1265
|
+
//# sourceMappingURL=chunk-LL3VKMZM.js.map
|