@@ -109,11 +109,13 @@ async function withColumnarDriver<T>(
109109 * their names match envelope fields, including direct rows made only of write
110110 * marker aliases. When OP-SQLite supplies object rows and raw columnar rows
111111 * together, the already-decoded object rows are authoritative. Malformed or
112- * undocumented result carriers reject. A bare array whose first entry can be
112+ * incomplete result carriers reject. A bare array whose first entry can be
113113 * both a row and a statement envelope has no self-describing interpretation;
114- * its driver must declare `rows` or `statement-results` mode.
114+ * its driver must declare `rows` or `statement-results` mode. The declared
115+ * mode, rather than reserved field names, decides markerless `{ rows }`
116+ * wrappers.
115117 * Source: SQLiteDriver's public query contract and op-sqlite's documented
116- * execute `{ rows, columnNames }` and executeAsync
118+ * required `QueryResult.rows`, execute `{ rows, columnNames }`, and executeAsync
117119 * `{ rawRows, columnNames, rowsAffected }` envelopes.
118120 * Domain: deterministic object-row wrappers and columnar rows, including
119121 * empty, asymmetric/reordered multirow, legal reserved-looking SQL aliases,
@@ -262,6 +264,37 @@ it(`requires an explicit mode for an ambiguous bare result array`, async () => {
262264 ) . resolves . toEqual ( [ `nested` ] )
263265} )
264266
267+ it ( `uses the declared mode for a markerless statement-array envelope` , async ( ) => {
268+ const ambiguous = [ { rows : [ { id : `nested-row` } ] } ]
269+
270+ await expect ( queryInjectedResult ( ambiguous ) ) . rejects . toThrow (
271+ `ambiguous bare result array` ,
272+ )
273+ await expect ( queryInjectedResult ( ambiguous , `rows` ) ) . resolves . toEqual (
274+ ambiguous ,
275+ )
276+ await expect (
277+ queryInjectedResult ( ambiguous , `statement-results` ) ,
278+ ) . resolves . toEqual ( [ { id : `nested-row` } ] )
279+ } )
280+
281+ it ( `materializes a declared statement row list exactly once` , async ( ) => {
282+ const expected = [ { id : `first` } , { id : `second` } ]
283+ const requestedIndexes : Array < number > = [ ]
284+ const rows = {
285+ length : expected . length ,
286+ item : ( index : number ) => {
287+ requestedIndexes . push ( index )
288+ return expected [ index ]
289+ } ,
290+ }
291+
292+ await expect (
293+ queryInjectedResult ( [ { rowsAffected : 0 , rows } ] , `statement-results` ) ,
294+ ) . resolves . toEqual ( expected )
295+ expect ( requestedIndexes ) . toEqual ( [ 0 , 1 ] )
296+ } )
297+
265298it ( `reads op-sqlite execute rows when columnNames metadata is also present` , async ( ) => {
266299 await expect (
267300 queryInjectedResult ( {
@@ -701,7 +734,7 @@ const malformedColumnarResults: ReadonlyArray<{
701734 result : { mysteryRows : [ [ `1` ] ] } ,
702735 } ,
703736 {
704- name : `undocumented res carrier` ,
737+ name : `optional res field without the required rows carrier` ,
705738 result : { rowsAffected : 0 , res : [ { id : `legacy` } ] } ,
706739 } ,
707740 {
@@ -770,6 +803,35 @@ it(`supports exactly one results wrapper`, async () => {
770803 expect ( queryExecutions ) . toBe ( 1 )
771804} )
772805
806+ it ( `rejects a multi-statement results wrapper instead of dropping results` , async ( ) => {
807+ await expect (
808+ queryInjectedResult ( {
809+ results : [
810+ { rows : [ { id : `first-statement` } ] } ,
811+ { rows : [ { id : `second-statement` } ] } ,
812+ ] ,
813+ } ) ,
814+ ) . rejects . toThrow ( `invalid nested results carrier` )
815+ } )
816+
817+ it ( `models OP-SQLite writes with an empty rows carrier` , async ( ) => {
818+ const dbPath = createTempSqlitePath ( )
819+ const database = createOpSQLiteTestDatabase ( {
820+ filename : dbPath ,
821+ resultShape : `execute-async-columnar` ,
822+ } )
823+ activeCleanupFns . push ( ( ) => Promise . resolve ( database . close ( ) ) )
824+ const executeAsync = database . executeAsync
825+ if ( ! executeAsync ) {
826+ throw new Error ( `columnar fixture must expose executeAsync` )
827+ }
828+
829+ await executeAsync ( `CREATE TABLE write_receipt (id INTEGER PRIMARY KEY)` )
830+ await expect (
831+ executeAsync ( `INSERT INTO write_receipt (id) VALUES (?)` , [ 1 ] ) ,
832+ ) . resolves . toMatchObject ( { rowsAffected : 1 , rows : [ ] } )
833+ } )
834+
773835it . each ( [
774836 {
775837 name : `second results wrapper` ,
0 commit comments