@@ -966,6 +966,9 @@ protected function buildComputedColumns(Builder $query, array &$selects): void
966966 throw new \InvalidArgumentException ("Invalid computed column ' {$ name }': {$ errors }" );
967967 }
968968
969+ // Extract and create auto-joins for relationship paths in the expression
970+ $ this ->createJoinsForComputedColumn ($ query , $ expression , $ tableName );
971+
969972 // Resolve column references in the expression to use proper table aliases
970973 $ resolvedExpression = $ this ->resolveComputedColumnReferences ($ expression , $ tableName );
971974
@@ -1158,3 +1161,73 @@ protected function validateQueryConfig(): void
11581161 // }
11591162 }
11601163}
1164+
1165+ /**
1166+ * Extract relationship paths from a computed column expression and create necessary joins.
1167+ *
1168+ * This method parses the expression to find column references like "asset.financials.monthly_hire_revenue"
1169+ * and ensures that all necessary auto-joins are created for the relationship paths.
1170+ */
1171+ protected function createJoinsForComputedColumn (Builder $ query , string $ expression , string $ rootTable ): void
1172+ {
1173+ // First, expand any computed column references in the expression
1174+ $ computedColumns = $ this ->queryConfig ['computed_columns ' ] ?? [];
1175+ $ computedColumnMap = [];
1176+ foreach ($ computedColumns as $ col ) {
1177+ $ computedColumnMap [$ col ['name ' ]] = $ col ['expression ' ];
1178+ }
1179+
1180+ // Recursively expand computed column references
1181+ $ maxDepth = 10 ;
1182+ $ depth = 0 ;
1183+ $ expandedExpression = $ expression ;
1184+ while ($ depth < $ maxDepth ) {
1185+ $ changed = false ;
1186+ foreach ($ computedColumnMap as $ name => $ expr ) {
1187+ if (preg_match ('/\b ' . preg_quote ($ name , '/ ' ) . '\b/ ' , $ expandedExpression )) {
1188+ $ expandedExpression = preg_replace ('/\b ' . preg_quote ($ name , '/ ' ) . '\b/ ' , '( ' . $ expr . ') ' , $ expandedExpression );
1189+ $ changed = true ;
1190+ }
1191+ }
1192+ if (!$ changed ) {
1193+ break ;
1194+ }
1195+ $ depth ++;
1196+ }
1197+
1198+ // Now extract all column references that look like relationship paths (e.g., "asset.financials.monthly_hire_revenue")
1199+ // We need to match patterns like: word.word.word (but not inside string literals)
1200+
1201+ // First, remove string literals to avoid matching inside them
1202+ $ cleanedExpression = preg_replace ("/'[^']*'/ " , '' , $ expandedExpression );
1203+ $ cleanedExpression = preg_replace ('/"[^"]*"/ ' , '' , $ cleanedExpression );
1204+
1205+ // Match column references with dots (relationship paths)
1206+ preg_match_all ('/\b([a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*)\b/i ' , $ cleanedExpression , $ matches );
1207+
1208+ if (empty ($ matches [1 ])) {
1209+ return ;
1210+ }
1211+
1212+ // Extract unique relationship paths (everything except the final column name)
1213+ $ relationshipPaths = [];
1214+ foreach ($ matches [1 ] as $ columnPath ) {
1215+ $ parts = explode ('. ' , $ columnPath );
1216+ if (count ($ parts ) >= 2 ) {
1217+ // Remove the last part (column name) to get the relationship path
1218+ array_pop ($ parts );
1219+ $ relationshipPath = implode ('. ' , $ parts );
1220+ $ relationshipPaths [$ relationshipPath ] = true ;
1221+ }
1222+ }
1223+
1224+ // Create auto-joins for each unique relationship path
1225+ foreach (array_keys ($ relationshipPaths ) as $ path ) {
1226+ try {
1227+ $ this ->applyAutoJoinPath ($ query , $ rootTable , $ path );
1228+ } catch (\Exception $ e ) {
1229+ // If the join fails, it might not be a valid relationship path
1230+ // Just continue - the error will be caught later when resolving the column
1231+ }
1232+ }
1233+ }
0 commit comments