Skip to content

Commit 1056dfa

Browse files
committed
test(sea): lock fetchAll Array<object> drain — pecotesting e2e
Adds an end-to-end test against pecotesting (gated by `DATABRICKS_PECOTESTING_*` env vars; skipped when absent) that exercises `DBSQLOperation.fetchAll()` on the SEA backend and asserts the returned shape matches the Thrift backend's `Array<object>` convention. Implementation status — fetchAll on the SEA path is already wired: - The facade `DBSQLOperation.fetchAll` (`lib/DBSQLOperation.ts`) loops over `fetchChunk` with `disableBuffering: true` until `hasMoreRows()` returns false. Backend-agnostic. - `SeaOperationBackend.fetchChunk` already routes through the `ResultSlicer` over the napi statement's `fetchNextBatch()` IPC stream. So this test is regression-locking the existing path, not exercising new code. Three cases: 1. 100-row range — drain to a flat Array of 100 objects, each with the `id` key. 2. Empty result set — drain to an empty array (no zero-row crash). 3. Multi-column mixed types — assert all four columns round-trip into JS objects with correct types (`id`, `d` Double, `is_even` Boolean, `name` String). Uses the internal `useSEA: true` flag on `client.connect(...)` to route through `SeaBackend`. The `useSEA` flag is consumed via a non-exported cast (see `lib/DBSQLClient.ts:244`) so the public `.d.ts` doesn't ship it — mirrors Python's `use_sea` kwarg pattern. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent f0b22cc commit 1056dfa

1 file changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) 2026 Databricks, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* End-to-end check that `DBSQLOperation.fetchAll()` works on the SEA
17+
* backend with the same `Array<object>` row shape the Thrift backend
18+
* already produces.
19+
*
20+
* The drain primitive is already implemented:
21+
* - The facade `DBSQLOperation.fetchAll` (`lib/DBSQLOperation.ts`)
22+
* loops over `fetchChunk` with `disableBuffering: true` until
23+
* `hasMoreRows()` returns false.
24+
* - The SEA backend's `SeaOperationBackend.fetchChunk` is wired
25+
* through the `ResultSlicer` over the napi statement's
26+
* `fetchNextBatch()` stream.
27+
*
28+
* This test exercises the full path: open client with `useSEA=true`,
29+
* run a SELECT with a known row count, drain via fetchAll, assert
30+
* row count + shape match Thrift's `Array<object>` convention.
31+
*
32+
* Gated on `DATABRICKS_PECOTESTING_*` env vars; skipped when absent.
33+
*/
34+
35+
import { expect } from 'chai';
36+
import { DBSQLClient } from '../../../lib';
37+
import type { ConnectionOptions } from '../../../lib/contracts/IDBSQLClient';
38+
39+
interface InternalConnectionOptionsAccess {
40+
useSEA?: boolean;
41+
}
42+
43+
describe('SEA fetchAll — Array<object> row drain', function suite() {
44+
this.timeout(180_000);
45+
46+
const host = process.env.DATABRICKS_PECOTESTING_SERVER_HOSTNAME || process.env.E2E_HOST;
47+
const path = process.env.DATABRICKS_PECOTESTING_HTTP_PATH || process.env.E2E_PATH;
48+
const token = process.env.DATABRICKS_PECOTESTING_TOKEN || process.env.E2E_ACCESS_TOKEN;
49+
50+
before(function gate() {
51+
if (!host || !path || !token) {
52+
// eslint-disable-next-line no-invalid-this
53+
this.skip();
54+
}
55+
});
56+
57+
async function connect() {
58+
const client = new DBSQLClient();
59+
// `useSEA` is an internal opt-in flag (not on the public TS
60+
// surface; see `lib/contracts/InternalConnectionOptions.ts`).
61+
// Cast through `unknown` to satisfy strict-mode.
62+
const options = {
63+
host: host as string,
64+
path: path as string,
65+
token: token as string,
66+
useSEA: true,
67+
} as ConnectionOptions & InternalConnectionOptionsAccess;
68+
await client.connect(options as unknown as ConnectionOptions);
69+
return client;
70+
}
71+
72+
it('drains 100 rows from range(0, 100) into a flat Array<object>', async () => {
73+
const client = await connect();
74+
try {
75+
const session = await client.openSession();
76+
try {
77+
const operation = await session.executeStatement('SELECT * FROM range(0, 100)');
78+
try {
79+
const rows = await operation.fetchAll();
80+
expect(rows).to.be.an('array');
81+
expect(rows.length).to.equal(100);
82+
// `range(0, n)` returns a single `id` column with values 0..n-1.
83+
// Every row must be a plain object with that key.
84+
for (let i = 0; i < rows.length; i += 1) {
85+
const row = rows[i] as Record<string, unknown>;
86+
expect(row).to.have.property('id');
87+
}
88+
} finally {
89+
await operation.close();
90+
}
91+
} finally {
92+
await session.close();
93+
}
94+
} finally {
95+
await client.close();
96+
}
97+
});
98+
99+
it('drains an empty result set into an empty array', async () => {
100+
const client = await connect();
101+
try {
102+
const session = await client.openSession();
103+
try {
104+
const operation = await session.executeStatement(
105+
'SELECT * FROM range(0, 0)',
106+
);
107+
try {
108+
const rows = await operation.fetchAll();
109+
expect(rows).to.be.an('array');
110+
expect(rows.length).to.equal(0);
111+
} finally {
112+
await operation.close();
113+
}
114+
} finally {
115+
await session.close();
116+
}
117+
} finally {
118+
await client.close();
119+
}
120+
});
121+
122+
it('drains a multi-column result set with mixed types', async () => {
123+
const client = await connect();
124+
try {
125+
const session = await client.openSession();
126+
try {
127+
// Three primitive columns + a string. Drain and assert each
128+
// row carries all four keys with the expected values.
129+
const operation = await session.executeStatement(
130+
`SELECT id,
131+
CAST(id AS DOUBLE) AS d,
132+
id % 2 = 0 AS is_even,
133+
CONCAT('row-', CAST(id AS STRING)) AS name
134+
FROM range(0, 10)`,
135+
);
136+
try {
137+
const rows = await operation.fetchAll();
138+
expect(rows).to.have.length(10);
139+
for (const row of rows as Array<Record<string, unknown>>) {
140+
expect(row).to.have.all.keys('id', 'd', 'is_even', 'name');
141+
expect(row.name).to.match(/^row-\d+$/);
142+
expect(row.is_even).to.be.a('boolean');
143+
}
144+
} finally {
145+
await operation.close();
146+
}
147+
} finally {
148+
await session.close();
149+
}
150+
} finally {
151+
await client.close();
152+
}
153+
});
154+
});

0 commit comments

Comments
 (0)