// Prepared-statement vs concatenated-SQL benchmark. // Demonstrates the plan cache win for parameterized queries — the same // `?` template hits cache on every call after the first; concatenated // SQL strings vary by value and miss every time. #include "FiveSqlDef.ch" #define ITERS 1000 PROCEDURE Main() LOCAL t0, t1, i ErrorBlock( {|e| QOut( "TRAP: " + e:description + " " + e:operation ), Break(e) } ) ? "================================================================" ? " FiveSql2 Prepared-Statement Benchmark" ? " " + hb_ntos( ITERS ) + " iterations per pattern" ? "================================================================" ? SetupTable() /* A: concatenated INSERT — SQL text changes per iteration, every * call misses the plan cache and re-parses. */ t0 := hb_MilliSeconds() FOR i := 1 TO ITERS five_SQL( "INSERT INTO bench_prep (id, val) VALUES (" + hb_ntos( i ) + ", 'a')" ) NEXT t1 := hb_MilliSeconds() R( "CONCAT_INSERT", t1 - t0 ) TruncateTable() /* B: prepared INSERT — same SQL text every iteration, cache hits * from the 2nd call onward. */ t0 := hb_MilliSeconds() FOR i := 1 TO ITERS five_SQL( "INSERT INTO bench_prep (id, val) VALUES (?, ?)", { i, "a" } ) NEXT t1 := hb_MilliSeconds() R( "PREPARED_INSERT", t1 - t0 ) TruncateTable() /* C: concatenated SELECT by id. */ t0 := hb_MilliSeconds() FOR i := 1 TO ITERS five_SQL( "SELECT val FROM bench_prep WHERE id = " + hb_ntos( i ) ) NEXT t1 := hb_MilliSeconds() R( "CONCAT_SELECT", t1 - t0 ) /* D: prepared SELECT by id. */ t0 := hb_MilliSeconds() FOR i := 1 TO ITERS five_SQL( "SELECT val FROM bench_prep WHERE id = ?", { i } ) NEXT t1 := hb_MilliSeconds() R( "PREPARED_SELECT", t1 - t0 ) CleanupTable() ? ? "================================================================" RETURN STATIC PROCEDURE SetupTable() IF hb_FileExists( "bench_prep.dbf" ) FErase( "bench_prep.dbf" ) ENDIF dbCreate( "bench_prep.dbf", { ; { "ID", "N", 10, 0 }, ; { "VAL", "C", 10, 0 } ; } ) /* Pre-populate enough rows so SELECT benchmark has real data. */ USE bench_prep.dbf NEW EXCLUSIVE LOCAL i FOR i := 1 TO ITERS dbAppend() FieldPut( 1, i ) FieldPut( 2, "a" ) NEXT dbCommit() CLOSE bench_prep RETURN STATIC PROCEDURE TruncateTable() USE bench_prep.dbf NEW EXCLUSIVE dbZap() CLOSE bench_prep RETURN STATIC PROCEDURE CleanupTable() dbCloseAll() FErase( "bench_prep.dbf" ) RETURN STATIC FUNCTION R( cLabel, nMs ) ? " ", PadR( cLabel, 18 ) + Str( nMs, 7 ) + " ms " + ; Str( nMs * 1000 / ITERS, 8, 2 ) + " us/query" RETURN NIL