@@ -115,21 +115,57 @@ export async function createDatabase(
115115 } ;
116116
117117 type Method = 'run' | 'get' | 'all' ;
118+ type ParamStyle = 'positional' | 'named' | 'none' ;
119+ type PositionalKind = 'numeric' | 'anonymous' | undefined ;
120+ interface SqlMeta {
121+ style : ParamStyle ;
122+ positionalKind ?: PositionalKind ;
123+ paramCount : number ;
124+ }
125+
126+ const analyzeSql = ( sql : string ) : SqlMeta => {
127+ // Very lightweight analysis; sufficient for our test/usage.
128+ const namedRe = / [: @ $ ] [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * / g;
129+ const numericRe = / \? ( \d + ) / g;
130+ const anonRe = / \? (? ! \d ) / g;
131+ const hasNamed = namedRe . test ( sql ) ;
132+ if ( hasNamed ) return { style : 'named' , paramCount : 0 } ;
133+ let maxIndex = 0 ;
134+ let m : RegExpExecArray | null ;
135+ numericRe . lastIndex = 0 ;
136+ while ( ( m = numericRe . exec ( sql ) ) ) {
137+ const idx = parseInt ( m [ 1 ] ! , 10 ) ;
138+ if ( idx > maxIndex ) maxIndex = idx ;
139+ }
140+ if ( maxIndex > 0 ) return { style : 'positional' , positionalKind : 'numeric' , paramCount : maxIndex } ;
141+ // Count anonymous ? occurrences
142+ const qs = sql . match ( anonRe ) ?. length ?? 0 ;
143+ if ( qs > 0 ) return { style : 'positional' , positionalKind : 'anonymous' , paramCount : qs } ;
144+ return { style : 'none' , paramCount : 0 } ;
145+ } ;
146+
118147 const mapParams = ( v : any ) : any | undefined => {
119148 if ( v == null ) return undefined ;
120149 if ( Array . isArray ( v ) ) return toNumericParamObj ( normVals ( v ) ?? [ ] ) ;
121150 return normObj ( v ) ;
122151 } ;
123- const callWithParams = ( stmt : any , method : Method , v : any ) => {
152+ const callWithParams = ( stmt : any , method : Method , v : any , meta : SqlMeta ) => {
124153 const params = mapParams ( v ) ;
125- if ( DEBUG ) dlog ( `stmt.${ method } ()` , { params : summarize ( params ) } ) ;
154+ if ( DEBUG ) dlog ( `stmt.${ method } ()` , { params : summarize ( params ) , meta } ) ;
155+ // Prefer varargs for positional placeholders to avoid CI differences.
156+ if ( Array . isArray ( v ) && meta . style === 'positional' ) {
157+ const a = normVals ( v ) ?? [ ] ;
158+ const toUse = meta . paramCount > 0 && a . length > meta . paramCount ? a . slice ( 0 , meta . paramCount ) : a ;
159+ if ( a . length > toUse . length && DEBUG ) dlog ( 'slicing extra params for positional binding' , { have : a . length , use : toUse . length } ) ;
160+ return stmt [ method ] ( ...toUse ) ;
161+ }
162+ // Fallback: deterministic bind-first with (possibly named) object.
126163 if ( params === undefined ) return stmt [ method ] ( ) ;
127- // Deterministic: bind first, then call without inline params.
128164 stmt . bind ( params ) ;
129165 return stmt [ method ] ( ) ;
130166 } ;
131167
132- const wrapStmt = ( stmt : any ) : Statement => {
168+ const wrapStmt = ( stmt : any , meta : SqlMeta ) : Statement => {
133169 let bound : any [ ] | undefined = undefined ;
134170 return {
135171 bind ( values : any [ ] ) {
@@ -139,19 +175,19 @@ export async function createDatabase(
139175 finalize ( ) { } ,
140176 get ( values ?: any [ ] ) {
141177 const v = ( values ?? bound ) as any ;
142- return callWithParams ( stmt , 'get' , v ) ;
178+ return callWithParams ( stmt , 'get' , v , meta ) ;
143179 } ,
144180 run ( values : any [ ] ) {
145181 const v = ( values ?? bound ) as any ;
146- callWithParams ( stmt , 'run' , v ) ;
182+ callWithParams ( stmt , 'run' , v , meta ) ;
147183 bound = undefined ;
148184 } ,
149185 async reset ( ) {
150186 return this ;
151187 } ,
152188 all ( values : any [ ] ) {
153189 const v = ( values ?? bound ) as any ;
154- return callWithParams ( stmt , 'all' , v ) ;
190+ return callWithParams ( stmt , 'all' , v , meta ) ;
155191 } ,
156192 step ( ) {
157193 return false ;
@@ -175,8 +211,9 @@ export async function createDatabase(
175211 await ( prev . reset ?.( ) as any ) ;
176212 return prev ;
177213 }
178- const stmt = open ( ) . prepare ( sql ) ;
179- const wrapped = wrapStmt ( stmt ) ;
214+ const meta = analyzeSql ( sql ) ;
215+ const stmt = open ( ) . prepare ( sql ) ;
216+ const wrapped = wrapStmt ( stmt , meta ) ;
180217 if ( id != null ) statements . set ( id , wrapped ) ;
181218 return wrapped ;
182219 } ,
0 commit comments