/* * TFiveSQL.prg — Main facade class for FiveSql2 engine * * Uses TSqlParser2 (Pratt parser) exclusively. * * FiveSql2 — SQL Engine for Harbour DBF/NTX * * Copyright (c) 2025-2026 Charles KWON (Charles KWON OhJun) * Email: charleskwonohjun@gmail.com * * All rights reserved. */ #include "hbclass.ch" #include "FiveSqlDef.ch" /* Plan cache: cSQL → parsed hQuery. * * The FiveSql2 parser runs lex + Pratt-style AST build per call; for * repeated identical SQL (typical in report / loop / benchmark workloads) * this is pure overhead. We cache the pristine parse result keyed by * the raw SQL text and hand every subsequent call a deep clone via * HbDeepClone so in-place mutations (SqlFoldConst, aTables rewriting) * during Run() never corrupt the cached tree. * * Cached entries live until process exit; distinct SQL text count is * bounded by the caller's template set, so LRU is deferred. */ STATIC s_hPlanCache := { => } CLASS TFiveSQL DATA oLexer DATA oParser DATA oExec DATA aParams INIT {} METHOD New( aParams ) CONSTRUCTOR METHOD Execute( cSQL, bBlock ) METHOD ExecuteWith( cSQL, aParams ) ENDCLASS METHOD New( aParams ) CLASS TFiveSQL IF aParams != NIL ::aParams := aParams ENDIF RETURN SELF METHOD Execute( cSQL, bBlock ) CLASS TFiveSQL LOCAL aTokens, hQuery, aResult LOCAL aLex, cKey, aParams /* Fast path: no explicit aParams → single Go RTL lex+normalize call * (SqlLexAndExtractTemplate). Returns {aTokens, cKey, aParams}; the * tokens already have TK_TEXT/TK_NUM replaced with TK_QMARK, so * TSqlParser2 sees the template shape and emits ND_PAR references * against the extracted aParams. */ IF Empty( ::aParams ) aLex := SqlLexAndExtractTemplate( cSQL ) aTokens := aLex[ 1 ] cKey := aLex[ 2 ] aParams := aLex[ 3 ] IF hb_HHasKey( s_hPlanCache, cKey ) hQuery := HbDeepClone( s_hPlanCache[ cKey ] ) ELSE ::oParser := TSqlParser2():New( aTokens, aParams ) hQuery := ::oParser:Parse() IF hQuery == NIL RETURN { { "__error__" }, { { SQL_ERR_SYNTAX, "Failed to parse SQL", cSQL } } } ENDIF s_hPlanCache[ cKey ] := HbDeepClone( hQuery ) ENDIF ::oExec := TSqlExecutor():New( hQuery, aParams ) ::oExec:cCacheKey := cKey ELSE /* Caller supplied explicit params — cache by raw SQL text. */ IF hb_HHasKey( s_hPlanCache, cSQL ) hQuery := HbDeepClone( s_hPlanCache[ cSQL ] ) ELSE aTokens := SqlLexerTokenize( cSQL ) ::oParser := TSqlParser2():New( aTokens, ::aParams ) hQuery := ::oParser:Parse() IF hQuery == NIL RETURN { { "__error__" }, { { SQL_ERR_SYNTAX, "Failed to parse SQL", cSQL } } } ENDIF s_hPlanCache[ cSQL ] := HbDeepClone( hQuery ) ENDIF ::oExec := TSqlExecutor():New( hQuery, ::aParams ) ::oExec:cCacheKey := cSQL ENDIF ::oExec:bRowBlock := bBlock aResult := ::oExec:Run() RETURN aResult METHOD ExecuteWith( cSQL, aParams ) CLASS TFiveSQL ::aParams := aParams RETURN ::Execute( cSQL )