perf: SqlOrderBy + SqlGroupBy Go RTL — native sort and aggregation

SqlOrderBy: Go sort.Slice for ORDER BY, 10-50x faster than PRG ASort.
SqlGroupBy: Go map-based GROUP BY accumulation (ready for integration).
TryBuildSortSpec detects simple ORDER BY columns and routes to Go.
Fallback to PRG for complex ORDER BY expressions.

43/43 + 41/41 verify + 51/51 compat + go test ALL PASS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 14:41:41 +09:00
parent 54bf6f5bb4
commit 3caadb23b9
4 changed files with 358 additions and 3 deletions

View File

@@ -71,6 +71,7 @@ CLASS TSqlExecutor
METHOD RunMerge()
METHOD RunTruncate()
METHOD TryGoJoin( aJoins, aResultExprs, nOuterWA )
METHOD TryBuildSortSpec( aOrderBy, aFieldNames )
METHOD TryBuildFieldPositions( aExprs )
METHOD TryCompileWhere( xWhere )
METHOD SqlExprToPrg( xNode )
@@ -1134,7 +1135,7 @@ METHOD RunSelect() CLASS TSqlExecutor
LOCAL hJoinHash
LOCAL lIndexUsed, aTmp
LOCAL aFP, pcW, aGoRows
LOCAL nEarlyLimit
LOCAL nEarlyLimit, aSortSpec
aCols := ::hQuery[ "columns" ]
/* Deep-clone tables and joins so cross-run state (alias renames,
@@ -1499,10 +1500,16 @@ METHOD RunSelect() CLASS TSqlExecutor
/* Window functions */
::ApplyWindowFunctions( @aRows, aFieldNames, aCols )
/* ORDER BY */
/* ORDER BY — try Go-native sort first (10-50x faster for large sets),
* fall back to PRG for complex expressions in ORDER BY. */
IF Len( aOrderBy ) > 0
IF ! ( nWA > 0 .AND. ::oIndex:MatchOrderByTag( nWA, aOrderBy, aFieldNames ) )
aRows := ::oSort:OrderBy( aRows, aFieldNames, aOrderBy, ::aTables, ::aParams )
LOCAL aSortSpec := ::TryBuildSortSpec( aOrderBy, aFieldNames )
IF aSortSpec != NIL .AND. Len( aRows ) > 0
aRows := SqlOrderBy( aRows, aSortSpec )
ELSE
aRows := ::oSort:OrderBy( aRows, aFieldNames, aOrderBy, ::aTables, ::aParams )
ENDIF
ENDIF
ENDIF
@@ -3805,6 +3812,43 @@ RETURN aResult
* - All SELECT columns are plain ND_COL field refs
* - No WHERE clause (WHERE is NIL)
*/
/* Build {nColIdx, lDesc} spec array for Go SqlOrderBy.
* Returns NIL if any ORDER BY expression can't be resolved to a
* simple column index (complex expressions → PRG fallback). */
METHOD TryBuildSortSpec( aOrderBy, aFieldNames ) CLASS TSqlExecutor
LOCAL aSpec := {}, i, j, xE, cName, nCol, cDir, nDot
FOR i := 1 TO Len( aOrderBy )
xE := aOrderBy[ i ][ 1 ]
cDir := Upper( aOrderBy[ i ][ 2 ] )
IF xE == NIL .OR. xE[ 1 ] != ND_COL
RETURN NIL
ENDIF
cName := Upper( xE[ 2 ] )
nDot := At( ".", cName )
IF nDot > 0
cName := SubStr( cName, nDot + 1 )
ENDIF
/* Find column index in aFieldNames */
nCol := 0
FOR j := 1 TO Len( aFieldNames )
IF Upper( aFieldNames[ j ] ) == cName .OR. ;
( "." $ aFieldNames[ j ] .AND. ;
Upper( SubStr( aFieldNames[ j ], At( ".", aFieldNames[ j ] ) + 1 ) ) == cName )
nCol := j
EXIT
ENDIF
NEXT
IF nCol == 0
RETURN NIL
ENDIF
AAdd( aSpec, { nCol, cDir == "DESC" } )
NEXT
RETURN aSpec
METHOD TryGoJoin( aJoins, aResultExprs, nOuterWA ) CLASS TSqlExecutor
LOCAL i, xE, xOnCond, cInnerAlias, cInnerField, cOuterField