@visactor/vquery 0.4.7 → 0.4.9

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.
@@ -123,7 +123,7 @@ const applySelect = (qb, select)=>{
123
123
  const expression = eb.ref(field);
124
124
  if (item.aggr) {
125
125
  const { func } = item.aggr;
126
- const alias = item.alias ?? field;
126
+ const sqlAlias = field;
127
127
  if ([
128
128
  'avg',
129
129
  'sum',
@@ -134,29 +134,99 @@ const applySelect = (qb, select)=>{
134
134
  'stddev',
135
135
  'median'
136
136
  ].includes(func)) {
137
- if ('variance' === func) return external_kysely_sql`var_samp(${expression})`.as(alias);
138
- if ('variancePop' === func) return external_kysely_sql`var_pop(${expression})`.as(alias);
139
- return external_kysely_sql`${external_kysely_sql.raw(func)}(${expression})`.as(alias);
137
+ if ('variance' === func) return external_kysely_sql`var_samp(${expression})`.as(sqlAlias);
138
+ if ('variancePop' === func) return external_kysely_sql`var_pop(${expression})`.as(sqlAlias);
139
+ return external_kysely_sql`${external_kysely_sql.raw(func)}(${expression})`.as(sqlAlias);
140
140
  }
141
- if ('count' === func) return external_kysely_sql`CAST(count(${expression}) AS INTEGER)`.as(alias);
141
+ if ('count' === func) return external_kysely_sql`CAST(count(${expression}) AS INTEGER)`.as(sqlAlias);
142
142
  if ('quantile' === func) {
143
143
  const q = item.aggr.quantile ?? 0.5;
144
- return external_kysely_sql`quantile(${expression}, ${q})`.as(alias);
145
- } else if ('count_distinct' === func) return external_kysely_sql`CAST(count(distinct ${expression}) AS INTEGER)`.as(alias);
144
+ return external_kysely_sql`quantile(${expression}, ${q})`.as(sqlAlias);
145
+ } else if ('count_distinct' === func) return external_kysely_sql`CAST(count(distinct ${expression}) AS INTEGER)`.as(sqlAlias);
146
146
  else if (func.startsWith('to_')) {
147
147
  const dateTrunc = func.replace('to_', '');
148
148
  const format = DATE_FORMAT_MAP[dateTrunc];
149
- if (format) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(alias);
150
- if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(alias);
149
+ if (format) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(sqlAlias);
150
+ if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(sqlAlias);
151
151
  }
152
152
  }
153
- const alias = item.alias ?? field;
154
- return expression.as(alias);
153
+ return expression.as(field);
155
154
  }
156
155
  return item;
157
156
  }));
158
157
  return qb.selectAll();
159
158
  };
159
+ const applyHaving = (having)=>{
160
+ const toRaw = (h)=>{
161
+ if ('op' in h && 'conditions' in h) {
162
+ const parts = h.conditions.map((c)=>toRaw(c));
163
+ const sep = external_kysely_sql` ${external_kysely_sql.raw(h.op)} `;
164
+ return external_kysely_sql`(${external_kysely_sql.join(parts, sep)})`;
165
+ }
166
+ const leaf = h;
167
+ const field = leaf.field;
168
+ const value = leaf.value;
169
+ const op = leaf.op;
170
+ if ([
171
+ 'sum',
172
+ 'avg',
173
+ 'count',
174
+ 'min',
175
+ 'max'
176
+ ].includes(op)) {
177
+ const aggrExpr = external_kysely_sql`${external_kysely_sql.raw(op.toLowerCase())}(${external_kysely_sql.ref(field)})`;
178
+ return external_kysely_sql`${aggrExpr} = ${external_kysely_sql.val(value)}`;
179
+ }
180
+ switch(op){
181
+ case 'is null':
182
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
183
+ case 'is not null':
184
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is not null`;
185
+ case 'in':
186
+ {
187
+ const items = Array.isArray(value) ? value : [
188
+ value
189
+ ];
190
+ return external_kysely_sql`${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
191
+ }
192
+ case 'not in':
193
+ {
194
+ const items = Array.isArray(value) ? value : [
195
+ value
196
+ ];
197
+ return external_kysely_sql`not ${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
198
+ }
199
+ case 'between':
200
+ {
201
+ const [a, b] = value;
202
+ return external_kysely_sql`${external_kysely_sql.ref(field)} between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
203
+ }
204
+ case 'not between':
205
+ {
206
+ const [a, b] = value;
207
+ return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
208
+ }
209
+ default:
210
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(op)} ${external_kysely_sql.val(value)}`;
211
+ }
212
+ };
213
+ return toRaw(having);
214
+ };
215
+ const resolveHavingAliases = (having, aliasToFieldMap)=>{
216
+ if (!having) return having;
217
+ if ('op' in having && 'conditions' in having) return {
218
+ ...having,
219
+ conditions: having.conditions.map((c)=>resolveHavingAliases(c, aliasToFieldMap))
220
+ };
221
+ if ('field' in having) {
222
+ const resolvedField = aliasToFieldMap[having.field] || having.field;
223
+ return {
224
+ ...having,
225
+ field: resolvedField
226
+ };
227
+ }
228
+ return having;
229
+ };
160
230
  const convertDSLToSQL = (dsl, tableName)=>{
161
231
  const db = new Kysely({
162
232
  dialect: new PostgresDialect()
@@ -164,8 +234,26 @@ const convertDSLToSQL = (dsl, tableName)=>{
164
234
  let qb = db.selectFrom(tableName);
165
235
  qb = applySelect(qb, dsl.select);
166
236
  if (dsl.where) qb = qb.where(applyWhere(dsl.where));
167
- qb = applyGroupBy(qb, dsl.groupBy);
168
- if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy)qb = qb.orderBy(o.field, o.order ?? 'asc');
237
+ const aliasToFieldMap = {};
238
+ if (dsl.select && Array.isArray(dsl.select)) {
239
+ for (const item of dsl.select)if ('object' == typeof item && null !== item) {
240
+ const field = item.field;
241
+ const alias = item.alias;
242
+ if (alias && field) aliasToFieldMap[alias] = field;
243
+ }
244
+ }
245
+ let resolvedGroupBy = dsl.groupBy;
246
+ if (dsl.groupBy && dsl.groupBy.length > 0) resolvedGroupBy = dsl.groupBy.map((g)=>aliasToFieldMap[g] || g);
247
+ qb = applyGroupBy(qb, resolvedGroupBy);
248
+ if (dsl.having) {
249
+ const resolvedHaving = resolveHavingAliases(dsl.having, aliasToFieldMap);
250
+ qb = qb.having(applyHaving(resolvedHaving));
251
+ }
252
+ if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy){
253
+ const fieldStr = o.field;
254
+ const resolvedField = aliasToFieldMap[fieldStr] || fieldStr;
255
+ qb = qb.orderBy(resolvedField, o.order ?? 'asc');
256
+ }
169
257
  qb = applyLimit(qb, dsl.limit);
170
258
  const compiled = qb.compile();
171
259
  return inlineParameters(compiled.sql, compiled.parameters);
@@ -0,0 +1,3 @@
1
+ import { Having, HavingClause } from '../../types';
2
+ import type { RawBuilder } from 'kysely';
3
+ export declare const applyHaving: <T>(having: Having<T> | HavingClause<T>) => RawBuilder<boolean>;
@@ -2,3 +2,4 @@ export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
3
  export { applyLimit } from './limit';
4
4
  export { applySelect } from './select';
5
+ export { applyHaving } from './having';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Having 子句类型定义
3
+ * 用于聚合后的数据筛选
4
+ */
5
+ export type Having<T> = HavingGroup<T>;
6
+ export type HavingGroup<T> = {
7
+ op: 'and' | 'or';
8
+ conditions: Array<HavingClause<T>>;
9
+ };
10
+ export type HavingClause<T> = HavingLeaf<T> | HavingGroup<T>;
11
+ /**
12
+ * Having 叶子节点
13
+ * 支持聚合函数操作符
14
+ */
15
+ export type HavingLeaf<T> = {
16
+ [K in keyof T]: {
17
+ [O in HavingOperator]: {
18
+ field: K;
19
+ op: O;
20
+ } & (O extends 'is null' | 'is not null' ? {
21
+ value?: never;
22
+ } : O extends 'in' | 'not in' ? {
23
+ value: T[K] | T[K][];
24
+ } : O extends 'between' | 'not between' ? {
25
+ value: [T[K], T[K]];
26
+ } : {
27
+ value: T[K];
28
+ });
29
+ }[HavingOperator];
30
+ }[keyof T];
31
+ /**
32
+ * Having 操作符
33
+ * 聚合函数操作符
34
+ */
35
+ export type HavingOperator = 'sum' | 'avg' | 'count' | 'min' | 'max' | '=' | '!=' | '>' | '>=' | '<' | '<=' | 'between' | 'not between' | 'in' | 'not in' | 'is null' | 'is not null';
@@ -2,10 +2,12 @@ import { GroupBy } from './GroupBy';
2
2
  import { OrderBy } from './OrderBy';
3
3
  import { Select } from './Select';
4
4
  import { Where } from './Where';
5
+ import { Having } from './Having';
5
6
  export interface QueryDSL<Table> {
6
7
  select: Select<Table>;
7
8
  where?: Where<Table>;
8
9
  groupBy?: GroupBy<Table>;
10
+ having?: Having<Table>;
9
11
  orderBy?: OrderBy<Table>;
10
12
  limit?: number;
11
13
  }
@@ -3,3 +3,4 @@ export type { GroupBy } from './GroupBy';
3
3
  export type { OrderBy } from './OrderBy';
4
4
  export type { Select } from './Select';
5
5
  export type { Where, WhereClause, WhereLeaf, WhereGroup } from './Where';
6
+ export type { Having, HavingClause, HavingLeaf, HavingGroup, HavingOperator } from './Having';
@@ -161,7 +161,7 @@ const applySelect = (qb, select)=>{
161
161
  const expression = eb.ref(field);
162
162
  if (item.aggr) {
163
163
  const { func } = item.aggr;
164
- const alias = item.alias ?? field;
164
+ const sqlAlias = field;
165
165
  if ([
166
166
  'avg',
167
167
  'sum',
@@ -172,29 +172,99 @@ const applySelect = (qb, select)=>{
172
172
  'stddev',
173
173
  'median'
174
174
  ].includes(func)) {
175
- if ('variance' === func) return (0, external_kysely_namespaceObject.sql)`var_samp(${expression})`.as(alias);
176
- if ('variancePop' === func) return (0, external_kysely_namespaceObject.sql)`var_pop(${expression})`.as(alias);
177
- return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.raw(func)}(${expression})`.as(alias);
175
+ if ('variance' === func) return (0, external_kysely_namespaceObject.sql)`var_samp(${expression})`.as(sqlAlias);
176
+ if ('variancePop' === func) return (0, external_kysely_namespaceObject.sql)`var_pop(${expression})`.as(sqlAlias);
177
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.raw(func)}(${expression})`.as(sqlAlias);
178
178
  }
179
- if ('count' === func) return (0, external_kysely_namespaceObject.sql)`CAST(count(${expression}) AS INTEGER)`.as(alias);
179
+ if ('count' === func) return (0, external_kysely_namespaceObject.sql)`CAST(count(${expression}) AS INTEGER)`.as(sqlAlias);
180
180
  if ('quantile' === func) {
181
181
  const q = item.aggr.quantile ?? 0.5;
182
- return (0, external_kysely_namespaceObject.sql)`quantile(${expression}, ${q})`.as(alias);
183
- } else if ('count_distinct' === func) return (0, external_kysely_namespaceObject.sql)`CAST(count(distinct ${expression}) AS INTEGER)`.as(alias);
182
+ return (0, external_kysely_namespaceObject.sql)`quantile(${expression}, ${q})`.as(sqlAlias);
183
+ } else if ('count_distinct' === func) return (0, external_kysely_namespaceObject.sql)`CAST(count(distinct ${expression}) AS INTEGER)`.as(sqlAlias);
184
184
  else if (func.startsWith('to_')) {
185
185
  const dateTrunc = func.replace('to_', '');
186
186
  const format = DATE_FORMAT_MAP[dateTrunc];
187
- if (format) return (0, external_kysely_namespaceObject.sql)`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(alias);
188
- if ('quarter' === dateTrunc) return (0, external_kysely_namespaceObject.sql)`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(alias);
187
+ if (format) return (0, external_kysely_namespaceObject.sql)`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(sqlAlias);
188
+ if ('quarter' === dateTrunc) return (0, external_kysely_namespaceObject.sql)`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(sqlAlias);
189
189
  }
190
190
  }
191
- const alias = item.alias ?? field;
192
- return expression.as(alias);
191
+ return expression.as(field);
193
192
  }
194
193
  return item;
195
194
  }));
196
195
  return qb.selectAll();
197
196
  };
197
+ const applyHaving = (having)=>{
198
+ const toRaw = (h)=>{
199
+ if ('op' in h && 'conditions' in h) {
200
+ const parts = h.conditions.map((c)=>toRaw(c));
201
+ const sep = (0, external_kysely_namespaceObject.sql)` ${external_kysely_namespaceObject.sql.raw(h.op)} `;
202
+ return (0, external_kysely_namespaceObject.sql)`(${external_kysely_namespaceObject.sql.join(parts, sep)})`;
203
+ }
204
+ const leaf = h;
205
+ const field = leaf.field;
206
+ const value = leaf.value;
207
+ const op = leaf.op;
208
+ if ([
209
+ 'sum',
210
+ 'avg',
211
+ 'count',
212
+ 'min',
213
+ 'max'
214
+ ].includes(op)) {
215
+ const aggrExpr = (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.raw(op.toLowerCase())}(${external_kysely_namespaceObject.sql.ref(field)})`;
216
+ return (0, external_kysely_namespaceObject.sql)`${aggrExpr} = ${external_kysely_namespaceObject.sql.val(value)}`;
217
+ }
218
+ switch(op){
219
+ case 'is null':
220
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is null`;
221
+ case 'is not null':
222
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is not null`;
223
+ case 'in':
224
+ {
225
+ const items = Array.isArray(value) ? value : [
226
+ value
227
+ ];
228
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} in (${external_kysely_namespaceObject.sql.join(items.map((v)=>external_kysely_namespaceObject.sql.val(v)))})`;
229
+ }
230
+ case 'not in':
231
+ {
232
+ const items = Array.isArray(value) ? value : [
233
+ value
234
+ ];
235
+ return (0, external_kysely_namespaceObject.sql)`not ${external_kysely_namespaceObject.sql.ref(field)} in (${external_kysely_namespaceObject.sql.join(items.map((v)=>external_kysely_namespaceObject.sql.val(v)))})`;
236
+ }
237
+ case 'between':
238
+ {
239
+ const [a, b] = value;
240
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} between ${external_kysely_namespaceObject.sql.val(a)} and ${external_kysely_namespaceObject.sql.val(b)}`;
241
+ }
242
+ case 'not between':
243
+ {
244
+ const [a, b] = value;
245
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} not between ${external_kysely_namespaceObject.sql.val(a)} and ${external_kysely_namespaceObject.sql.val(b)}`;
246
+ }
247
+ default:
248
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} ${external_kysely_namespaceObject.sql.raw(op)} ${external_kysely_namespaceObject.sql.val(value)}`;
249
+ }
250
+ };
251
+ return toRaw(having);
252
+ };
253
+ const resolveHavingAliases = (having, aliasToFieldMap)=>{
254
+ if (!having) return having;
255
+ if ('op' in having && 'conditions' in having) return {
256
+ ...having,
257
+ conditions: having.conditions.map((c)=>resolveHavingAliases(c, aliasToFieldMap))
258
+ };
259
+ if ('field' in having) {
260
+ const resolvedField = aliasToFieldMap[having.field] || having.field;
261
+ return {
262
+ ...having,
263
+ field: resolvedField
264
+ };
265
+ }
266
+ return having;
267
+ };
198
268
  const convertDSLToSQL = (dsl, tableName)=>{
199
269
  const db = new external_kysely_namespaceObject.Kysely({
200
270
  dialect: new PostgresDialect()
@@ -202,8 +272,26 @@ const convertDSLToSQL = (dsl, tableName)=>{
202
272
  let qb = db.selectFrom(tableName);
203
273
  qb = applySelect(qb, dsl.select);
204
274
  if (dsl.where) qb = qb.where(applyWhere(dsl.where));
205
- qb = applyGroupBy(qb, dsl.groupBy);
206
- if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy)qb = qb.orderBy(o.field, o.order ?? 'asc');
275
+ const aliasToFieldMap = {};
276
+ if (dsl.select && Array.isArray(dsl.select)) {
277
+ for (const item of dsl.select)if ('object' == typeof item && null !== item) {
278
+ const field = item.field;
279
+ const alias = item.alias;
280
+ if (alias && field) aliasToFieldMap[alias] = field;
281
+ }
282
+ }
283
+ let resolvedGroupBy = dsl.groupBy;
284
+ if (dsl.groupBy && dsl.groupBy.length > 0) resolvedGroupBy = dsl.groupBy.map((g)=>aliasToFieldMap[g] || g);
285
+ qb = applyGroupBy(qb, resolvedGroupBy);
286
+ if (dsl.having) {
287
+ const resolvedHaving = resolveHavingAliases(dsl.having, aliasToFieldMap);
288
+ qb = qb.having(applyHaving(resolvedHaving));
289
+ }
290
+ if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy){
291
+ const fieldStr = o.field;
292
+ const resolvedField = aliasToFieldMap[fieldStr] || fieldStr;
293
+ qb = qb.orderBy(resolvedField, o.order ?? 'asc');
294
+ }
207
295
  qb = applyLimit(qb, dsl.limit);
208
296
  const compiled = qb.compile();
209
297
  return inlineParameters(compiled.sql, compiled.parameters);
@@ -0,0 +1,3 @@
1
+ import { Having, HavingClause } from '../../types';
2
+ import type { RawBuilder } from 'kysely';
3
+ export declare const applyHaving: <T>(having: Having<T> | HavingClause<T>) => RawBuilder<boolean>;
@@ -2,3 +2,4 @@ export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
3
  export { applyLimit } from './limit';
4
4
  export { applySelect } from './select';
5
+ export { applyHaving } from './having';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Having 子句类型定义
3
+ * 用于聚合后的数据筛选
4
+ */
5
+ export type Having<T> = HavingGroup<T>;
6
+ export type HavingGroup<T> = {
7
+ op: 'and' | 'or';
8
+ conditions: Array<HavingClause<T>>;
9
+ };
10
+ export type HavingClause<T> = HavingLeaf<T> | HavingGroup<T>;
11
+ /**
12
+ * Having 叶子节点
13
+ * 支持聚合函数操作符
14
+ */
15
+ export type HavingLeaf<T> = {
16
+ [K in keyof T]: {
17
+ [O in HavingOperator]: {
18
+ field: K;
19
+ op: O;
20
+ } & (O extends 'is null' | 'is not null' ? {
21
+ value?: never;
22
+ } : O extends 'in' | 'not in' ? {
23
+ value: T[K] | T[K][];
24
+ } : O extends 'between' | 'not between' ? {
25
+ value: [T[K], T[K]];
26
+ } : {
27
+ value: T[K];
28
+ });
29
+ }[HavingOperator];
30
+ }[keyof T];
31
+ /**
32
+ * Having 操作符
33
+ * 聚合函数操作符
34
+ */
35
+ export type HavingOperator = 'sum' | 'avg' | 'count' | 'min' | 'max' | '=' | '!=' | '>' | '>=' | '<' | '<=' | 'between' | 'not between' | 'in' | 'not in' | 'is null' | 'is not null';
@@ -2,10 +2,12 @@ import { GroupBy } from './GroupBy';
2
2
  import { OrderBy } from './OrderBy';
3
3
  import { Select } from './Select';
4
4
  import { Where } from './Where';
5
+ import { Having } from './Having';
5
6
  export interface QueryDSL<Table> {
6
7
  select: Select<Table>;
7
8
  where?: Where<Table>;
8
9
  groupBy?: GroupBy<Table>;
10
+ having?: Having<Table>;
9
11
  orderBy?: OrderBy<Table>;
10
12
  limit?: number;
11
13
  }
@@ -3,3 +3,4 @@ export type { GroupBy } from './GroupBy';
3
3
  export type { OrderBy } from './OrderBy';
4
4
  export type { Select } from './Select';
5
5
  export type { Where, WhereClause, WhereLeaf, WhereGroup } from './Where';
6
+ export type { Having, HavingClause, HavingLeaf, HavingGroup, HavingOperator } from './Having';
@@ -124,7 +124,7 @@ const applySelect = (qb, select)=>{
124
124
  const expression = eb.ref(field);
125
125
  if (item.aggr) {
126
126
  const { func } = item.aggr;
127
- const alias = item.alias ?? field;
127
+ const sqlAlias = field;
128
128
  if ([
129
129
  'avg',
130
130
  'sum',
@@ -135,29 +135,99 @@ const applySelect = (qb, select)=>{
135
135
  'stddev',
136
136
  'median'
137
137
  ].includes(func)) {
138
- if ('variance' === func) return external_kysely_sql`var_samp(${expression})`.as(alias);
139
- if ('variancePop' === func) return external_kysely_sql`var_pop(${expression})`.as(alias);
140
- return external_kysely_sql`${external_kysely_sql.raw(func)}(${expression})`.as(alias);
138
+ if ('variance' === func) return external_kysely_sql`var_samp(${expression})`.as(sqlAlias);
139
+ if ('variancePop' === func) return external_kysely_sql`var_pop(${expression})`.as(sqlAlias);
140
+ return external_kysely_sql`${external_kysely_sql.raw(func)}(${expression})`.as(sqlAlias);
141
141
  }
142
- if ('count' === func) return external_kysely_sql`CAST(count(${expression}) AS INTEGER)`.as(alias);
142
+ if ('count' === func) return external_kysely_sql`CAST(count(${expression}) AS INTEGER)`.as(sqlAlias);
143
143
  if ('quantile' === func) {
144
144
  const q = item.aggr.quantile ?? 0.5;
145
- return external_kysely_sql`quantile(${expression}, ${q})`.as(alias);
146
- } else if ('count_distinct' === func) return external_kysely_sql`CAST(count(distinct ${expression}) AS INTEGER)`.as(alias);
145
+ return external_kysely_sql`quantile(${expression}, ${q})`.as(sqlAlias);
146
+ } else if ('count_distinct' === func) return external_kysely_sql`CAST(count(distinct ${expression}) AS INTEGER)`.as(sqlAlias);
147
147
  else if (func.startsWith('to_')) {
148
148
  const dateTrunc = func.replace('to_', '');
149
149
  const format = DATE_FORMAT_MAP[dateTrunc];
150
- if (format) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(alias);
151
- if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(alias);
150
+ if (format) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), ${format})`.as(sqlAlias);
151
+ if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(sqlAlias);
152
152
  }
153
153
  }
154
- const alias = item.alias ?? field;
155
- return expression.as(alias);
154
+ return expression.as(field);
156
155
  }
157
156
  return item;
158
157
  }));
159
158
  return qb.selectAll();
160
159
  };
160
+ const applyHaving = (having)=>{
161
+ const toRaw = (h)=>{
162
+ if ('op' in h && 'conditions' in h) {
163
+ const parts = h.conditions.map((c)=>toRaw(c));
164
+ const sep = external_kysely_sql` ${external_kysely_sql.raw(h.op)} `;
165
+ return external_kysely_sql`(${external_kysely_sql.join(parts, sep)})`;
166
+ }
167
+ const leaf = h;
168
+ const field = leaf.field;
169
+ const value = leaf.value;
170
+ const op = leaf.op;
171
+ if ([
172
+ 'sum',
173
+ 'avg',
174
+ 'count',
175
+ 'min',
176
+ 'max'
177
+ ].includes(op)) {
178
+ const aggrExpr = external_kysely_sql`${external_kysely_sql.raw(op.toLowerCase())}(${external_kysely_sql.ref(field)})`;
179
+ return external_kysely_sql`${aggrExpr} = ${external_kysely_sql.val(value)}`;
180
+ }
181
+ switch(op){
182
+ case 'is null':
183
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
184
+ case 'is not null':
185
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is not null`;
186
+ case 'in':
187
+ {
188
+ const items = Array.isArray(value) ? value : [
189
+ value
190
+ ];
191
+ return external_kysely_sql`${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
192
+ }
193
+ case 'not in':
194
+ {
195
+ const items = Array.isArray(value) ? value : [
196
+ value
197
+ ];
198
+ return external_kysely_sql`not ${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
199
+ }
200
+ case 'between':
201
+ {
202
+ const [a, b] = value;
203
+ return external_kysely_sql`${external_kysely_sql.ref(field)} between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
204
+ }
205
+ case 'not between':
206
+ {
207
+ const [a, b] = value;
208
+ return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
209
+ }
210
+ default:
211
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(op)} ${external_kysely_sql.val(value)}`;
212
+ }
213
+ };
214
+ return toRaw(having);
215
+ };
216
+ const resolveHavingAliases = (having, aliasToFieldMap)=>{
217
+ if (!having) return having;
218
+ if ('op' in having && 'conditions' in having) return {
219
+ ...having,
220
+ conditions: having.conditions.map((c)=>resolveHavingAliases(c, aliasToFieldMap))
221
+ };
222
+ if ('field' in having) {
223
+ const resolvedField = aliasToFieldMap[having.field] || having.field;
224
+ return {
225
+ ...having,
226
+ field: resolvedField
227
+ };
228
+ }
229
+ return having;
230
+ };
161
231
  const convertDSLToSQL = (dsl, tableName)=>{
162
232
  const db = new Kysely({
163
233
  dialect: new PostgresDialect()
@@ -165,8 +235,26 @@ const convertDSLToSQL = (dsl, tableName)=>{
165
235
  let qb = db.selectFrom(tableName);
166
236
  qb = applySelect(qb, dsl.select);
167
237
  if (dsl.where) qb = qb.where(applyWhere(dsl.where));
168
- qb = applyGroupBy(qb, dsl.groupBy);
169
- if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy)qb = qb.orderBy(o.field, o.order ?? 'asc');
238
+ const aliasToFieldMap = {};
239
+ if (dsl.select && Array.isArray(dsl.select)) {
240
+ for (const item of dsl.select)if ('object' == typeof item && null !== item) {
241
+ const field = item.field;
242
+ const alias = item.alias;
243
+ if (alias && field) aliasToFieldMap[alias] = field;
244
+ }
245
+ }
246
+ let resolvedGroupBy = dsl.groupBy;
247
+ if (dsl.groupBy && dsl.groupBy.length > 0) resolvedGroupBy = dsl.groupBy.map((g)=>aliasToFieldMap[g] || g);
248
+ qb = applyGroupBy(qb, resolvedGroupBy);
249
+ if (dsl.having) {
250
+ const resolvedHaving = resolveHavingAliases(dsl.having, aliasToFieldMap);
251
+ qb = qb.having(applyHaving(resolvedHaving));
252
+ }
253
+ if (dsl.orderBy && dsl.orderBy.length > 0) for (const o of dsl.orderBy){
254
+ const fieldStr = o.field;
255
+ const resolvedField = aliasToFieldMap[fieldStr] || fieldStr;
256
+ qb = qb.orderBy(resolvedField, o.order ?? 'asc');
257
+ }
170
258
  qb = applyLimit(qb, dsl.limit);
171
259
  const compiled = qb.compile();
172
260
  return inlineParameters(compiled.sql, compiled.parameters);
@@ -0,0 +1,3 @@
1
+ import { Having, HavingClause } from '../../types';
2
+ import type { RawBuilder } from 'kysely';
3
+ export declare const applyHaving: <T>(having: Having<T> | HavingClause<T>) => RawBuilder<boolean>;
@@ -2,3 +2,4 @@ export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
3
  export { applyLimit } from './limit';
4
4
  export { applySelect } from './select';
5
+ export { applyHaving } from './having';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Having 子句类型定义
3
+ * 用于聚合后的数据筛选
4
+ */
5
+ export type Having<T> = HavingGroup<T>;
6
+ export type HavingGroup<T> = {
7
+ op: 'and' | 'or';
8
+ conditions: Array<HavingClause<T>>;
9
+ };
10
+ export type HavingClause<T> = HavingLeaf<T> | HavingGroup<T>;
11
+ /**
12
+ * Having 叶子节点
13
+ * 支持聚合函数操作符
14
+ */
15
+ export type HavingLeaf<T> = {
16
+ [K in keyof T]: {
17
+ [O in HavingOperator]: {
18
+ field: K;
19
+ op: O;
20
+ } & (O extends 'is null' | 'is not null' ? {
21
+ value?: never;
22
+ } : O extends 'in' | 'not in' ? {
23
+ value: T[K] | T[K][];
24
+ } : O extends 'between' | 'not between' ? {
25
+ value: [T[K], T[K]];
26
+ } : {
27
+ value: T[K];
28
+ });
29
+ }[HavingOperator];
30
+ }[keyof T];
31
+ /**
32
+ * Having 操作符
33
+ * 聚合函数操作符
34
+ */
35
+ export type HavingOperator = 'sum' | 'avg' | 'count' | 'min' | 'max' | '=' | '!=' | '>' | '>=' | '<' | '<=' | 'between' | 'not between' | 'in' | 'not in' | 'is null' | 'is not null';
@@ -2,10 +2,12 @@ import { GroupBy } from './GroupBy';
2
2
  import { OrderBy } from './OrderBy';
3
3
  import { Select } from './Select';
4
4
  import { Where } from './Where';
5
+ import { Having } from './Having';
5
6
  export interface QueryDSL<Table> {
6
7
  select: Select<Table>;
7
8
  where?: Where<Table>;
8
9
  groupBy?: GroupBy<Table>;
10
+ having?: Having<Table>;
9
11
  orderBy?: OrderBy<Table>;
10
12
  limit?: number;
11
13
  }
@@ -3,3 +3,4 @@ export type { GroupBy } from './GroupBy';
3
3
  export type { OrderBy } from './OrderBy';
4
4
  export type { Select } from './Select';
5
5
  export type { Where, WhereClause, WhereLeaf, WhereGroup } from './Where';
6
+ export type { Having, HavingClause, HavingLeaf, HavingGroup, HavingOperator } from './Having';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visactor/vquery",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {