@visactor/vquery 0.4.8 → 0.4.10

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.
Files changed (28) hide show
  1. package/dist/browser/esm/browser.js +92 -9
  2. package/dist/browser/esm/sql-builder/builders/having.d.ts +3 -0
  3. package/dist/browser/esm/sql-builder/builders/index.d.ts +2 -0
  4. package/dist/browser/esm/sql-builder/builders/order.d.ts +7 -0
  5. package/dist/browser/esm/sql-builder/builders/where.d.ts +2 -2
  6. package/dist/browser/esm/sql-builder/dslToSQL.d.ts +1 -1
  7. package/dist/browser/esm/types/dsl/Having.d.ts +35 -0
  8. package/dist/browser/esm/types/dsl/QueryDSL.d.ts +2 -0
  9. package/dist/browser/esm/types/dsl/index.d.ts +1 -0
  10. package/dist/node/cjs/node.cjs +92 -9
  11. package/dist/node/cjs/sql-builder/builders/having.d.ts +3 -0
  12. package/dist/node/cjs/sql-builder/builders/index.d.ts +2 -0
  13. package/dist/node/cjs/sql-builder/builders/order.d.ts +7 -0
  14. package/dist/node/cjs/sql-builder/builders/where.d.ts +2 -2
  15. package/dist/node/cjs/sql-builder/dslToSQL.d.ts +1 -1
  16. package/dist/node/cjs/types/dsl/Having.d.ts +35 -0
  17. package/dist/node/cjs/types/dsl/QueryDSL.d.ts +2 -0
  18. package/dist/node/cjs/types/dsl/index.d.ts +1 -0
  19. package/dist/node/esm/node.js +92 -9
  20. package/dist/node/esm/sql-builder/builders/having.d.ts +3 -0
  21. package/dist/node/esm/sql-builder/builders/index.d.ts +2 -0
  22. package/dist/node/esm/sql-builder/builders/order.d.ts +7 -0
  23. package/dist/node/esm/sql-builder/builders/where.d.ts +2 -2
  24. package/dist/node/esm/sql-builder/dslToSQL.d.ts +1 -1
  25. package/dist/node/esm/types/dsl/Having.d.ts +35 -0
  26. package/dist/node/esm/types/dsl/QueryDSL.d.ts +2 -0
  27. package/dist/node/esm/types/dsl/index.d.ts +1 -0
  28. package/package.json +1 -1
@@ -51,7 +51,17 @@ const inlineParameters = (sql, params)=>{
51
51
  });
52
52
  return sql;
53
53
  };
54
- const applyWhere = (where)=>{
54
+ const operatorMap = {
55
+ gt: '>',
56
+ gte: '>=',
57
+ lt: '<',
58
+ lte: '<=',
59
+ eq: '=',
60
+ neq: '!='
61
+ };
62
+ const toSqlOperator = (op)=>operatorMap[op] ?? op;
63
+ const applyWhere = (qb, where)=>{
64
+ if (!where) return qb;
55
65
  const toRaw = (w)=>{
56
66
  if ('op' in w && 'conditions' in w) {
57
67
  const parts = w.conditions.map((c)=>toRaw(c));
@@ -61,6 +71,7 @@ const applyWhere = (where)=>{
61
71
  const leaf = w;
62
72
  const field = leaf.field;
63
73
  const value = leaf.value;
74
+ const sqlOp = toSqlOperator(leaf.op);
64
75
  switch(leaf.op){
65
76
  case 'is null':
66
77
  return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
@@ -91,10 +102,10 @@ const applyWhere = (where)=>{
91
102
  return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
92
103
  }
93
104
  default:
94
- return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(leaf.op)} ${external_kysely_sql.val(value)}`;
105
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(sqlOp)} ${external_kysely_sql.val(value)}`;
95
106
  }
96
107
  };
97
- return toRaw(where);
108
+ return qb.where(toRaw(where));
98
109
  };
99
110
  const applyGroupBy = (qb, fields)=>{
100
111
  if (fields && fields.length > 0) {
@@ -103,10 +114,82 @@ const applyGroupBy = (qb, fields)=>{
103
114
  }
104
115
  return qb;
105
116
  };
117
+ const having_operatorMap = {
118
+ gt: '>',
119
+ gte: '>=',
120
+ lt: '<',
121
+ lte: '<=',
122
+ eq: '=',
123
+ neq: '!='
124
+ };
125
+ const having_toSqlOperator = (op)=>having_operatorMap[op] ?? op;
126
+ const applyHaving = (qb, having)=>{
127
+ if (!having) return qb;
128
+ const toRaw = (h)=>{
129
+ if ('op' in h && 'conditions' in h) {
130
+ const parts = h.conditions.map((c)=>toRaw(c));
131
+ const sep = external_kysely_sql` ${external_kysely_sql.raw(h.op)} `;
132
+ return external_kysely_sql`(${external_kysely_sql.join(parts, sep)})`;
133
+ }
134
+ const leaf = h;
135
+ const field = leaf.field;
136
+ const value = leaf.value;
137
+ const op = having_toSqlOperator(leaf.op);
138
+ if ([
139
+ 'sum',
140
+ 'avg',
141
+ 'count',
142
+ 'min',
143
+ 'max'
144
+ ].includes(op)) {
145
+ const aggrExpr = external_kysely_sql`${external_kysely_sql.raw(op.toLowerCase())}(${external_kysely_sql.ref(field)})`;
146
+ return external_kysely_sql`${aggrExpr} = ${external_kysely_sql.val(value)}`;
147
+ }
148
+ switch(leaf.op){
149
+ case 'is null':
150
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
151
+ case 'is not null':
152
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is not null`;
153
+ case 'in':
154
+ {
155
+ const items = Array.isArray(value) ? value : [
156
+ value
157
+ ];
158
+ return external_kysely_sql`${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
159
+ }
160
+ case 'not in':
161
+ {
162
+ const items = Array.isArray(value) ? value : [
163
+ value
164
+ ];
165
+ return external_kysely_sql`not ${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
166
+ }
167
+ case 'between':
168
+ {
169
+ const [a, b] = value;
170
+ return external_kysely_sql`${external_kysely_sql.ref(field)} between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
171
+ }
172
+ case 'not between':
173
+ {
174
+ const [a, b] = value;
175
+ return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
176
+ }
177
+ default:
178
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(op)} ${external_kysely_sql.val(value)}`;
179
+ }
180
+ };
181
+ return qb.having(toRaw(having));
182
+ };
106
183
  const applyLimit = (qb, limit)=>{
107
184
  if (limit && 'number' == typeof limit) qb = qb.limit(limit);
108
185
  return qb;
109
186
  };
187
+ const applyOrder = (qb, orderBy)=>{
188
+ if (orderBy && orderBy.length > 0) orderBy.forEach((o)=>{
189
+ qb = qb.orderBy(o.field, o.order ?? 'asc');
190
+ });
191
+ return qb;
192
+ };
110
193
  const DATE_FORMAT_MAP = {
111
194
  year: '%Y',
112
195
  month: '%Y-%m',
@@ -121,9 +204,9 @@ const applySelect = (qb, select)=>{
121
204
  if (isSelectItem(item)) {
122
205
  const field = item.field;
123
206
  const expression = eb.ref(field);
207
+ const alias = item.alias ?? field;
124
208
  if (item.aggr) {
125
209
  const { func } = item.aggr;
126
- const alias = item.alias ?? field;
127
210
  if ([
128
211
  'avg',
129
212
  'sum',
@@ -150,7 +233,6 @@ const applySelect = (qb, select)=>{
150
233
  if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(alias);
151
234
  }
152
235
  }
153
- const alias = item.alias ?? field;
154
236
  return expression.as(alias);
155
237
  }
156
238
  return item;
@@ -163,12 +245,13 @@ const convertDSLToSQL = (dsl, tableName)=>{
163
245
  });
164
246
  let qb = db.selectFrom(tableName);
165
247
  qb = applySelect(qb, dsl.select);
166
- if (dsl.where) qb = qb.where(applyWhere(dsl.where));
248
+ qb = applyWhere(qb, dsl.where);
167
249
  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');
250
+ qb = applyHaving(qb, dsl.having);
251
+ qb = applyOrder(qb, dsl.orderBy);
169
252
  qb = applyLimit(qb, dsl.limit);
170
- const compiled = qb.compile();
171
- return inlineParameters(compiled.sql, compiled.parameters);
253
+ const { sql, parameters } = qb.compile();
254
+ return inlineParameters(sql, parameters);
172
255
  };
173
256
  const READ_FUNCTION_MAP = {
174
257
  csv: 'read_csv_auto',
@@ -0,0 +1,3 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ import { Having, HavingClause } from '../../types';
3
+ export declare const applyHaving: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, having?: Having<T> | HavingClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,4 +1,6 @@
1
1
  export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
+ export { applyHaving } from './having';
3
4
  export { applyLimit } from './limit';
5
+ export { applyOrder } from './order';
4
6
  export { applySelect } from './select';
@@ -0,0 +1,7 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ type OrderByItem = {
3
+ field: string;
4
+ order?: 'asc' | 'desc';
5
+ };
6
+ export declare const applyOrder: <DB, TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, orderBy?: Array<OrderByItem>) => SelectQueryBuilder<DB, TB, O>;
7
+ export {};
@@ -1,3 +1,3 @@
1
1
  import { Where, WhereClause } from '../../types';
2
- import type { RawBuilder } from 'kysely';
3
- export declare const applyWhere: <T>(where: Where<T> | WhereClause<T>) => RawBuilder<boolean>;
2
+ import type { SelectQueryBuilder } from 'kysely';
3
+ export declare const applyWhere: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, where?: Where<T> | WhereClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,2 +1,2 @@
1
- import { QueryDSL, VQueryDSL } from '../types';
1
+ import type { QueryDSL, VQueryDSL } from '../types';
2
2
  export declare const convertDSLToSQL: <T, TableName extends string>(dsl: QueryDSL<T> | VQueryDSL<T>, tableName: TableName) => string;
@@ -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';
@@ -1,4 +1,5 @@
1
1
  import { GroupBy } from './GroupBy';
2
+ import { Having } from './Having';
2
3
  import { OrderBy } from './OrderBy';
3
4
  import { Select } from './Select';
4
5
  import { Where } from './Where';
@@ -6,6 +7,7 @@ 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';
@@ -89,7 +89,17 @@ const inlineParameters = (sql, params)=>{
89
89
  });
90
90
  return sql;
91
91
  };
92
- const applyWhere = (where)=>{
92
+ const operatorMap = {
93
+ gt: '>',
94
+ gte: '>=',
95
+ lt: '<',
96
+ lte: '<=',
97
+ eq: '=',
98
+ neq: '!='
99
+ };
100
+ const toSqlOperator = (op)=>operatorMap[op] ?? op;
101
+ const applyWhere = (qb, where)=>{
102
+ if (!where) return qb;
93
103
  const toRaw = (w)=>{
94
104
  if ('op' in w && 'conditions' in w) {
95
105
  const parts = w.conditions.map((c)=>toRaw(c));
@@ -99,6 +109,7 @@ const applyWhere = (where)=>{
99
109
  const leaf = w;
100
110
  const field = leaf.field;
101
111
  const value = leaf.value;
112
+ const sqlOp = toSqlOperator(leaf.op);
102
113
  switch(leaf.op){
103
114
  case 'is null':
104
115
  return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is null`;
@@ -129,10 +140,10 @@ const applyWhere = (where)=>{
129
140
  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)}`;
130
141
  }
131
142
  default:
132
- return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} ${external_kysely_namespaceObject.sql.raw(leaf.op)} ${external_kysely_namespaceObject.sql.val(value)}`;
143
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} ${external_kysely_namespaceObject.sql.raw(sqlOp)} ${external_kysely_namespaceObject.sql.val(value)}`;
133
144
  }
134
145
  };
135
- return toRaw(where);
146
+ return qb.where(toRaw(where));
136
147
  };
137
148
  const applyGroupBy = (qb, fields)=>{
138
149
  if (fields && fields.length > 0) {
@@ -141,10 +152,82 @@ const applyGroupBy = (qb, fields)=>{
141
152
  }
142
153
  return qb;
143
154
  };
155
+ const having_operatorMap = {
156
+ gt: '>',
157
+ gte: '>=',
158
+ lt: '<',
159
+ lte: '<=',
160
+ eq: '=',
161
+ neq: '!='
162
+ };
163
+ const having_toSqlOperator = (op)=>having_operatorMap[op] ?? op;
164
+ const applyHaving = (qb, having)=>{
165
+ if (!having) return qb;
166
+ const toRaw = (h)=>{
167
+ if ('op' in h && 'conditions' in h) {
168
+ const parts = h.conditions.map((c)=>toRaw(c));
169
+ const sep = (0, external_kysely_namespaceObject.sql)` ${external_kysely_namespaceObject.sql.raw(h.op)} `;
170
+ return (0, external_kysely_namespaceObject.sql)`(${external_kysely_namespaceObject.sql.join(parts, sep)})`;
171
+ }
172
+ const leaf = h;
173
+ const field = leaf.field;
174
+ const value = leaf.value;
175
+ const op = having_toSqlOperator(leaf.op);
176
+ if ([
177
+ 'sum',
178
+ 'avg',
179
+ 'count',
180
+ 'min',
181
+ 'max'
182
+ ].includes(op)) {
183
+ const aggrExpr = (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.raw(op.toLowerCase())}(${external_kysely_namespaceObject.sql.ref(field)})`;
184
+ return (0, external_kysely_namespaceObject.sql)`${aggrExpr} = ${external_kysely_namespaceObject.sql.val(value)}`;
185
+ }
186
+ switch(leaf.op){
187
+ case 'is null':
188
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is null`;
189
+ case 'is not null':
190
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} is not null`;
191
+ case 'in':
192
+ {
193
+ const items = Array.isArray(value) ? value : [
194
+ value
195
+ ];
196
+ 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)))})`;
197
+ }
198
+ case 'not in':
199
+ {
200
+ const items = Array.isArray(value) ? value : [
201
+ value
202
+ ];
203
+ 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)))})`;
204
+ }
205
+ case 'between':
206
+ {
207
+ const [a, b] = value;
208
+ 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)}`;
209
+ }
210
+ case 'not between':
211
+ {
212
+ const [a, b] = value;
213
+ 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)}`;
214
+ }
215
+ default:
216
+ return (0, external_kysely_namespaceObject.sql)`${external_kysely_namespaceObject.sql.ref(field)} ${external_kysely_namespaceObject.sql.raw(op)} ${external_kysely_namespaceObject.sql.val(value)}`;
217
+ }
218
+ };
219
+ return qb.having(toRaw(having));
220
+ };
144
221
  const applyLimit = (qb, limit)=>{
145
222
  if (limit && 'number' == typeof limit) qb = qb.limit(limit);
146
223
  return qb;
147
224
  };
225
+ const applyOrder = (qb, orderBy)=>{
226
+ if (orderBy && orderBy.length > 0) orderBy.forEach((o)=>{
227
+ qb = qb.orderBy(o.field, o.order ?? 'asc');
228
+ });
229
+ return qb;
230
+ };
148
231
  const DATE_FORMAT_MAP = {
149
232
  year: '%Y',
150
233
  month: '%Y-%m',
@@ -159,9 +242,9 @@ const applySelect = (qb, select)=>{
159
242
  if (isSelectItem(item)) {
160
243
  const field = item.field;
161
244
  const expression = eb.ref(field);
245
+ const alias = item.alias ?? field;
162
246
  if (item.aggr) {
163
247
  const { func } = item.aggr;
164
- const alias = item.alias ?? field;
165
248
  if ([
166
249
  'avg',
167
250
  'sum',
@@ -188,7 +271,6 @@ const applySelect = (qb, select)=>{
188
271
  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);
189
272
  }
190
273
  }
191
- const alias = item.alias ?? field;
192
274
  return expression.as(alias);
193
275
  }
194
276
  return item;
@@ -201,12 +283,13 @@ const convertDSLToSQL = (dsl, tableName)=>{
201
283
  });
202
284
  let qb = db.selectFrom(tableName);
203
285
  qb = applySelect(qb, dsl.select);
204
- if (dsl.where) qb = qb.where(applyWhere(dsl.where));
286
+ qb = applyWhere(qb, dsl.where);
205
287
  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');
288
+ qb = applyHaving(qb, dsl.having);
289
+ qb = applyOrder(qb, dsl.orderBy);
207
290
  qb = applyLimit(qb, dsl.limit);
208
- const compiled = qb.compile();
209
- return inlineParameters(compiled.sql, compiled.parameters);
291
+ const { sql, parameters } = qb.compile();
292
+ return inlineParameters(sql, parameters);
210
293
  };
211
294
  const READ_FUNCTION_MAP = {
212
295
  csv: 'read_csv_auto',
@@ -0,0 +1,3 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ import { Having, HavingClause } from '../../types';
3
+ export declare const applyHaving: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, having?: Having<T> | HavingClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,4 +1,6 @@
1
1
  export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
+ export { applyHaving } from './having';
3
4
  export { applyLimit } from './limit';
5
+ export { applyOrder } from './order';
4
6
  export { applySelect } from './select';
@@ -0,0 +1,7 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ type OrderByItem = {
3
+ field: string;
4
+ order?: 'asc' | 'desc';
5
+ };
6
+ export declare const applyOrder: <DB, TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, orderBy?: Array<OrderByItem>) => SelectQueryBuilder<DB, TB, O>;
7
+ export {};
@@ -1,3 +1,3 @@
1
1
  import { Where, WhereClause } from '../../types';
2
- import type { RawBuilder } from 'kysely';
3
- export declare const applyWhere: <T>(where: Where<T> | WhereClause<T>) => RawBuilder<boolean>;
2
+ import type { SelectQueryBuilder } from 'kysely';
3
+ export declare const applyWhere: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, where?: Where<T> | WhereClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,2 +1,2 @@
1
- import { QueryDSL, VQueryDSL } from '../types';
1
+ import type { QueryDSL, VQueryDSL } from '../types';
2
2
  export declare const convertDSLToSQL: <T, TableName extends string>(dsl: QueryDSL<T> | VQueryDSL<T>, tableName: TableName) => string;
@@ -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';
@@ -1,4 +1,5 @@
1
1
  import { GroupBy } from './GroupBy';
2
+ import { Having } from './Having';
2
3
  import { OrderBy } from './OrderBy';
3
4
  import { Select } from './Select';
4
5
  import { Where } from './Where';
@@ -6,6 +7,7 @@ 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';
@@ -52,7 +52,17 @@ const inlineParameters = (sql, params)=>{
52
52
  });
53
53
  return sql;
54
54
  };
55
- const applyWhere = (where)=>{
55
+ const operatorMap = {
56
+ gt: '>',
57
+ gte: '>=',
58
+ lt: '<',
59
+ lte: '<=',
60
+ eq: '=',
61
+ neq: '!='
62
+ };
63
+ const toSqlOperator = (op)=>operatorMap[op] ?? op;
64
+ const applyWhere = (qb, where)=>{
65
+ if (!where) return qb;
56
66
  const toRaw = (w)=>{
57
67
  if ('op' in w && 'conditions' in w) {
58
68
  const parts = w.conditions.map((c)=>toRaw(c));
@@ -62,6 +72,7 @@ const applyWhere = (where)=>{
62
72
  const leaf = w;
63
73
  const field = leaf.field;
64
74
  const value = leaf.value;
75
+ const sqlOp = toSqlOperator(leaf.op);
65
76
  switch(leaf.op){
66
77
  case 'is null':
67
78
  return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
@@ -92,10 +103,10 @@ const applyWhere = (where)=>{
92
103
  return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
93
104
  }
94
105
  default:
95
- return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(leaf.op)} ${external_kysely_sql.val(value)}`;
106
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(sqlOp)} ${external_kysely_sql.val(value)}`;
96
107
  }
97
108
  };
98
- return toRaw(where);
109
+ return qb.where(toRaw(where));
99
110
  };
100
111
  const applyGroupBy = (qb, fields)=>{
101
112
  if (fields && fields.length > 0) {
@@ -104,10 +115,82 @@ const applyGroupBy = (qb, fields)=>{
104
115
  }
105
116
  return qb;
106
117
  };
118
+ const having_operatorMap = {
119
+ gt: '>',
120
+ gte: '>=',
121
+ lt: '<',
122
+ lte: '<=',
123
+ eq: '=',
124
+ neq: '!='
125
+ };
126
+ const having_toSqlOperator = (op)=>having_operatorMap[op] ?? op;
127
+ const applyHaving = (qb, having)=>{
128
+ if (!having) return qb;
129
+ const toRaw = (h)=>{
130
+ if ('op' in h && 'conditions' in h) {
131
+ const parts = h.conditions.map((c)=>toRaw(c));
132
+ const sep = external_kysely_sql` ${external_kysely_sql.raw(h.op)} `;
133
+ return external_kysely_sql`(${external_kysely_sql.join(parts, sep)})`;
134
+ }
135
+ const leaf = h;
136
+ const field = leaf.field;
137
+ const value = leaf.value;
138
+ const op = having_toSqlOperator(leaf.op);
139
+ if ([
140
+ 'sum',
141
+ 'avg',
142
+ 'count',
143
+ 'min',
144
+ 'max'
145
+ ].includes(op)) {
146
+ const aggrExpr = external_kysely_sql`${external_kysely_sql.raw(op.toLowerCase())}(${external_kysely_sql.ref(field)})`;
147
+ return external_kysely_sql`${aggrExpr} = ${external_kysely_sql.val(value)}`;
148
+ }
149
+ switch(leaf.op){
150
+ case 'is null':
151
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is null`;
152
+ case 'is not null':
153
+ return external_kysely_sql`${external_kysely_sql.ref(field)} is not null`;
154
+ case 'in':
155
+ {
156
+ const items = Array.isArray(value) ? value : [
157
+ value
158
+ ];
159
+ return external_kysely_sql`${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
160
+ }
161
+ case 'not in':
162
+ {
163
+ const items = Array.isArray(value) ? value : [
164
+ value
165
+ ];
166
+ return external_kysely_sql`not ${external_kysely_sql.ref(field)} in (${external_kysely_sql.join(items.map((v)=>external_kysely_sql.val(v)))})`;
167
+ }
168
+ case 'between':
169
+ {
170
+ const [a, b] = value;
171
+ return external_kysely_sql`${external_kysely_sql.ref(field)} between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
172
+ }
173
+ case 'not between':
174
+ {
175
+ const [a, b] = value;
176
+ return external_kysely_sql`${external_kysely_sql.ref(field)} not between ${external_kysely_sql.val(a)} and ${external_kysely_sql.val(b)}`;
177
+ }
178
+ default:
179
+ return external_kysely_sql`${external_kysely_sql.ref(field)} ${external_kysely_sql.raw(op)} ${external_kysely_sql.val(value)}`;
180
+ }
181
+ };
182
+ return qb.having(toRaw(having));
183
+ };
107
184
  const applyLimit = (qb, limit)=>{
108
185
  if (limit && 'number' == typeof limit) qb = qb.limit(limit);
109
186
  return qb;
110
187
  };
188
+ const applyOrder = (qb, orderBy)=>{
189
+ if (orderBy && orderBy.length > 0) orderBy.forEach((o)=>{
190
+ qb = qb.orderBy(o.field, o.order ?? 'asc');
191
+ });
192
+ return qb;
193
+ };
111
194
  const DATE_FORMAT_MAP = {
112
195
  year: '%Y',
113
196
  month: '%Y-%m',
@@ -122,9 +205,9 @@ const applySelect = (qb, select)=>{
122
205
  if (isSelectItem(item)) {
123
206
  const field = item.field;
124
207
  const expression = eb.ref(field);
208
+ const alias = item.alias ?? field;
125
209
  if (item.aggr) {
126
210
  const { func } = item.aggr;
127
- const alias = item.alias ?? field;
128
211
  if ([
129
212
  'avg',
130
213
  'sum',
@@ -151,7 +234,6 @@ const applySelect = (qb, select)=>{
151
234
  if ('quarter' === dateTrunc) return external_kysely_sql`strftime(CAST(${expression} AS TIMESTAMP), '%Y') || '-Q' || date_part('quarter', CAST(${expression} AS TIMESTAMP))`.as(alias);
152
235
  }
153
236
  }
154
- const alias = item.alias ?? field;
155
237
  return expression.as(alias);
156
238
  }
157
239
  return item;
@@ -164,12 +246,13 @@ const convertDSLToSQL = (dsl, tableName)=>{
164
246
  });
165
247
  let qb = db.selectFrom(tableName);
166
248
  qb = applySelect(qb, dsl.select);
167
- if (dsl.where) qb = qb.where(applyWhere(dsl.where));
249
+ qb = applyWhere(qb, dsl.where);
168
250
  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');
251
+ qb = applyHaving(qb, dsl.having);
252
+ qb = applyOrder(qb, dsl.orderBy);
170
253
  qb = applyLimit(qb, dsl.limit);
171
- const compiled = qb.compile();
172
- return inlineParameters(compiled.sql, compiled.parameters);
254
+ const { sql, parameters } = qb.compile();
255
+ return inlineParameters(sql, parameters);
173
256
  };
174
257
  const READ_FUNCTION_MAP = {
175
258
  csv: 'read_csv_auto',
@@ -0,0 +1,3 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ import { Having, HavingClause } from '../../types';
3
+ export declare const applyHaving: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, having?: Having<T> | HavingClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,4 +1,6 @@
1
1
  export { applyWhere } from './where';
2
2
  export { applyGroupBy } from './groupBy';
3
+ export { applyHaving } from './having';
3
4
  export { applyLimit } from './limit';
5
+ export { applyOrder } from './order';
4
6
  export { applySelect } from './select';
@@ -0,0 +1,7 @@
1
+ import type { SelectQueryBuilder } from 'kysely';
2
+ type OrderByItem = {
3
+ field: string;
4
+ order?: 'asc' | 'desc';
5
+ };
6
+ export declare const applyOrder: <DB, TB extends keyof DB & string, O>(qb: SelectQueryBuilder<DB, TB, O>, orderBy?: Array<OrderByItem>) => SelectQueryBuilder<DB, TB, O>;
7
+ export {};
@@ -1,3 +1,3 @@
1
1
  import { Where, WhereClause } from '../../types';
2
- import type { RawBuilder } from 'kysely';
3
- export declare const applyWhere: <T>(where: Where<T> | WhereClause<T>) => RawBuilder<boolean>;
2
+ import type { SelectQueryBuilder } from 'kysely';
3
+ export declare const applyWhere: <DB, TB extends keyof DB & string, O, T>(qb: SelectQueryBuilder<DB, TB, O>, where?: Where<T> | WhereClause<T>) => SelectQueryBuilder<DB, TB, O>;
@@ -1,2 +1,2 @@
1
- import { QueryDSL, VQueryDSL } from '../types';
1
+ import type { QueryDSL, VQueryDSL } from '../types';
2
2
  export declare const convertDSLToSQL: <T, TableName extends string>(dsl: QueryDSL<T> | VQueryDSL<T>, tableName: TableName) => string;
@@ -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';
@@ -1,4 +1,5 @@
1
1
  import { GroupBy } from './GroupBy';
2
+ import { Having } from './Having';
2
3
  import { OrderBy } from './OrderBy';
3
4
  import { Select } from './Select';
4
5
  import { Where } from './Where';
@@ -6,6 +7,7 @@ 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.8",
3
+ "version": "0.4.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {