@reasoningco/infer 0.1.4 → 0.1.6
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/catalog.d.ts.map +1 -1
- package/dist/catalog.js +58 -11
- package/dist/catalog.js.map +1 -1
- package/dist/connectors.d.ts +2 -0
- package/dist/connectors.d.ts.map +1 -1
- package/dist/connectors.js +22 -5
- package/dist/connectors.js.map +1 -1
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +5 -1
- package/dist/dates.js.map +1 -1
- package/dist/entity-resolver.d.ts.map +1 -1
- package/dist/entity-resolver.js +3 -1
- package/dist/entity-resolver.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -11
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +17 -5
- package/dist/llm.js.map +1 -1
- package/dist/query-validator.d.ts.map +1 -1
- package/dist/query-validator.js +7 -0
- package/dist/query-validator.js.map +1 -1
- package/dist/schemas.d.ts +50 -50
- package/dist/schemas.js +10 -10
- package/dist/schemas.js.map +1 -1
- package/dist/sql-builder.d.ts.map +1 -1
- package/dist/sql-builder.js +94 -33
- package/dist/sql-builder.js.map +1 -1
- package/dist/sql-validator.d.ts.map +1 -1
- package/dist/sql-validator.js +18 -8
- package/dist/sql-validator.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/schemas.d.ts
CHANGED
|
@@ -2,15 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const FilterSchema: z.ZodObject<{
|
|
3
3
|
field: z.ZodString;
|
|
4
4
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
5
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
5
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
6
6
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
value: string | number | boolean | (string | number)[];
|
|
8
7
|
field: string;
|
|
9
8
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
9
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
10
10
|
}, {
|
|
11
|
-
value: string | number | boolean | (string | number)[];
|
|
12
11
|
field: string;
|
|
13
12
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
13
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
14
14
|
}>;
|
|
15
15
|
export declare const EntityFilterSchema: z.ZodObject<{
|
|
16
16
|
resolver: z.ZodString;
|
|
@@ -33,15 +33,15 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
33
33
|
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
34
|
field: z.ZodString;
|
|
35
35
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
36
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
36
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
37
37
|
}, "strip", z.ZodTypeAny, {
|
|
38
|
-
value: string | number | boolean | (string | number)[];
|
|
39
38
|
field: string;
|
|
40
39
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
40
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
41
41
|
}, {
|
|
42
|
-
value: string | number | boolean | (string | number)[];
|
|
43
42
|
field: string;
|
|
44
43
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
44
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
45
45
|
}>, "many">>>;
|
|
46
46
|
}, "strip", z.ZodTypeAny, {
|
|
47
47
|
mode: "not_exists" | "exists";
|
|
@@ -49,9 +49,9 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
49
49
|
joinColumn: string;
|
|
50
50
|
foreignColumn: string;
|
|
51
51
|
filters: {
|
|
52
|
-
value: string | number | boolean | (string | number)[];
|
|
53
52
|
field: string;
|
|
54
53
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
54
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
55
55
|
}[];
|
|
56
56
|
}, {
|
|
57
57
|
mode: "not_exists" | "exists";
|
|
@@ -59,9 +59,9 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
59
59
|
joinColumn: string;
|
|
60
60
|
foreignColumn: string;
|
|
61
61
|
filters?: {
|
|
62
|
-
value: string | number | boolean | (string | number)[];
|
|
63
62
|
field: string;
|
|
64
63
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
64
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
65
65
|
}[] | undefined;
|
|
66
66
|
}>;
|
|
67
67
|
export declare const DateRangeSchema: z.ZodObject<{
|
|
@@ -92,40 +92,44 @@ export declare const OrderBySchema: z.ZodObject<{
|
|
|
92
92
|
}>;
|
|
93
93
|
export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
94
94
|
dataset: z.ZodString;
|
|
95
|
-
select: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
96
|
-
metrics: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
97
|
-
groupBy: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
95
|
+
select: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
96
|
+
metrics: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
97
|
+
groupBy: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
98
98
|
timeBucket: z.ZodEffects<z.ZodOptional<z.ZodEnum<["hour", "day", "week", "month", "quarter", "year"]>>, "month" | "day" | "week" | "quarter" | "year" | "hour" | undefined, unknown>;
|
|
99
99
|
extractBucket: z.ZodEffects<z.ZodOptional<z.ZodEnum<["hour_of_day", "day_of_week", "month_of_year", "quarter", "year", "week"]>>, "week" | "quarter" | "year" | "hour_of_day" | "day_of_week" | "month_of_year" | undefined, unknown>;
|
|
100
|
-
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
100
|
+
filters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
101
101
|
field: z.ZodString;
|
|
102
102
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
103
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
103
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
104
104
|
}, "strip", z.ZodTypeAny, {
|
|
105
|
-
value: string | number | boolean | (string | number)[];
|
|
106
105
|
field: string;
|
|
107
106
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
107
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
108
108
|
}, {
|
|
109
|
-
value: string | number | boolean | (string | number)[];
|
|
110
109
|
field: string;
|
|
111
110
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
112
|
-
|
|
111
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
112
|
+
}>, "many">>>, {
|
|
113
|
+
field: string;
|
|
114
|
+
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
115
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
116
|
+
}[], unknown>;
|
|
113
117
|
having: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
114
118
|
field: z.ZodString;
|
|
115
119
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
116
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
120
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
117
121
|
}, "strip", z.ZodTypeAny, {
|
|
118
|
-
value: string | number | boolean | (string | number)[];
|
|
119
122
|
field: string;
|
|
120
123
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
124
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
121
125
|
}, {
|
|
122
|
-
value: string | number | boolean | (string | number)[];
|
|
123
126
|
field: string;
|
|
124
127
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
128
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
125
129
|
}>, "many">>>, {
|
|
126
|
-
value: string | number | boolean | (string | number)[];
|
|
127
130
|
field: string;
|
|
128
131
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
132
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
129
133
|
}[], unknown>;
|
|
130
134
|
existsFilters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
131
135
|
mode: z.ZodEnum<["exists", "not_exists"]>;
|
|
@@ -135,15 +139,15 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
135
139
|
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
136
140
|
field: z.ZodString;
|
|
137
141
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
138
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
142
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
139
143
|
}, "strip", z.ZodTypeAny, {
|
|
140
|
-
value: string | number | boolean | (string | number)[];
|
|
141
144
|
field: string;
|
|
142
145
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
146
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
143
147
|
}, {
|
|
144
|
-
value: string | number | boolean | (string | number)[];
|
|
145
148
|
field: string;
|
|
146
149
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
150
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
147
151
|
}>, "many">>>;
|
|
148
152
|
}, "strip", z.ZodTypeAny, {
|
|
149
153
|
mode: "not_exists" | "exists";
|
|
@@ -151,9 +155,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
151
155
|
joinColumn: string;
|
|
152
156
|
foreignColumn: string;
|
|
153
157
|
filters: {
|
|
154
|
-
value: string | number | boolean | (string | number)[];
|
|
155
158
|
field: string;
|
|
156
159
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
160
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
157
161
|
}[];
|
|
158
162
|
}, {
|
|
159
163
|
mode: "not_exists" | "exists";
|
|
@@ -161,9 +165,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
161
165
|
joinColumn: string;
|
|
162
166
|
foreignColumn: string;
|
|
163
167
|
filters?: {
|
|
164
|
-
value: string | number | boolean | (string | number)[];
|
|
165
168
|
field: string;
|
|
166
169
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
170
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
167
171
|
}[] | undefined;
|
|
168
172
|
}>, "many">>>, {
|
|
169
173
|
mode: "not_exists" | "exists";
|
|
@@ -171,12 +175,12 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
171
175
|
joinColumn: string;
|
|
172
176
|
foreignColumn: string;
|
|
173
177
|
filters: {
|
|
174
|
-
value: string | number | boolean | (string | number)[];
|
|
175
178
|
field: string;
|
|
176
179
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
180
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
177
181
|
}[];
|
|
178
182
|
}[], unknown>;
|
|
179
|
-
entityFilters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
183
|
+
entityFilters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
180
184
|
resolver: z.ZodString;
|
|
181
185
|
input: z.ZodString;
|
|
182
186
|
outputColumn: z.ZodString;
|
|
@@ -188,7 +192,11 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
188
192
|
input: string;
|
|
189
193
|
resolver: string;
|
|
190
194
|
outputColumn: string;
|
|
191
|
-
}>, "many"
|
|
195
|
+
}>, "many">>>, {
|
|
196
|
+
input: string;
|
|
197
|
+
resolver: string;
|
|
198
|
+
outputColumn: string;
|
|
199
|
+
}[], unknown>;
|
|
192
200
|
dateRange: z.ZodEffects<z.ZodOptional<z.ZodObject<{
|
|
193
201
|
mode: z.ZodEnum<["relative", "explicit", "latest", "none"]>;
|
|
194
202
|
phrase: z.ZodOptional<z.ZodString>;
|
|
@@ -210,7 +218,7 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
210
218
|
endDate?: string | undefined;
|
|
211
219
|
phrase?: string | undefined;
|
|
212
220
|
} | undefined, unknown>;
|
|
213
|
-
orderBy: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
221
|
+
orderBy: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
214
222
|
field: z.ZodString;
|
|
215
223
|
dir: z.ZodDefault<z.ZodEnum<["asc", "desc"]>>;
|
|
216
224
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -219,22 +227,25 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
219
227
|
}, {
|
|
220
228
|
field: string;
|
|
221
229
|
dir?: "desc" | "asc" | undefined;
|
|
222
|
-
}>, "many"
|
|
230
|
+
}>, "many">>>, {
|
|
231
|
+
field: string;
|
|
232
|
+
dir: "desc" | "asc";
|
|
233
|
+
}[], unknown>;
|
|
223
234
|
limit: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
|
|
224
235
|
answerMode: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodEnum<["rows", "aggregate_table", "scalar"]>>>, "aggregate_table" | "rows" | "scalar", unknown>;
|
|
225
236
|
compareMode: z.ZodEffects<z.ZodOptional<z.ZodEnum<["previous_period", "year_over_year"]>>, "previous_period" | "year_over_year" | undefined, unknown>;
|
|
226
237
|
}, "strip", z.ZodTypeAny, {
|
|
227
238
|
select: string[];
|
|
228
239
|
having: {
|
|
229
|
-
value: string | number | boolean | (string | number)[];
|
|
230
240
|
field: string;
|
|
231
241
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
242
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
232
243
|
}[];
|
|
233
244
|
dataset: string;
|
|
234
245
|
filters: {
|
|
235
|
-
value: string | number | boolean | (string | number)[];
|
|
236
246
|
field: string;
|
|
237
247
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
248
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
238
249
|
}[];
|
|
239
250
|
metrics: string[];
|
|
240
251
|
groupBy: string[];
|
|
@@ -244,9 +255,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
244
255
|
joinColumn: string;
|
|
245
256
|
foreignColumn: string;
|
|
246
257
|
filters: {
|
|
247
|
-
value: string | number | boolean | (string | number)[];
|
|
248
258
|
field: string;
|
|
249
259
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
260
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
250
261
|
}[];
|
|
251
262
|
}[];
|
|
252
263
|
entityFilters: {
|
|
@@ -271,28 +282,17 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
271
282
|
compareMode?: "previous_period" | "year_over_year" | undefined;
|
|
272
283
|
}, {
|
|
273
284
|
dataset: string;
|
|
274
|
-
select?:
|
|
285
|
+
select?: unknown;
|
|
275
286
|
having?: unknown;
|
|
276
|
-
filters?:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
280
|
-
}[] | undefined;
|
|
281
|
-
metrics?: string[] | undefined;
|
|
282
|
-
groupBy?: string[] | undefined;
|
|
287
|
+
filters?: unknown;
|
|
288
|
+
metrics?: unknown;
|
|
289
|
+
groupBy?: unknown;
|
|
283
290
|
timeBucket?: unknown;
|
|
284
291
|
extractBucket?: unknown;
|
|
285
292
|
existsFilters?: unknown;
|
|
286
|
-
entityFilters?:
|
|
287
|
-
input: string;
|
|
288
|
-
resolver: string;
|
|
289
|
-
outputColumn: string;
|
|
290
|
-
}[] | undefined;
|
|
293
|
+
entityFilters?: unknown;
|
|
291
294
|
dateRange?: unknown;
|
|
292
|
-
orderBy?:
|
|
293
|
-
field: string;
|
|
294
|
-
dir?: "desc" | "asc" | undefined;
|
|
295
|
-
}[] | undefined;
|
|
295
|
+
orderBy?: unknown;
|
|
296
296
|
limit?: unknown;
|
|
297
297
|
answerMode?: unknown;
|
|
298
298
|
compareMode?: unknown;
|
package/dist/schemas.js
CHANGED
|
@@ -5,10 +5,10 @@ export const FilterSchema = z.object({
|
|
|
5
5
|
op: z.enum(["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]),
|
|
6
6
|
value: z.union([
|
|
7
7
|
z.string(),
|
|
8
|
-
z.number(),
|
|
8
|
+
z.number().finite(),
|
|
9
9
|
z.boolean(),
|
|
10
|
-
z.array(z.union([z.string(), z.number()])),
|
|
11
|
-
]),
|
|
10
|
+
z.array(z.union([z.string(), z.number().finite()])),
|
|
11
|
+
]).optional(),
|
|
12
12
|
});
|
|
13
13
|
// ── Entity filter schema ──
|
|
14
14
|
export const EntityFilterSchema = z.object({
|
|
@@ -39,15 +39,15 @@ export const OrderBySchema = z.object({
|
|
|
39
39
|
// ── Smart query plan schema ──
|
|
40
40
|
export const SmartQueryPlanSchema = z.object({
|
|
41
41
|
dataset: z.string(),
|
|
42
|
-
select: z.array(z.string()).optional().default([]),
|
|
43
|
-
metrics: z.array(z.string()).optional().default([]),
|
|
44
|
-
groupBy: z.array(z.string()).optional().default([]),
|
|
42
|
+
select: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
43
|
+
metrics: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
44
|
+
groupBy: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
45
45
|
timeBucket: z.preprocess((v) => (v === null ? undefined : v), z.enum(["hour", "day", "week", "month", "quarter", "year"]).optional()),
|
|
46
46
|
extractBucket: z.preprocess((v) => (v === null ? undefined : v), z.enum(["hour_of_day", "day_of_week", "month_of_year", "quarter", "year", "week"]).optional()),
|
|
47
|
-
filters: z.array(FilterSchema).optional().default([]),
|
|
47
|
+
filters: z.preprocess((v) => (v === null ? [] : v), z.array(FilterSchema).optional().default([])),
|
|
48
48
|
having: z.preprocess((v) => (v === null ? [] : v), z.array(FilterSchema).optional().default([])),
|
|
49
49
|
existsFilters: z.preprocess((v) => (v === null ? [] : v), z.array(ExistsFilterSchema).optional().default([])),
|
|
50
|
-
entityFilters: z.array(EntityFilterSchema).optional().default([]),
|
|
50
|
+
entityFilters: z.preprocess((v) => (v === null ? [] : v), z.array(EntityFilterSchema).optional().default([])),
|
|
51
51
|
dateRange: z.preprocess((v) => {
|
|
52
52
|
if (v === null)
|
|
53
53
|
return undefined;
|
|
@@ -55,8 +55,8 @@ export const SmartQueryPlanSchema = z.object({
|
|
|
55
55
|
return undefined;
|
|
56
56
|
return v;
|
|
57
57
|
}, DateRangeSchema.optional()),
|
|
58
|
-
orderBy: z.array(OrderBySchema).optional().default([]),
|
|
59
|
-
limit: z.preprocess((v) => (v === null ? undefined : v), z.number().int().positive().max(200).optional()),
|
|
58
|
+
orderBy: z.preprocess((v) => (v === null ? [] : v), z.array(OrderBySchema).optional().default([])),
|
|
59
|
+
limit: z.preprocess((v) => (v === null ? undefined : v), z.number().finite().int().positive().max(200).optional()),
|
|
60
60
|
answerMode: z.preprocess((v) => (v === null ? undefined : v), z.enum(["rows", "aggregate_table", "scalar"]).optional().default("rows")),
|
|
61
61
|
compareMode: z.preprocess((v) => (v === null ? undefined : v), z.enum(['previous_period', 'year_over_year']).optional()),
|
|
62
62
|
});
|
package/dist/schemas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,sBAAsB;AAEtB,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;IACxG,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;QACb,CAAC,CAAC,MAAM,EAAE;QACV,CAAC,CAAC,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-builder.d.ts","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIpF,iBAAS,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM3C;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,YAAY,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"sql-builder.d.ts","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIpF,iBAAS,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM3C;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,YAAY,GAAG,WAAW,CAwZpI"}
|
package/dist/sql-builder.js
CHANGED
|
@@ -2,8 +2,8 @@ import { InferError } from './errors.js';
|
|
|
2
2
|
// Use pg driver's native escapeIdentifier pattern
|
|
3
3
|
// Since we can't import pg at compile time (it's optional), we implement the safe quoting
|
|
4
4
|
function quoteIdentifier(id) {
|
|
5
|
-
// Validate: identifiers must be alphanumeric
|
|
6
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id)) {
|
|
5
|
+
// Validate: identifiers must be alphanumeric, underscore, or hyphen (no SQL injection)
|
|
6
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(id)) {
|
|
7
7
|
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid identifier: ${id}`);
|
|
8
8
|
}
|
|
9
9
|
return `"${id.replace(/"/g, '""')}"`;
|
|
@@ -29,6 +29,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
29
29
|
// ── Auto-resolve FK columns to human-readable names ──
|
|
30
30
|
const fkNameColumns = [];
|
|
31
31
|
const fkGroupByRefs = [];
|
|
32
|
+
const fkLabelAliases = new Set();
|
|
32
33
|
if (catalog && plan.dataset.joins) {
|
|
33
34
|
const columnsToResolve = plan.metrics.length > 0 ? plan.groupBy : plan.select;
|
|
34
35
|
for (const col of columnsToResolve) {
|
|
@@ -45,16 +46,39 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
45
46
|
const nameCol = targetDs.selectableColumns.find(c => ['name', 'title', 'label', 'display_name', 'full_name', 'username', 'email'].includes(c));
|
|
46
47
|
if (!nameCol)
|
|
47
48
|
continue;
|
|
48
|
-
|
|
49
|
+
let alias = bareTable;
|
|
50
|
+
if (joinAliases.has(bareTable)) {
|
|
51
|
+
// Table already joined — use a unique alias to avoid conflicts (e.g. two FKs to same table)
|
|
52
|
+
alias = `${bareTable}__${col}`;
|
|
53
|
+
if (!joinAliases.has(alias)) {
|
|
54
|
+
const targetFull = `${quoteIdentifier(targetDs.table.schema)}.${quoteIdentifier(targetDs.table.name)}`;
|
|
55
|
+
joinAliases.set(alias, alias);
|
|
56
|
+
joinClauses.push(`LEFT JOIN ${targetFull} AS ${quoteIdentifier(alias)} ON ${quoteIdentifier(mainAlias)}.${quoteIdentifier(col)} = ${quoteIdentifier(alias)}.${quoteIdentifier(joinDef.foreignColumn)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
49
60
|
const targetFull = `${quoteIdentifier(targetDs.table.schema)}.${quoteIdentifier(targetDs.table.name)}`;
|
|
50
61
|
joinAliases.set(bareTable, bareTable);
|
|
51
62
|
joinClauses.push(`LEFT JOIN ${targetFull} AS ${quoteIdentifier(bareTable)} ON ${quoteIdentifier(mainAlias)}.${quoteIdentifier(col)} = ${quoteIdentifier(bareTable)}.${quoteIdentifier(joinDef.foreignColumn)}`);
|
|
52
63
|
}
|
|
53
|
-
const fkRef = `${quoteIdentifier(
|
|
54
|
-
|
|
64
|
+
const fkRef = `${quoteIdentifier(alias)}.${quoteIdentifier(nameCol)}`;
|
|
65
|
+
// Use a unique alias that won't collide with existing columns
|
|
66
|
+
const fkAlias = col.replace(/_id$/, '') + '_name';
|
|
67
|
+
const safeFkAlias = plan.dataset.selectableColumns.includes(fkAlias) ? `${fkAlias}_fk` : fkAlias;
|
|
68
|
+
fkNameColumns.push(`${fkRef} AS ${quoteIdentifier(safeFkAlias)}`);
|
|
55
69
|
fkGroupByRefs.push(fkRef);
|
|
70
|
+
fkLabelAliases.add(safeFkAlias);
|
|
56
71
|
}
|
|
57
72
|
}
|
|
73
|
+
// ── Validate time/extract buckets at runtime ──
|
|
74
|
+
const VALID_TIME_BUCKETS = new Set(['hour', 'day', 'week', 'month', 'quarter', 'year']);
|
|
75
|
+
const VALID_EXTRACT_BUCKETS = new Set(['hour_of_day', 'day_of_week', 'month_of_year', 'quarter', 'year', 'week']);
|
|
76
|
+
if (plan.timeBucket && !VALID_TIME_BUCKETS.has(plan.timeBucket)) {
|
|
77
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid timeBucket: ${plan.timeBucket}`);
|
|
78
|
+
}
|
|
79
|
+
if (plan.extractBucket && !VALID_EXTRACT_BUCKETS.has(plan.extractBucket)) {
|
|
80
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid extractBucket: ${plan.extractBucket}`);
|
|
81
|
+
}
|
|
58
82
|
// ── Determine which groupBy columns need DATE_TRUNC or EXTRACT ──
|
|
59
83
|
const dateGroupColumns = new Set();
|
|
60
84
|
const extractGroupColumns = new Set();
|
|
@@ -83,7 +107,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
83
107
|
case 'quarter': return `EXTRACT(QUARTER FROM ${ref})`;
|
|
84
108
|
case 'year': return `EXTRACT(YEAR FROM ${ref})`;
|
|
85
109
|
case 'week': return `EXTRACT(WEEK FROM ${ref})`;
|
|
86
|
-
default:
|
|
110
|
+
default: throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid extractBucket: ${plan.extractBucket}`);
|
|
87
111
|
}
|
|
88
112
|
};
|
|
89
113
|
// Helper: resolve a column reference (supports dot-notation for joins)
|
|
@@ -147,11 +171,11 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
147
171
|
return expr;
|
|
148
172
|
// Replace bare column refs (not already qualified with table.) inside the expression
|
|
149
173
|
// Match word chars that aren't SQL keywords or already qualified
|
|
150
|
-
return expr.replace(/"([a-zA-Z_][a-zA-Z0-9_]*)"/g, (match, colName) => {
|
|
174
|
+
return expr.replace(/(?<!\.)("([a-zA-Z_][a-zA-Z0-9_]*)")(?!\.)/g, (match, quoted, colName) => {
|
|
151
175
|
// Check if this column exists on the main dataset
|
|
152
176
|
if (plan.dataset.selectableColumns.includes(colName) ||
|
|
153
177
|
plan.dataset.filterableColumns.includes(colName)) {
|
|
154
|
-
return `${quoteIdentifier(mainAlias)}.${
|
|
178
|
+
return `${quoteIdentifier(mainAlias)}.${quoted}`;
|
|
155
179
|
}
|
|
156
180
|
return match;
|
|
157
181
|
});
|
|
@@ -164,14 +188,14 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
164
188
|
selectCols = [...groupCols, ...metricCols];
|
|
165
189
|
}
|
|
166
190
|
else if (plan.select.length > 0) {
|
|
167
|
-
selectCols = plan.select.map(col =>
|
|
191
|
+
selectCols = plan.select.map(col => resolveColRef(col));
|
|
168
192
|
}
|
|
169
193
|
else if (plan.groupBy.length > 0 && plan.metrics.length === 0) {
|
|
170
194
|
// In rows mode with groupBy but no explicit select, only select grouped columns
|
|
171
|
-
selectCols = plan.groupBy.map(col =>
|
|
195
|
+
selectCols = plan.groupBy.map(col => resolveColRef(col));
|
|
172
196
|
}
|
|
173
197
|
else {
|
|
174
|
-
selectCols = plan.dataset.selectableColumns.map(col =>
|
|
198
|
+
selectCols = plan.dataset.selectableColumns.map(col => resolveColRef(col));
|
|
175
199
|
}
|
|
176
200
|
// Append FK name columns (auto-resolved human-readable names)
|
|
177
201
|
if (fkNameColumns.length > 0) {
|
|
@@ -182,19 +206,23 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
182
206
|
}
|
|
183
207
|
// ── Build WHERE clause ──
|
|
184
208
|
const whereParts = [];
|
|
185
|
-
// Tenant isolation (ALWAYS injected server-side)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
209
|
+
// Tenant isolation (ALWAYS injected server-side when multi-tenant)
|
|
210
|
+
let tenantParamIdx = null;
|
|
211
|
+
if (ctx.tenantColumn !== undefined || (ctx.tenantId && ctx.tenantId.trim() !== '')) {
|
|
212
|
+
if (!ctx.tenantId || ctx.tenantId.trim() === '') {
|
|
213
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', 'Tenant ID is required but was not provided');
|
|
214
|
+
}
|
|
215
|
+
const tenantCol = ctx.tenantColumn ?? 'tenant_id';
|
|
216
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tenantCol)) {
|
|
217
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid tenant column name: ${tenantCol}`);
|
|
218
|
+
}
|
|
219
|
+
const tenantRef = joinClauses.length > 0
|
|
220
|
+
? `${quoteIdentifier(mainAlias)}.${quoteIdentifier(tenantCol)}`
|
|
221
|
+
: quoteIdentifier(tenantCol);
|
|
222
|
+
params.push(ctx.tenantId);
|
|
223
|
+
tenantParamIdx = paramIdx;
|
|
224
|
+
whereParts.push(`${tenantRef} = $${paramIdx++}`);
|
|
192
225
|
}
|
|
193
|
-
const tenantRef = joinClauses.length > 0
|
|
194
|
-
? `${quoteIdentifier(mainAlias)}.${quoteIdentifier(tenantCol)}`
|
|
195
|
-
: quoteIdentifier(tenantCol);
|
|
196
|
-
params.push(ctx.tenantId);
|
|
197
|
-
whereParts.push(`${tenantRef} = $${paramIdx++}`);
|
|
198
226
|
// Location scoping (optional)
|
|
199
227
|
if (ctx.locationIds && ctx.locationIds.length > 0) {
|
|
200
228
|
const locationRef = joinClauses.length > 0
|
|
@@ -206,9 +234,9 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
206
234
|
// Date range
|
|
207
235
|
if (plan.dateRange && plan.dataset.dateColumn) {
|
|
208
236
|
params.push(plan.dateRange.startDate);
|
|
209
|
-
whereParts.push(`${
|
|
237
|
+
whereParts.push(`${resolveColRef(plan.dataset.dateColumn)} >= $${paramIdx++}`);
|
|
210
238
|
params.push(plan.dateRange.endDate);
|
|
211
|
-
whereParts.push(`${
|
|
239
|
+
whereParts.push(`${resolveColRef(plan.dataset.dateColumn)} <= $${paramIdx++}`);
|
|
212
240
|
}
|
|
213
241
|
// User filters (parameterized)
|
|
214
242
|
for (const filter of plan.filters) {
|
|
@@ -218,7 +246,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
218
246
|
col = buildExtractExpr(resolveColRef(filter.field));
|
|
219
247
|
}
|
|
220
248
|
else {
|
|
221
|
-
col =
|
|
249
|
+
col = resolveColRef(filter.field);
|
|
222
250
|
}
|
|
223
251
|
const op = sanitizeOperator(filter.op);
|
|
224
252
|
if (op === 'IS NULL') {
|
|
@@ -243,7 +271,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
243
271
|
// Entity filters (resolved IDs)
|
|
244
272
|
for (const entity of plan.resolvedEntities) {
|
|
245
273
|
if (entity.resolvedIds.length > 0) {
|
|
246
|
-
const col =
|
|
274
|
+
const col = resolveColRef(entity.outputColumn);
|
|
247
275
|
params.push(entity.resolvedIds);
|
|
248
276
|
whereParts.push(`${col} = ANY($${paramIdx++})`);
|
|
249
277
|
}
|
|
@@ -266,6 +294,11 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
266
294
|
subWhere += ` AND ${quoteIdentifier(subAlias)}.${quoteIdentifier(f.field)} ${op} $${paramIdx++}`;
|
|
267
295
|
}
|
|
268
296
|
}
|
|
297
|
+
// Add tenant isolation to EXISTS subquery
|
|
298
|
+
if (tenantParamIdx !== null) {
|
|
299
|
+
const tenantCol = ctx.tenantColumn ?? 'tenant_id';
|
|
300
|
+
subWhere += ` AND ${quoteIdentifier(subAlias)}.${quoteIdentifier(tenantCol)} = $${tenantParamIdx}`;
|
|
301
|
+
}
|
|
269
302
|
const existsOp = ef.mode === 'not_exists' ? 'NOT EXISTS' : 'EXISTS';
|
|
270
303
|
whereParts.push(`${existsOp} (SELECT 1 FROM ${targetTable} AS ${quoteIdentifier(subAlias)} WHERE ${subWhere})`);
|
|
271
304
|
}
|
|
@@ -288,6 +321,10 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
288
321
|
else if (op === 'IS NOT NULL') {
|
|
289
322
|
havingParts.push(`${fieldRef} IS NOT NULL`);
|
|
290
323
|
}
|
|
324
|
+
else if (op === 'IN' || op === 'NOT IN') {
|
|
325
|
+
params.push(filter.value);
|
|
326
|
+
havingParts.push(`${fieldRef} ${op === 'NOT IN' ? 'NOT ' : ''}= ANY($${paramIdx++})`);
|
|
327
|
+
}
|
|
291
328
|
else {
|
|
292
329
|
params.push(filter.value);
|
|
293
330
|
havingParts.push(`${fieldRef} ${op} $${paramIdx++}`);
|
|
@@ -295,17 +332,33 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
295
332
|
}
|
|
296
333
|
havingSql = `HAVING ${havingParts.join(' AND ')}`;
|
|
297
334
|
}
|
|
335
|
+
// Helper: check if a field is a computed alias (not a real table column)
|
|
336
|
+
const isComputedAlias = (field) => {
|
|
337
|
+
if (plan.metrics.some(m => m.id === field))
|
|
338
|
+
return true;
|
|
339
|
+
if (fkLabelAliases.has(field))
|
|
340
|
+
return true;
|
|
341
|
+
if (field.endsWith('_delta') || field.endsWith('_growth_pct'))
|
|
342
|
+
return true;
|
|
343
|
+
if (plan.extractBucket && (field.endsWith('_hour') || field.endsWith('_dow') || field.endsWith('_month')))
|
|
344
|
+
return true;
|
|
345
|
+
return false;
|
|
346
|
+
};
|
|
298
347
|
// ── Build ORDER BY (direction whitelisted to ASC/DESC only) ──
|
|
299
348
|
const orderBySql = plan.orderBy.length > 0
|
|
300
349
|
? `ORDER BY ${plan.orderBy.map(o => {
|
|
301
|
-
const ref =
|
|
302
|
-
?
|
|
303
|
-
:
|
|
304
|
-
|
|
350
|
+
const ref = isComputedAlias(o.field)
|
|
351
|
+
? quoteIdentifier(o.field)
|
|
352
|
+
: extractGroupColumns.has(o.field) && plan.extractBucket
|
|
353
|
+
? buildExtractExpr(resolveColRef(o.field))
|
|
354
|
+
: dateGroupColumns.has(o.field) && plan.timeBucket
|
|
355
|
+
? formatGroupByRef(o.field)
|
|
356
|
+
: resolveColRef(o.field);
|
|
357
|
+
return `${ref} ${o.dir === 'desc' ? 'DESC NULLS LAST' : 'ASC'}`;
|
|
305
358
|
}).join(', ')}`
|
|
306
359
|
: '';
|
|
307
360
|
// ── LIMIT (always applied) ──
|
|
308
|
-
params.push(plan.limit);
|
|
361
|
+
params.push(Math.max(1, plan.limit));
|
|
309
362
|
const limitSql = `LIMIT $${paramIdx++}`;
|
|
310
363
|
// ── OFFSET (optional pagination) ──
|
|
311
364
|
let offsetSql = '';
|
|
@@ -331,8 +384,16 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
331
384
|
// compareMode: wrap in CTE with LAG window functions
|
|
332
385
|
if (plan.compareMode && plan.metrics.length > 0 && (plan.timeBucket || plan.extractBucket)) {
|
|
333
386
|
const metricAliases = plan.metrics.map(m => quoteIdentifier(m.id));
|
|
387
|
+
const timeCol = plan.groupBy.find(col => {
|
|
388
|
+
const t = plan.dataset.columnTypes[col];
|
|
389
|
+
return t === 'date' || t === 'timestamp';
|
|
390
|
+
}) || plan.groupBy[0] || 'period';
|
|
391
|
+
// Use the column alias as it appears in the inner query (after DATE_TRUNC/EXTRACT)
|
|
392
|
+
const timeAlias = colAlias(timeCol);
|
|
393
|
+
const partitionCols = plan.groupBy.filter(col => col !== timeCol).map(c => colAlias(c));
|
|
394
|
+
const partitionBy = partitionCols.length > 0 ? `PARTITION BY ${partitionCols.map(c => quoteIdentifier(c)).join(', ')} ` : '';
|
|
334
395
|
const lagCols = metricAliases.map(alias => {
|
|
335
|
-
return `${alias} - LAG(${alias}) OVER (ORDER BY ${quoteIdentifier(
|
|
396
|
+
return `${alias} - LAG(${alias}) OVER (${partitionBy}ORDER BY ${quoteIdentifier(timeAlias)}) AS ${alias.replace(/"$/, '_delta"')}`;
|
|
336
397
|
});
|
|
337
398
|
const wrappedSql = `WITH _base AS (\n${sql}\n)\nSELECT *, ${lagCols.join(', ')} FROM _base`;
|
|
338
399
|
return { sql: wrappedSql, params };
|