@@ -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