@sixb/ducklake 0.1.1 → 0.1.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ducklake-row-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-row-reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,UAAU,EAAE,MAAM,YAAY,CAAA;AACrE,OAAO,KAAK,EAAkB,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAEnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAE9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAKxE;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,SAAS,EAAE,sBAAsB;IAG7C,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAAC,UAAU,CAAC;YAoDxD,aAAa;YAad,cAAc;IAuB5B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,uBAAuB;CAKhC"}
1
+ {"version":3,"file":"ducklake-row-reader.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-row-reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA2B,UAAU,EAAE,MAAM,YAAY,CAAA;AACrE,OAAO,KAAK,EAAkB,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAEnF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAGtD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAE9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAYxE;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,SAAS,EAAE,sBAAsB;IAG7C,QAAQ,CAAC,KAAK,EAAE,oBAAoB,GAAG,aAAa,CAAC,UAAU,CAAC;YAoExD,aAAa;YAad,cAAc;IAuB5B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,uBAAuB;CAKhC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ducklake-sql-executor.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-sql-executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA8C,UAAU,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAEL,KAAK,wBAAwB,EAE7B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EAEpB,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAQtD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA;AAyB5E;;GAEG;AACH,qBAAa,mBAAoB,YAAW,eAAe,CAAC,QAAQ,CAAC;IASjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAZzB,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,QAAQ,CAAC,YAAY;;;;MAIpB;gBAGkB,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,EAAE,wBAAwB;IAG5C,OAAO,CAAC,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC;IAkB9E,OAAO,CAAC,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;YAkC7E,cAAc;YA2Bd,oBAAoB;YAcpB,oBAAoB;YAepB,oBAAoB;YAuBpB,+BAA+B;YAsC/B,iBAAiB;CA+BhC"}
1
+ {"version":3,"file":"ducklake-sql-executor.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-sql-executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA8C,UAAU,EAAE,MAAM,YAAY,CAAA;AACxF,OAAO,EAEL,KAAK,wBAAwB,EAE7B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EAEpB,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAStD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAA;AAoC5E;;GAEG;AACH,qBAAa,mBAAoB,YAAW,eAAe,CAAC,QAAQ,CAAC;IASjE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAZzB,QAAQ,CAAC,OAAO,EAAG,QAAQ,CAAS;IACpC,QAAQ,CAAC,YAAY;;;;MAIpB;gBAGkB,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,sBAAsB,EACjC,MAAM,EAAE,wBAAwB;IAG5C,OAAO,CAAC,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC;IAkB9E,OAAO,CAAC,KAAK,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,wBAAwB,CAAC;YAmC7E,cAAc;YA2Bd,oBAAoB;YAcpB,oBAAoB;YAepB,oBAAoB;YAuBpB,+BAA+B;YAoD/B,iBAAiB;CAuChC"}
@@ -46,6 +46,7 @@ export declare class DuckLakeWriteCoordinator {
46
46
  private versionForCommittedWrite;
47
47
  private applyStagedRows;
48
48
  private setCommitMetadata;
49
+ private ensureInitialNoOpHasSnapshot;
49
50
  private versionForNoOpCommit;
50
51
  private rollbackTransaction;
51
52
  private latestVersionForCommit;
@@ -1 +1 @@
1
- {"version":3,"file":"ducklake-write-coordinator.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-write-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EAGtB,wBAAwB,EACxB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,EACL,KAAK,sBAAsB,EAI5B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAExE,OAAO,KAAK,EAAE,sBAAsB,EAA0B,MAAM,4BAA4B,CAAA;AAKhG,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC,UAAU,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IAClD,YAAY,CACV,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAAA;CACnC;AAeD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACxD;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAHT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,sBAAsB;IAG9C,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4BpE,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAkC5D,WAAW;YAyBX,WAAW;IA4CnB,oBAAoB,CACxB,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,wBAAwB,CAAC;IAM9B,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQlF,sCAAsC,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,wBAAwB,CAAC;YAiBtB,sCAAsC;YAUtC,uBAAuB;YA2CvB,4BAA4B;YA0D5B,wBAAwB;YAiExB,eAAe;YAyBf,iBAAiB;YAkCjB,oBAAoB;YAmBpB,mBAAmB;YASnB,sBAAsB;YAUtB,uBAAuB;YAYvB,wBAAwB;YAiCxB,iBAAiB;IA0B/B,OAAO,CAAC,2BAA2B;CAgBpC"}
1
+ {"version":3,"file":"ducklake-write-coordinator.d.ts","sourceRoot":"","sources":["../../src/internal/ducklake-write-coordinator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EAGtB,wBAAwB,EACxB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAEtD,OAAO,EACL,KAAK,sBAAsB,EAI5B,MAAM,sBAAsB,CAAA;AAG7B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAA;AAC9E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAA;AAExE,OAAO,KAAK,EAAE,sBAAsB,EAA0B,MAAM,4BAA4B,CAAA;AAYhG,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAA;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC,UAAU,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IAClD,YAAY,CACV,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAAA;CACnC;AAeD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACxD;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAHT,OAAO,EAAE,sBAAsB,EAC/B,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,sBAAsB,EAChC,SAAS,EAAE,sBAAsB;IAG9C,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4BpE,UAAU,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAkC5D,WAAW;YAyBX,WAAW;IA4CnB,oBAAoB,CACxB,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,wBAAwB,CAAC;IAM9B,iBAAiB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAQlF,sCAAsC,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,iCAAiC,GACvC,OAAO,CAAC,wBAAwB,CAAC;YAiBtB,sCAAsC;YAUtC,uBAAuB;YA2CvB,4BAA4B;YA2D5B,wBAAwB;YAiExB,eAAe;YAyBf,iBAAiB;YAkCjB,4BAA4B;YA0B5B,oBAAoB;YAmBpB,mBAAmB;YASnB,sBAAsB;YAUtB,uBAAuB;YAYvB,wBAAwB;YAiCxB,iBAAiB;IA0B/B,OAAO,CAAC,2BAA2B;CAgBpC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixb/ducklake",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "DuckDB/DuckLake lake storage provider for Sixb",
5
5
  "keywords": [
6
6
  "sixb",
@@ -44,10 +44,10 @@
44
44
  "@duckdb/node-api": "^1.5.2-r.1"
45
45
  },
46
46
  "peerDependencies": {
47
- "@sixb/core": "^0.1.1"
47
+ "@sixb/core": "^0.1.4"
48
48
  },
49
49
  "devDependencies": {
50
- "@sixb/core": "0.1.1",
50
+ "@sixb/core": "0.1.4",
51
51
  "@types/bun": "latest",
52
52
  "typescript": "^5.7.0"
53
53
  },
@@ -108,6 +108,7 @@ class DuckLakeMergeSession implements LakeMergeSession {
108
108
  if (batch.length >= MERGE_CHANGE_BATCH_SIZE) {
109
109
  await this.appendBatchToStagingTable(batch)
110
110
  batch = []
111
+ await Bun.sleep(0)
111
112
  }
112
113
  }
113
114
 
@@ -2,6 +2,7 @@ import type { DatasetColumnDefinition, DatasetRow } from "@sixb/core"
2
2
  import type { DatasetVersion, ReadDatasetRowsInput } from "@sixb/core/lake-storage"
3
3
  import { LakeStorageError } from "@sixb/core/lake-storage"
4
4
  import type { DuckLakeStorageOptions } from "../types"
5
+ import { getBigIntLike } from "./duckdb-row"
5
6
  import type { DuckDbQueryRuntime } from "./duckdb-runtime"
6
7
  import type { DuckLakeConnectionManager } from "./ducklake-connection-manager"
7
8
  import { type DatasetTableRef, resolveDatasetTableRef } from "./ducklake-dataset-table-ref"
@@ -10,6 +11,13 @@ import { normalizeReadValue } from "./schema"
10
11
  import { qualifiedTableName, quoteIdentifier } from "./sql"
11
12
  import { parseVersionId } from "./versions"
12
13
 
14
+ // Large reads release the provider's single DuckDB queue slot between bounded pages. Besides
15
+ // bounding converted-row memory, this lets a JavaScript pipeline feed one DuckLake dataset into
16
+ // another: the destination appender can use the runtime after each page instead of waiting forever
17
+ // behind its own still-open source stream.
18
+ const READ_PAGE_ROWS = 5_000
19
+ const PHYSICAL_ROW_ID_ALIAS = "__sixb_physical_row_id"
20
+
13
21
  /**
14
22
  * Reads dataset rows from DuckLake snapshots.
15
23
  *
@@ -27,50 +35,66 @@ export class DuckLakeRowReader {
27
35
  async *readRows(input: ReadDatasetRowsInput): AsyncIterable<DatasetRow> {
28
36
  this.connections.assertOpen()
29
37
 
38
+ // Keep the attachment lease across pages so local catalogs do not pay DETACH/ATTACH for every
39
+ // page. Individual query() calls still release the DuckDB queue before rows are yielded.
30
40
  const lease = await this.connections.acquireAttachedRuntime()
31
41
  try {
32
42
  const runtime = lease.runtime
33
-
34
- // Step 1: resolve the physical table from DuckLake metadata directly. Row
35
- // previews must not go through broad catalog introspection.
43
+ // Resolve latest exactly once so a paged read stays pinned even if another writer commits
44
+ // between pages. DuckLake snapshots are immutable; every page names this snapshot.
36
45
  const tableRef = await resolveDatasetTableRef(this.options, runtime, input.datasetId)
37
46
  if (!tableRef) {
38
47
  throw new LakeStorageError(`[SixbDuckLake] Unknown dataset '${input.datasetId}'.`)
39
48
  }
40
-
41
49
  const version = await this.resolveVersion(runtime, tableRef, input.versionId)
42
- const snapshotId = parseVersionId(version.versionId)
43
50
  const selectedColumns = this.resolveReadColumns(
44
51
  tableRef.datasetId,
45
52
  version.schema,
46
53
  input.columns
47
54
  )
48
-
49
- // Step 2: query DuckLake at the exact snapshot id. Version ids are just
50
- // `ducklake:<snapshot_id>`, so reads can use native DuckLake time travel
51
- // directly after validation.
55
+ const snapshotId = parseVersionId(version.versionId)
52
56
  const columnsSql = selectedColumns.map((column) => quoteIdentifier(column.name)).join(", ")
53
57
  const tableSql = qualifiedTableName(this.options, tableRef.tableName)
54
58
  // A user column named `rowid` shadows DuckDB's virtual row id. In that edge case the
55
59
  // connection-level invariant still preserves insertion order; otherwise make it explicit.
56
- const orderSql = version.schema.columns.some(
57
- (column) => column.name.toLowerCase() === "rowid"
58
- )
59
- ? ""
60
- : " ORDER BY rowid"
61
- const limitSql =
62
- input.limit === undefined ? "" : ` LIMIT ${Math.max(0, Math.trunc(input.limit))}`
63
- const offsetSql =
64
- input.offset === undefined ? "" : ` OFFSET ${Math.max(0, Math.trunc(input.offset))}`
65
- const sql = `SELECT ${columnsSql} FROM ${tableSql} AT (VERSION => ${snapshotId})${orderSql}${limitSql}${offsetSql}`
66
-
67
- // HTTP previews are bounded, so materialize them eagerly and release the
68
- // runtime queue as soon as DuckDB has returned the small page.
69
- const rows = input.limit === undefined ? runtime.streamRows(sql) : await runtime.query(sql)
70
-
71
- // Step 3: DuckDB returns driver-native values. Normalize each projected
72
- // column through the schema that was active at the resolved version.
73
- yield* this.normalizeRows(rows, selectedColumns)
60
+ const schemaNames = new Set(version.schema.columns.map((column) => column.name.toLowerCase()))
61
+ const canUsePhysicalRowId =
62
+ !schemaNames.has("rowid") && !schemaNames.has(PHYSICAL_ROW_ID_ALIAS.toLowerCase())
63
+ const orderSql = canUsePhysicalRowId ? " ORDER BY rowid" : ""
64
+ const baseSql = `SELECT ${columnsSql} FROM ${tableSql} AT (VERSION => ${snapshotId})`
65
+ const requestedOffset = Math.max(0, Math.trunc(input.offset ?? 0))
66
+ let remaining = input.limit === undefined ? undefined : Math.max(0, Math.trunc(input.limit))
67
+ if (remaining === 0) return
68
+
69
+ // Materialize one bounded page and release its queue operation before yielding it. A
70
+ // consumer may enqueue another DuckLake operation while processing the yielded rows.
71
+ // Holding a native stream open here would make that operation wait behind itself.
72
+ let offset = requestedOffset
73
+ let physicalCursor: bigint | null = null
74
+ while (true) {
75
+ const pageRows =
76
+ remaining === undefined ? READ_PAGE_ROWS : Math.min(READ_PAGE_ROWS, remaining)
77
+ const pageSql = canUsePhysicalRowId
78
+ ? `SELECT rowid AS ${quoteIdentifier(PHYSICAL_ROW_ID_ALIAS)}, ${columnsSql}
79
+ FROM ${tableSql} AT (VERSION => ${snapshotId})
80
+ ${physicalCursor === null ? "" : `WHERE rowid > ${physicalCursor}`}
81
+ ORDER BY rowid LIMIT ${pageRows}
82
+ ${physicalCursor === null && offset > 0 ? `OFFSET ${offset}` : ""}`
83
+ : `${baseSql}${orderSql} LIMIT ${pageRows} OFFSET ${offset}`
84
+ const rows = await runtime.query(pageSql)
85
+ if (rows.length === 0) return
86
+
87
+ yield* this.normalizeRows(rows, selectedColumns)
88
+ offset += rows.length
89
+ if (remaining !== undefined) {
90
+ remaining -= rows.length
91
+ if (remaining === 0) return
92
+ }
93
+ if (canUsePhysicalRowId) {
94
+ physicalCursor = getBigIntLike(rows[rows.length - 1]!, PHYSICAL_ROW_ID_ALIAS)
95
+ }
96
+ if (rows.length < pageRows) return
97
+ }
74
98
  } finally {
75
99
  await lease.release()
76
100
  }
@@ -15,13 +15,14 @@ import {
15
15
  applyDatasetRowsFromRelation,
16
16
  assertDatasetWriteMode,
17
17
  } from "./dataset-row-commit"
18
+ import { findDatasetColumnTypeForDuckDbSql } from "./duckdb-column-types"
18
19
  import { getString } from "./duckdb-row"
19
20
  import type { DuckDbQueryRuntime } from "./duckdb-runtime"
20
21
  import type { DuckLakeConnectionManager } from "./ducklake-connection-manager"
21
22
  import type { DuckLakeDatasetCatalog } from "./ducklake-dataset-catalog"
22
23
  import type { DuckLakeSnapshotReader } from "./ducklake-snapshot-reader"
23
24
  import type { DuckLakeWriteCoordinator } from "./ducklake-write-coordinator"
24
- import { duckDbTypeToDatasetColumnType } from "./schema"
25
+ import { datasetColumnTypeToDuckDbSql } from "./schema"
25
26
  import { quoteIdentifier } from "./sql"
26
27
  import {
27
28
  type DuckLakeSqlTransformSourceRelation,
@@ -42,9 +43,20 @@ interface ApplySqlTransformInput {
42
43
  readonly target: DatasetDefinition
43
44
  readonly mode: DatasetWriteMode
44
45
  readonly sql: string
46
+ readonly checkedCasts: readonly SqlTransformCheckedCast[]
45
47
  readonly previousRowCount?: number
46
48
  }
47
49
 
50
+ interface SqlTransformResultColumn {
51
+ readonly name: string
52
+ readonly typeSql: string
53
+ }
54
+
55
+ interface SqlTransformCheckedCast {
56
+ readonly columnName: string
57
+ readonly targetTypeSql: string
58
+ }
59
+
48
60
  /**
49
61
  * DuckDB-backed SQL transform executor for DuckLakeStorage.
50
62
  */
@@ -95,7 +107,7 @@ export class DuckLakeSqlExecutor implements LakeSqlExecutor<"duckdb"> {
95
107
  sql: input.sql,
96
108
  })
97
109
 
98
- await this.assertResultSchemaMatchesTarget(runtime, target, sql)
110
+ const checkedCasts = await this.assertResultSchemaMatchesTarget(runtime, target, sql)
99
111
 
100
112
  return this.writes.commitDatasetVersionOnExclusiveRuntime(runtime, {
101
113
  dataset: target,
@@ -110,6 +122,7 @@ export class DuckLakeSqlExecutor implements LakeSqlExecutor<"duckdb"> {
110
122
  target,
111
123
  mode: input.mode,
112
124
  sql,
125
+ checkedCasts,
113
126
  previousRowCount: context.previousRowCount,
114
127
  }),
115
128
  })
@@ -199,12 +212,13 @@ export class DuckLakeSqlExecutor implements LakeSqlExecutor<"duckdb"> {
199
212
  runtime: DuckDbQueryRuntime,
200
213
  target: DatasetDefinition,
201
214
  sql: string
202
- ): Promise<void> {
215
+ ): Promise<readonly SqlTransformCheckedCast[]> {
203
216
  const rows = await runtime.query(
204
217
  `DESCRIBE SELECT * FROM (${sql}) AS sixb_sql_transform_result_schema`
205
218
  )
206
219
  const actualColumns = rows.map((row) => resultColumnFromDescribeRow(row))
207
220
  const expectedColumns = target.schema.columns
221
+ const checkedCasts: SqlTransformCheckedCast[] = []
208
222
 
209
223
  if (actualColumns.length !== expectedColumns.length) {
210
224
  throwResultSchemaMismatch(
@@ -224,13 +238,26 @@ export class DuckLakeSqlExecutor implements LakeSqlExecutor<"duckdb"> {
224
238
  )
225
239
  }
226
240
 
227
- if (actual.type !== expected.type) {
228
- throwResultSchemaMismatch(
229
- target.id,
230
- `column '${expected.name}' should have type '${expected.type}', got '${actual.type}'`
231
- )
241
+ const actualDatasetType = findDatasetColumnTypeForDuckDbSql(actual.typeSql)
242
+ if (actualDatasetType === expected.type) {
243
+ continue
232
244
  }
245
+
246
+ if (isCheckedInt64Cast(actual.typeSql, expected.type)) {
247
+ checkedCasts.push({
248
+ columnName: expected.name,
249
+ targetTypeSql: datasetColumnTypeToDuckDbSql(expected.type),
250
+ })
251
+ continue
252
+ }
253
+
254
+ throwResultSchemaMismatch(
255
+ target.id,
256
+ `column '${expected.name}' should have type '${expected.type}', got '${actualDatasetType ?? actual.typeSql}'`
257
+ )
233
258
  }
259
+
260
+ return checkedCasts
234
261
  }
235
262
 
236
263
  private async applySqlTransform(input: ApplySqlTransformInput): Promise<ApplyDatasetRowsResult> {
@@ -243,6 +270,14 @@ export class DuckLakeSqlExecutor implements LakeSqlExecutor<"duckdb"> {
243
270
 
244
271
  let result: ApplyDatasetRowsResult
245
272
  try {
273
+ for (const checkedCast of input.checkedCasts) {
274
+ await input.runtime.run(
275
+ `ALTER TABLE ${tempTable} ALTER COLUMN ${quoteIdentifier(
276
+ checkedCast.columnName
277
+ )} SET DATA TYPE ${checkedCast.targetTypeSql}`
278
+ )
279
+ }
280
+
246
281
  result = await applyDatasetRowsFromRelation({
247
282
  options: this.options,
248
283
  runtime: input.runtime,
@@ -280,11 +315,19 @@ function buildPreviewSql(sql: string, limit: number): string {
280
315
 
281
316
  function resultColumnFromDescribeRow(
282
317
  row: Readonly<Record<string, unknown>>
283
- ): DatasetColumnDefinition {
318
+ ): SqlTransformResultColumn {
284
319
  const name = getString(row, "column_name")
285
- const type = duckDbTypeToDatasetColumnType(getString(row, "column_type"))
320
+ const typeSql = getString(row, "column_type")
321
+
322
+ return { name, typeSql }
323
+ }
286
324
 
287
- return { name, type }
325
+ function isCheckedInt64Cast(
326
+ actualTypeSql: string,
327
+ expectedType: DatasetColumnDefinition["type"]
328
+ ): boolean {
329
+ const normalizedType = actualTypeSql.trim().toUpperCase()
330
+ return expectedType === "int64" && (normalizedType === "HUGEINT" || normalizedType === "INT128")
288
331
  }
289
332
 
290
333
  function normalizePreviewRow(row: Readonly<Record<string, unknown>>): DatasetRow {
@@ -27,7 +27,14 @@ import type { DuckLakeDatasetCatalog } from "./ducklake-dataset-catalog"
27
27
  import { createDuckLakeMergeSession, type DuckLakeCommitMergeInput } from "./ducklake-merge-session"
28
28
  import type { DuckLakeSnapshotReader, DuckLakeVersionSummary } from "./ducklake-snapshot-reader"
29
29
  import { createDuckLakeWriteSession, type DuckLakeCommitWriteInput } from "./ducklake-write-session"
30
- import { duckLakeAlias, duckLakeMetadataTableName, quoteIdentifier, quoteSqlString } from "./sql"
30
+ import { encodeDatasetTableName } from "./names"
31
+ import {
32
+ duckLakeAlias,
33
+ duckLakeMetadataTableName,
34
+ qualifiedTableName,
35
+ quoteIdentifier,
36
+ quoteSqlString,
37
+ } from "./sql"
31
38
  import { parseCommitMetadata, type SixbCommitMetadata } from "./versions"
32
39
 
33
40
  export interface DuckLakeCommitDatasetVersionInput {
@@ -325,6 +332,7 @@ export class DuckLakeWriteCoordinator {
325
332
  previousRowCount: latestVersion?.rowCount,
326
333
  validatedPrimaryKeyColumns: latestVersion?.validatedPrimaryKeyColumns,
327
334
  })
335
+ await this.ensureInitialNoOpHasSnapshot(input, changeResult, latestVersion)
328
336
  const commitId = randomUUID()
329
337
  await this.setCommitMetadata(
330
338
  input,
@@ -479,6 +487,32 @@ export class DuckLakeWriteCoordinator {
479
487
  )
480
488
  }
481
489
 
490
+ private async ensureInitialNoOpHasSnapshot(
491
+ input: DuckLakeCommitVersionOutcomeRuntimeInput,
492
+ changeResult: ApplyDatasetRowsResult,
493
+ knownLatestVersion: DuckLakeVersionSummary | null
494
+ ): Promise<void> {
495
+ if (changeResult.dataChangeExpected || input.allowInitialNoOp) {
496
+ return
497
+ }
498
+
499
+ const latestVersion =
500
+ knownLatestVersion ??
501
+ (await this.snapshots.getLatestVersionSummaryForDefinition(input.runtime, input.dataset))
502
+ if (latestVersion) {
503
+ return
504
+ }
505
+
506
+ // DuckLake 1.5.2 does not create a snapshot for a transaction whose data and metadata are
507
+ // unchanged, and set_commit_message alone cannot force one. Reapplying the current table
508
+ // comment is an idempotent metadata write: it leaves the visible DatasetDefinition unchanged
509
+ // while giving the first empty write a real DuckLake snapshot for time travel and lineage.
510
+ const table = qualifiedTableName(this.options, encodeDatasetTableName(input.dataset.id))
511
+ const descriptionSql =
512
+ input.dataset.description === undefined ? "NULL" : quoteSqlString(input.dataset.description)
513
+ await input.runtime.run(`COMMENT ON TABLE ${table} IS ${descriptionSql}`)
514
+ }
515
+
482
516
  private async versionForNoOpCommit(
483
517
  runtime: DuckDbQueryRuntime,
484
518
  definition: DatasetDefinition,
@@ -106,6 +106,10 @@ class DuckLakeWriteSession implements LakeWriteSession {
106
106
  await this.appendBatchToStagingTable(batch, batchPrimaryKeys)
107
107
  batch = []
108
108
  batchPrimaryKeys = new Set()
109
+ // Native appender promises can settle through microtasks quickly enough to starve timers
110
+ // across a large in-memory iterable. Yield once per bounded batch so API traffic remains
111
+ // responsive without adding per-row scheduling overhead.
112
+ await Bun.sleep(0)
109
113
  }
110
114
  }
111
115