@@ -614,6 +614,7 @@ export class SqlCompilerImpl implements SqlCompiler {
614614
615615 // Special handling for array access fields - we need to create field extraction
616616 // expressions to flatten nested array elements to root level
617+ log ( `arrayAccessFields: ` , arrayAccessFields )
617618 if ( arrayAccessFields . length > 0 && ! isJoinQuery ) {
618619 const arrayFieldsProject : Record < string , any > = { } ;
619620
@@ -697,120 +698,10 @@ export class SqlCompilerImpl implements SqlCompiler {
697698 log ( 'JOIN query from:' , JSON . stringify ( ast . from , null , 2 ) ) ;
698699 log ( 'JOIN query where:' , JSON . stringify ( ast . where , null , 2 ) ) ;
699700
700- // Add the $lookup stages we've already configured
701- const lookupStages = aggregateCommand . pipeline . filter ( ( stage ) => '$lookup' in stage ) ;
702- log ( 'Current $lookup stages:' , JSON . stringify ( lookupStages , null , 2 ) ) ;
703-
704701 // For JOIN queries, we need to handle the projection differently to flatten the results
705702 // First, we'll create a projection that preserves the table aliases in the pipeline
706703 const renamedFieldsProject : Record < string , any > = { } ;
707704
708- // Process each column and create a flattened naming structure
709- for ( const column of ast . columns ) {
710- if ( typeof column === 'object' && column . expr ) {
711- const table = column . expr . table ;
712- const field = column . expr . column ;
713-
714- // The field name that will be used in the output
715- // If there's an alias, use it, otherwise use just the field name
716- const outputName = column . as || field ;
717-
718- if ( table ) {
719- // Different handling based on whether it's from the main table or a joined table
720- if ( this . currentTableAliases . has ( table ) ) {
721- const isMainTable = table === ast . from [ 0 ] . as ;
722-
723- if ( isMainTable ) {
724- // Fields from the main table can be accessed directly
725- // Handle field names with table prefixes in the field itself (e.g., m.title in field)
726- if ( field . includes ( '.' ) ) {
727- const fieldParts = field . split ( '.' ) ;
728- const actualField = fieldParts [ fieldParts . length - 1 ] ;
729- renamedFieldsProject [ outputName ] = `$${ fieldParts [ 1 ] } ` ; // Use main table field
730- log ( `$Main table field with prefix: ${ field } -> ${ outputName } = $${ fieldParts [ 1 ] } ` ) ;
731- } else {
732- // Simple field from main table
733- renamedFieldsProject [ outputName ] = `$${ field } ` ;
734- log ( `$Main table field mapping: ${ outputName } = $${ field } ` ) ;
735- }
736-
737- // If the output name has a table prefix and no alias was explicitly provided,
738- // we also add a version without the prefix for compatibility
739- if ( outputName . includes ( '.' ) && ! column . as ) {
740- const outParts = outputName . split ( '.' ) ;
741- const cleanName = outParts [ outParts . length - 1 ] ;
742- renamedFieldsProject [ cleanName ] = `$${ field } ` ;
743- log ( `$Added clean field name for compatibility: ${ cleanName } = $${ field } ` ) ;
744- }
745- } else {
746- // Fields from joined tables need the alias prefix
747- if ( field . includes ( '.' ) ) {
748- const fieldParts = field . split ( '.' ) ;
749- const actualField = fieldParts [ fieldParts . length - 1 ] ;
750- renamedFieldsProject [ outputName ] = `$${ table } .${ actualField } ` ;
751- log ( `$Joined table field with prefix: ${ field } -> ${ outputName } = $${ table } .${ actualField } ` ) ;
752- } else {
753- renamedFieldsProject [ outputName ] = `$${ table } .${ field } ` ;
754- log ( `$Joined table field mapping: ${ outputName } = $${ table } .${ field } ` ) ;
755- }
756- }
757- } else {
758- // Not a recognized alias, but still has a table prefix
759- renamedFieldsProject [ outputName ] = `$${ table } .${ field } ` ;
760- }
761- } else if ( column . expr . type === 'column_ref' && column . expr . column ) {
762- // Handle case where column is a direct column reference without table
763- // For JOINS, we still need to know which table it belongs to
764-
765- // If no table specified, try to determine which table it belongs to
766- // For simplicity, assume it's from the main table
767- renamedFieldsProject [ outputName ] = `$${ column . expr . column } ` ;
768- log ( `Simple column mapping: ${ outputName } = $${ column . expr . column } ` ) ;
769- } else {
770- // No table prefix specified, assume it's from the main table
771- renamedFieldsProject [ outputName ] = `$${ field } ` ;
772- }
773- } else if (
774- column === '*' ||
775- ( typeof column === 'object' && column . expr && column . expr . type === 'star' )
776- ) {
777- // For SELECT *, we need to merge all fields from all tables
778- // This is a more complex case that needs a special projection approach
779-
780- // For star queries in JOIN context, we need to use MongoDB's $mergeObjects
781- // to bring fields from joined documents up to the top level
782-
783- // First, create a base object with all fields from the main table
784- renamedFieldsProject [ 'mainFields' ] = '$$ROOT' ;
785-
786- // Then, for each joined table, create a merge field
787- for ( let i = 1 ; i < ast . from . length ; i ++ ) {
788- const joinedTable = ast . from [ i ] . as || this . extractTableName ( ast . from [ i ] ) ;
789- // Use all fields from the joined table, directly available at the top level
790- // This preserves their original field names
791- renamedFieldsProject [ joinedTable ] = `$${ joinedTable } ` ;
792- }
793-
794- // Use MongoDB's $replaceRoot to promote all fields to the root level
795- // This will be a separate stage after the projection
796- const mergeObjects = [ '$mainFields' ] ;
797- for ( let i = 1 ; i < ast . from . length ; i ++ ) {
798- const joinedTable = ast . from [ i ] . as || this . extractTableName ( ast . from [ i ] ) ;
799- mergeObjects . push ( `$${ joinedTable } ` ) ;
800- }
801-
802- // We will add the $replaceRoot stage after this projection
803- aggregateCommand . pipeline . push ( {
804- $replaceRoot : {
805- newRoot : {
806- $mergeObjects : mergeObjects ,
807- } ,
808- } ,
809- } ) ;
810-
811- log ( 'Added $replaceRoot stage for merging joined tables in SELECT *' ) ;
812- }
813- }
814705
815706 // Add a final stage to correctly handle JOIN results
816707 // We need the column values to be accessible directly at the top level,
@@ -1478,23 +1369,25 @@ export class SqlCompilerImpl implements SqlCompiler {
14781369 if ( 'column' in column . expr && column . expr . column ) {
14791370 // First check if the column has a table reference that might be an alias
14801371 let fieldName ;
1481- if ( column . expr . table && column . expr . column ) {
1372+ if ( column . expr . table && column . expr . column && this . currentTableAliases . has ( column . expr . table ) ) {
14821373 fieldName = `${ column . expr . table } .${ column . expr . column } ` ;
14831374 log ( `$Using table-prefixed field in projection: ${ fieldName } ` ) ;
14841375 } else {
14851376 fieldName = this . processFieldName ( column . expr . column ) ;
14861377 }
1487- fieldsToProject . push ( fieldName ) ;
1378+ const outputName = this . extractOutputField ( fieldName , column . as )
1379+ fieldsToProject . push ( outputName ) ;
14881380 } else if ( column . expr . type === 'column_ref' && column . expr . column ) {
14891381 // Handle column_ref with possible table
14901382 let fieldName ;
1491- if ( column . expr . table && column . expr . column ) {
1383+ if ( column . expr . table && column . expr . column && this . currentTableAliases . has ( column . expr . table ) ) {
14921384 fieldName = `${ column . expr . table } .${ column . expr . column } ` ;
14931385 log ( `Using table-prefixed field in column_ref projection: ${ fieldName } ` ) ;
14941386 } else {
14951387 fieldName = this . processFieldName ( column . expr . column ) ;
14961388 }
1497- fieldsToProject . push ( fieldName ) ;
1389+ const outputName = this . extractOutputField ( fieldName , column . as )
1390+ fieldsToProject . push ( outputName ) ;
14981391 } else if (
14991392 column . expr . type === 'binary_expr' &&
15001393 column . expr . operator === '.' &&
@@ -1513,7 +1406,8 @@ export class SqlCompilerImpl implements SqlCompiler {
15131406 }
15141407 if ( fieldName && column . expr . right . column ) {
15151408 fieldName += '.' + column . expr . right . column ;
1516- fieldsToProject . push ( fieldName ) ;
1409+ const outputName = this . extractOutputField ( fieldName , column . as )
1410+ fieldsToProject . push ( outputName ) ;
15171411 }
15181412 }
15191413 } else if ( 'type' in column && column . type === 'column_ref' && column . column ) {
@@ -1525,17 +1419,19 @@ export class SqlCompilerImpl implements SqlCompiler {
15251419 } else {
15261420 fieldName = this . processFieldName ( column . column ) ;
15271421 }
1528- fieldsToProject . push ( fieldName ) ;
1422+ const outputName = this . extractOutputField ( fieldName , column . as )
1423+ fieldsToProject . push ( outputName ) ;
15291424 } else if ( 'column' in column ) {
15301425 // Handle direct column with possible table
15311426 let fieldName ;
1532- if ( column . table && column . column ) {
1427+ if ( column . table && column . column && this . currentTableAliases . has ( column . table ) ) {
15331428 fieldName = `${ column . table } .${ column . column } ` ;
15341429 log ( `Using table-prefixed field in direct column: ${ fieldName } ` ) ;
15351430 } else {
15361431 fieldName = this . processFieldName ( column . column ) ;
15371432 }
1538- fieldsToProject . push ( fieldName ) ;
1433+ const outputName = this . extractOutputField ( fieldName , column . as )
1434+ fieldsToProject . push ( outputName ) ;
15391435 }
15401436 } else if ( typeof column === 'string' ) {
15411437 const fieldName = this . processFieldName ( column ) ;
@@ -1618,6 +1514,7 @@ export class SqlCompilerImpl implements SqlCompiler {
16181514 }
16191515 } else {
16201516 // Regular top-level field
1517+ log ( `Adding ${ fieldName } to projection` )
16211518 projection [ fieldName ] = 1 ;
16221519 }
16231520 } ) ;
0 commit comments