@@ -433,40 +433,56 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
433
433
out . returnVal = ( ) => opt . resultRows ;
434
434
}
435
435
if ( opt . callback || opt . resultRows ) {
436
- switch ( ( undefined === opt . rowMode )
437
- ? 'array' : opt . rowMode ) {
438
- case 'object' : out . cbArg = ( stmt ) => stmt . get ( Object . create ( null ) ) ; break ;
439
- case 'array' : out . cbArg = ( stmt ) => stmt . get ( [ ] ) ; break ;
440
- case 'stmt' :
441
- if ( Array . isArray ( opt . resultRows ) ) {
442
- toss3 ( "exec(): invalid rowMode for a resultRows array: must" ,
443
- "be one of 'array', 'object'," ,
444
- "a result column number, or column name reference." ) ;
445
- }
446
- out . cbArg = ( stmt ) => stmt ;
436
+ switch ( ( undefined === opt . rowMode ) ? 'array' : opt . rowMode ) {
437
+ case 'object' :
438
+ out . cbArg = ( stmt , cache ) => {
439
+ if ( ! cache . columnNames ) cache . columnNames = stmt . getColumnNames ( [ ] ) ;
440
+ /* https://sqlite.org/forum/forumpost/3632183d2470617d:
441
+ conversion of rows to objects (key/val pairs) is
442
+ somewhat expensive for large data sets because of the
443
+ native-to-JS conversion of the column names. If we
444
+ instead cache the names and build objects from that
445
+ list of strings, it can run twice as fast. The
446
+ difference is not noticeable for small data sets but
447
+ becomes human-perceivable when enough rows are
448
+ involved. */
449
+ const row = stmt . get ( [ ] ) ;
450
+ const rv = Object . create ( null ) ;
451
+ for ( const i in cache . columnNames ) rv [ cache . columnNames [ i ] ] = row [ i ] ;
452
+ return rv ;
453
+ } ;
454
+ break ;
455
+ case 'array' : out . cbArg = ( stmt ) => stmt . get ( [ ] ) ; break ;
456
+ case 'stmt' :
457
+ if ( Array . isArray ( opt . resultRows ) ) {
458
+ toss3 ( "exec(): invalid rowMode for a resultRows array: must" ,
459
+ "be one of 'array', 'object'," ,
460
+ "a result column number, or column name reference." ) ;
461
+ }
462
+ out . cbArg = ( stmt ) => stmt ;
463
+ break ;
464
+ default :
465
+ if ( util . isInt32 ( opt . rowMode ) ) {
466
+ out . cbArg = ( stmt ) => stmt . get ( opt . rowMode ) ;
447
467
break ;
448
- default :
449
- if ( util . isInt32 ( opt . rowMode ) ) {
450
- out . cbArg = ( stmt ) => stmt . get ( opt . rowMode ) ;
451
- break ;
452
- } else if ( 'string' === typeof opt . rowMode
453
- && opt . rowMode . length > 1
454
- && '$' === opt . rowMode [ 0 ] ) {
455
- /* "$X": fetch column named "X" (case-sensitive!). Prior
456
- to 2022-12-14 ":X" and "@X" were also permitted, but
457
- having so many options is unnecessary and likely to
458
- cause confusion. */
459
- const $colName = opt . rowMode . substr ( 1 ) ;
460
- out . cbArg = ( stmt ) => {
461
- const rc = stmt . get ( Object . create ( null ) ) [ $colName ] ;
462
- return ( undefined === rc )
463
- ? toss3 ( capi . SQLITE_NOTFOUND ,
464
- "exec(): unknown result column:" , $colName )
465
- : rc ;
466
- } ;
467
- break ;
468
- }
469
- toss3 ( "Invalid rowMode:" , opt . rowMode ) ;
468
+ } else if ( 'string' === typeof opt . rowMode
469
+ && opt . rowMode . length > 1
470
+ && '$' === opt . rowMode [ 0 ] ) {
471
+ /* "$X": fetch column named "X" (case-sensitive!). Prior
472
+ to 2022-12-14 ":X" and "@X" were also permitted, but
473
+ having so many options is unnecessary and likely to
474
+ cause confusion. */
475
+ const $colName = opt . rowMode . substr ( 1 ) ;
476
+ out . cbArg = ( stmt ) => {
477
+ const rc = stmt . get ( Object . create ( null ) ) [ $colName ] ;
478
+ return ( undefined === rc )
479
+ ? toss3 ( capi . SQLITE_NOTFOUND ,
480
+ "exec(): unknown result column:" , $colName )
481
+ : rc ;
482
+ } ;
483
+ break ;
484
+ }
485
+ toss3 ( "Invalid rowMode:" , opt . rowMode ) ;
470
486
}
471
487
}
472
488
return out ;
@@ -884,10 +900,15 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
884
900
and names. */ ) ? 0 : 1 ;
885
901
evalFirstResult = false ;
886
902
if ( arg . cbArg || resultRows ) {
903
+ const cbArgCache = Object . create ( null )
904
+ /* 2nd arg for arg.cbArg, used by (at least) row-to-object
905
+ converter */ ;
887
906
for ( ; stmt . step ( ) ; stmt . _lockedByExec = false ) {
888
- if ( 0 === gotColNames ++ ) stmt . getColumnNames ( opt . columnNames ) ;
907
+ if ( 0 === gotColNames ++ ) {
908
+ stmt . getColumnNames ( cbArgCache . columnNames = ( opt . columnNames || [ ] ) ) ;
909
+ }
889
910
stmt . _lockedByExec = true ;
890
- const row = arg . cbArg ( stmt ) ;
911
+ const row = arg . cbArg ( stmt , cbArgCache ) ;
891
912
if ( resultRows ) resultRows . push ( row ) ;
892
913
if ( callback && false === callback . call ( opt , row , stmt ) ) {
893
914
break ;
0 commit comments