@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/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
- try {
240
- const queryInfo = await getQueryMetrics(auth, statementId, signal);
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 (statementId) => onProgress(result.status, enableMetrics ? await fetchMetrics(auth, statementId, signal) : void 0) : void 0;
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: options.wait_timeout ?? "50s",
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
- while (!TERMINAL_STATES.has(result.status.state)) {
268
- if (signal?.aborted) {
269
- await cancelStatement(auth, result.statement_id).catch(() => {
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
- await emitProgress?.(result.statement_id);
274
- await delay(POLL_INTERVAL_MS, signal);
275
- result = await getStatement(auth, result.statement_id, signal);
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?.(result.statement_id);
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
- statementResult.statement_id
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 = () => output.destroy(new AbortError("Stream aborted"));
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
- mergeChunksToStream(statementResult, auth, manifest, format, output, signal, forceMerge).catch((err) => output.destroy(err));
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
- if (urls.length === 1 && !forceMerge)
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 urls = extractExternalLinks(statementResult.result?.external_links);
613
- if (urls.length > 0)
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.push(...extractExternalLinks(chunkData.external_links));
690
+ addChunkLinks(chunkUrls, chunkData.external_links);
623
691
  }
624
- return chunkUrls;
692
+ return flattenChunkUrls(chunkUrls);
625
693
  }
626
- function extractExternalLinks(externalLinks) {
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
- return externalLinks.map((link) => link.external_link).filter(isNonEmptyString);
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
- statementResult.statement_id
739
+ statementId
646
740
  );
647
741
  }
648
- const stream = fetchStream(statementResult, auth, signal ? { signal } : {});
649
- await consumeJsonArrayStream(stream, mapRow, onEachRow, signal);
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
- const statementId = statementResult.statement_id;
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
- if (!statementResult.result?.external_links)
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 totalChunks = statementResult.manifest?.total_chunk_count;
722
- const externalLinks = statementResult.result.external_links;
723
- const isSingleChunk = totalChunks === void 0 ? externalLinks.length <= 1 : totalChunks <= 1;
724
- if (isSingleChunk && externalLinks.length <= 1)
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
- const manifest = validateSucceededResult(statementResult);
733
- const totalRowCount = manifest.total_row_count ?? 0;
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
- ...manifest,
874
+ ...validatedManifest,
739
875
  total_chunk_count: 1,
740
876
  total_byte_count: uploadResult.byte_count,
741
877
  chunks: [