diff --git a/_FiveSql2/src/FiveSqlCls.prg b/_FiveSql2/src/FiveSqlCls.prg index d42a052..62b0a4d 100644 --- a/_FiveSql2/src/FiveSqlCls.prg +++ b/_FiveSql2/src/FiveSqlCls.prg @@ -12,14 +12,32 @@ #include "FiveSqlDef.ch" /* - * five_SQL( cSQL [, aParams ] ) --> aResult + * five_SQL( cSQL [, aParams ] [, bBlock ] ) --> aResult | NIL * * Execute a SQL statement against the current DBF workareas. - * Returns { aFieldNames, aRows } on success, - * { {"__error__"}, {{nCode, cMsg, cSQL}} } on failure. + * + * Two return modes: + * 1. Without bBlock: returns { aFieldNames, aRows } on success, + * or { {"__error__"}, {{nCode, cMsg, cSQL}} } on failure. + * 2. With bBlock: streams matching rows into the block, spreading + * the SELECT list as positional params. Returns NIL. + * Block mode is the high-performance path — no + * intermediate row array is built. + * + * Block mode only fires for simple SELECT queries that the fast path + * already supports (single table, no JOIN, no GROUP BY, no aggregates, + * all projections are plain column refs). Complex queries fall back to + * array mode even when a block is supplied, and the block is invoked + * once per row after the fact as a compatibility layer. + * + * Accepts both parameter positions so existing callers still work: + * five_SQL( cSQL ) + * five_SQL( cSQL, aParams ) + * five_SQL( cSQL, aParams, bBlock ) + * five_SQL( cSQL, NIL, bBlock ) */ -FUNCTION five_SQL( cSQL, aParams ) +FUNCTION five_SQL( cSQL, aParams, bBlock ) LOCAL oSql := TFiveSQL():New( aParams ) -RETURN oSql:Execute( cSQL ) +RETURN oSql:Execute( cSQL, bBlock ) diff --git a/_FiveSql2/src/TFiveSQL.prg b/_FiveSql2/src/TFiveSQL.prg index 5f7b2db..1f4ccab 100644 --- a/_FiveSql2/src/TFiveSQL.prg +++ b/_FiveSql2/src/TFiveSQL.prg @@ -22,7 +22,7 @@ CLASS TFiveSQL DATA aParams INIT {} METHOD New( aParams ) CONSTRUCTOR - METHOD Execute( cSQL ) + METHOD Execute( cSQL, bBlock ) METHOD ExecuteWith( cSQL, aParams ) ENDCLASS @@ -37,7 +37,7 @@ METHOD New( aParams ) CLASS TFiveSQL RETURN SELF -METHOD Execute( cSQL ) CLASS TFiveSQL +METHOD Execute( cSQL, bBlock ) CLASS TFiveSQL LOCAL aTokens, hQuery, aResult @@ -54,6 +54,7 @@ METHOD Execute( cSQL ) CLASS TFiveSQL ENDIF ::oExec := TSqlExecutor():New( hQuery, ::aParams ) + ::oExec:bRowBlock := bBlock aResult := ::oExec:Run() RETURN aResult diff --git a/_FiveSql2/src/TSqlExecutor.prg b/_FiveSql2/src/TSqlExecutor.prg index 4a3c74c..7ce5b7c 100644 --- a/_FiveSql2/src/TSqlExecutor.prg +++ b/_FiveSql2/src/TSqlExecutor.prg @@ -33,6 +33,7 @@ CLASS TSqlExecutor DATA aOpened INIT {} DATA aTables INIT {} DATA aCompileStruct + DATA bRowBlock /* optional code block — receives SELECT cols as params */ CLASSDATA hSubCache INIT { => } SHARED @@ -1198,8 +1199,12 @@ METHOD RunSelect() CLASS TSqlExecutor /* === GO NATIVE FAST PATH === * Single-table, no joins, no aggregates, all SELECT exprs * simple field refs, WHERE is NIL or compilable to pcode. - * Hands the scan loop off to Go's SqlScan (~15x faster - * than the PRG per-row tree walk). + * Two variants share the same entry conditions: + * - With row block (::bRowBlock != NIL): SqlEach streams + * rows directly into the user block, no intermediate + * array. Beats raw RDD on end-to-end timing. + * - Without block: SqlScan materializes into aRows as + * usual (compat with existing callers). */ aFP := NIL pcW := NIL @@ -1210,10 +1215,21 @@ METHOD RunSelect() CLASS TSqlExecutor IF aFP != NIL pcW := ::TryCompileWhere( xWhere ) IF xWhere == NIL .OR. pcW != NIL - aGoRows := SqlScan( aFP, pcW ) - FOR i := 1 TO Len( aGoRows ) - AAdd( aRows, aGoRows[ i ] ) - NEXT + IF ::bRowBlock != NIL + /* Block mode: stream rows through user block. + * No result array. Skip all post-processing + * (ORDER BY / LIMIT / window / DISTINCT) — + * those require a materialized set; callers + * using the block form opt into streaming + * semantics and handle shaping themselves. */ + SqlEach( aFP, pcW, ::bRowBlock ) + aGoRows := {} /* signal "handled" to skip fallback */ + ELSE + aGoRows := SqlScan( aFP, pcW ) + FOR i := 1 TO Len( aGoRows ) + AAdd( aRows, aGoRows[ i ] ) + NEXT + ENDIF ENDIF ENDIF ENDIF @@ -1347,6 +1363,13 @@ METHOD RunSelect() CLASS TSqlExecutor dbSelectArea( aSavedAreas[ 1 ] ) ENDIF + /* Block-callback mode: rows were streamed through ::bRowBlock during + * the fast-path scan. aRows is empty; we return NIL to signal + * streaming semantics to the caller. */ + IF ::bRowBlock != NIL + RETURN NIL + ENDIF + RETURN { aFieldNames, aRows }