@object-ui/core 0.3.0 → 0.5.0
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/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +8 -0
- package/dist/actions/ActionRunner.d.ts +40 -0
- package/dist/actions/ActionRunner.js +160 -0
- package/dist/actions/index.d.ts +8 -0
- package/dist/actions/index.js +8 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +10 -0
- package/dist/builder/schema-builder.d.ts +7 -0
- package/dist/builder/schema-builder.js +4 -6
- package/dist/evaluator/ExpressionCache.d.ts +101 -0
- package/dist/evaluator/ExpressionCache.js +135 -0
- package/dist/evaluator/ExpressionContext.d.ts +51 -0
- package/dist/evaluator/ExpressionContext.js +110 -0
- package/dist/evaluator/ExpressionEvaluator.d.ts +117 -0
- package/dist/evaluator/ExpressionEvaluator.js +220 -0
- package/dist/evaluator/index.d.ts +10 -0
- package/dist/evaluator/index.js +10 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +16 -5
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.js +6 -0
- package/dist/query/query-ast.d.ts +32 -0
- package/dist/query/query-ast.js +268 -0
- package/dist/registry/PluginScopeImpl.d.ts +80 -0
- package/dist/registry/PluginScopeImpl.js +243 -0
- package/dist/registry/PluginSystem.d.ts +66 -0
- package/dist/registry/PluginSystem.js +142 -0
- package/dist/registry/Registry.d.ts +80 -4
- package/dist/registry/Registry.js +119 -7
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/filter-converter.d.ts +57 -0
- package/dist/utils/filter-converter.js +100 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.js +9 -0
- package/dist/validation/schema-validator.d.ts +7 -0
- package/dist/validation/schema-validator.js +4 -6
- package/dist/validation/validation-engine.d.ts +70 -0
- package/dist/validation/validation-engine.js +363 -0
- package/dist/validation/validators/index.d.ts +16 -0
- package/dist/validation/validators/index.js +16 -0
- package/dist/validation/validators/object-validation-engine.d.ts +118 -0
- package/dist/validation/validators/object-validation-engine.js +538 -0
- package/package.json +26 -7
- package/src/actions/ActionRunner.ts +195 -0
- package/src/actions/index.ts +9 -0
- package/src/adapters/README.md +180 -0
- package/src/adapters/index.ts +10 -0
- package/src/builder/schema-builder.ts +8 -0
- package/src/evaluator/ExpressionCache.ts +192 -0
- package/src/evaluator/ExpressionContext.ts +118 -0
- package/src/evaluator/ExpressionEvaluator.ts +267 -0
- package/src/evaluator/__tests__/ExpressionCache.test.ts +135 -0
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +101 -0
- package/src/evaluator/index.ts +11 -0
- package/src/index.test.ts +8 -0
- package/src/index.ts +18 -5
- package/src/query/__tests__/query-ast.test.ts +211 -0
- package/src/query/__tests__/window-functions.test.ts +275 -0
- package/src/query/index.ts +7 -0
- package/src/query/query-ast.ts +341 -0
- package/src/registry/PluginScopeImpl.ts +259 -0
- package/src/registry/PluginSystem.ts +161 -0
- package/src/registry/Registry.ts +133 -8
- package/src/registry/__tests__/PluginSystem.test.ts +226 -0
- package/src/registry/__tests__/Registry.test.ts +293 -0
- package/src/registry/__tests__/plugin-scope-integration.test.ts +283 -0
- package/src/types/index.ts +8 -0
- package/src/utils/__tests__/filter-converter.test.ts +118 -0
- package/src/utils/filter-converter.ts +133 -0
- package/src/validation/__tests__/object-validation-engine.test.ts +567 -0
- package/src/validation/__tests__/validation-engine.test.ts +102 -0
- package/src/validation/index.ts +10 -0
- package/src/validation/schema-validator.ts +8 -0
- package/src/validation/validation-engine.ts +461 -0
- package/src/validation/validators/index.ts +25 -0
- package/src/validation/validators/object-validation-engine.ts +722 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +2 -0
- package/src/builder/schema-builder.d.ts +0 -287
- package/src/builder/schema-builder.js +0 -505
- package/src/index.d.ts +0 -4
- package/src/index.js +0 -7
- package/src/registry/Registry.d.ts +0 -49
- package/src/registry/Registry.js +0 -36
- package/src/types/index.d.ts +0 -12
- package/src/types/index.js +0 -1
- package/src/validation/schema-validator.d.ts +0 -87
- package/src/validation/schema-validator.js +0 -280
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @object-ui/core - Query AST Builder Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { QueryASTBuilder } from '../query-ast';
|
|
7
|
+
import type { QuerySchema } from '@object-ui/types';
|
|
8
|
+
|
|
9
|
+
describe('QueryASTBuilder', () => {
|
|
10
|
+
const builder = new QueryASTBuilder();
|
|
11
|
+
|
|
12
|
+
describe('Basic Query Building', () => {
|
|
13
|
+
it('should build simple SELECT query', () => {
|
|
14
|
+
const query: QuerySchema = {
|
|
15
|
+
object: 'users',
|
|
16
|
+
fields: ['id', 'name', 'email'],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const ast = builder.build(query);
|
|
20
|
+
|
|
21
|
+
expect(ast.select.type).toBe('select');
|
|
22
|
+
expect(ast.select.fields).toHaveLength(3);
|
|
23
|
+
expect(ast.from.table).toBe('users');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should build SELECT * when no fields specified', () => {
|
|
27
|
+
const query: QuerySchema = {
|
|
28
|
+
object: 'users',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ast = builder.build(query);
|
|
32
|
+
|
|
33
|
+
expect(ast.select.fields).toHaveLength(1);
|
|
34
|
+
expect(ast.select.fields[0]).toMatchObject({
|
|
35
|
+
type: 'field',
|
|
36
|
+
name: '*',
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should build query with WHERE clause', () => {
|
|
41
|
+
const query: QuerySchema = {
|
|
42
|
+
object: 'users',
|
|
43
|
+
fields: ['id', 'name'],
|
|
44
|
+
filter: {
|
|
45
|
+
conditions: [
|
|
46
|
+
{
|
|
47
|
+
field: 'status',
|
|
48
|
+
operator: 'equals',
|
|
49
|
+
value: 'active',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ast = builder.build(query);
|
|
56
|
+
|
|
57
|
+
expect(ast.where).toBeDefined();
|
|
58
|
+
expect(ast.where?.type).toBe('where');
|
|
59
|
+
expect(ast.where?.condition.type).toBe('operator');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should build query with ORDER BY', () => {
|
|
63
|
+
const query: QuerySchema = {
|
|
64
|
+
object: 'users',
|
|
65
|
+
fields: ['id', 'name'],
|
|
66
|
+
sort: [
|
|
67
|
+
{ field: 'created_at', order: 'desc' },
|
|
68
|
+
{ field: 'name', order: 'asc' },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const ast = builder.build(query);
|
|
73
|
+
|
|
74
|
+
expect(ast.order_by).toBeDefined();
|
|
75
|
+
expect(ast.order_by?.fields).toHaveLength(2);
|
|
76
|
+
expect(ast.order_by?.fields[0].direction).toBe('desc');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should build query with LIMIT and OFFSET', () => {
|
|
80
|
+
const query: QuerySchema = {
|
|
81
|
+
object: 'users',
|
|
82
|
+
fields: ['id', 'name'],
|
|
83
|
+
limit: 10,
|
|
84
|
+
offset: 20,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const ast = builder.build(query);
|
|
88
|
+
|
|
89
|
+
expect(ast.limit).toBeDefined();
|
|
90
|
+
expect(ast.limit?.value).toBe(10);
|
|
91
|
+
expect(ast.offset).toBeDefined();
|
|
92
|
+
expect(ast.offset?.value).toBe(20);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Advanced Query Building', () => {
|
|
97
|
+
it('should build query with JOIN', () => {
|
|
98
|
+
const query: QuerySchema = {
|
|
99
|
+
object: 'users',
|
|
100
|
+
fields: ['id', 'name', 'orders.total'],
|
|
101
|
+
joins: [
|
|
102
|
+
{
|
|
103
|
+
type: 'left',
|
|
104
|
+
object: 'orders',
|
|
105
|
+
on: {
|
|
106
|
+
local_field: 'id',
|
|
107
|
+
foreign_field: 'user_id',
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const ast = builder.build(query);
|
|
114
|
+
|
|
115
|
+
expect(ast.joins).toBeDefined();
|
|
116
|
+
expect(ast.joins).toHaveLength(1);
|
|
117
|
+
expect(ast.joins?.[0].join_type).toBe('left');
|
|
118
|
+
expect(ast.joins?.[0].table).toBe('orders');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should build query with aggregations', () => {
|
|
122
|
+
const query: QuerySchema = {
|
|
123
|
+
object: 'orders',
|
|
124
|
+
aggregations: [
|
|
125
|
+
{
|
|
126
|
+
function: 'count',
|
|
127
|
+
alias: 'total_count',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
function: 'sum',
|
|
131
|
+
field: 'amount',
|
|
132
|
+
alias: 'total_amount',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const ast = builder.build(query);
|
|
138
|
+
|
|
139
|
+
expect(ast.select.fields).toHaveLength(2);
|
|
140
|
+
expect(ast.select.fields[0]).toMatchObject({
|
|
141
|
+
type: 'aggregate',
|
|
142
|
+
function: 'count',
|
|
143
|
+
alias: 'total_count',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should build query with GROUP BY', () => {
|
|
148
|
+
const query: QuerySchema = {
|
|
149
|
+
object: 'orders',
|
|
150
|
+
fields: ['user_id'],
|
|
151
|
+
group_by: ['user_id'],
|
|
152
|
+
aggregations: [
|
|
153
|
+
{
|
|
154
|
+
function: 'count',
|
|
155
|
+
alias: 'order_count',
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const ast = builder.build(query);
|
|
161
|
+
|
|
162
|
+
expect(ast.group_by).toBeDefined();
|
|
163
|
+
expect(ast.group_by?.fields).toHaveLength(1);
|
|
164
|
+
expect(ast.group_by?.fields[0]).toMatchObject({
|
|
165
|
+
type: 'field',
|
|
166
|
+
name: 'user_id',
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Complex Filters', () => {
|
|
172
|
+
it('should build query with nested AND/OR filters', () => {
|
|
173
|
+
const query: QuerySchema = {
|
|
174
|
+
object: 'users',
|
|
175
|
+
filter: {
|
|
176
|
+
operator: 'and',
|
|
177
|
+
conditions: [
|
|
178
|
+
{
|
|
179
|
+
field: 'status',
|
|
180
|
+
operator: 'equals',
|
|
181
|
+
value: 'active',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
groups: [
|
|
185
|
+
{
|
|
186
|
+
operator: 'or',
|
|
187
|
+
conditions: [
|
|
188
|
+
{
|
|
189
|
+
field: 'role',
|
|
190
|
+
operator: 'equals',
|
|
191
|
+
value: 'admin',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
field: 'role',
|
|
195
|
+
operator: 'equals',
|
|
196
|
+
value: 'moderator',
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const ast = builder.build(query);
|
|
205
|
+
|
|
206
|
+
expect(ast.where).toBeDefined();
|
|
207
|
+
expect(ast.where?.condition.operator).toBe('and');
|
|
208
|
+
expect(ast.where?.condition.operands.length).toBeGreaterThan(0);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @object-ui/core - Window Function Tests
|
|
11
|
+
*
|
|
12
|
+
* Tests for ObjectStack Spec v0.7.1 window function support
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect } from 'vitest';
|
|
16
|
+
import { QueryASTBuilder } from '../query-ast';
|
|
17
|
+
import type { WindowNode, WindowFunction } from '@object-ui/types';
|
|
18
|
+
|
|
19
|
+
describe('QueryASTBuilder - Window Functions', () => {
|
|
20
|
+
const builder = new QueryASTBuilder();
|
|
21
|
+
|
|
22
|
+
describe('buildWindow', () => {
|
|
23
|
+
it('should build a simple row_number window function', () => {
|
|
24
|
+
const config = {
|
|
25
|
+
function: 'row_number' as WindowFunction,
|
|
26
|
+
alias: 'row_num',
|
|
27
|
+
partitionBy: ['department'],
|
|
28
|
+
orderBy: [{ field: 'salary', direction: 'desc' as const }],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// @ts-expect-error - testing private method
|
|
32
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
33
|
+
|
|
34
|
+
expect(result).toMatchObject({
|
|
35
|
+
type: 'window',
|
|
36
|
+
function: 'row_number',
|
|
37
|
+
alias: 'row_num',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(result.partitionBy).toHaveLength(1);
|
|
41
|
+
expect(result.partitionBy![0]).toMatchObject({
|
|
42
|
+
type: 'field',
|
|
43
|
+
name: 'department',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result.orderBy).toHaveLength(1);
|
|
47
|
+
expect(result.orderBy![0]).toMatchObject({
|
|
48
|
+
field: { type: 'field', name: 'salary' },
|
|
49
|
+
direction: 'desc',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should build a rank window function with multiple partition fields', () => {
|
|
54
|
+
const config = {
|
|
55
|
+
function: 'rank' as WindowFunction,
|
|
56
|
+
alias: 'rank_val',
|
|
57
|
+
partitionBy: ['department', 'location'],
|
|
58
|
+
orderBy: [
|
|
59
|
+
{ field: 'performance_score', direction: 'desc' as const },
|
|
60
|
+
{ field: 'tenure_years', direction: 'desc' as const },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// @ts-expect-error - testing private method
|
|
65
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
66
|
+
|
|
67
|
+
expect(result).toMatchObject({
|
|
68
|
+
type: 'window',
|
|
69
|
+
function: 'rank',
|
|
70
|
+
alias: 'rank_val',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.partitionBy).toHaveLength(2);
|
|
74
|
+
expect(result.orderBy).toHaveLength(2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should build a lag window function with offset and default value', () => {
|
|
78
|
+
const config = {
|
|
79
|
+
function: 'lag' as WindowFunction,
|
|
80
|
+
field: 'revenue',
|
|
81
|
+
alias: 'prev_month_revenue',
|
|
82
|
+
partitionBy: ['product_id'],
|
|
83
|
+
orderBy: [{ field: 'month', direction: 'asc' as const }],
|
|
84
|
+
offset: 1,
|
|
85
|
+
defaultValue: 0,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// @ts-expect-error - testing private method
|
|
89
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
90
|
+
|
|
91
|
+
expect(result).toMatchObject({
|
|
92
|
+
type: 'window',
|
|
93
|
+
function: 'lag',
|
|
94
|
+
alias: 'prev_month_revenue',
|
|
95
|
+
offset: 1,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result.field).toMatchObject({
|
|
99
|
+
type: 'field',
|
|
100
|
+
name: 'revenue',
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(result.defaultValue).toMatchObject({
|
|
104
|
+
type: 'literal',
|
|
105
|
+
value: 0,
|
|
106
|
+
data_type: 'number',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should build a lead window function', () => {
|
|
111
|
+
const config = {
|
|
112
|
+
function: 'lead' as WindowFunction,
|
|
113
|
+
field: 'sales',
|
|
114
|
+
alias: 'next_day_sales',
|
|
115
|
+
orderBy: [{ field: 'date', direction: 'asc' as const }],
|
|
116
|
+
offset: 1,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// @ts-expect-error - testing private method
|
|
120
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
121
|
+
|
|
122
|
+
expect(result).toMatchObject({
|
|
123
|
+
type: 'window',
|
|
124
|
+
function: 'lead',
|
|
125
|
+
alias: 'next_day_sales',
|
|
126
|
+
offset: 1,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should build aggregate window functions (sum, avg, count)', () => {
|
|
131
|
+
const sumConfig = {
|
|
132
|
+
function: 'sum' as WindowFunction,
|
|
133
|
+
field: 'amount',
|
|
134
|
+
alias: 'running_total',
|
|
135
|
+
orderBy: [{ field: 'date', direction: 'asc' as const }],
|
|
136
|
+
frame: {
|
|
137
|
+
unit: 'rows' as const,
|
|
138
|
+
start: 'unbounded_preceding' as const,
|
|
139
|
+
end: 'current_row' as const,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// @ts-expect-error - testing private method
|
|
144
|
+
const result: WindowNode = builder.buildWindow(sumConfig);
|
|
145
|
+
|
|
146
|
+
expect(result).toMatchObject({
|
|
147
|
+
type: 'window',
|
|
148
|
+
function: 'sum',
|
|
149
|
+
alias: 'running_total',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(result.field).toMatchObject({
|
|
153
|
+
type: 'field',
|
|
154
|
+
name: 'amount',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.frame).toEqual({
|
|
158
|
+
unit: 'rows',
|
|
159
|
+
start: 'unbounded_preceding',
|
|
160
|
+
end: 'current_row',
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should build first_value window function', () => {
|
|
165
|
+
const config = {
|
|
166
|
+
function: 'first_value' as WindowFunction,
|
|
167
|
+
field: 'price',
|
|
168
|
+
alias: 'first_price',
|
|
169
|
+
partitionBy: ['product_category'],
|
|
170
|
+
orderBy: [{ field: 'created_at', direction: 'asc' as const }],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// @ts-expect-error - testing private method
|
|
174
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
175
|
+
|
|
176
|
+
expect(result).toMatchObject({
|
|
177
|
+
type: 'window',
|
|
178
|
+
function: 'first_value',
|
|
179
|
+
alias: 'first_price',
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should build last_value window function', () => {
|
|
184
|
+
const config = {
|
|
185
|
+
function: 'last_value' as WindowFunction,
|
|
186
|
+
field: 'status',
|
|
187
|
+
alias: 'latest_status',
|
|
188
|
+
partitionBy: ['customer_id'],
|
|
189
|
+
orderBy: [{ field: 'updated_at', direction: 'desc' as const }],
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// @ts-expect-error - testing private method
|
|
193
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
194
|
+
|
|
195
|
+
expect(result).toMatchObject({
|
|
196
|
+
type: 'window',
|
|
197
|
+
function: 'last_value',
|
|
198
|
+
alias: 'latest_status',
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should handle window function without partition by', () => {
|
|
203
|
+
const config = {
|
|
204
|
+
function: 'row_number' as WindowFunction,
|
|
205
|
+
alias: 'global_row_num',
|
|
206
|
+
orderBy: [{ field: 'created_at', direction: 'asc' as const }],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// @ts-expect-error - testing private method
|
|
210
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
211
|
+
|
|
212
|
+
expect(result.partitionBy).toBeUndefined();
|
|
213
|
+
expect(result.orderBy).toBeDefined();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle window function with frame specification', () => {
|
|
217
|
+
const config = {
|
|
218
|
+
function: 'avg' as WindowFunction,
|
|
219
|
+
field: 'temperature',
|
|
220
|
+
alias: 'moving_avg_3days',
|
|
221
|
+
orderBy: [{ field: 'date', direction: 'asc' as const }],
|
|
222
|
+
frame: {
|
|
223
|
+
unit: 'rows' as const,
|
|
224
|
+
start: { type: 'preceding' as const, offset: 2 },
|
|
225
|
+
end: 'current_row' as const,
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// @ts-expect-error - testing private method
|
|
230
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
231
|
+
|
|
232
|
+
expect(result.frame).toEqual({
|
|
233
|
+
unit: 'rows',
|
|
234
|
+
start: { type: 'preceding', offset: 2 },
|
|
235
|
+
end: 'current_row',
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should build dense_rank window function', () => {
|
|
240
|
+
const config = {
|
|
241
|
+
function: 'dense_rank' as WindowFunction,
|
|
242
|
+
alias: 'dense_rank_val',
|
|
243
|
+
partitionBy: ['team'],
|
|
244
|
+
orderBy: [{ field: 'score', direction: 'desc' as const }],
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// @ts-expect-error - testing private method
|
|
248
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
249
|
+
|
|
250
|
+
expect(result).toMatchObject({
|
|
251
|
+
type: 'window',
|
|
252
|
+
function: 'dense_rank',
|
|
253
|
+
alias: 'dense_rank_val',
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should build percent_rank window function', () => {
|
|
258
|
+
const config = {
|
|
259
|
+
function: 'percent_rank' as WindowFunction,
|
|
260
|
+
alias: 'percentile',
|
|
261
|
+
partitionBy: ['class'],
|
|
262
|
+
orderBy: [{ field: 'exam_score', direction: 'desc' as const }],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// @ts-expect-error - testing private method
|
|
266
|
+
const result: WindowNode = builder.buildWindow(config);
|
|
267
|
+
|
|
268
|
+
expect(result).toMatchObject({
|
|
269
|
+
type: 'window',
|
|
270
|
+
function: 'percent_rank',
|
|
271
|
+
alias: 'percentile',
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|