@@ -4,6 +4,7 @@ import path from 'path';
44import { fileURLToPath } from 'url' ;
55import runQuery from '../index.cjs' ;
66import { batchedAsyncGeneratorToArray , TypeFidelity } from '@evidence-dev/db-commons' ;
7+ import fs from 'fs/promises' ;
78import 'dotenv/config' ;
89
910test ( 'basic select from needful_things.duckdb' , async ( ) => {
@@ -198,6 +199,20 @@ test('query runs', async () => {
198199 }
199200} ) ;
200201
202+ test ( 'expectedRowCount present for UNION with NULL-first-row' , async ( ) => {
203+ // Simple reproduction: first row has NULL, second row has a value
204+ const query = `select NULL as a UNION ALL select 1 as a` ;
205+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
206+ // consume rows to ensure the stream runs
207+ const arr = [ ] ;
208+ for await ( const batch of rows ( ) ) {
209+ arr . push ( ...batch ) ;
210+ }
211+ assert . equal ( arr . length , 2 ) ;
212+ assert . type ( expectedRowCount , 'number' ) ;
213+ assert . equal ( expectedRowCount , 2 ) ;
214+ } ) ;
215+
201216test ( 'query batches results properly' , async ( ) => {
202217 try {
203218 const { rows, expectedRowCount } = await runQuery (
@@ -220,4 +235,131 @@ test('query batches results properly', async () => {
220235 }
221236} ) ;
222237
238+ test ( 'handles leading SET statements before main query' , async ( ) => {
239+ try {
240+ const query = `
241+ SET VARIABLE VAR1 = DATE '2023-10-04';
242+ SET VARIABLE VAR2 = '23';
243+ select 1 union all select 2 union all select 3;
244+ ` ;
245+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
246+ const arr = [ ] ;
247+ for await ( const batch of rows ( ) ) {
248+ arr . push ( batch ) ;
249+ }
250+ // should batch into [2,1]
251+ assert . equal ( arr [ 0 ] . length , 2 ) ;
252+ assert . equal ( arr [ 1 ] . length , 1 ) ;
253+ assert . equal ( expectedRowCount , 3 ) ;
254+ } catch ( e ) {
255+ throw Error ( e ) ;
256+ }
257+ } ) ;
258+
259+ test ( 'semicolon inside single-quoted string should not split statements' , async ( ) => {
260+ try {
261+ const query = "SET V='this;is;a;string'; select 1 union all select 2;" ;
262+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
263+ const arr = [ ] ;
264+ for await ( const batch of rows ( ) ) {
265+ arr . push ( batch ) ;
266+ }
267+ assert . equal ( expectedRowCount , 2 ) ;
268+ assert . equal ( arr [ 0 ] . length , 2 ) ;
269+ } catch ( e ) {
270+ throw Error ( e ) ;
271+ }
272+ } ) ;
273+
274+ test ( 'semicolon inside block comment should not split statements' , async ( ) => {
275+ try {
276+ const query = "/* comment; still comment; */ select 1 union all select 2;" ;
277+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
278+ const arr = [ ] ;
279+ for await ( const batch of rows ( ) ) {
280+ arr . push ( batch ) ;
281+ }
282+ assert . equal ( expectedRowCount , 2 ) ;
283+ assert . equal ( arr [ 0 ] . length , 2 ) ;
284+ } catch ( e ) {
285+ throw Error ( e ) ;
286+ }
287+ } ) ;
288+
289+ test ( 'handles leading SET statements before main query' , async ( ) => {
290+ try {
291+ const query = `
292+ SET VARIABLE VAR1 = DATE '2023-10-04';
293+ SET VARIABLE VAR2 = '23';
294+ select 1 union all select 2 union all select 3;
295+ ` ;
296+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
297+ const arr = [ ] ;
298+ for await ( const batch of rows ( ) ) {
299+ arr . push ( batch ) ;
300+ }
301+ // should batch into [2,1]
302+ assert . equal ( arr [ 0 ] . length , 2 ) ;
303+ assert . equal ( arr [ 1 ] . length , 1 ) ;
304+ assert . equal ( expectedRowCount , 3 ) ;
305+ } catch ( e ) {
306+ throw Error ( e ) ;
307+ }
308+ } ) ;
309+
310+ test ( 'semicolon inside single-quoted string should not split statements' , async ( ) => {
311+ try {
312+ const query = "SET V='this;is;a;string'; select 1 union all select 2;" ;
313+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
314+ const arr = [ ] ;
315+ for await ( const batch of rows ( ) ) {
316+ arr . push ( batch ) ;
317+ }
318+ assert . equal ( expectedRowCount , 2 ) ;
319+ assert . equal ( arr [ 0 ] . length , 2 ) ;
320+ } catch ( e ) {
321+ throw Error ( e ) ;
322+ }
323+ } ) ;
324+
325+ test ( 'semicolon inside block comment should not split statements' , async ( ) => {
326+ try {
327+ const query = '/* comment; still comment; */ select 1 union all select 2;' ;
328+ const { rows, expectedRowCount } = await runQuery ( query , undefined , 2 ) ;
329+ const arr = [ ] ;
330+ for await ( const batch of rows ( ) ) {
331+ arr . push ( batch ) ;
332+ }
333+ assert . equal ( expectedRowCount , 2 ) ;
334+ assert . equal ( arr [ 0 ] . length , 2 ) ;
335+ } catch ( e ) {
336+ throw Error ( e ) ;
337+ }
338+ } ) ;
339+
340+ test ( 'USE statement before query should apply to metadata queries' , async ( ) => {
341+ try {
342+ // Create a temporary on-disk DuckDB file to test that USE affects
343+ // subsequent queries in the same session. We create a schema and table
344+ // in prefix statements, then USE the schema and select from the table.
345+ // Use an in-memory database so we can CREATE schema/table in the test
346+ const dbFile = ':memory:' ;
347+ const query = `
348+ CREATE SCHEMA s;
349+ CREATE TABLE s.t AS SELECT 1 AS x UNION ALL SELECT 2 AS x;
350+ USE s;
351+ SELECT x FROM t;
352+ ` ;
353+ const { rows, expectedRowCount } = await runQuery ( query , { filename : dbFile } , 2 ) ;
354+ const arr = [ ] ;
355+ for await ( const batch of rows ( ) ) {
356+ arr . push ( batch ) ;
357+ }
358+ assert . equal ( expectedRowCount , 2 ) ;
359+ assert . equal ( arr [ 0 ] . length , 2 ) ;
360+ } catch ( e ) {
361+ throw Error ( e ) ;
362+ }
363+ } ) ;
364+
223365test . run ( ) ;
0 commit comments