@gblikas/querykit 0.0.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/.cursor/BUGBOT.md +21 -0
- package/.cursor/rules/01-project-structure.mdc +77 -0
- package/.cursor/rules/02-typescript-standards.mdc +105 -0
- package/.cursor/rules/03-testing-standards.mdc +78 -0
- package/.cursor/rules/04-query-language.mdc +79 -0
- package/.cursor/rules/05-solid-principles.mdc +118 -0
- package/.cursor/rules/liqe-readme-docs.mdc +438 -0
- package/.devcontainer/devcontainer.json +25 -0
- package/.eslintignore +1 -0
- package/.eslintrc.js +39 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/ci.yml +114 -0
- package/.github/workflows/publish.yml +61 -0
- package/.husky/pre-commit +30 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +187 -0
- package/LICENSE +674 -0
- package/README.md +237 -0
- package/dist/adapters/drizzle/index.d.ts +122 -0
- package/dist/adapters/drizzle/index.js +166 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +25 -0
- package/dist/adapters/types.d.ts +60 -0
- package/dist/adapters/types.js +8 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +118 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +18 -0
- package/dist/parser/parser.d.ts +51 -0
- package/dist/parser/parser.js +201 -0
- package/dist/parser/types.d.ts +68 -0
- package/dist/parser/types.js +5 -0
- package/dist/query/builder.d.ts +61 -0
- package/dist/query/builder.js +188 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +18 -0
- package/dist/query/types.d.ts +79 -0
- package/dist/query/types.js +2 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +18 -0
- package/dist/security/types.d.ts +181 -0
- package/dist/security/types.js +43 -0
- package/dist/security/validator.d.ts +191 -0
- package/dist/security/validator.js +344 -0
- package/dist/translators/drizzle/index.d.ts +73 -0
- package/dist/translators/drizzle/index.js +260 -0
- package/dist/translators/index.d.ts +8 -0
- package/dist/translators/index.js +27 -0
- package/dist/translators/sql/index.d.ts +108 -0
- package/dist/translators/sql/index.js +252 -0
- package/dist/translators/types.d.ts +39 -0
- package/dist/translators/types.js +8 -0
- package/examples/qk-next/README.md +35 -0
- package/examples/qk-next/app/favicon.ico +0 -0
- package/examples/qk-next/app/globals.css +122 -0
- package/examples/qk-next/app/layout.tsx +121 -0
- package/examples/qk-next/app/page.tsx +813 -0
- package/examples/qk-next/app/providers.tsx +80 -0
- package/examples/qk-next/components/aurora-background.tsx +12 -0
- package/examples/qk-next/components/github-stars.tsx +51 -0
- package/examples/qk-next/components/mode-toggle.tsx +27 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
- package/examples/qk-next/components/theme-provider.tsx +11 -0
- package/examples/qk-next/components/ui/card.tsx +92 -0
- package/examples/qk-next/components/ui/command.tsx +184 -0
- package/examples/qk-next/components/ui/dialog.tsx +143 -0
- package/examples/qk-next/components/ui/drawer.tsx +135 -0
- package/examples/qk-next/components/ui/hover-card.tsx +44 -0
- package/examples/qk-next/components/ui/icons.tsx +148 -0
- package/examples/qk-next/components/ui/sonner.tsx +26 -0
- package/examples/qk-next/components/ui/table.tsx +117 -0
- package/examples/qk-next/components.json +21 -0
- package/examples/qk-next/eslint.config.mjs +21 -0
- package/examples/qk-next/jsrepo.json +13 -0
- package/examples/qk-next/lib/utils.ts +6 -0
- package/examples/qk-next/next.config.ts +8 -0
- package/examples/qk-next/package.json +48 -0
- package/examples/qk-next/pnpm-lock.yaml +5558 -0
- package/examples/qk-next/postcss.config.mjs +5 -0
- package/examples/qk-next/public/file.svg +1 -0
- package/examples/qk-next/public/globe.svg +1 -0
- package/examples/qk-next/public/next.svg +1 -0
- package/examples/qk-next/public/vercel.svg +1 -0
- package/examples/qk-next/public/window.svg +1 -0
- package/examples/qk-next/tsconfig.json +42 -0
- package/examples/qk-next/types/sonner.d.ts +3 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
- package/src/adapters/drizzle/index.ts +299 -0
- package/src/adapters/index.ts +11 -0
- package/src/adapters/types.ts +72 -0
- package/src/index.ts +194 -0
- package/src/integration.test.ts +202 -0
- package/src/parser/index.ts +2 -0
- package/src/parser/parser.test.ts +1056 -0
- package/src/parser/parser.ts +268 -0
- package/src/parser/types.ts +97 -0
- package/src/query/builder.test.ts +272 -0
- package/src/query/builder.ts +274 -0
- package/src/query/index.ts +2 -0
- package/src/query/types.ts +107 -0
- package/src/security/index.ts +2 -0
- package/src/security/types.ts +210 -0
- package/src/security/validator.test.ts +459 -0
- package/src/security/validator.ts +395 -0
- package/src/security.test.ts +366 -0
- package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
- package/src/translators/drizzle/index.test.ts +45 -0
- package/src/translators/drizzle/index.ts +346 -0
- package/src/translators/index.ts +14 -0
- package/src/translators/sql/index.test.ts +45 -0
- package/src/translators/sql/index.ts +331 -0
- package/src/translators/sql/sql-translator.test.ts +419 -0
- package/src/translators/types.ts +44 -0
- package/src/types/sonner.d.ts +3 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { IComparisonExpression, QueryExpression } from '../parser/types';
|
|
2
|
+
import { DEFAULT_SECURITY_OPTIONS, ISecurityOptions } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when a query violates security constraints
|
|
6
|
+
*
|
|
7
|
+
* This error is thrown when a query attempts to bypass security settings
|
|
8
|
+
* such as accessing unauthorized fields, exceeding complexity limits,
|
|
9
|
+
* or using potentially dangerous patterns.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* try {
|
|
14
|
+
* queryValidator.validate(parsedQuery);
|
|
15
|
+
* } catch (error) {
|
|
16
|
+
* if (error instanceof QuerySecurityError) {
|
|
17
|
+
* console.error('Security violation:', error.message);
|
|
18
|
+
* // Return appropriate error response to client
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export class QuerySecurityError extends Error {
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'QuerySecurityError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validates query expressions against security constraints
|
|
32
|
+
*
|
|
33
|
+
* The QuerySecurityValidator ensures that queries comply with the security
|
|
34
|
+
* rules defined in the ISecurityOptions. It should be used before executing
|
|
35
|
+
* any query to prevent potential security issues or resource exhaustion.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { QuerySecurityValidator, ISecurityOptions } from 'querykit';
|
|
40
|
+
* import { parseQuery } from 'querykit/parser';
|
|
41
|
+
*
|
|
42
|
+
* // Define security options
|
|
43
|
+
* const securityOptions: ISecurityOptions = {
|
|
44
|
+
* allowedFields: ['id', 'name', 'createdAt'],
|
|
45
|
+
* denyFields: ['password'],
|
|
46
|
+
* maxQueryDepth: 5
|
|
47
|
+
* };
|
|
48
|
+
*
|
|
49
|
+
* // Create validator
|
|
50
|
+
* const validator = new QuerySecurityValidator(securityOptions);
|
|
51
|
+
*
|
|
52
|
+
* // Use in API endpoint
|
|
53
|
+
* app.get('/users', (req, res) => {
|
|
54
|
+
* try {
|
|
55
|
+
* // Parse the query from request
|
|
56
|
+
* const queryStr = req.query.filter || '';
|
|
57
|
+
* const parsedQuery = parseQuery(queryStr);
|
|
58
|
+
*
|
|
59
|
+
* // Validate against security rules
|
|
60
|
+
* validator.validate(parsedQuery, userSchema);
|
|
61
|
+
*
|
|
62
|
+
* // If validation passes, execute the query
|
|
63
|
+
* const results = executeQuery(parsedQuery);
|
|
64
|
+
* res.json(results);
|
|
65
|
+
* } catch (error) {
|
|
66
|
+
* if (error instanceof QuerySecurityError) {
|
|
67
|
+
* res.status(400).json({ error: error.message });
|
|
68
|
+
* } else {
|
|
69
|
+
* res.status(500).json({ error: 'Server error' });
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export class QuerySecurityValidator {
|
|
76
|
+
private options: Required<ISecurityOptions>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Creates a new QuerySecurityValidator instance
|
|
80
|
+
*
|
|
81
|
+
* @param options - Security options to apply. If not provided, default options will be used.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Create with default security settings
|
|
86
|
+
* const defaultValidator = new QuerySecurityValidator();
|
|
87
|
+
*
|
|
88
|
+
* // Create with custom security settings
|
|
89
|
+
* const strictValidator = new QuerySecurityValidator({
|
|
90
|
+
* maxQueryDepth: 3,
|
|
91
|
+
* maxClauseCount: 10,
|
|
92
|
+
* maxValueLength: 500
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
constructor(options: ISecurityOptions = {}) {
|
|
97
|
+
this.options = {
|
|
98
|
+
...DEFAULT_SECURITY_OPTIONS,
|
|
99
|
+
...options
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate a query expression against security constraints
|
|
105
|
+
*
|
|
106
|
+
* This is the main method that should be called before executing any query.
|
|
107
|
+
* It performs all security checks defined in the options.
|
|
108
|
+
*
|
|
109
|
+
* @param expression - The parsed query expression to validate
|
|
110
|
+
* @param schema - Optional schema definition to validate fields against
|
|
111
|
+
* @throws {QuerySecurityError} If the query violates any security constraints
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* import { parseQuery } from 'querykit/parser';
|
|
116
|
+
*
|
|
117
|
+
* const validator = new QuerySecurityValidator();
|
|
118
|
+
*
|
|
119
|
+
* // Simple validation
|
|
120
|
+
* try {
|
|
121
|
+
* const query = parseQuery('user.name == "John" && user.priority > 2');
|
|
122
|
+
* validator.validate(query);
|
|
123
|
+
* // Query is safe to execute
|
|
124
|
+
* } catch (error) {
|
|
125
|
+
* // Handle security violation
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* // Validation with schema
|
|
129
|
+
* const userSchema = {
|
|
130
|
+
* user: {
|
|
131
|
+
* id: 'number',
|
|
132
|
+
* name: 'string',
|
|
133
|
+
* priority: 'number',
|
|
134
|
+
* email: 'string'
|
|
135
|
+
* }
|
|
136
|
+
* };
|
|
137
|
+
*
|
|
138
|
+
* try {
|
|
139
|
+
* const query = parseQuery('user.email == "john@example.com"');
|
|
140
|
+
* validator.validate(query, userSchema);
|
|
141
|
+
* // Query is safe to execute
|
|
142
|
+
* } catch (error) {
|
|
143
|
+
* // Handle security violation
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
public validate(
|
|
148
|
+
expression: QueryExpression,
|
|
149
|
+
schema?: Record<string, Record<string, unknown>>
|
|
150
|
+
): void {
|
|
151
|
+
// Check for field restrictions if specified
|
|
152
|
+
this.validateFields(expression, schema);
|
|
153
|
+
|
|
154
|
+
// Check query complexity
|
|
155
|
+
this.validateQueryDepth(expression, 0);
|
|
156
|
+
this.validateClauseCount(expression);
|
|
157
|
+
|
|
158
|
+
// Check value lengths
|
|
159
|
+
this.validateValueLengths(expression);
|
|
160
|
+
|
|
161
|
+
// Sanitize wildcard patterns if enabled
|
|
162
|
+
if (this.options.sanitizeWildcards) {
|
|
163
|
+
this.sanitizeWildcards(expression);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Validate that query fields are allowed and not denied
|
|
169
|
+
*
|
|
170
|
+
* @private
|
|
171
|
+
* @param expression - The query expression to validate
|
|
172
|
+
* @param schema - Optional schema definition to validate fields against
|
|
173
|
+
*/
|
|
174
|
+
private validateFields(
|
|
175
|
+
expression: QueryExpression,
|
|
176
|
+
schema?: Record<string, Record<string, unknown>>
|
|
177
|
+
): void {
|
|
178
|
+
const fieldSet = new Set<string>();
|
|
179
|
+
this.collectFields(expression, fieldSet);
|
|
180
|
+
|
|
181
|
+
// Create a set of allowed fields
|
|
182
|
+
const allowedFields = new Set<string>();
|
|
183
|
+
|
|
184
|
+
// If allowedFields is empty and schema is provided, use schema fields
|
|
185
|
+
if (this.options.allowedFields.length === 0 && schema) {
|
|
186
|
+
for (const table in schema) {
|
|
187
|
+
if (typeof schema[table] === 'object') {
|
|
188
|
+
for (const field in schema[table]) {
|
|
189
|
+
allowedFields.add(`${table}.${field}`);
|
|
190
|
+
allowedFields.add(field);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
this.options.allowedFields.forEach(field => allowedFields.add(field));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Create a set of denied fields
|
|
199
|
+
const deniedFields = new Set<string>(this.options.denyFields);
|
|
200
|
+
|
|
201
|
+
// Check each field in the query
|
|
202
|
+
for (const field of fieldSet) {
|
|
203
|
+
// Security fix: Generic error to prevent field enumeration attacks
|
|
204
|
+
if (
|
|
205
|
+
deniedFields.has(field) ||
|
|
206
|
+
(allowedFields.size > 0 && !allowedFields.has(field))
|
|
207
|
+
) {
|
|
208
|
+
throw new QuerySecurityError('Invalid query parameters');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Validate that query depth does not exceed the maximum
|
|
215
|
+
*
|
|
216
|
+
* @private
|
|
217
|
+
* @param expression - The query expression to validate
|
|
218
|
+
* @param currentDepth - The current depth level in the recursion
|
|
219
|
+
*/
|
|
220
|
+
private validateQueryDepth(
|
|
221
|
+
expression: QueryExpression,
|
|
222
|
+
currentDepth: number
|
|
223
|
+
): void {
|
|
224
|
+
if (currentDepth > this.options.maxQueryDepth) {
|
|
225
|
+
throw new QuerySecurityError(
|
|
226
|
+
`Query exceeds maximum depth of ${this.options.maxQueryDepth}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (expression.type === 'logical') {
|
|
231
|
+
this.validateQueryDepth(expression.left, currentDepth + 1);
|
|
232
|
+
if (expression.right) {
|
|
233
|
+
this.validateQueryDepth(expression.right, currentDepth + 1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Validate that the number of clauses does not exceed the maximum
|
|
240
|
+
*
|
|
241
|
+
* @private
|
|
242
|
+
* @param expression - The query expression to validate
|
|
243
|
+
*/
|
|
244
|
+
private validateClauseCount(expression: QueryExpression): void {
|
|
245
|
+
const count = this.countClauses(expression);
|
|
246
|
+
if (count > this.options.maxClauseCount) {
|
|
247
|
+
throw new QuerySecurityError(
|
|
248
|
+
`Query exceeds maximum clause count of ${this.options.maxClauseCount} (found ${count})`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Count the number of clauses in a query expression
|
|
255
|
+
*
|
|
256
|
+
* @private
|
|
257
|
+
* @param expression - The query expression to count clauses in
|
|
258
|
+
* @returns The total number of comparison clauses
|
|
259
|
+
*/
|
|
260
|
+
private countClauses(expression: QueryExpression): number {
|
|
261
|
+
if (expression.type === 'comparison') {
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let count = 0;
|
|
266
|
+
count += this.countClauses(expression.left);
|
|
267
|
+
if (expression.right) {
|
|
268
|
+
count += this.countClauses(expression.right);
|
|
269
|
+
}
|
|
270
|
+
return count;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Validate that string values do not exceed maximum length
|
|
275
|
+
* Security: Enhanced to prevent type confusion attacks via arrays/objects
|
|
276
|
+
*
|
|
277
|
+
* @private
|
|
278
|
+
* @param expression - The query expression to validate
|
|
279
|
+
*/
|
|
280
|
+
private validateValueLengths(expression: QueryExpression): void {
|
|
281
|
+
if (expression.type === 'comparison') {
|
|
282
|
+
const { value } = expression;
|
|
283
|
+
|
|
284
|
+
// Check string values
|
|
285
|
+
if (
|
|
286
|
+
typeof value === 'string' &&
|
|
287
|
+
value.length > this.options.maxValueLength
|
|
288
|
+
) {
|
|
289
|
+
throw new QuerySecurityError(
|
|
290
|
+
`Query contains a string value that exceeds maximum length of ${this.options.maxValueLength} characters`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Security fix: Enhanced array validation to prevent bypass
|
|
295
|
+
if (Array.isArray(value)) {
|
|
296
|
+
if (value.length > 100) {
|
|
297
|
+
// Limit array size
|
|
298
|
+
throw new QuerySecurityError('Array values cannot exceed 100 items');
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
for (const item of value) {
|
|
302
|
+
if (
|
|
303
|
+
typeof item === 'string' &&
|
|
304
|
+
item.length > this.options.maxValueLength
|
|
305
|
+
) {
|
|
306
|
+
throw new QuerySecurityError(
|
|
307
|
+
`Query contains a string value in array that exceeds maximum length of ${this.options.maxValueLength} characters`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Security fix: Prevent object injection in arrays
|
|
312
|
+
if (typeof item === 'object' && item !== null) {
|
|
313
|
+
throw new QuerySecurityError(
|
|
314
|
+
'Object values are not allowed in arrays'
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Security fix: Prevent object values entirely
|
|
321
|
+
if (
|
|
322
|
+
typeof value === 'object' &&
|
|
323
|
+
value !== null &&
|
|
324
|
+
!Array.isArray(value)
|
|
325
|
+
) {
|
|
326
|
+
throw new QuerySecurityError('Object values are not allowed');
|
|
327
|
+
}
|
|
328
|
+
} else {
|
|
329
|
+
this.validateValueLengths(expression.left);
|
|
330
|
+
if (expression.right) {
|
|
331
|
+
this.validateValueLengths(expression.right);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Sanitize wildcard patterns in LIKE queries to prevent regex DoS
|
|
338
|
+
* Security: Enhanced to prevent ReDoS attacks via catastrophic backtracking
|
|
339
|
+
*
|
|
340
|
+
* @private
|
|
341
|
+
* @param expression - The query expression to sanitize
|
|
342
|
+
*/
|
|
343
|
+
private sanitizeWildcards(expression: QueryExpression): void {
|
|
344
|
+
if (expression.type === 'comparison') {
|
|
345
|
+
const { operator, value } = expression;
|
|
346
|
+
|
|
347
|
+
// Only sanitize LIKE operators with string values
|
|
348
|
+
if (operator === 'LIKE' && typeof value === 'string') {
|
|
349
|
+
// Security fix: Count wildcards to prevent ReDoS
|
|
350
|
+
const wildcardCount = (value.match(/[*?]/g) || []).length;
|
|
351
|
+
if (wildcardCount > 10) {
|
|
352
|
+
throw new QuerySecurityError('Excessive wildcard usage');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Security fix: Prevent alternating patterns that cause catastrophic backtracking
|
|
356
|
+
// Pattern like "*a*b*c*d*e*f" (alternating * and non-* chars)
|
|
357
|
+
if (/(\*[^*]+){5,}/.test(value)) {
|
|
358
|
+
throw new QuerySecurityError('Complex wildcard patterns not allowed');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Enhanced sanitization: limit consecutive wildcards
|
|
362
|
+
const sanitized = value
|
|
363
|
+
.replace(/\*{2,}/g, '*') // Limit consecutive asterisks
|
|
364
|
+
.replace(/\?{2,}/g, '?'); // Limit consecutive question marks
|
|
365
|
+
(expression as IComparisonExpression).value = sanitized;
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
this.sanitizeWildcards(expression.left);
|
|
369
|
+
if (expression.right) {
|
|
370
|
+
this.sanitizeWildcards(expression.right);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Collect all field names used in the query
|
|
377
|
+
*
|
|
378
|
+
* @private
|
|
379
|
+
* @param expression - The query expression to collect fields from
|
|
380
|
+
* @param fieldSet - Set to store the collected field names
|
|
381
|
+
*/
|
|
382
|
+
private collectFields(
|
|
383
|
+
expression: QueryExpression,
|
|
384
|
+
fieldSet: Set<string>
|
|
385
|
+
): void {
|
|
386
|
+
if (expression.type === 'comparison') {
|
|
387
|
+
fieldSet.add(expression.field);
|
|
388
|
+
} else {
|
|
389
|
+
this.collectFields(expression.left, fieldSet);
|
|
390
|
+
if (expression.right) {
|
|
391
|
+
this.collectFields(expression.right, fieldSet);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|