Skip to content

Commit bd8a011

Browse files
committed
a little refactor
1 parent 33a71ef commit bd8a011

1 file changed

Lines changed: 140 additions & 149 deletions

File tree

packages/lib/src/compiler.ts

Lines changed: 140 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,135 @@ export class SqlCompilerImpl implements SqlCompiler {
5454
return result;
5555
}
5656

57+
/**
58+
* Extract limit and offset values from an AST
59+
*/
60+
private extractLimitOffset(ast: any): { limit?: number; skip?: number } {
61+
const result: { limit?: number; skip?: number } = {};
62+
63+
if (!ast.limit) return result;
64+
65+
log('Extracting limit/offset from AST:', JSON.stringify(ast.limit, null, 2));
66+
67+
if (
68+
typeof ast.limit === 'object' &&
69+
'value' in ast.limit &&
70+
!Array.isArray(ast.limit.value)
71+
) {
72+
// Standard LIMIT format (without OFFSET)
73+
result.limit = Number(ast.limit.value);
74+
} else if (
75+
typeof ast.limit === 'object' &&
76+
'seperator' in ast.limit &&
77+
Array.isArray(ast.limit.value) &&
78+
ast.limit.value.length > 0
79+
) {
80+
// Handle PostgreSQL style LIMIT [OFFSET]
81+
if (ast.limit.seperator === 'offset') {
82+
if (ast.limit.value.length === 1) {
83+
// Only OFFSET specified
84+
result.skip = Number(ast.limit.value[0].value);
85+
} else if (ast.limit.value.length >= 2) {
86+
// Both LIMIT and OFFSET
87+
result.limit = Number(ast.limit.value[0].value);
88+
result.skip = Number(ast.limit.value[1].value);
89+
}
90+
} else {
91+
// Just LIMIT
92+
result.limit = Number(ast.limit.value[0].value);
93+
}
94+
}
95+
96+
return result;
97+
}
98+
99+
/**
100+
* Extract field path from a column object
101+
*/
102+
private extractFieldPath(column: any): string {
103+
let fieldPath = '';
104+
105+
if (typeof column === 'string') {
106+
return this.processFieldName(column);
107+
}
108+
109+
if (typeof column !== 'object') return '';
110+
111+
// Extract field path from different column formats
112+
if ('expr' in column && column.expr) {
113+
// Special case for specs.size.diagonal where it appears as schema: specs, column: size.diagonal
114+
if (column.expr.schema && column.expr.column && column.expr.column.includes('.')) {
115+
fieldPath = `${column.expr.schema}.${column.expr.column}`;
116+
log(`Found multi-level nested field with schema: ${fieldPath}`);
117+
} else if ('column' in column.expr && column.expr.column) {
118+
fieldPath = this.processFieldName(column.expr.column);
119+
} else if (column.expr.type === 'column_ref' && column.expr.column) {
120+
// Also check for schema in column_ref
121+
if (column.expr.schema && column.expr.column.includes('.')) {
122+
fieldPath = `${column.expr.schema}.${column.expr.column}`;
123+
log(`Found multi-level nested field in column_ref: ${fieldPath}`);
124+
} else {
125+
fieldPath = this.processFieldName(column.expr.column);
126+
}
127+
} else if (column.expr.type === 'binary_expr' && column.expr.operator === '.') {
128+
// This case should have been handled by handleNestedFieldExpressions
129+
// But as a fallback, try to extract the path
130+
log(
131+
'Binary expression in projection that should have been processed:',
132+
JSON.stringify(column.expr, null, 2)
133+
);
134+
135+
if (
136+
column.expr.left &&
137+
column.expr.left.column &&
138+
column.expr.right &&
139+
column.expr.right.column
140+
) {
141+
fieldPath = `${column.expr.left.column}.${column.expr.right.column}`;
142+
}
143+
}
144+
} else if ('type' in column && column.type === 'column_ref' && column.column) {
145+
// Check for schema in direct column_ref
146+
if (column.schema && column.column.includes('.')) {
147+
fieldPath = `${column.schema}.${column.column}`;
148+
log(`Found multi-level nested field in column type: ${fieldPath}`);
149+
} else {
150+
fieldPath = this.processFieldName(column.column);
151+
}
152+
} else if ('column' in column) {
153+
// Check for schema in simple column
154+
if (column.schema && column.column.includes('.')) {
155+
fieldPath = `${column.schema}.${column.column}`;
156+
log(`Found multi-level nested field in direct column: ${fieldPath}`);
157+
} else {
158+
fieldPath = this.processFieldName(column.column);
159+
}
160+
}
161+
162+
return fieldPath;
163+
}
164+
165+
/**
166+
* Add a field to a MongoDB projection object
167+
*/
168+
private addFieldToProjection(projection: Record<string, any>, fieldPath: string): void {
169+
if (!fieldPath) return;
170+
171+
log(`Processing field path for projection: ${fieldPath}`);
172+
173+
if (fieldPath.includes('.')) {
174+
// For nested fields, create a name with underscores instead of dots
175+
const fieldNameWithUnderscores = fieldPath.replace(/\./g, '_');
176+
177+
// Add to projection with the path-based name
178+
projection[fieldNameWithUnderscores] = `$${fieldPath}`;
179+
log(`Added nested field to projection: ${fieldNameWithUnderscores} = $${fieldPath}`);
180+
} else {
181+
// Regular field
182+
projection[fieldPath] = 1;
183+
}
184+
}
185+
57186
/**
58187
* Compile a SELECT statement into a MongoDB FIND command or AGGREGATE command
59188
*/
@@ -140,37 +269,12 @@ export class SqlCompilerImpl implements SqlCompiler {
140269
}
141270

142271
// Handle LIMIT and OFFSET
143-
if (ast.limit) {
144-
log('Limit found in AST:', JSON.stringify(ast.limit, null, 2));
145-
if (
146-
typeof ast.limit === 'object' &&
147-
'value' in ast.limit &&
148-
!Array.isArray(ast.limit.value)
149-
) {
150-
// Standard LIMIT format (without OFFSET)
151-
aggregateCommand.pipeline.push({ $limit: Number(ast.limit.value) });
152-
} else if (
153-
typeof ast.limit === 'object' &&
154-
'seperator' in ast.limit &&
155-
Array.isArray(ast.limit.value)
156-
) {
157-
// Handle PostgreSQL style LIMIT [OFFSET]
158-
if (ast.limit.value.length > 0) {
159-
if (ast.limit.seperator === 'offset') {
160-
if (ast.limit.value.length === 1) {
161-
// Only OFFSET specified
162-
aggregateCommand.pipeline.push({ $skip: Number(ast.limit.value[0].value) });
163-
} else if (ast.limit.value.length >= 2) {
164-
// Both LIMIT and OFFSET
165-
aggregateCommand.pipeline.push({ $skip: Number(ast.limit.value[1].value) });
166-
aggregateCommand.pipeline.push({ $limit: Number(ast.limit.value[0].value) });
167-
}
168-
} else {
169-
// Just LIMIT
170-
aggregateCommand.pipeline.push({ $limit: Number(ast.limit.value[0].value) });
171-
}
172-
}
173-
}
272+
const { limit, skip } = this.extractLimitOffset(ast);
273+
if (skip !== undefined) {
274+
aggregateCommand.pipeline.push({ $skip: skip });
275+
}
276+
if (limit !== undefined) {
277+
aggregateCommand.pipeline.push({ $limit: limit });
174278
}
175279

176280
// Add projection for SELECT columns
@@ -187,92 +291,8 @@ export class SqlCompilerImpl implements SqlCompiler {
187291
continue;
188292
}
189293

190-
if (typeof column === 'object') {
191-
let fieldPath = '';
192-
193-
// Extract field path from different column formats
194-
if ('expr' in column && column.expr) {
195-
// Special case for specs.size.diagonal where it appears as schema: specs, column: size.diagonal
196-
if (column.expr.schema && column.expr.column && column.expr.column.includes('.')) {
197-
fieldPath = `${column.expr.schema}.${column.expr.column}`;
198-
log(`Found multi-level nested field with schema: ${fieldPath}`);
199-
} else if ('column' in column.expr && column.expr.column) {
200-
fieldPath = this.processFieldName(column.expr.column);
201-
} else if (column.expr.type === 'column_ref' && column.expr.column) {
202-
// Also check for schema in column_ref
203-
if (column.expr.schema && column.expr.column.includes('.')) {
204-
fieldPath = `${column.expr.schema}.${column.expr.column}`;
205-
log(`Found multi-level nested field in column_ref: ${fieldPath}`);
206-
} else {
207-
fieldPath = this.processFieldName(column.expr.column);
208-
}
209-
} else if (column.expr.type === 'binary_expr' && column.expr.operator === '.') {
210-
// This case should have been handled by handleNestedFieldExpressions
211-
// But as a fallback, try to extract the path
212-
log(
213-
'Binary expression in projection that should have been processed:',
214-
JSON.stringify(column.expr, null, 2)
215-
);
216-
217-
if (
218-
column.expr.left &&
219-
column.expr.left.column &&
220-
column.expr.right &&
221-
column.expr.right.column
222-
) {
223-
fieldPath = `${column.expr.left.column}.${column.expr.right.column}`;
224-
}
225-
}
226-
} else if ('type' in column && column.type === 'column_ref' && column.column) {
227-
// Check for schema in direct column_ref
228-
if (column.schema && column.column.includes('.')) {
229-
fieldPath = `${column.schema}.${column.column}`;
230-
log(`Found multi-level nested field in column type: ${fieldPath}`);
231-
} else {
232-
fieldPath = this.processFieldName(column.column);
233-
}
234-
} else if ('column' in column) {
235-
// Check for schema in simple column
236-
if (column.schema && column.column.includes('.')) {
237-
fieldPath = `${column.schema}.${column.column}`;
238-
log(`Found multi-level nested field in direct column: ${fieldPath}`);
239-
} else {
240-
fieldPath = this.processFieldName(column.column);
241-
}
242-
}
243-
244-
// If we found a field path, add it to the projection
245-
if (fieldPath) {
246-
log(`Processing field path for projection: ${fieldPath}`);
247-
248-
if (fieldPath.includes('.')) {
249-
// For nested fields, create a name with underscores instead of dots
250-
const fieldNameWithUnderscores = fieldPath.replace(/\./g, '_');
251-
252-
// Add to projection with the path-based name
253-
projection[fieldNameWithUnderscores] = `$${fieldPath}`;
254-
log(
255-
`Added nested field to projection: ${fieldNameWithUnderscores} = $${fieldPath}`
256-
);
257-
} else {
258-
// Regular field
259-
projection[fieldPath] = 1;
260-
}
261-
}
262-
} else if (typeof column === 'string') {
263-
const fieldPath = this.processFieldName(column);
264-
265-
if (fieldPath.includes('.')) {
266-
// For nested fields, create a name with underscores instead of dots
267-
const fieldNameWithUnderscores = fieldPath.replace(/\./g, '_');
268-
269-
// Add to projection with the path-based name
270-
projection[fieldNameWithUnderscores] = `$${fieldPath}`;
271-
} else {
272-
// Regular field
273-
projection[fieldPath] = 1;
274-
}
275-
}
294+
const fieldPath = this.extractFieldPath(column);
295+
this.addFieldToProjection(projection, fieldPath);
276296
}
277297

278298
// Add the projection stage if we have fields to project
@@ -294,38 +314,9 @@ export class SqlCompilerImpl implements SqlCompiler {
294314
};
295315

296316
// Handle LIMIT and OFFSET
297-
if (ast.limit) {
298-
log('Limit found in AST:', JSON.stringify(ast.limit, null, 2));
299-
if (
300-
typeof ast.limit === 'object' &&
301-
'value' in ast.limit &&
302-
!Array.isArray(ast.limit.value)
303-
) {
304-
// Standard PostgreSQL LIMIT format (without OFFSET)
305-
findCommand.limit = Number(ast.limit.value);
306-
} else if (
307-
typeof ast.limit === 'object' &&
308-
'seperator' in ast.limit &&
309-
Array.isArray(ast.limit.value)
310-
) {
311-
// Handle PostgreSQL style LIMIT [OFFSET]
312-
if (ast.limit.value.length > 0) {
313-
if (ast.limit.seperator === 'offset') {
314-
if (ast.limit.value.length === 1) {
315-
// Only OFFSET specified
316-
findCommand.skip = Number(ast.limit.value[0].value);
317-
} else if (ast.limit.value.length >= 2) {
318-
// Both LIMIT and OFFSET specified
319-
findCommand.limit = Number(ast.limit.value[0].value);
320-
findCommand.skip = Number(ast.limit.value[1].value);
321-
}
322-
} else {
323-
// Regular LIMIT without OFFSET
324-
findCommand.limit = Number(ast.limit.value[0].value);
325-
}
326-
}
327-
}
328-
}
317+
const { limit, skip } = this.extractLimitOffset(ast);
318+
if (limit !== undefined) findCommand.limit = limit;
319+
if (skip !== undefined) findCommand.skip = skip;
329320

330321
// Handle ORDER BY
331322
if (ast.orderby) {

0 commit comments

Comments
 (0)