@bitofsky/databricks-sql 1.0.1 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -22
- package/dist/index.cjs +185 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +185 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -236,18 +236,22 @@ var TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
|
236
236
|
]);
|
|
237
237
|
var POLL_INTERVAL_MS = 5e3;
|
|
238
238
|
async function fetchMetrics(auth, statementId, signal) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return queryInfo.metrics;
|
|
242
|
-
} catch {
|
|
243
|
-
return void 0;
|
|
244
|
-
}
|
|
239
|
+
const queryInfo = await getQueryMetrics(auth, statementId, signal);
|
|
240
|
+
return queryInfo.metrics;
|
|
245
241
|
}
|
|
246
242
|
async function executeStatement(query, auth, options = {}) {
|
|
247
243
|
const warehouseId = options.warehouse_id ?? extractWarehouseId(auth.httpPath);
|
|
248
|
-
const { signal, onProgress, enableMetrics } = options;
|
|
244
|
+
const { signal, onProgress, enableMetrics, logger } = options;
|
|
245
|
+
const waitTimeout = options.wait_timeout ?? (onProgress ? "0s" : "50s");
|
|
246
|
+
let cancelIssued = false;
|
|
249
247
|
throwIfAborted(signal, "executeStatement");
|
|
250
|
-
const emitProgress = onProgress ? async (
|
|
248
|
+
const emitProgress = onProgress ? async () => result ? onProgress(
|
|
249
|
+
result,
|
|
250
|
+
enableMetrics ? await fetchMetrics(auth, result.statement_id, signal).catch((e) => {
|
|
251
|
+
logger?.error?.(`executeStatement Failed to fetch query metrics for statement ${result?.statement_id}: ${String(e)}`, { statementId: result?.statement_id });
|
|
252
|
+
return void 0;
|
|
253
|
+
}) : void 0
|
|
254
|
+
) : void 0 : void 0;
|
|
251
255
|
const request = Object.fromEntries(
|
|
252
256
|
Object.entries({
|
|
253
257
|
warehouse_id: warehouseId,
|
|
@@ -256,25 +260,50 @@ async function executeStatement(query, auth, options = {}) {
|
|
|
256
260
|
disposition: options.disposition,
|
|
257
261
|
format: options.format,
|
|
258
262
|
on_wait_timeout: options.on_wait_timeout ?? "CONTINUE",
|
|
259
|
-
wait_timeout:
|
|
263
|
+
wait_timeout: waitTimeout,
|
|
260
264
|
row_limit: options.row_limit,
|
|
261
265
|
catalog: options.catalog,
|
|
262
266
|
schema: options.schema,
|
|
263
267
|
parameters: options.parameters
|
|
264
268
|
}).filter(([, v]) => v !== void 0)
|
|
265
269
|
);
|
|
270
|
+
logger?.info?.(`executeStatement Executing statement on warehouse ${warehouseId}...`);
|
|
266
271
|
let result = await postStatement(auth, request, signal);
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
const cancelStatementSafely = async () => {
|
|
273
|
+
if (cancelIssued) return;
|
|
274
|
+
logger?.info?.("executeStatement Abort signal received during executeStatement.");
|
|
275
|
+
cancelIssued = true;
|
|
276
|
+
await cancelStatement(auth, result.statement_id).catch((err) => {
|
|
277
|
+
logger?.error?.("executeStatement Failed to cancel statement after abort.", err);
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
if (signal?.aborted) {
|
|
281
|
+
await cancelStatementSafely();
|
|
282
|
+
throw new AbortError("Aborted during polling");
|
|
283
|
+
}
|
|
284
|
+
const onAbort = () => cancelStatementSafely().catch(() => {
|
|
285
|
+
});
|
|
286
|
+
try {
|
|
287
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
288
|
+
while (!TERMINAL_STATES.has(result.status.state)) {
|
|
289
|
+
logger?.info?.(`executeStatement Statement ${result.statement_id} in state ${result.status.state}; polling for status...`);
|
|
290
|
+
await delay(POLL_INTERVAL_MS, signal);
|
|
291
|
+
result = await getStatement(auth, result.statement_id, signal);
|
|
292
|
+
await emitProgress?.();
|
|
293
|
+
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (err instanceof AbortError || signal?.aborted) {
|
|
296
|
+
logger?.info?.("executeStatement Abort detected in executeStatement polling loop.");
|
|
297
|
+
await cancelStatementSafely();
|
|
271
298
|
throw new AbortError("Aborted during polling");
|
|
272
299
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
300
|
+
logger?.error?.(`executeStatement Error during executeStatement polling: ${String(err)}`);
|
|
301
|
+
throw err;
|
|
302
|
+
} finally {
|
|
303
|
+
logger?.info?.(`executeStatement Statement ${result.statement_id} reached final state: ${result.status.state}`);
|
|
304
|
+
signal?.removeEventListener("abort", onAbort);
|
|
276
305
|
}
|
|
277
|
-
await emitProgress?.(
|
|
306
|
+
await emitProgress?.();
|
|
278
307
|
if (result.status.state === "SUCCEEDED")
|
|
279
308
|
return result;
|
|
280
309
|
if (result.status.state === "CANCELED")
|
|
@@ -581,52 +610,110 @@ function convertBoolean(value) {
|
|
|
581
610
|
import { PassThrough } from "stream";
|
|
582
611
|
import { mergeStreamsFromUrls } from "@bitofsky/merge-streams";
|
|
583
612
|
function fetchStream(statementResult, auth, options = {}) {
|
|
584
|
-
const { signal, forceMerge } = options;
|
|
613
|
+
const { signal, forceMerge, logger } = options;
|
|
585
614
|
const manifest = validateSucceededResult(statementResult);
|
|
586
615
|
const format = manifest.format;
|
|
616
|
+
const statementId = statementResult.statement_id;
|
|
617
|
+
const baseLog = { statementId, manifest, format, forceMerge };
|
|
587
618
|
if (statementResult.result?.data_array) {
|
|
619
|
+
logger?.error?.(
|
|
620
|
+
`fetchStream only supports EXTERNAL_LINKS results for statement ${statementId}.`,
|
|
621
|
+
{ ...baseLog, hasDataArray: true }
|
|
622
|
+
);
|
|
588
623
|
throw new DatabricksSqlError(
|
|
589
624
|
"fetchStream only supports EXTERNAL_LINKS results",
|
|
590
625
|
"UNSUPPORTED_FORMAT",
|
|
591
|
-
|
|
626
|
+
statementId
|
|
592
627
|
);
|
|
593
628
|
}
|
|
629
|
+
logger?.info?.(`fetchStream creating stream for statement ${statementId}.`, {
|
|
630
|
+
...baseLog,
|
|
631
|
+
hasExternalLinks: Boolean(statementResult.result?.external_links?.length)
|
|
632
|
+
});
|
|
594
633
|
const output = new PassThrough();
|
|
595
634
|
if (signal) {
|
|
596
|
-
const onAbort = () =>
|
|
635
|
+
const onAbort = () => {
|
|
636
|
+
logger?.info?.(`fetchStream abort signal received while streaming statement ${statementId}.`, baseLog);
|
|
637
|
+
output.destroy(new AbortError("Stream aborted"));
|
|
638
|
+
};
|
|
597
639
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
598
640
|
output.once("close", () => signal.removeEventListener("abort", onAbort));
|
|
599
641
|
}
|
|
600
|
-
|
|
642
|
+
output.on("error", (err) => {
|
|
643
|
+
if (err instanceof AbortError)
|
|
644
|
+
return;
|
|
645
|
+
if (output.listenerCount("error") === 1)
|
|
646
|
+
throw err;
|
|
647
|
+
});
|
|
648
|
+
mergeChunksToStream(statementResult, auth, manifest, format, output, signal, forceMerge, logger).catch((err) => {
|
|
649
|
+
logger?.error?.(`fetchStream error while streaming statement ${statementId}.`, {
|
|
650
|
+
...baseLog,
|
|
651
|
+
error: err
|
|
652
|
+
});
|
|
653
|
+
output.destroy(err);
|
|
654
|
+
});
|
|
601
655
|
return output;
|
|
602
656
|
}
|
|
603
|
-
async function mergeChunksToStream(statementResult, auth, manifest, format, output, signal, forceMerge) {
|
|
657
|
+
async function mergeChunksToStream(statementResult, auth, manifest, format, output, signal, forceMerge, logger) {
|
|
658
|
+
const statementId = statementResult.statement_id;
|
|
659
|
+
const baseLog = { statementId, manifest, format, forceMerge };
|
|
660
|
+
logger?.info?.(`fetchStream collecting external links for statement ${statementId}.`, baseLog);
|
|
604
661
|
const urls = await collectExternalUrls(statementResult, auth, manifest, signal);
|
|
605
|
-
if (urls.length === 0)
|
|
662
|
+
if (urls.length === 0) {
|
|
663
|
+
logger?.info?.(`fetchStream no external links found for statement ${statementId}.`, baseLog);
|
|
606
664
|
return void output.end();
|
|
607
|
-
|
|
665
|
+
}
|
|
666
|
+
if (urls.length === 1 && !forceMerge) {
|
|
667
|
+
logger?.info?.(`fetchStream piping single external link for statement ${statementId}.`, {
|
|
668
|
+
...baseLog,
|
|
669
|
+
urlCount: urls.length
|
|
670
|
+
});
|
|
608
671
|
return pipeUrlToOutput(urls[0], output, signal);
|
|
672
|
+
}
|
|
673
|
+
logger?.info?.(`fetchStream merging ${urls.length} external links for statement ${statementId}.`, {
|
|
674
|
+
...baseLog,
|
|
675
|
+
urlCount: urls.length
|
|
676
|
+
});
|
|
609
677
|
return mergeStreamsFromUrls(format, signal ? { urls, output, signal } : { urls, output });
|
|
610
678
|
}
|
|
611
679
|
async function collectExternalUrls(statementResult, auth, manifest, signal) {
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
return urls;
|
|
680
|
+
const chunkUrls = /* @__PURE__ */ new Map();
|
|
681
|
+
addChunkLinks(chunkUrls, statementResult.result?.external_links);
|
|
615
682
|
if (!manifest.total_chunk_count)
|
|
616
|
-
return
|
|
617
|
-
const chunkUrls = [];
|
|
683
|
+
return flattenChunkUrls(chunkUrls);
|
|
618
684
|
for (let i = 0; i < manifest.total_chunk_count; i++) {
|
|
685
|
+
if (chunkUrls.has(i))
|
|
686
|
+
continue;
|
|
619
687
|
if (signal?.aborted)
|
|
620
688
|
throw new AbortError("Aborted while collecting URLs");
|
|
621
689
|
const chunkData = await getChunk(auth, statementResult.statement_id, i, signal);
|
|
622
|
-
chunkUrls
|
|
690
|
+
addChunkLinks(chunkUrls, chunkData.external_links);
|
|
623
691
|
}
|
|
624
|
-
return chunkUrls;
|
|
692
|
+
return flattenChunkUrls(chunkUrls);
|
|
625
693
|
}
|
|
626
|
-
function
|
|
694
|
+
function addChunkLinks(chunkUrls, externalLinks) {
|
|
627
695
|
if (!externalLinks)
|
|
696
|
+
return;
|
|
697
|
+
for (const link of externalLinks) {
|
|
698
|
+
if (!isNonEmptyString(link.external_link))
|
|
699
|
+
continue;
|
|
700
|
+
const existing = chunkUrls.get(link.chunk_index);
|
|
701
|
+
if (existing) {
|
|
702
|
+
existing.push(link.external_link);
|
|
703
|
+
} else {
|
|
704
|
+
chunkUrls.set(link.chunk_index, [link.external_link]);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function flattenChunkUrls(chunkUrls) {
|
|
709
|
+
if (chunkUrls.size === 0)
|
|
628
710
|
return [];
|
|
629
|
-
|
|
711
|
+
const sorted = [...chunkUrls.entries()].sort(([a], [b]) => a - b);
|
|
712
|
+
const urls = [];
|
|
713
|
+
for (const [, links] of sorted) {
|
|
714
|
+
urls.push(...links);
|
|
715
|
+
}
|
|
716
|
+
return urls;
|
|
630
717
|
}
|
|
631
718
|
function isNonEmptyString(value) {
|
|
632
719
|
return typeof value === "string" && value.length > 0;
|
|
@@ -634,31 +721,46 @@ function isNonEmptyString(value) {
|
|
|
634
721
|
|
|
635
722
|
// src/api/fetchRow.ts
|
|
636
723
|
async function fetchRow(statementResult, auth, options = {}) {
|
|
637
|
-
const { signal, onEachRow, format } = options;
|
|
724
|
+
const { signal, onEachRow, format, logger } = options;
|
|
638
725
|
const manifest = validateSucceededResult(statementResult);
|
|
726
|
+
const statementId = statementResult.statement_id;
|
|
727
|
+
const logContext = { statementId, manifest, requestedFormat: format };
|
|
639
728
|
const mapRow = createRowMapper(manifest, format);
|
|
729
|
+
logger?.info?.(`fetchRow fetching rows for statement ${statementId}.`, {
|
|
730
|
+
...logContext,
|
|
731
|
+
resultType: statementResult.result?.external_links ? "EXTERNAL_LINKS" : "INLINE"
|
|
732
|
+
});
|
|
640
733
|
if (statementResult.result?.external_links) {
|
|
641
734
|
if (manifest.format !== "JSON_ARRAY") {
|
|
735
|
+
logger?.error?.(`fetchRow only supports JSON_ARRAY for external_links; got ${manifest.format}.`, logContext);
|
|
642
736
|
throw new DatabricksSqlError(
|
|
643
737
|
`fetchRow only supports JSON_ARRAY for external_links. Received: ${manifest.format}`,
|
|
644
738
|
"UNSUPPORTED_FORMAT",
|
|
645
|
-
|
|
739
|
+
statementId
|
|
646
740
|
);
|
|
647
741
|
}
|
|
648
|
-
|
|
649
|
-
|
|
742
|
+
logger?.info?.(`fetchRow streaming external links for statement ${statementId}.`, logContext);
|
|
743
|
+
const stream = fetchStream(statementResult, auth, {
|
|
744
|
+
...signal ? { signal } : {},
|
|
745
|
+
...logger ? { logger } : {}
|
|
746
|
+
});
|
|
747
|
+
await consumeJsonArrayStream(stream, mapRow, onEachRow, signal, logger, logContext);
|
|
650
748
|
return;
|
|
651
749
|
}
|
|
652
750
|
const totalChunks = manifest.total_chunk_count;
|
|
653
751
|
const dataArray = statementResult.result?.data_array;
|
|
654
752
|
if (dataArray) {
|
|
753
|
+
logger?.info?.(`fetchRow processing inline rows for statement ${statementId}.`, {
|
|
754
|
+
...logContext,
|
|
755
|
+
inlineRows: dataArray.length
|
|
756
|
+
});
|
|
655
757
|
for (const row of dataArray) {
|
|
656
758
|
if (signal?.aborted) throw new AbortError("Aborted");
|
|
657
759
|
onEachRow?.(mapRow(row));
|
|
658
760
|
}
|
|
659
761
|
}
|
|
660
762
|
if (totalChunks > 1) {
|
|
661
|
-
|
|
763
|
+
logger?.info?.(`fetchRow processing ${totalChunks} chunks for statement ${statementId}.`, logContext);
|
|
662
764
|
for (let chunkIndex = 1; chunkIndex < totalChunks; chunkIndex++) {
|
|
663
765
|
if (signal?.aborted) throw new AbortError("Aborted");
|
|
664
766
|
const chunk = await getChunk(auth, statementId, chunkIndex, signal);
|
|
@@ -677,10 +779,14 @@ async function fetchRow(statementResult, auth, options = {}) {
|
|
|
677
779
|
}
|
|
678
780
|
}
|
|
679
781
|
}
|
|
680
|
-
async function consumeJsonArrayStream(stream, mapRow, onEachRow, signal) {
|
|
782
|
+
async function consumeJsonArrayStream(stream, mapRow, onEachRow, signal, logger, logContext) {
|
|
681
783
|
const jsonStream = stream.pipe(parser()).pipe(streamArray());
|
|
682
784
|
for await (const item of jsonStream) {
|
|
683
785
|
if (signal?.aborted) {
|
|
786
|
+
logger?.info?.("fetchRow abort detected while streaming JSON_ARRAY rows.", {
|
|
787
|
+
...logContext,
|
|
788
|
+
aborted: signal.aborted
|
|
789
|
+
});
|
|
684
790
|
stream.destroy(new AbortError("Aborted"));
|
|
685
791
|
throw new AbortError("Aborted");
|
|
686
792
|
}
|
|
@@ -698,44 +804,74 @@ async function consumeJsonArrayStream(stream, mapRow, onEachRow, signal) {
|
|
|
698
804
|
// src/api/fetchAll.ts
|
|
699
805
|
async function fetchAll(statementResult, auth, options = {}) {
|
|
700
806
|
const rows = [];
|
|
807
|
+
const statementId = statementResult.statement_id;
|
|
808
|
+
const manifest = statementResult.manifest;
|
|
809
|
+
const logContext = { statementId, manifest, requestedFormat: options.format };
|
|
701
810
|
const fetchOptions = {
|
|
702
811
|
// Collect rows as they are streamed in.
|
|
703
812
|
onEachRow: (row) => {
|
|
704
813
|
rows.push(row);
|
|
705
814
|
}
|
|
706
815
|
};
|
|
816
|
+
const { logger } = options;
|
|
817
|
+
logger?.info?.(`fetchAll fetching all rows for statement ${statementId}.`, logContext);
|
|
707
818
|
if (options.signal)
|
|
708
819
|
fetchOptions.signal = options.signal;
|
|
709
820
|
if (options.format)
|
|
710
821
|
fetchOptions.format = options.format;
|
|
822
|
+
if (options.logger)
|
|
823
|
+
fetchOptions.logger = options.logger;
|
|
711
824
|
await fetchRow(statementResult, auth, fetchOptions);
|
|
825
|
+
logger?.info?.(`fetchAll fetched ${rows.length} rows for statement ${statementId}.`, {
|
|
826
|
+
...logContext,
|
|
827
|
+
rowCount: rows.length,
|
|
828
|
+
resolvedFormat: options.format ?? manifest?.format
|
|
829
|
+
});
|
|
712
830
|
return rows;
|
|
713
831
|
}
|
|
714
832
|
|
|
715
833
|
// src/api/mergeExternalLinks.ts
|
|
716
834
|
async function mergeExternalLinks(statementResult, auth, options) {
|
|
717
|
-
const { signal, mergeStreamToExternalLink, forceMerge } = options;
|
|
718
|
-
|
|
835
|
+
const { signal, mergeStreamToExternalLink, forceMerge, logger } = options;
|
|
836
|
+
const statementId = statementResult.statement_id;
|
|
837
|
+
const manifest = statementResult.manifest;
|
|
838
|
+
const externalLinks = statementResult.result?.external_links;
|
|
839
|
+
const totalChunks = manifest?.total_chunk_count ?? 0;
|
|
840
|
+
const logContext = { statementId, manifest, totalChunks, forceMerge };
|
|
841
|
+
if (!externalLinks) {
|
|
842
|
+
logger?.info?.(`mergeExternalLinks no external links to merge for statement ${statementId}.`, logContext);
|
|
719
843
|
return statementResult;
|
|
844
|
+
}
|
|
720
845
|
if (!forceMerge) {
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
846
|
+
const isSingleChunk = totalChunks <= 1;
|
|
847
|
+
if (isSingleChunk) {
|
|
848
|
+
logger?.info?.(`mergeExternalLinks skipping merge for single external link in statement ${statementId}.`, {
|
|
849
|
+
...logContext,
|
|
850
|
+
totalChunks
|
|
851
|
+
});
|
|
725
852
|
return statementResult;
|
|
853
|
+
}
|
|
726
854
|
}
|
|
855
|
+
logger?.info?.(`mergeExternalLinks merging external links for statement ${statementId}.`, logContext);
|
|
727
856
|
const stream = fetchStream(statementResult, auth, {
|
|
728
857
|
...signal ? { signal } : {},
|
|
729
|
-
...forceMerge !== void 0 ? { forceMerge } : {}
|
|
858
|
+
...forceMerge !== void 0 ? { forceMerge } : {},
|
|
859
|
+
...logger ? { logger } : {}
|
|
730
860
|
});
|
|
861
|
+
logger?.info?.(`mergeExternalLinks uploading merged external link for statement ${statementId}.`, logContext);
|
|
731
862
|
const uploadResult = await mergeStreamToExternalLink(stream);
|
|
732
|
-
|
|
733
|
-
|
|
863
|
+
logger?.info?.(`mergeExternalLinks uploaded merged external link for statement ${statementId}.`, {
|
|
864
|
+
...logContext,
|
|
865
|
+
byteCount: uploadResult.byte_count,
|
|
866
|
+
expiration: uploadResult.expiration
|
|
867
|
+
});
|
|
868
|
+
const validatedManifest = validateSucceededResult(statementResult);
|
|
869
|
+
const totalRowCount = validatedManifest.total_row_count ?? 0;
|
|
734
870
|
return {
|
|
735
871
|
statement_id: statementResult.statement_id,
|
|
736
872
|
status: statementResult.status,
|
|
737
873
|
manifest: {
|
|
738
|
-
...
|
|
874
|
+
...validatedManifest,
|
|
739
875
|
total_chunk_count: 1,
|
|
740
876
|
total_byte_count: uploadResult.byte_count,
|
|
741
877
|
chunks: [
|