From f21b6f425208ceaa9f9b36f1c6d98ea44ffbbf37 Mon Sep 17 00:00:00 2001 From: Bidek56 Date: Sun, 24 Mar 2024 20:15:10 -0400 Subject: [PATCH 1/2] Initial version of read_delta --- Cargo.toml | 1 + ...8ab0-6b13-421c-bb67-ca09945eb281-0.parquet | Bin 0 -> 1550 bytes .../_delta_log/00000000000000000000.json | 4 ++ __tests__/io.test.ts | 9 +++ biome.json | 8 ++- bun.lockb | Bin 132386 -> 132782 bytes package.json | 4 +- polars/index.ts | 1 + polars/io.ts | 26 +++++++++ src/dataframe.rs | 26 +++++++++ src/delta.rs | 52 ++++++++++++++++++ src/lib.rs | 1 + 12 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 __tests__/examples/delta/sample.table/0-07808ab0-6b13-421c-bb67-ca09945eb281-0.parquet create mode 100644 __tests__/examples/delta/sample.table/_delta_log/00000000000000000000.json create mode 100644 src/delta.rs diff --git a/Cargo.toml b/Cargo.toml index 8df6ced9e..f98ee82fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ thiserror = "1" smartstring = { version = "1" } serde_json = { version = "1" } either = "1.9" +deltalake = "0.17.1" [dependencies.polars] features = [ diff --git a/__tests__/examples/delta/sample.table/0-07808ab0-6b13-421c-bb67-ca09945eb281-0.parquet b/__tests__/examples/delta/sample.table/0-07808ab0-6b13-421c-bb67-ca09945eb281-0.parquet new file mode 100644 index 0000000000000000000000000000000000000000..4187ad570ee4938730977d10dc9aeedc394cf8b5 GIT binary patch literal 1550 zcmcIlPfrs;6rbsCmu;y?(An&oJ?udyjVT}`MM6CIw)|6zmIi_tFWb7b#M0Jv>n7%#^;XV{ybi}appX|#SmH(wIYxyc z40o1gM=o;MJdeQPYyxANYucM7CVz2PWSl%RMjcz*ZnZ?hN?Sj;^^?oDT$+DjadN1b zi3GziiZ8~nFU74Qm2n5xRt&6Ff+l{Fm<8>2BSh?&ur3HRVu+no00gGISRB_f@(;!IBs=}fy~-lg@Zk%!bS+i07# z0cy6{#aJxcr;3n^-hLi(g?{QS_m%eF9L<#<4EZJL{sVL0P2CjPuHRz|%I`4TZ-hGP z2s+Pq`r54!woc;+>$#nfN9ms1dNqh*uitU&n9Gxt{I0HoQ8`c0PnGj5&un9_S`T*v zVKR{!PpEq}&WbLssQRae*)8b^;eI@%Rz4lD(vIsC2X;dTcAw<`JlDaTgLQ0JX=z%Y zjO&a!uAPB$`SIp_d@T02#1yqd@Bv^AA8HQ)uuy{~fEpBPs^%kLm9hfBeR^ZR`^Fwk zExe$5Kw*IbEK=AmDSm+Rn&Mxk;@MJ)2VnV8z1mdtP$m5AI)*vKEfy4se@K&5N=cmh zC`IYPi8Vlo(nB@aps=uoBvY99OSC(HEedf;87L`Fn-8XgC*{|OUnM*)g0qpY1}o$P zwzb}*#f?~xGUc2e((dn8X66Wr^{$lw%=X$r^s*D~PgL9Oi6Dwvhu%RL?d`Oh-gJKQ T_T-F1zlXCgo7WgC;XlD2S{)Bi literal 0 HcmV?d00001 diff --git a/__tests__/examples/delta/sample.table/_delta_log/00000000000000000000.json b/__tests__/examples/delta/sample.table/_delta_log/00000000000000000000.json new file mode 100644 index 000000000..2ad03164b --- /dev/null +++ b/__tests__/examples/delta/sample.table/_delta_log/00000000000000000000.json @@ -0,0 +1,4 @@ +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} +{"metaData":{"id":"b27252b0-26cc-49c5-b79d-12dd6647ffba","name":null,"description":null,"format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"foo\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"fruits\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"B\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"cars\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"createdTime":1711300448856,"configuration":{}}} +{"add":{"path":"0-07808ab0-6b13-421c-bb67-ca09945eb281-0.parquet","partitionValues":{},"size":1550,"modificationTime":1711300448855,"dataChange":true,"stats":"{\"numRecords\": 5, \"minValues\": {\"foo\": \"5\", \"fruits\": \"apple\", \"B\": 1, \"cars\": \"audi\"}, \"maxValues\": {\"foo\": \"you have a,b,c\", \"fruits\": \"banana\", \"B\": 5, \"cars\": \"beetle\"}, \"nullCount\": {\"foo\": 0, \"fruits\": 0, \"B\": 0, \"cars\": 0}}","tags":null,"deletionVector":null,"baseRowId":null,"defaultRowCommitVersion":null,"clusteringProvider":null}} +{"commitInfo":{"timestamp":1711300448856,"operation":"CREATE TABLE","operationParameters":{"location":"file:///examples/delta/sample.table","protocol":"{\"minReaderVersion\":1,\"minWriterVersion\":2}","metadata":"{\"configuration\":{},\"createdTime\":1711300448856,\"description\":null,\"format\":{\"options\":{},\"provider\":\"parquet\"},\"id\":\"b27252b0-26cc-49c5-b79d-12dd6647ffba\",\"name\":null,\"partitionColumns\":[],\"schemaString\":\"{\\\"type\\\":\\\"struct\\\",\\\"fields\\\":[{\\\"name\\\":\\\"foo\\\",\\\"type\\\":\\\"string\\\",\\\"nullable\\\":true,\\\"metadata\\\":{}},{\\\"name\\\":\\\"fruits\\\",\\\"type\\\":\\\"string\\\",\\\"nullable\\\":true,\\\"metadata\\\":{}},{\\\"name\\\":\\\"B\\\",\\\"type\\\":\\\"long\\\",\\\"nullable\\\":true,\\\"metadata\\\":{}},{\\\"name\\\":\\\"cars\\\",\\\"type\\\":\\\"string\\\",\\\"nullable\\\":true,\\\"metadata\\\":{}}]}\"}","mode":"ErrorIfExists"},"clientVersion":"delta-rs.0.17.1"}} \ No newline at end of file diff --git a/__tests__/io.test.ts b/__tests__/io.test.ts index 4e7ba9093..f1a824a14 100644 --- a/__tests__/io.test.ts +++ b/__tests__/io.test.ts @@ -18,6 +18,8 @@ const ipcpath = path.resolve(__dirname, "./examples/foods.ipc"); const jsonpath = path.resolve(__dirname, "./examples/foods.json"); // eslint-disable-next-line no-undef const singlejsonpath = path.resolve(__dirname, "./examples/single_foods.json"); +// eslint-disable-next-line no-undef +const deltapath = path.resolve(__dirname, "./examples/delta/sample.table"); describe("read:csv", () => { it("can read from a csv file", () => { const df = pl.readCSV(csvpath); @@ -235,6 +237,13 @@ describe("scan", () => { }); }); +describe("delta", () => { + test("delta:scan", async () => { + const df = await pl.readDelta(deltapath).collect(); + expect(df.shape).toEqual({ height: 5, width: 4 }); + }); +}); + describe("parquet", () => { beforeEach(() => { pl.readCSV(csvpath).writeParquet(parquetpath); diff --git a/biome.json b/biome.json index bf712a99e..e76001db8 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/1.0.0/schema.json", + "$schema": "https://biomejs.dev/schemas/1.6.2/schema.json", "organizeImports": { "enabled": false }, @@ -24,7 +24,8 @@ "ignore": [ "polars/native-polars.js", "./docs/*", - "./bin/*" + "./bin/*", + "**/delta/sample.table/*" ] }, "formatter": { @@ -33,7 +34,8 @@ "ignore": [ "polars/native-polars.js", "./docs/*", - "./bin/*" + "./bin/*", + "**/delta/sample.table/*" ] }, "javascript": { diff --git a/bun.lockb b/bun.lockb index 5f637beae36e13f89ebc4a83f5dc75a856341f97..de32f4a8ac85f362eb1861f2b7c41a7182edfaa6 100755 GIT binary patch delta 19552 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 mlLl2VRuWDXjP7qG)h9KzL)X< delta 19338 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 | number[]; projection: number; diff --git a/src/dataframe.rs b/src/dataframe.rs index 2d9fa2f9b..75d9efd71 100644 --- a/src/dataframe.rs +++ b/src/dataframe.rs @@ -1,9 +1,12 @@ +use crate::delta::read_delta_table; +use crate::export::JsLazyFrame; use crate::file::*; use crate::prelude::*; use crate::series::JsSeries; use napi::JsUnknown; use polars::frame::row::{infer_schema, Row}; use polars::frame::NullStrategy; +use polars_io::pl_async::get_runtime; use polars_io::RowIndex; use std::borrow::Borrow; @@ -271,6 +274,29 @@ pub fn read_json( Ok(df.into()) } +#[napi(object)] +pub struct ReadDeltaOptions { + pub version: Option, + pub columns: Option>, + pub parallel: Wrap, +} + +#[napi(catch_unwind)] +pub fn read_delta(path: String, + options: ReadDeltaOptions +) -> napi::Result { + let table: std::prelude::v1::Result = get_runtime().block_on(async { + read_delta_table(&path, options).await + }); + + let ldf:LazyFrame = match table { + Ok(table) => table, + Err(err) => return Err(napi::Error::from_reason(err.to_string())) + }; + + Ok(LazyFrame::from(ldf).into()) +} + #[napi(object)] pub struct ReadParquetOptions { pub columns: Option>, diff --git a/src/delta.rs b/src/delta.rs new file mode 100644 index 000000000..713c9889b --- /dev/null +++ b/src/delta.rs @@ -0,0 +1,52 @@ +use polars_core::prelude::DataFrame; +use polars_io::{parquet::ParquetReader, SerReader}; +use polars_lazy::{dsl::UnionArgs, frame::{IntoLazy, LazyFrame}}; +use polars::prelude::concat; +use deltalake::{DeltaTableBuilder, DeltaTableError}; +use crate::dataframe::ReadDeltaOptions; + +pub async fn read_delta_table(path: &str, + options: ReadDeltaOptions, + ) -> Result { + let mut db = DeltaTableBuilder::from_uri(path) + .with_allow_http(false); + + // if version specified, add it + if options.version.is_some() { + db = db.with_version(options.version.unwrap()); + } + + let dt = db.load().await?; + + // show all active files in the table + let files: Vec<_> = dt.get_file_uris()?.collect(); + + let mut df_collection: Vec = vec![]; + + for file in files.into_iter() { + let base = std::path::Path::new(path); + let file_path = std::path::Path::new(&file); + let full_path = base.join(file_path); + let mut file = std::fs::File::open(full_path).unwrap(); + + let columns = options.columns.clone(); + let parallel = options.parallel.0; + + let df = ParquetReader::new(&mut file) + .with_columns(columns) + .read_parallel(parallel) + .finish().unwrap(); + + df_collection.push(df); + } + + let empty_head = df_collection[0].clone().lazy().limit(0); + + Ok(df_collection.into_iter().fold(empty_head, |acc, df| concat([acc, df.lazy()], + UnionArgs { + rechunk: false, + parallel: false, + ..Default::default() + }).unwrap())) + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9671a06e7..e001a4efe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub mod series; pub mod set; pub mod utils; pub mod sql; +pub mod delta; pub use polars_core; From c108f08891eee3d7a85a83ce813fa07aaefc287a Mon Sep 17 00:00:00 2001 From: Bidek56 Date: Sun, 24 Mar 2024 20:21:33 -0400 Subject: [PATCH 2/2] Updating yarn.lock --- yarn.lock | 94 +++++++++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/yarn.lock b/yarn.lock index e9d388f29..94585fafe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -408,18 +408,18 @@ __metadata: languageName: node linkType: hard -"@biomejs/biome@npm:^1.6.1": - version: 1.6.1 - resolution: "@biomejs/biome@npm:1.6.1" - dependencies: - "@biomejs/cli-darwin-arm64": 1.6.1 - "@biomejs/cli-darwin-x64": 1.6.1 - "@biomejs/cli-linux-arm64": 1.6.1 - "@biomejs/cli-linux-arm64-musl": 1.6.1 - "@biomejs/cli-linux-x64": 1.6.1 - "@biomejs/cli-linux-x64-musl": 1.6.1 - "@biomejs/cli-win32-arm64": 1.6.1 - "@biomejs/cli-win32-x64": 1.6.1 +"@biomejs/biome@npm:^1.6.2": + version: 1.6.2 + resolution: "@biomejs/biome@npm:1.6.2" + dependencies: + "@biomejs/cli-darwin-arm64": 1.6.2 + "@biomejs/cli-darwin-x64": 1.6.2 + "@biomejs/cli-linux-arm64": 1.6.2 + "@biomejs/cli-linux-arm64-musl": 1.6.2 + "@biomejs/cli-linux-x64": 1.6.2 + "@biomejs/cli-linux-x64-musl": 1.6.2 + "@biomejs/cli-win32-arm64": 1.6.2 + "@biomejs/cli-win32-x64": 1.6.2 dependenciesMeta: "@biomejs/cli-darwin-arm64": optional: true @@ -439,62 +439,62 @@ __metadata: optional: true bin: biome: bin/biome - checksum: 4d012438d5ce52418ce7af4cb2285180afe767b8df2465cf7890fb2f8b01920bf29d7a8d78c5c4379b589f6e735db8f0ad7502b78ec943b65efd0e74eea5abb2 + checksum: 3e2d1b5d54aa3ebac1f758bd7ad524d7e45f95f82436900da592ff8b3a3398863a28b098e6a6799f9af33cb7367c15ef4af10399cd9d28587d0f817e977ed994 languageName: node linkType: hard -"@biomejs/cli-darwin-arm64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-darwin-arm64@npm:1.6.1" +"@biomejs/cli-darwin-arm64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-darwin-arm64@npm:1.6.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@biomejs/cli-darwin-x64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-darwin-x64@npm:1.6.1" +"@biomejs/cli-darwin-x64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-darwin-x64@npm:1.6.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@biomejs/cli-linux-arm64-musl@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-linux-arm64-musl@npm:1.6.1" +"@biomejs/cli-linux-arm64-musl@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-linux-arm64-musl@npm:1.6.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@biomejs/cli-linux-arm64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-linux-arm64@npm:1.6.1" +"@biomejs/cli-linux-arm64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-linux-arm64@npm:1.6.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@biomejs/cli-linux-x64-musl@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-linux-x64-musl@npm:1.6.1" +"@biomejs/cli-linux-x64-musl@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-linux-x64-musl@npm:1.6.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@biomejs/cli-linux-x64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-linux-x64@npm:1.6.1" +"@biomejs/cli-linux-x64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-linux-x64@npm:1.6.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@biomejs/cli-win32-arm64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-win32-arm64@npm:1.6.1" +"@biomejs/cli-win32-arm64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-win32-arm64@npm:1.6.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@biomejs/cli-win32-x64@npm:1.6.1": - version: 1.6.1 - resolution: "@biomejs/cli-win32-x64@npm:1.6.1" +"@biomejs/cli-win32-x64@npm:1.6.2": + version: 1.6.2 + resolution: "@biomejs/cli-win32-x64@npm:1.6.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -3004,7 +3004,7 @@ __metadata: version: 0.0.0-use.local resolution: "nodejs-polars@workspace:." dependencies: - "@biomejs/biome": ^1.6.1 + "@biomejs/biome": ^1.6.2 "@napi-rs/cli": ^2.18.0 "@types/chance": ^1.1.6 "@types/jest": ^29.5.12 @@ -3015,7 +3015,7 @@ __metadata: ts-jest: ^29.1.2 ts-node: ^10.9.2 typedoc: ^0.25.12 - typescript: 5.4.2 + typescript: 5.4.3 languageName: unknown linkType: soft @@ -3733,23 +3733,23 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.4.2": - version: 5.4.2 - resolution: "typescript@npm:5.4.2" +"typescript@npm:5.4.3": + version: 5.4.3 + resolution: "typescript@npm:5.4.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 96d80fde25a09bcb04d399082fb27a808a9e17c2111e43849d2aafbd642d835e4f4ef0de09b0ba795ec2a700be6c4c2c3f62bf4660c05404c948727b5bbfb32a + checksum: d74d731527e35e64d8d2dcf2f897cf8cfbc3428be0ad7c48434218ba4ae41239f53be7c90714089db1068c05cae22436af2ecba71fd36ecc5e7a9118af060198 languageName: node linkType: hard -"typescript@patch:typescript@5.4.2#~builtin": - version: 5.4.2 - resolution: "typescript@patch:typescript@npm%3A5.4.2#~builtin::version=5.4.2&hash=14eedb" +"typescript@patch:typescript@5.4.3#~builtin": + version: 5.4.3 + resolution: "typescript@patch:typescript@npm%3A5.4.3#~builtin::version=5.4.3&hash=14eedb" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: c1b669146bca5529873aae60870e243fa8140c85f57ca32c42f898f586d73ce4a6b4f6bb02ae312729e214d7f5859a0c70da3e527a116fdf5ad00c9fc733ecc6 + checksum: 3a62fe90aa79d68c9ce38ea5edb2957e62801c733b99f0e5a2b8b50922761f68f7d9a40d28c544b449866e81185cddb93cba2496d0ff3fa52ef5b1f8bcace38c languageName: node linkType: hard