Skip to content

Commit 2943302

Browse files
committed
feat: 重构查询模式,统一过滤条件,更新分页和排序功能
1 parent 6f2664c commit 2943302

File tree

1 file changed

+29
-137
lines changed

1 file changed

+29
-137
lines changed

packages/spec/src/data/query.zod.ts

Lines changed: 29 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,5 @@
11
import { z } from 'zod';
2-
3-
/**
4-
* Filter Operator Enum
5-
* Standard SQL/NoSQL operators supported by the engine.
6-
*/
7-
export const FilterOperator = z.enum([
8-
'=', '!=', '<>',
9-
'>', '>=', '<', '<=',
10-
'startswith', 'contains', 'notcontains',
11-
'between', 'in', 'notin',
12-
'is_null', 'is_not_null'
13-
]);
14-
15-
/**
16-
* Filter Logic Operator
17-
*/
18-
export const LogicOperator = z.enum(['and', 'or', 'not']);
19-
20-
/**
21-
* Recursive Filter Node
22-
* Represents the "Where" clause.
23-
*
24-
* Structure: [Field, Operator, Value] OR [Logic, Filter, Filter...]
25-
* Examples:
26-
* - Simple: ["amount", ">", 1000]
27-
* - Logic: [["status", "=", "closed"], "or", ["amount", ">", 1000]]
28-
*/
29-
export const FilterNodeSchema: z.ZodType<any> = z.lazy(() =>
30-
z.union([
31-
// Leaf Node: [Field, Operator, Value]
32-
z.tuple([z.string(), FilterOperator, z.any()]),
33-
34-
// Logic Node: [Expression, "or", Expression]
35-
z.array(z.union([z.string(), FilterNodeSchema]))
36-
])
37-
);
2+
import { FilterConditionSchema } from './filter.zod';
383

394
/**
405
* Sort Node
@@ -248,7 +213,7 @@ export const JoinNodeSchema: z.ZodType<any> = z.lazy(() =>
248213
type: JoinType.describe('Join type'),
249214
object: z.string().describe('Object/table to join'),
250215
alias: z.string().optional().describe('Table alias'),
251-
on: FilterNodeSchema.describe('Join condition'),
216+
on: FilterConditionSchema.describe('Join condition'),
252217
subquery: z.lazy(() => QuerySchema).optional().describe('Subquery instead of object'),
253218
})
254219
);
@@ -423,101 +388,28 @@ export const FieldNodeSchema: z.ZodType<any> = z.lazy(() =>
423388
* This schema represents ObjectQL - a universal query language that abstracts
424389
* SQL, NoSQL, and SaaS APIs into a single unified interface.
425390
*
426-
* Key Features:
427-
* - **Filtering**: WHERE clauses with nested logic
428-
* - **Aggregations**: GROUP BY with COUNT, SUM, AVG, MIN, MAX
429-
* - **Joins**: INNER, LEFT, RIGHT, FULL OUTER joins
430-
* - **Window Functions**: ROW_NUMBER, RANK, LAG, LEAD, running totals
431-
* - **Subqueries**: Nested queries in joins and filters
432-
* - **Sorting & Pagination**: ORDER BY, LIMIT, OFFSET
391+
* Updates (v2):
392+
* - Aligned with modern ORM standards (Prisma/TypeORM)
393+
* - Added `cursor` based pagination support
394+
* - Renamed `top`/`skip` to `limit`/`offset`
395+
* - Unified filtering syntax with `FilterConditionSchema`
433396
*
434397
* @example
435398
* // Simple query: SELECT name, email FROM account WHERE status = 'active'
436399
* {
437400
* object: 'account',
438401
* fields: ['name', 'email'],
439-
* filters: ['status', '=', 'active']
440-
* }
441-
*
442-
* @example
443-
* // Aggregation: SELECT region, SUM(amount) as total FROM sales GROUP BY region HAVING total > 10000
444-
* {
445-
* object: 'sales',
446-
* fields: ['region'],
447-
* aggregations: [
448-
* { function: 'sum', field: 'amount', alias: 'total' }
449-
* ],
450-
* groupBy: ['region'],
451-
* having: ['total', '>', 10000]
402+
* where: { status: 'active' }
452403
* }
453404
*
454405
* @example
455-
* // Join: SELECT o.*, c.name FROM orders o INNER JOIN customers c ON o.customer_id = c.id
406+
* // Pagination with Limit/Offset
456407
* {
457-
* object: 'order',
458-
* fields: ['id', 'amount'],
459-
* joins: [
460-
* {
461-
* type: 'inner',
462-
* object: 'customer',
463-
* alias: 'c',
464-
* on: ['order.customer_id', '=', 'c.id']
465-
* }
466-
* ]
467-
* }
468-
*
469-
* @example
470-
* // Window Function: Top 5 orders per customer
471-
* {
472-
* object: 'order',
473-
* fields: ['customer_id', 'amount', 'created_at'],
474-
* windowFunctions: [
475-
* {
476-
* function: 'row_number',
477-
* alias: 'customer_order_rank',
478-
* over: {
479-
* partitionBy: ['customer_id'],
480-
* orderBy: [{ field: 'amount', order: 'desc' }]
481-
* }
482-
* }
483-
* ]
484-
* }
485-
*
486-
* @example
487-
* // Complex: Customer lifetime value with rankings
488-
* {
489-
* object: 'customer',
490-
* fields: ['id', 'name'],
491-
* joins: [
492-
* {
493-
* type: 'left',
494-
* object: 'order',
495-
* alias: 'o',
496-
* on: ['customer.id', '=', 'o.customer_id']
497-
* }
498-
* ],
499-
* aggregations: [
500-
* { function: 'count', field: 'o.id', alias: 'order_count' },
501-
* { function: 'sum', field: 'o.amount', alias: 'lifetime_value' }
502-
* ],
503-
* groupBy: ['customer.id', 'customer.name'],
504-
* having: ['order_count', '>', 0],
505-
* sort: [{ field: 'lifetime_value', order: 'desc' }],
506-
* top: 100
507-
* }
508-
*
509-
* @example
510-
* // Salesforce SOQL: SELECT Name, (SELECT LastName FROM Contacts) FROM Account
511-
* {
512-
* object: 'account',
513-
* fields: ['name'],
514-
* joins: [
515-
* {
516-
* type: 'left',
517-
* object: 'contact',
518-
* on: ['account.id', '=', 'contact.account_id']
519-
* }
520-
* ]
408+
* object: 'post',
409+
* where: { published: true },
410+
* orderBy: [{ field: 'created_at', order: 'desc' }],
411+
* limit: 20,
412+
* offset: 40
521413
* }
522414
*/
523415
export const QuerySchema = z.object({
@@ -527,38 +419,38 @@ export const QuerySchema = z.object({
527419
/** Select Clause */
528420
fields: z.array(FieldNodeSchema).optional().describe('Fields to retrieve'),
529421

530-
/** Aggregations */
531-
aggregations: z.array(AggregationNodeSchema).optional().describe('Aggregation functions (GROUP BY)'),
422+
/** Where Clause (Filtering) */
423+
where: FilterConditionSchema.optional().describe('Filtering criteria (WHERE)'),
532424

533-
/** Window Functions */
534-
windowFunctions: z.array(WindowFunctionNodeSchema).optional().describe('Window functions with OVER clause'),
425+
/** Order By Clause (Sorting) */
426+
orderBy: z.array(SortNodeSchema).optional().describe('Sorting instructions (ORDER BY)'),
535427

536-
/** Where Clause */
537-
filters: FilterNodeSchema.optional().describe('Filtering criteria'),
428+
/** Pagination */
429+
limit: z.number().optional().describe('Max records to return (LIMIT)'),
430+
offset: z.number().optional().describe('Records to skip (OFFSET)'),
431+
cursor: z.record(z.any()).optional().describe('Cursor for keyset pagination'),
538432

539433
/** Joins */
540-
joins: z.array(JoinNodeSchema).optional().describe('Table joins'),
434+
joins: z.array(JoinNodeSchema).optional().describe('Explicit Table Joins'),
435+
436+
/** Aggregations */
437+
aggregations: z.array(AggregationNodeSchema).optional().describe('Aggregation functions'),
541438

542439
/** Group By Clause */
543440
groupBy: z.array(z.string()).optional().describe('GROUP BY fields'),
544441

545442
/** Having Clause */
546-
having: FilterNodeSchema.optional().describe('HAVING clause for aggregation filtering'),
547-
548-
/** Order By Clause */
549-
sort: z.array(SortNodeSchema).optional().describe('Sorting instructions'),
443+
having: FilterConditionSchema.optional().describe('HAVING clause for aggregation filtering'),
550444

551-
/** Pagination */
552-
top: z.number().optional().describe('Limit results'),
553-
skip: z.number().optional().describe('Offset results'),
445+
/** Window Functions */
446+
windowFunctions: z.array(WindowFunctionNodeSchema).optional().describe('Window functions with OVER clause'),
554447

555448
/** Subquery flag */
556449
distinct: z.boolean().optional().describe('SELECT DISTINCT flag'),
557450
});
558451

559452
export type QueryAST = z.infer<typeof QuerySchema>;
560453
export type QueryInput = z.input<typeof QuerySchema>;
561-
export type FilterNode = z.infer<typeof FilterNodeSchema>;
562454
export type SortNode = z.infer<typeof SortNodeSchema>;
563455
export type AggregationNode = z.infer<typeof AggregationNodeSchema>;
564456
export type JoinNode = z.infer<typeof JoinNodeSchema>;

0 commit comments

Comments
 (0)