Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
{"$comment":"This file only protects against accidental modifications. It is not a security mechanism and does not protect against malicious changes.","files":{"Cargo.lock":"7e9531348378d95a8608c5bf70101359593210a489cee507a8c599e86577721b","Cargo.toml":"5d4b733f52915af48de73da105287fcc64df0cbc4919f1290c142630c1d6f01d","Cargo.toml.orig":"9a56a9627787ea5478d45c50e239b1189e0ac8c43b00c7d02620e01b16974aa7","LICENSE.txt":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","NOTICE.txt":"f5b6a58f5d2082e403c49663d87566328d40cf03f16abb01f52632a09b97c55e","src/fixed.rs":"3d50978232ea100c75bf04802872e26e9a7424ce99d4bff65908b116ddf38c55","src/lib.rs":"e1ad621392f637376f45c1437892c16fd992434ac273d7acf74a9628dd7b4cd1","src/list.rs":"6d55cfa0388d70639afcf0bd961aa90dbaac538e97cfae1822ce15f60431d047","src/run.rs":"f57be3f159d23218072713a4743a8cfd062d172929a1aa15a6d488414abb35b8","src/variable.rs":"1d448346c2365873f93ae6de1eb22760910767f9dc93dda59dea32ad7db50c38"},"package":"e3a931b520a2a5e22033e01a6f2486b4cdc26f9106b759abeebc320f125e94d7"}
+724
View File
@@ -0,0 +1,724 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"const-random",
"getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "arrow-array"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8a4ab47b3f3eac60f7fd31b81e9028fda018607bcc63451aca4f2b755269862"
dependencies = [
"ahash",
"arrow-buffer",
"arrow-data",
"arrow-schema",
"chrono",
"half",
"hashbrown",
"num-complex",
"num-integer",
"num-traits",
]
[[package]]
name = "arrow-buffer"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d18b89b4c4f4811d0858175e79541fe98e33e18db3b011708bc287b1240593f"
dependencies = [
"bytes",
"half",
"num-bigint",
"num-traits",
]
[[package]]
name = "arrow-cast"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "722b5c41dd1d14d0a879a1bce92c6fe33f546101bb2acce57a209825edd075b3"
dependencies = [
"arrow-array",
"arrow-buffer",
"arrow-data",
"arrow-ord",
"arrow-schema",
"arrow-select",
"atoi",
"base64",
"chrono",
"half",
"lexical-core",
"num-traits",
"ryu",
]
[[package]]
name = "arrow-data"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1683705c63dcf0d18972759eda48489028cbbff67af7d6bef2c6b7b74ab778a"
dependencies = [
"arrow-buffer",
"arrow-schema",
"half",
"num-integer",
"num-traits",
]
[[package]]
name = "arrow-ord"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "082342947d4e5a2bcccf029a0a0397e21cb3bb8421edd9571d34fb5dd2670256"
dependencies = [
"arrow-array",
"arrow-buffer",
"arrow-data",
"arrow-schema",
"arrow-select",
]
[[package]]
name = "arrow-row"
version = "57.3.1"
dependencies = [
"arrow-array",
"arrow-buffer",
"arrow-cast",
"arrow-data",
"arrow-ord",
"arrow-schema",
"half",
"rand",
]
[[package]]
name = "arrow-schema"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4cf0d4a6609679e03002167a61074a21d7b1ad9ea65e462b2c0a97f8a3b2bc6"
[[package]]
name = "arrow-select"
version = "57.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b320d86a9806923663bb0fd9baa65ecaba81cb0cd77ff8c1768b9716b4ef891"
dependencies = [
"ahash",
"arrow-array",
"arrow-buffer",
"arrow-data",
"arrow-schema",
"num-traits",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"num-traits",
"windows-link",
]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"num-traits",
"zerocopy",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lexical-core"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594"
dependencies = [
"lexical-parse-float",
"lexical-parse-integer",
"lexical-util",
"lexical-write-float",
"lexical-write-integer",
]
[[package]]
name = "lexical-parse-float"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56"
dependencies = [
"lexical-parse-integer",
"lexical-util",
]
[[package]]
name = "lexical-parse-integer"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34"
dependencies = [
"lexical-util",
]
[[package]]
name = "lexical-util"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17"
[[package]]
name = "lexical-write-float"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361"
dependencies = [
"lexical-util",
"lexical-write-integer",
]
[[package]]
name = "lexical-write-integer"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df"
dependencies = [
"lexical-util",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libm"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zerocopy"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+76
View File
@@ -0,0 +1,76 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
rust-version = "1.85"
name = "arrow-row"
version = "57.3.1"
authors = ["Apache Arrow <dev@arrow.apache.org>"]
build = false
include = [
"benches/*.rs",
"src/**/*.rs",
"Cargo.toml",
"LICENSE.txt",
"NOTICE.txt",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Arrow row format"
homepage = "https://github.com/apache/arrow-rs"
readme = false
keywords = ["arrow"]
license = "Apache-2.0"
repository = "https://github.com/apache/arrow-rs"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
[lib]
name = "arrow_row"
path = "src/lib.rs"
bench = false
[dependencies.arrow-array]
version = "57.3.1"
[dependencies.arrow-buffer]
version = "57.3.1"
[dependencies.arrow-data]
version = "57.3.1"
[dependencies.arrow-schema]
version = "57.3.1"
[dependencies.half]
version = "2.1"
default-features = false
[dev-dependencies.arrow-cast]
version = "57.3.1"
[dev-dependencies.arrow-ord]
version = "57.3.1"
[dev-dependencies.rand]
version = "0.9"
features = [
"std",
"std_rng",
"thread_rng",
]
default-features = false
+49
View File
@@ -0,0 +1,49 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
[package]
name = "arrow-row"
version = { workspace = true }
description = "Arrow row format"
homepage = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }
keywords = { workspace = true }
include = { workspace = true }
edition = { workspace = true }
rust-version = { workspace = true }
[lib]
name = "arrow_row"
bench = false
[package.metadata.docs.rs]
all-features = true
[dependencies]
arrow-array = { workspace = true }
arrow-buffer = { workspace = true }
arrow-data = { workspace = true }
arrow-schema = { workspace = true }
half = { version = "2.1", default-features = false }
[dev-dependencies]
arrow-cast = { workspace = true }
arrow-ord = { workspace = true }
rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"] }
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+84
View File
@@ -0,0 +1,84 @@
Apache Arrow
Copyright 2016-2019 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
This product includes software from the SFrame project (BSD, 3-clause).
* Copyright (C) 2015 Dato, Inc.
* Copyright (c) 2009 Carnegie Mellon University.
This product includes software from the Feather project (Apache 2.0)
https://github.com/wesm/feather
This product includes software from the DyND project (BSD 2-clause)
https://github.com/libdynd
This product includes software from the LLVM project
* distributed under the University of Illinois Open Source
This product includes software from the google-lint project
* Copyright (c) 2009 Google Inc. All rights reserved.
This product includes software from the mman-win32 project
* Copyright https://code.google.com/p/mman-win32/
* Licensed under the MIT License;
This product includes software from the LevelDB project
* Copyright (c) 2011 The LevelDB Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* Moved from Kudu http://github.com/cloudera/kudu
This product includes software from the CMake project
* Copyright 2001-2009 Kitware, Inc.
* Copyright 2012-2014 Continuum Analytics, Inc.
* All rights reserved.
This product includes software from https://github.com/matthew-brett/multibuild (BSD 2-clause)
* Copyright (c) 2013-2016, Matt Terry and Matthew Brett; all rights reserved.
This product includes software from the Ibis project (Apache 2.0)
* Copyright (c) 2015 Cloudera, Inc.
* https://github.com/cloudera/ibis
This product includes software from Dremio (Apache 2.0)
* Copyright (C) 2017-2018 Dremio Corporation
* https://github.com/dremio/dremio-oss
This product includes software from Google Guava (Apache 2.0)
* Copyright (C) 2007 The Guava Authors
* https://github.com/google/guava
This product include software from CMake (BSD 3-Clause)
* CMake - Cross Platform Makefile Generator
* Copyright 2000-2019 Kitware, Inc. and Contributors
The web site includes files generated by Jekyll.
--------------------------------------------------------------------------------
This product includes code from Apache Kudu, which includes the following in
its NOTICE file:
Apache Kudu
Copyright 2016 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
Portions of this software were developed at
Cloudera, Inc (http://www.cloudera.com/).
--------------------------------------------------------------------------------
This product includes code from Apache ORC, which includes the following in
its NOTICE file:
Apache ORC
Copyright 2013-2019 The Apache Software Foundation
This product includes software developed by The Apache Software
Foundation (http://www.apache.org/).
This product includes software developed by Hewlett-Packard:
(c) Copyright [2014-2015] Hewlett-Packard Development Company, L.P
+509
View File
@@ -0,0 +1,509 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::array::PrimitiveArray;
use crate::null_sentinel;
use arrow_array::builder::BufferBuilder;
use arrow_array::{ArrowPrimitiveType, BooleanArray, FixedSizeBinaryArray};
use arrow_buffer::{
ArrowNativeType, BooleanBuffer, Buffer, IntervalDayTime, IntervalMonthDayNano, MutableBuffer,
NullBuffer, bit_util, i256,
};
use arrow_data::{ArrayData, ArrayDataBuilder};
use arrow_schema::{DataType, SortOptions};
use half::f16;
pub trait FromSlice {
fn from_slice(slice: &[u8], invert: bool) -> Self;
}
impl<const N: usize> FromSlice for [u8; N] {
#[inline]
fn from_slice(slice: &[u8], invert: bool) -> Self {
let mut t: Self = slice.try_into().unwrap();
if invert {
t.iter_mut().for_each(|o| *o = !*o);
}
t
}
}
/// Encodes a value of a particular fixed width type into bytes according to the rules
/// described on [`super::RowConverter`]
pub trait FixedLengthEncoding: Copy {
const ENCODED_LEN: usize = 1 + std::mem::size_of::<Self::Encoded>();
type Encoded: Sized + Copy + FromSlice + AsRef<[u8]> + AsMut<[u8]>;
fn encode(self) -> Self::Encoded;
fn decode(encoded: Self::Encoded) -> Self;
}
impl FixedLengthEncoding for bool {
type Encoded = [u8; 1];
fn encode(self) -> [u8; 1] {
[self as u8]
}
fn decode(encoded: Self::Encoded) -> Self {
encoded[0] != 0
}
}
macro_rules! encode_signed {
($n:expr, $t:ty) => {
impl FixedLengthEncoding for $t {
type Encoded = [u8; $n];
fn encode(self) -> [u8; $n] {
let mut b = self.to_be_bytes();
// Toggle top "sign" bit to ensure consistent sort order
b[0] ^= 0x80;
b
}
fn decode(mut encoded: Self::Encoded) -> Self {
// Toggle top "sign" bit
encoded[0] ^= 0x80;
Self::from_be_bytes(encoded)
}
}
};
}
encode_signed!(1, i8);
encode_signed!(2, i16);
encode_signed!(4, i32);
encode_signed!(8, i64);
encode_signed!(16, i128);
encode_signed!(32, i256);
macro_rules! encode_unsigned {
($n:expr, $t:ty) => {
impl FixedLengthEncoding for $t {
type Encoded = [u8; $n];
fn encode(self) -> [u8; $n] {
self.to_be_bytes()
}
fn decode(encoded: Self::Encoded) -> Self {
Self::from_be_bytes(encoded)
}
}
};
}
encode_unsigned!(1, u8);
encode_unsigned!(2, u16);
encode_unsigned!(4, u32);
encode_unsigned!(8, u64);
impl FixedLengthEncoding for f16 {
type Encoded = [u8; 2];
fn encode(self) -> [u8; 2] {
// https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260
let s = self.to_bits() as i16;
let val = s ^ (((s >> 15) as u16) >> 1) as i16;
val.encode()
}
fn decode(encoded: Self::Encoded) -> Self {
let bits = i16::decode(encoded);
let val = bits ^ (((bits >> 15) as u16) >> 1) as i16;
Self::from_bits(val as u16)
}
}
impl FixedLengthEncoding for f32 {
type Encoded = [u8; 4];
fn encode(self) -> [u8; 4] {
// https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260
let s = self.to_bits() as i32;
let val = s ^ (((s >> 31) as u32) >> 1) as i32;
val.encode()
}
fn decode(encoded: Self::Encoded) -> Self {
let bits = i32::decode(encoded);
let val = bits ^ (((bits >> 31) as u32) >> 1) as i32;
Self::from_bits(val as u32)
}
}
impl FixedLengthEncoding for f64 {
type Encoded = [u8; 8];
fn encode(self) -> [u8; 8] {
// https://github.com/rust-lang/rust/blob/9c20b2a8cc7588decb6de25ac6a7912dcef24d65/library/core/src/num/f32.rs#L1176-L1260
let s = self.to_bits() as i64;
let val = s ^ (((s >> 63) as u64) >> 1) as i64;
val.encode()
}
fn decode(encoded: Self::Encoded) -> Self {
let bits = i64::decode(encoded);
let val = bits ^ (((bits >> 63) as u64) >> 1) as i64;
Self::from_bits(val as u64)
}
}
impl FixedLengthEncoding for IntervalDayTime {
type Encoded = [u8; 8];
fn encode(self) -> Self::Encoded {
let mut out = [0_u8; 8];
out[..4].copy_from_slice(&self.days.encode());
out[4..].copy_from_slice(&self.milliseconds.encode());
out
}
fn decode(encoded: Self::Encoded) -> Self {
Self {
days: i32::decode(encoded[..4].try_into().unwrap()),
milliseconds: i32::decode(encoded[4..].try_into().unwrap()),
}
}
}
impl FixedLengthEncoding for IntervalMonthDayNano {
type Encoded = [u8; 16];
fn encode(self) -> Self::Encoded {
let mut out = [0_u8; 16];
out[..4].copy_from_slice(&self.months.encode());
out[4..8].copy_from_slice(&self.days.encode());
out[8..].copy_from_slice(&self.nanoseconds.encode());
out
}
fn decode(encoded: Self::Encoded) -> Self {
Self {
months: i32::decode(encoded[..4].try_into().unwrap()),
days: i32::decode(encoded[4..8].try_into().unwrap()),
nanoseconds: i64::decode(encoded[8..].try_into().unwrap()),
}
}
}
/// Returns the total encoded length (including null byte) for a value of type `T::Native`
pub const fn encoded_len<T>(_col: &PrimitiveArray<T>) -> usize
where
T: ArrowPrimitiveType,
T::Native: FixedLengthEncoding,
{
T::Native::ENCODED_LEN
}
/// Fixed width types are encoded as
///
/// - 1 byte `0` if null or `1` if valid
/// - bytes of [`FixedLengthEncoding`]
pub fn encode<T: FixedLengthEncoding>(
data: &mut [u8],
offsets: &mut [usize],
values: &[T],
nulls: &NullBuffer,
opts: SortOptions,
) {
for (value_idx, is_valid) in nulls.iter().enumerate() {
let offset = &mut offsets[value_idx + 1];
let end_offset = *offset + T::ENCODED_LEN;
if is_valid {
let to_write = &mut data[*offset..end_offset];
to_write[0] = 1;
let mut encoded = values[value_idx].encode();
if opts.descending {
// Flip bits to reverse order
encoded.as_mut().iter_mut().for_each(|v| *v = !*v)
}
to_write[1..].copy_from_slice(encoded.as_ref())
} else {
data[*offset] = null_sentinel(opts);
}
*offset = end_offset;
}
}
/// Encoding for non-nullable primitive arrays.
/// Iterates directly over the `values`, and skips NULLs-checking.
pub fn encode_not_null<T: FixedLengthEncoding>(
data: &mut [u8],
offsets: &mut [usize],
values: &[T],
opts: SortOptions,
) {
for (value_idx, val) in values.iter().enumerate() {
let offset = &mut offsets[value_idx + 1];
let end_offset = *offset + T::ENCODED_LEN;
let to_write = &mut data[*offset..end_offset];
to_write[0] = 1;
let mut encoded = val.encode();
if opts.descending {
// Flip bits to reverse order
encoded.as_mut().iter_mut().for_each(|v| *v = !*v)
}
to_write[1..].copy_from_slice(encoded.as_ref());
*offset = end_offset;
}
}
/// Boolean values are encoded as
///
/// - 1 byte `0` if null or `1` if valid
/// - bytes of [`FixedLengthEncoding`]
pub fn encode_boolean(
data: &mut [u8],
offsets: &mut [usize],
values: &BooleanBuffer,
nulls: &NullBuffer,
opts: SortOptions,
) {
for (idx, is_valid) in nulls.iter().enumerate() {
let offset = &mut offsets[idx + 1];
let end_offset = *offset + bool::ENCODED_LEN;
if is_valid {
let to_write = &mut data[*offset..end_offset];
to_write[0] = 1;
let mut encoded = values.value(idx).encode();
if opts.descending {
// Flip bits to reverse order
encoded.as_mut().iter_mut().for_each(|v| *v = !*v)
}
to_write[1..].copy_from_slice(encoded.as_ref())
} else {
data[*offset] = null_sentinel(opts);
}
*offset = end_offset;
}
}
/// Encoding for non-nullable boolean arrays.
/// Iterates directly over `values`, and skips NULLs-checking.
pub fn encode_boolean_not_null(
data: &mut [u8],
offsets: &mut [usize],
values: &BooleanBuffer,
opts: SortOptions,
) {
for (value_idx, val) in values.iter().enumerate() {
let offset = &mut offsets[value_idx + 1];
let end_offset = *offset + bool::ENCODED_LEN;
let to_write = &mut data[*offset..end_offset];
to_write[0] = 1;
let mut encoded = val.encode();
if opts.descending {
// Flip bits to reverse order
encoded.as_mut().iter_mut().for_each(|v| *v = !*v)
}
to_write[1..].copy_from_slice(encoded.as_ref());
*offset = end_offset;
}
}
pub fn encode_fixed_size_binary(
data: &mut [u8],
offsets: &mut [usize],
array: &FixedSizeBinaryArray,
opts: SortOptions,
) {
let len = array.value_length() as usize;
for (offset, maybe_val) in offsets.iter_mut().skip(1).zip(array.iter()) {
let end_offset = *offset + len + 1;
if let Some(val) = maybe_val {
let to_write = &mut data[*offset..end_offset];
to_write[0] = 1;
to_write[1..].copy_from_slice(&val[..len]);
if opts.descending {
// Flip bits to reverse order
to_write[1..1 + len].iter_mut().for_each(|v| *v = !*v)
}
} else {
data[*offset] = null_sentinel(opts);
}
*offset = end_offset;
}
}
/// Splits `len` bytes from `src`
#[inline]
fn split_off<'a>(src: &mut &'a [u8], len: usize) -> &'a [u8] {
let v = &src[..len];
*src = &src[len..];
v
}
/// Decodes a `BooleanArray` from rows
pub fn decode_bool(rows: &mut [&[u8]], options: SortOptions) -> BooleanArray {
let true_val = match options.descending {
true => !1,
false => 1,
};
let len = rows.len();
let mut null_count = 0;
let mut nulls = MutableBuffer::new(bit_util::ceil(len, 64) * 8);
let mut values = MutableBuffer::new(bit_util::ceil(len, 64) * 8);
let chunks = len / 64;
let remainder = len % 64;
for chunk in 0..chunks {
let mut null_packed = 0;
let mut values_packed = 0;
for bit_idx in 0..64 {
let i = split_off(&mut rows[bit_idx + chunk * 64], 2);
let (null, value) = (i[0] == 1, i[1] == true_val);
null_count += !null as usize;
null_packed |= (null as u64) << bit_idx;
values_packed |= (value as u64) << bit_idx;
}
nulls.push(null_packed);
values.push(values_packed);
}
if remainder != 0 {
let mut null_packed = 0;
let mut values_packed = 0;
for bit_idx in 0..remainder {
let i = split_off(&mut rows[bit_idx + chunks * 64], 2);
let (null, value) = (i[0] == 1, i[1] == true_val);
null_count += !null as usize;
null_packed |= (null as u64) << bit_idx;
values_packed |= (value as u64) << bit_idx;
}
nulls.push(null_packed);
values.push(values_packed);
}
let builder = ArrayDataBuilder::new(DataType::Boolean)
.len(rows.len())
.null_count(null_count)
.add_buffer(values.into())
.null_bit_buffer(Some(nulls.into()));
// SAFETY:
// Buffers are the correct length
unsafe { BooleanArray::from(builder.build_unchecked()) }
}
/// Decodes a single byte from each row, interpreting `0x01` as a valid value
/// and all other values as a null
///
/// Returns the null count and null buffer
pub fn decode_nulls(rows: &[&[u8]]) -> (usize, Buffer) {
let mut null_count = 0;
let buffer = MutableBuffer::collect_bool(rows.len(), |idx| {
let valid = rows[idx][0] == 1;
null_count += !valid as usize;
valid
})
.into();
(null_count, buffer)
}
/// Decodes a `ArrayData` from rows based on the provided `FixedLengthEncoding` `T`
///
/// # Safety
///
/// `data_type` must be appropriate native type for `T`
unsafe fn decode_fixed<T: FixedLengthEncoding + ArrowNativeType>(
rows: &mut [&[u8]],
data_type: DataType,
options: SortOptions,
) -> ArrayData {
let len = rows.len();
let mut values = BufferBuilder::<T>::new(len);
let (null_count, nulls) = decode_nulls(rows);
for row in rows {
let i = split_off(row, T::ENCODED_LEN);
let value = T::Encoded::from_slice(&i[1..], options.descending);
values.append(T::decode(value));
}
let builder = ArrayDataBuilder::new(data_type)
.len(len)
.null_count(null_count)
.add_buffer(values.finish())
.null_bit_buffer(Some(nulls));
// SAFETY: Buffers correct length
unsafe { builder.build_unchecked() }
}
/// Decodes a `PrimitiveArray` from rows
pub fn decode_primitive<T: ArrowPrimitiveType>(
rows: &mut [&[u8]],
data_type: DataType,
options: SortOptions,
) -> PrimitiveArray<T>
where
T::Native: FixedLengthEncoding,
{
assert!(PrimitiveArray::<T>::is_compatible(&data_type));
// SAFETY:
// Validated data type above
unsafe { decode_fixed::<T::Native>(rows, data_type, options).into() }
}
/// Decodes a `FixedLengthBinary` from rows
pub fn decode_fixed_size_binary(
rows: &mut [&[u8]],
size: i32,
options: SortOptions,
) -> FixedSizeBinaryArray {
let len = rows.len();
let mut values = MutableBuffer::new(size as usize * rows.len());
let (null_count, nulls) = decode_nulls(rows);
let encoded_len = size as usize + 1;
for row in rows {
let i = split_off(row, encoded_len);
values.extend_from_slice(&i[1..]);
}
if options.descending {
for v in values.as_slice_mut() {
*v = !*v;
}
}
let builder = ArrayDataBuilder::new(DataType::FixedSizeBinary(size))
.len(len)
.null_count(null_count)
.add_buffer(values.into())
.null_bit_buffer(Some(nulls));
// SAFETY: Buffers correct length
unsafe { builder.build_unchecked().into() }
}
File diff suppressed because it is too large Load Diff
+325
View File
@@ -0,0 +1,325 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::{LengthTracker, RowConverter, Rows, SortField, fixed, null_sentinel};
use arrow_array::{Array, FixedSizeListArray, GenericListArray, OffsetSizeTrait, new_null_array};
use arrow_buffer::{ArrowNativeType, Buffer, MutableBuffer};
use arrow_data::ArrayDataBuilder;
use arrow_schema::{ArrowError, DataType, SortOptions};
use std::{ops::Range, sync::Arc};
pub fn compute_lengths<O: OffsetSizeTrait>(
lengths: &mut [usize],
rows: &Rows,
array: &GenericListArray<O>,
) {
let shift = array.value_offsets()[0].as_usize();
let offsets = array.value_offsets().windows(2);
lengths
.iter_mut()
.zip(offsets)
.enumerate()
.for_each(|(idx, (length, offsets))| {
let start = offsets[0].as_usize() - shift;
let end = offsets[1].as_usize() - shift;
let range = array.is_valid(idx).then_some(start..end);
*length += encoded_len(rows, range);
});
}
fn encoded_len(rows: &Rows, range: Option<Range<usize>>) -> usize {
match range {
None => 1,
Some(range) => {
1 + range
.map(|i| super::variable::padded_length(Some(rows.row(i).as_ref().len())))
.sum::<usize>()
}
}
}
/// Encodes the provided `GenericListArray` to `out` with the provided `SortOptions`
///
/// `rows` should contain the encoded child elements
pub fn encode<O: OffsetSizeTrait>(
data: &mut [u8],
offsets: &mut [usize],
rows: &Rows,
opts: SortOptions,
array: &GenericListArray<O>,
) {
let shift = array.value_offsets()[0].as_usize();
offsets
.iter_mut()
.skip(1)
.zip(array.value_offsets().windows(2))
.enumerate()
.for_each(|(idx, (offset, offsets))| {
let start = offsets[0].as_usize() - shift;
let end = offsets[1].as_usize() - shift;
let range = array.is_valid(idx).then_some(start..end);
let out = &mut data[*offset..];
*offset += encode_one(out, rows, range, opts)
});
}
#[inline]
fn encode_one(
out: &mut [u8],
rows: &Rows,
range: Option<Range<usize>>,
opts: SortOptions,
) -> usize {
match range {
None => super::variable::encode_null(out, opts),
Some(range) if range.start == range.end => super::variable::encode_empty(out, opts),
Some(range) => {
let mut offset = 0;
for i in range {
let row = rows.row(i);
offset += super::variable::encode_one(&mut out[offset..], Some(row.data), opts);
}
offset += super::variable::encode_empty(&mut out[offset..], opts);
offset
}
}
}
/// Decodes an array from `rows` with the provided `options`
///
/// # Safety
///
/// `rows` must contain valid data for the provided `converter`
pub unsafe fn decode<O: OffsetSizeTrait>(
converter: &RowConverter,
rows: &mut [&[u8]],
field: &SortField,
validate_utf8: bool,
) -> Result<GenericListArray<O>, ArrowError> {
let opts = field.options;
let mut values_bytes = 0;
let mut offset = 0;
let mut offsets = Vec::with_capacity(rows.len() + 1);
offsets.push(O::usize_as(0));
for row in rows.iter_mut() {
let mut row_offset = 0;
loop {
let decoded = super::variable::decode_blocks(&row[row_offset..], opts, |x| {
values_bytes += x.len();
});
if decoded <= 1 {
offsets.push(O::usize_as(offset));
break;
}
row_offset += decoded;
offset += 1;
}
}
O::from_usize(offset).expect("overflow");
let mut null_count = 0;
let nulls = MutableBuffer::collect_bool(rows.len(), |x| {
let valid = rows[x][0] != null_sentinel(opts);
null_count += !valid as usize;
valid
});
let mut values_offsets = Vec::with_capacity(offset);
let mut values_bytes = Vec::with_capacity(values_bytes);
for row in rows.iter_mut() {
let mut row_offset = 0;
loop {
let decoded = super::variable::decode_blocks(&row[row_offset..], opts, |x| {
values_bytes.extend_from_slice(x)
});
row_offset += decoded;
if decoded <= 1 {
break;
}
values_offsets.push(values_bytes.len());
}
*row = &row[row_offset..];
}
if opts.descending {
values_bytes.iter_mut().for_each(|o| *o = !*o);
}
let mut last_value_offset = 0;
let mut child_rows: Vec<_> = values_offsets
.into_iter()
.map(|offset| {
let v = &values_bytes[last_value_offset..offset];
last_value_offset = offset;
v
})
.collect();
let child = unsafe { converter.convert_raw(&mut child_rows, validate_utf8) }?;
assert_eq!(child.len(), 1);
let child_data = child[0].to_data();
// Since RowConverter flattens certain data types (i.e. Dictionary),
// we need to use updated data type instead of original field
let corrected_type = match &field.data_type {
DataType::List(inner_field) => DataType::List(Arc::new(
inner_field
.as_ref()
.clone()
.with_data_type(child_data.data_type().clone()),
)),
DataType::LargeList(inner_field) => DataType::LargeList(Arc::new(
inner_field
.as_ref()
.clone()
.with_data_type(child_data.data_type().clone()),
)),
_ => unreachable!(),
};
let builder = ArrayDataBuilder::new(corrected_type)
.len(rows.len())
.null_count(null_count)
.null_bit_buffer(Some(nulls.into()))
.add_buffer(Buffer::from_vec(offsets))
.add_child_data(child_data);
Ok(GenericListArray::from(unsafe { builder.build_unchecked() }))
}
pub fn compute_lengths_fixed_size_list(
tracker: &mut LengthTracker,
rows: &Rows,
array: &FixedSizeListArray,
) {
let value_length = array.value_length().as_usize();
tracker.push_variable((0..array.len()).map(|idx| {
match array.is_valid(idx) {
true => {
1 + ((idx * value_length)..(idx + 1) * value_length)
.map(|child_idx| rows.row(child_idx).as_ref().len())
.sum::<usize>()
}
false => 1,
}
}))
}
/// Encodes the provided `FixedSizeListArray` to `out` with the provided `SortOptions`
///
/// `rows` should contain the encoded child elements
pub fn encode_fixed_size_list(
data: &mut [u8],
offsets: &mut [usize],
rows: &Rows,
opts: SortOptions,
array: &FixedSizeListArray,
) {
let null_sentinel = null_sentinel(opts);
offsets
.iter_mut()
.skip(1)
.enumerate()
.for_each(|(idx, offset)| {
let value_length = array.value_length().as_usize();
match array.is_valid(idx) {
true => {
data[*offset] = 0x01;
*offset += 1;
for child_idx in (idx * value_length)..(idx + 1) * value_length {
let row = rows.row(child_idx);
let end_offset = *offset + row.as_ref().len();
data[*offset..end_offset].copy_from_slice(row.as_ref());
*offset = end_offset;
}
}
false => {
data[*offset] = null_sentinel;
*offset += 1;
}
};
})
}
/// Decodes a fixed size list array from `rows` with the provided `options`
///
/// # Safety
///
/// `rows` must contain valid data for the provided `converter`
pub unsafe fn decode_fixed_size_list(
converter: &RowConverter,
rows: &mut [&[u8]],
field: &SortField,
validate_utf8: bool,
value_length: usize,
) -> Result<FixedSizeListArray, ArrowError> {
let list_type = &field.data_type;
let element_type = match list_type {
DataType::FixedSizeList(element_field, _) => element_field.data_type(),
_ => {
return Err(ArrowError::InvalidArgumentError(format!(
"Expected FixedSizeListArray, found: {list_type}",
)));
}
};
let len = rows.len();
let (null_count, nulls) = fixed::decode_nulls(rows);
let null_element_encoded = converter.convert_columns(&[new_null_array(element_type, 1)])?;
let null_element_encoded = null_element_encoded.row(0);
let null_element_slice = null_element_encoded.as_ref();
let mut child_rows = Vec::new();
for row in rows {
let valid = row[0] == 1;
let mut row_offset = 1;
if !valid {
for _ in 0..value_length {
child_rows.push(null_element_slice);
}
} else {
for _ in 0..value_length {
let mut temp_child_rows = vec![&row[row_offset..]];
unsafe { converter.convert_raw(&mut temp_child_rows, validate_utf8) }?;
let decoded_bytes = row.len() - row_offset - temp_child_rows[0].len();
let next_offset = row_offset + decoded_bytes;
child_rows.push(&row[row_offset..next_offset]);
row_offset = next_offset;
}
}
*row = &row[row_offset..]; // Update row for the next decoder
}
let children = unsafe { converter.convert_raw(&mut child_rows, validate_utf8) }?;
let child_data = children.iter().map(|c| c.to_data()).collect();
let builder = ArrayDataBuilder::new(list_type.clone())
.len(len)
.null_count(null_count)
.null_bit_buffer(Some(nulls))
.child_data(child_data);
Ok(FixedSizeListArray::from(unsafe {
builder.build_unchecked()
}))
}
+642
View File
@@ -0,0 +1,642 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::{RowConverter, Rows, SortField, variable};
use arrow_array::types::RunEndIndexType;
use arrow_array::{PrimitiveArray, RunArray};
use arrow_buffer::{ArrowNativeType, ScalarBuffer};
use arrow_schema::{ArrowError, SortOptions};
/// Computes the lengths of each row for a RunEndEncodedArray
pub fn compute_lengths<R: RunEndIndexType>(
lengths: &mut [usize],
rows: &Rows,
array: &RunArray<R>,
) {
let run_ends = array.run_ends().values();
let mut logical_start = 0;
// Iterate over each run and apply the same length to all logical positions in the run
for (physical_idx, &run_end) in run_ends.iter().enumerate() {
let logical_end = run_end.as_usize();
let row = rows.row(physical_idx);
let encoded_len = variable::encoded_len(Some(row.data));
// Add the same length for all logical positions in this run
for length in &mut lengths[logical_start..logical_end] {
*length += encoded_len;
}
logical_start = logical_end;
}
}
/// Encodes the provided `RunEndEncodedArray` to `out` with the provided `SortOptions`
///
/// `rows` should contain the encoded values
pub fn encode<R: RunEndIndexType>(
data: &mut [u8],
offsets: &mut [usize],
rows: &Rows,
opts: SortOptions,
array: &RunArray<R>,
) {
let run_ends = array.run_ends();
let mut logical_idx = 0;
let mut offset_idx = 1; // Skip first offset
// Iterate over each run
for physical_idx in 0..run_ends.values().len() {
let run_end = run_ends.values()[physical_idx].as_usize();
// Process all elements in this run
while logical_idx < run_end && offset_idx < offsets.len() {
let offset = &mut offsets[offset_idx];
let out = &mut data[*offset..];
// Use variable-length encoding to make the data self-describing
let row = rows.row(physical_idx);
let bytes_written = variable::encode_one(out, Some(row.data), opts);
*offset += bytes_written;
logical_idx += 1;
offset_idx += 1;
}
// Break if we've processed all offsets
if offset_idx >= offsets.len() {
break;
}
}
}
/// Decodes a RunEndEncodedArray from `rows` with the provided `options`
///
/// # Safety
///
/// `rows` must contain valid data for the provided `converter`
pub unsafe fn decode<R: RunEndIndexType>(
converter: &RowConverter,
rows: &mut [&[u8]],
field: &SortField,
validate_utf8: bool,
) -> Result<RunArray<R>, ArrowError> {
if rows.is_empty() {
let values = unsafe { converter.convert_raw(&mut [], validate_utf8) }?;
let run_ends_array = PrimitiveArray::<R>::try_new(ScalarBuffer::from(vec![]), None)?;
return RunArray::<R>::try_new(&run_ends_array, &values[0]);
}
// Decode each row's REE data and collect the decoded values
let mut decoded_values = Vec::new();
let mut run_ends = Vec::new();
let mut unique_row_indices = Vec::new();
// Process each row to extract its REE data (following decode_binary pattern)
let mut decoded_data = Vec::new();
for (idx, row) in rows.iter_mut().enumerate() {
decoded_data.clear();
// Extract the decoded value data from this row
let consumed = variable::decode_blocks(row, field.options, |block| {
decoded_data.extend_from_slice(block);
});
// Handle bit inversion for descending sort (following decode_binary pattern)
if field.options.descending {
decoded_data.iter_mut().for_each(|b| *b = !*b);
}
// Update the row to point past the consumed REE data
*row = &row[consumed..];
// Check if this decoded value is the same as the previous one to identify runs
let is_new_run =
idx == 0 || decoded_data != decoded_values[*unique_row_indices.last().unwrap()];
if is_new_run {
// This is a new unique value - end the previous run if any
if idx > 0 {
run_ends.push(R::Native::usize_as(idx));
}
unique_row_indices.push(decoded_values.len());
let capacity = decoded_data.capacity();
decoded_values.push(std::mem::replace(
&mut decoded_data,
Vec::with_capacity(capacity),
));
}
}
// Add the final run end
run_ends.push(R::Native::usize_as(rows.len()));
// Convert the unique decoded values using the row converter
let mut unique_rows: Vec<&[u8]> = decoded_values.iter().map(|v| v.as_slice()).collect();
let values = if unique_rows.is_empty() {
unsafe { converter.convert_raw(&mut [], validate_utf8) }?
} else {
unsafe { converter.convert_raw(&mut unique_rows, validate_utf8) }?
};
// Create run ends array
let run_ends_array = PrimitiveArray::<R>::try_new(ScalarBuffer::from(run_ends), None)?;
// Create the RunEndEncodedArray
RunArray::<R>::try_new(&run_ends_array, &values[0])
}
#[cfg(test)]
mod tests {
use crate::{RowConverter, SortField};
use arrow_array::cast::AsArray;
use arrow_array::types::{Int16Type, Int32Type, Int64Type, RunEndIndexType};
use arrow_array::{Array, Int64Array, PrimitiveArray, RunArray, StringArray};
use arrow_schema::{DataType, SortOptions};
use std::sync::Arc;
fn assert_roundtrip<R: RunEndIndexType>(
array: &RunArray<R>,
run_end_type: DataType,
values_type: DataType,
sort_options: Option<SortOptions>,
) {
let sort_field = if let Some(options) = sort_options {
SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", run_end_type, false)),
Arc::new(arrow_schema::Field::new("values", values_type, true)),
),
options,
)
} else {
SortField::new(DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", run_end_type, false)),
Arc::new(arrow_schema::Field::new("values", values_type, true)),
))
};
let converter = RowConverter::new(vec![sort_field]).unwrap();
let rows = converter
.convert_columns(&[Arc::new(array.clone())])
.unwrap();
let arrays = converter.convert_rows(&rows).unwrap();
let result = arrays[0].as_any().downcast_ref::<RunArray<R>>().unwrap();
assert_eq!(array, result);
}
#[test]
fn test_run_end_encoded_supports_datatype() {
// Test that the RowConverter correctly supports run-end encoded arrays
assert!(RowConverter::supports_datatype(&DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
)));
}
#[test]
fn test_run_end_encoded_round_trip_int16_int64s() {
// Test round-trip correctness for RunEndEncodedArray with Int64 values making sure it
// doesn't just work with eg. strings (which are all the other tests).
let values = Int64Array::from(vec![100, 200, 100, 300]);
let run_ends = vec![2, 3, 5, 6];
let array: RunArray<Int16Type> =
RunArray::try_new(&PrimitiveArray::from(run_ends), &values).unwrap();
assert_roundtrip(&array, DataType::Int16, DataType::Int64, None);
}
#[test]
fn test_run_end_encoded_round_trip_int32_int64s() {
// Test round-trip correctness for RunEndEncodedArray with Int64 values making sure it
// doesn't just work with eg. strings (which are all the other tests).
let values = Int64Array::from(vec![100, 200, 100, 300]);
let run_ends = vec![2, 3, 5, 6];
let array: RunArray<Int32Type> =
RunArray::try_new(&PrimitiveArray::from(run_ends), &values).unwrap();
assert_roundtrip(&array, DataType::Int32, DataType::Int64, None);
}
#[test]
fn test_run_end_encoded_round_trip_int64_int64s() {
// Test round-trip correctness for RunEndEncodedArray with Int64 values making sure it
// doesn't just work with eg. strings (which are all the other tests).
let values = Int64Array::from(vec![100, 200, 100, 300]);
let run_ends = vec![2, 3, 5, 6];
let array: RunArray<Int64Type> =
RunArray::try_new(&PrimitiveArray::from(run_ends), &values).unwrap();
assert_roundtrip(&array, DataType::Int64, DataType::Int64, None);
}
#[test]
fn test_run_end_encoded_round_trip_strings() {
// Test round-trip correctness for RunEndEncodedArray with strings
let array: RunArray<Int32Type> = vec!["b", "b", "a"].into_iter().collect();
assert_roundtrip(&array, DataType::Int32, DataType::Utf8, None);
}
#[test]
fn test_run_end_encoded_round_trip_strings_with_nulls() {
// Test round-trip correctness for RunEndEncodedArray with nulls
let array: RunArray<Int32Type> = vec![Some("b"), Some("b"), None, Some("a")]
.into_iter()
.collect();
assert_roundtrip(&array, DataType::Int32, DataType::Utf8, None);
}
#[test]
fn test_run_end_encoded_ascending_descending_round_trip() {
// Test round-trip correctness for ascending vs descending sort options
let values_asc =
arrow_array::StringArray::from(vec![Some("apple"), Some("banana"), Some("cherry")]);
let run_ends_asc = vec![2, 4, 6];
let run_array_asc: RunArray<Int32Type> = RunArray::try_new(
&arrow_array::PrimitiveArray::from(run_ends_asc),
&values_asc,
)
.unwrap();
// Test ascending order
assert_roundtrip(
&run_array_asc,
DataType::Int32,
DataType::Utf8,
Some(SortOptions {
descending: false,
nulls_first: true,
}),
);
// Test descending order
assert_roundtrip(
&run_array_asc,
DataType::Int32,
DataType::Utf8,
Some(SortOptions {
descending: true,
nulls_first: true,
}),
);
}
#[test]
fn test_run_end_encoded_sort_configurations_basic() {
// Test that different sort configurations work and can round-trip successfully
let test_array: RunArray<Int32Type> = vec!["test"].into_iter().collect();
// Test ascending order
assert_roundtrip(
&test_array,
DataType::Int32,
DataType::Utf8,
Some(SortOptions {
descending: false,
nulls_first: true,
}),
);
// Test descending order
assert_roundtrip(
&test_array,
DataType::Int32,
DataType::Utf8,
Some(SortOptions {
descending: true,
nulls_first: true,
}),
);
}
#[test]
fn test_run_end_encoded_nulls_first_last_configurations() {
// Test that nulls_first vs nulls_last configurations work
let simple_array: RunArray<Int32Type> = vec!["simple"].into_iter().collect();
let converter_nulls_first = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
SortOptions {
descending: false,
nulls_first: true,
},
)])
.unwrap();
let converter_nulls_last = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
SortOptions {
descending: false,
nulls_first: false,
},
)])
.unwrap();
// Test that both configurations can handle simple arrays
let rows_nulls_first = converter_nulls_first
.convert_columns(&[Arc::new(simple_array.clone())])
.unwrap();
let arrays_nulls_first = converter_nulls_first
.convert_rows(&rows_nulls_first)
.unwrap();
let result_nulls_first = arrays_nulls_first[0]
.as_any()
.downcast_ref::<RunArray<Int32Type>>()
.unwrap();
let rows_nulls_last = converter_nulls_last
.convert_columns(&[Arc::new(simple_array.clone())])
.unwrap();
let arrays_nulls_last = converter_nulls_last.convert_rows(&rows_nulls_last).unwrap();
let result_nulls_last = arrays_nulls_last[0]
.as_any()
.downcast_ref::<RunArray<Int32Type>>()
.unwrap();
// Both should successfully convert the simple array
assert_eq!(simple_array.len(), result_nulls_first.len());
assert_eq!(simple_array.len(), result_nulls_last.len());
}
#[test]
fn test_run_end_encoded_row_consumption() {
// This test verifies that ALL rows are properly consumed during decoding,
// not just the unique values. We test this by ensuring multi-column conversion
// works correctly - if rows aren't consumed properly, the second column would fail.
// Create a REE array with multiple runs
let array: RunArray<Int32Type> = vec!["a", "a", "b", "b", "b", "c"].into_iter().collect();
let string_array = StringArray::from(vec!["x", "y", "z", "w", "u", "v"]);
let multi_converter = RowConverter::new(vec![
SortField::new(DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
)),
SortField::new(DataType::Utf8),
])
.unwrap();
let multi_rows = multi_converter
.convert_columns(&[Arc::new(array.clone()), Arc::new(string_array.clone())])
.unwrap();
// Convert back - this will test that all rows are consumed properly
let arrays = multi_converter.convert_rows(&multi_rows).unwrap();
// Verify both columns round-trip correctly
let result_ree = arrays[0]
.as_any()
.downcast_ref::<RunArray<Int32Type>>()
.unwrap();
let result_string = arrays[1].as_any().downcast_ref::<StringArray>().unwrap();
// This should pass - both arrays should be identical to originals
assert_eq!(result_ree.values().as_ref(), array.values().as_ref());
assert_eq!(result_ree.run_ends().values(), array.run_ends().values());
assert_eq!(*result_string, string_array);
}
#[test]
fn test_run_end_encoded_sorting_behavior() {
// Test that the binary row encoding actually produces the correct sort order
// Create REE arrays with different values to test sorting
let array1: RunArray<Int32Type> = vec!["apple", "apple"].into_iter().collect();
let array2: RunArray<Int32Type> = vec!["banana", "banana"].into_iter().collect();
let array3: RunArray<Int32Type> = vec!["cherry", "cherry"].into_iter().collect();
// Test ascending sort
let converter_asc = RowConverter::new(vec![SortField::new(DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
))])
.unwrap();
let rows1_asc = converter_asc
.convert_columns(&[Arc::new(array1.clone())])
.unwrap();
let rows2_asc = converter_asc
.convert_columns(&[Arc::new(array2.clone())])
.unwrap();
let rows3_asc = converter_asc
.convert_columns(&[Arc::new(array3.clone())])
.unwrap();
// For ascending: apple < banana < cherry
// So row bytes should sort: rows1 < rows2 < rows3
assert!(
rows1_asc.row(0) < rows2_asc.row(0),
"apple should come before banana in ascending order"
);
assert!(
rows2_asc.row(0) < rows3_asc.row(0),
"banana should come before cherry in ascending order"
);
assert!(
rows1_asc.row(0) < rows3_asc.row(0),
"apple should come before cherry in ascending order"
);
// Test descending sort
let converter_desc = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
arrow_schema::SortOptions {
descending: true,
nulls_first: true,
},
)])
.unwrap();
let rows1_desc = converter_desc
.convert_columns(&[Arc::new(array1.clone())])
.unwrap();
let rows2_desc = converter_desc
.convert_columns(&[Arc::new(array2.clone())])
.unwrap();
let rows3_desc = converter_desc
.convert_columns(&[Arc::new(array3.clone())])
.unwrap();
// For descending: cherry > banana > apple
// So row bytes should sort: rows3 < rows2 < rows1 (because byte comparison is ascending)
assert!(
rows3_desc.row(0) < rows2_desc.row(0),
"cherry should come before banana in descending order (byte-wise)"
);
assert!(
rows2_desc.row(0) < rows1_desc.row(0),
"banana should come before apple in descending order (byte-wise)"
);
assert!(
rows3_desc.row(0) < rows1_desc.row(0),
"cherry should come before apple in descending order (byte-wise)"
);
}
#[test]
fn test_run_end_encoded_null_sorting() {
// Test null handling in sort order
let array_with_nulls: RunArray<Int32Type> = vec![None, None].into_iter().collect();
let array_with_values: RunArray<Int32Type> = vec!["apple", "apple"].into_iter().collect();
// Test nulls_first = true
let converter_nulls_first = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
arrow_schema::SortOptions {
descending: false,
nulls_first: true,
},
)])
.unwrap();
let rows_nulls = converter_nulls_first
.convert_columns(&[Arc::new(array_with_nulls.clone())])
.unwrap();
let rows_values = converter_nulls_first
.convert_columns(&[Arc::new(array_with_values.clone())])
.unwrap();
// nulls should come before values when nulls_first = true
assert!(
rows_nulls.row(0) < rows_values.row(0),
"nulls should come before values when nulls_first=true"
);
// Test nulls_first = false
let converter_nulls_last = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
arrow_schema::SortOptions {
descending: false,
nulls_first: false,
},
)])
.unwrap();
let rows_nulls_last = converter_nulls_last
.convert_columns(&[Arc::new(array_with_nulls.clone())])
.unwrap();
let rows_values_last = converter_nulls_last
.convert_columns(&[Arc::new(array_with_values.clone())])
.unwrap();
// values should come before nulls when nulls_first = false
assert!(
rows_values_last.row(0) < rows_nulls_last.row(0),
"values should come before nulls when nulls_first=false"
);
}
#[test]
fn test_run_end_encoded_mixed_sorting() {
// Test sorting with mixed values and nulls to ensure complex scenarios work
let array1: RunArray<Int32Type> = vec![Some("apple"), None].into_iter().collect();
let array2: RunArray<Int32Type> = vec![None, Some("banana")].into_iter().collect();
let array3: RunArray<Int32Type> =
vec![Some("cherry"), Some("cherry")].into_iter().collect();
let converter = RowConverter::new(vec![SortField::new_with_options(
DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
),
arrow_schema::SortOptions {
descending: false,
nulls_first: true,
},
)])
.unwrap();
let rows1 = converter.convert_columns(&[Arc::new(array1)]).unwrap();
let rows2 = converter.convert_columns(&[Arc::new(array2)]).unwrap();
let rows3 = converter.convert_columns(&[Arc::new(array3)]).unwrap();
// With nulls_first=true, ascending:
// Row 0: array1[0]="apple", array2[0]=null, array3[0]="cherry" -> null < apple < cherry
// Row 1: array1[1]=null, array2[1]="banana", array3[1]="cherry" -> null < banana < cherry
// Compare first rows: null < apple < cherry
assert!(rows2.row(0) < rows1.row(0), "null should come before apple");
assert!(
rows1.row(0) < rows3.row(0),
"apple should come before cherry"
);
// Compare second rows: null < banana < cherry
assert!(
rows1.row(1) < rows2.row(1),
"null should come before banana"
);
assert!(
rows2.row(1) < rows3.row(1),
"banana should come before cherry"
);
}
#[test]
fn test_run_end_encoded_empty() {
// Test converting / decoding an empty RunEndEncodedArray
let values: Vec<&str> = vec![];
let array: RunArray<Int32Type> = values.into_iter().collect();
let converter = RowConverter::new(vec![SortField::new(DataType::RunEndEncoded(
Arc::new(arrow_schema::Field::new("run_ends", DataType::Int32, false)),
Arc::new(arrow_schema::Field::new("values", DataType::Utf8, true)),
))])
.unwrap();
let rows = converter.convert_columns(&[Arc::new(array)]).unwrap();
assert_eq!(rows.num_rows(), 0);
// Likewise converting empty rows should yield an empty RunEndEncodedArray
let arrays = converter.convert_rows(&rows).unwrap();
assert_eq!(arrays.len(), 1);
// Verify both columns round-trip correctly
let result_ree = arrays[0].as_run::<Int32Type>();
assert_eq!(result_ree.len(), 0);
}
}
+420
View File
@@ -0,0 +1,420 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::null_sentinel;
use arrow_array::builder::BufferBuilder;
use arrow_array::types::ByteArrayType;
use arrow_array::*;
use arrow_buffer::bit_util::ceil;
use arrow_buffer::{ArrowNativeType, MutableBuffer};
use arrow_data::{ArrayDataBuilder, MAX_INLINE_VIEW_LEN};
use arrow_schema::{DataType, SortOptions};
use builder::make_view;
/// The block size of the variable length encoding
pub const BLOCK_SIZE: usize = 32;
/// The first block is split into `MINI_BLOCK_COUNT` mini-blocks
///
/// This helps to reduce the space amplification for small strings
pub const MINI_BLOCK_COUNT: usize = 4;
/// The mini block size
pub const MINI_BLOCK_SIZE: usize = BLOCK_SIZE / MINI_BLOCK_COUNT;
/// The continuation token
pub const BLOCK_CONTINUATION: u8 = 0xFF;
/// Indicates an empty string
pub const EMPTY_SENTINEL: u8 = 1;
/// Indicates a non-empty string
pub const NON_EMPTY_SENTINEL: u8 = 2;
/// Returns the length of the encoded representation of a byte array, including the null byte
#[inline]
pub fn encoded_len(a: Option<&[u8]>) -> usize {
padded_length(a.map(|x| x.len()))
}
/// Returns the padded length of the encoded length of the given length
#[inline]
pub fn padded_length(a: Option<usize>) -> usize {
match a {
Some(a) if a <= BLOCK_SIZE => 1 + ceil(a, MINI_BLOCK_SIZE) * (MINI_BLOCK_SIZE + 1),
// Each miniblock ends with a 1 byte continuation, therefore add
// `(MINI_BLOCK_COUNT - 1)` additional bytes over non-miniblock size
Some(a) => MINI_BLOCK_COUNT + ceil(a, BLOCK_SIZE) * (BLOCK_SIZE + 1),
None => 1,
}
}
/// Variable length values are encoded as
///
/// - single `0_u8` if null
/// - single `1_u8` if empty array
/// - `2_u8` if not empty, followed by one or more blocks
///
/// where a block is encoded as
///
/// - [`BLOCK_SIZE`] bytes of string data, padded with 0s
/// - `0xFF_u8` if this is not the last block for this string
/// - otherwise the length of the block as a `u8`
pub fn encode<'a, I: Iterator<Item = Option<&'a [u8]>>>(
data: &mut [u8],
offsets: &mut [usize],
i: I,
opts: SortOptions,
) {
for (offset, maybe_val) in offsets.iter_mut().skip(1).zip(i) {
*offset += encode_one(&mut data[*offset..], maybe_val, opts);
}
}
/// Calls [`encode`] with optimized iterator for generic byte arrays
pub(crate) fn encode_generic_byte_array<T: ByteArrayType>(
data: &mut [u8],
offsets: &mut [usize],
input_array: &GenericByteArray<T>,
opts: SortOptions,
) {
let input_offsets = input_array.value_offsets();
let bytes = input_array.values().as_slice();
if let Some(null_buffer) = input_array.nulls().filter(|x| x.null_count() > 0) {
let input_iter =
input_offsets
.windows(2)
.zip(null_buffer.iter())
.map(|(start_end, is_valid)| {
if is_valid {
let item_range = start_end[0].as_usize()..start_end[1].as_usize();
// SAFETY: the offsets of the input are valid by construction
// so it is ok to use unsafe here
let item = unsafe { bytes.get_unchecked(item_range) };
Some(item)
} else {
None
}
});
encode(data, offsets, input_iter, opts);
} else {
// Skip null checks
let input_iter = input_offsets.windows(2).map(|start_end| {
let item_range = start_end[0].as_usize()..start_end[1].as_usize();
// SAFETY: the offsets of the input are valid by construction
// so it is ok to use unsafe here
let item = unsafe { bytes.get_unchecked(item_range) };
Some(item)
});
encode(data, offsets, input_iter, opts);
}
}
pub fn encode_null(out: &mut [u8], opts: SortOptions) -> usize {
out[0] = null_sentinel(opts);
1
}
pub fn encode_empty(out: &mut [u8], opts: SortOptions) -> usize {
out[0] = match opts.descending {
true => !EMPTY_SENTINEL,
false => EMPTY_SENTINEL,
};
1
}
#[inline]
pub fn encode_one(out: &mut [u8], val: Option<&[u8]>, opts: SortOptions) -> usize {
match val {
None => encode_null(out, opts),
Some([]) => encode_empty(out, opts),
Some(val) => {
// Write `2_u8` to demarcate as non-empty, non-null string
out[0] = NON_EMPTY_SENTINEL;
let len = if val.len() <= BLOCK_SIZE {
1 + encode_blocks::<MINI_BLOCK_SIZE>(&mut out[1..], val)
} else {
let (initial, rem) = val.split_at(BLOCK_SIZE);
let offset = encode_blocks::<MINI_BLOCK_SIZE>(&mut out[1..], initial);
out[offset] = BLOCK_CONTINUATION;
1 + offset + encode_blocks::<BLOCK_SIZE>(&mut out[1 + offset..], rem)
};
if opts.descending {
// Invert bits
out[..len].iter_mut().for_each(|v| *v = !*v)
}
len
}
}
}
/// Writes `val` in `SIZE` blocks with the appropriate continuation tokens
#[inline]
fn encode_blocks<const SIZE: usize>(out: &mut [u8], val: &[u8]) -> usize {
let block_count = ceil(val.len(), SIZE);
let end_offset = block_count * (SIZE + 1);
let to_write = &mut out[..end_offset];
let chunks = val.chunks_exact(SIZE);
let remainder = chunks.remainder();
for (input, output) in chunks.clone().zip(to_write.chunks_exact_mut(SIZE + 1)) {
let input: &[u8; SIZE] = input.try_into().unwrap();
let out_block: &mut [u8; SIZE] = (&mut output[..SIZE]).try_into().unwrap();
*out_block = *input;
// Indicate that there are further blocks to follow
output[SIZE] = BLOCK_CONTINUATION;
}
if !remainder.is_empty() {
let start_offset = (block_count - 1) * (SIZE + 1);
to_write[start_offset..start_offset + remainder.len()].copy_from_slice(remainder);
*to_write.last_mut().unwrap() = remainder.len() as u8;
} else {
// We must overwrite the continuation marker written by the loop above
*to_write.last_mut().unwrap() = SIZE as u8;
}
end_offset
}
/// Decodes a single block of data
/// The `f` function accepts a slice of the decoded data, it may be called multiple times
pub fn decode_blocks(row: &[u8], options: SortOptions, mut f: impl FnMut(&[u8])) -> usize {
let (non_empty_sentinel, continuation) = match options.descending {
true => (!NON_EMPTY_SENTINEL, !BLOCK_CONTINUATION),
false => (NON_EMPTY_SENTINEL, BLOCK_CONTINUATION),
};
if row[0] != non_empty_sentinel {
// Empty or null string
return 1;
}
// Extracts the block length from the sentinel
let block_len = |sentinel: u8| match options.descending {
true => !sentinel as usize,
false => sentinel as usize,
};
let mut idx = 1;
for _ in 0..MINI_BLOCK_COUNT {
let sentinel = row[idx + MINI_BLOCK_SIZE];
if sentinel != continuation {
f(&row[idx..idx + block_len(sentinel)]);
return idx + MINI_BLOCK_SIZE + 1;
}
f(&row[idx..idx + MINI_BLOCK_SIZE]);
idx += MINI_BLOCK_SIZE + 1;
}
loop {
let sentinel = row[idx + BLOCK_SIZE];
if sentinel != continuation {
f(&row[idx..idx + block_len(sentinel)]);
return idx + BLOCK_SIZE + 1;
}
f(&row[idx..idx + BLOCK_SIZE]);
idx += BLOCK_SIZE + 1;
}
}
/// Returns the number of bytes of encoded data
fn decoded_len(row: &[u8], options: SortOptions) -> usize {
let mut len = 0;
decode_blocks(row, options, |block| len += block.len());
len
}
/// Decodes a binary array from `rows` with the provided `options`
pub fn decode_binary<I: OffsetSizeTrait>(
rows: &mut [&[u8]],
options: SortOptions,
) -> GenericBinaryArray<I> {
let len = rows.len();
let mut null_count = 0;
let nulls = MutableBuffer::collect_bool(len, |x| {
let valid = rows[x][0] != null_sentinel(options);
null_count += !valid as usize;
valid
});
let values_capacity = rows.iter().map(|row| decoded_len(row, options)).sum();
let mut offsets = BufferBuilder::<I>::new(len + 1);
offsets.append(I::zero());
let mut values = MutableBuffer::new(values_capacity);
for row in rows {
let offset = decode_blocks(row, options, |b| values.extend_from_slice(b));
*row = &row[offset..];
offsets.append(I::from_usize(values.len()).expect("offset overflow"))
}
if options.descending {
values.as_slice_mut().iter_mut().for_each(|o| *o = !*o)
}
let d = match I::IS_LARGE {
true => DataType::LargeBinary,
false => DataType::Binary,
};
let builder = ArrayDataBuilder::new(d)
.len(len)
.null_count(null_count)
.null_bit_buffer(Some(nulls.into()))
.add_buffer(offsets.finish())
.add_buffer(values.into());
// SAFETY:
// Valid by construction above
unsafe { GenericBinaryArray::from(builder.build_unchecked()) }
}
fn decode_binary_view_inner(
rows: &mut [&[u8]],
options: SortOptions,
validate_utf8: bool,
) -> BinaryViewArray {
let len = rows.len();
let inline_str_max_len = MAX_INLINE_VIEW_LEN as usize;
let mut null_count = 0;
let nulls = MutableBuffer::collect_bool(len, |x| {
let valid = rows[x][0] != null_sentinel(options);
null_count += !valid as usize;
valid
});
// If we are validating UTF-8, decode all string values (including short strings)
// into the values buffer and validate UTF-8 once. If not validating,
// we save memory by only copying long strings to the values buffer, as short strings
// will be inlined into the view and do not need to be stored redundantly.
let values_capacity = if validate_utf8 {
// Capacity for all long and short strings
rows.iter().map(|row| decoded_len(row, options)).sum()
} else {
// Capacity for all long strings plus room for one short string
rows.iter().fold(0, |acc, row| {
let len = decoded_len(row, options);
if len > inline_str_max_len {
acc + len
} else {
acc
}
}) + inline_str_max_len
};
let mut values = MutableBuffer::new(values_capacity);
let mut views = BufferBuilder::<u128>::new(len);
for row in rows {
let start_offset = values.len();
let offset = decode_blocks(row, options, |b| values.extend_from_slice(b));
// Measure string length via change in values buffer.
// Used to check if decoded value should be truncated (short string) when validate_utf8 is false
let decoded_len = values.len() - start_offset;
if row[0] == null_sentinel(options) {
debug_assert_eq!(offset, 1);
debug_assert_eq!(start_offset, values.len());
views.append(0);
} else {
// Safety: we just appended the data to the end of the buffer
let val = unsafe { values.get_unchecked_mut(start_offset..) };
if options.descending {
val.iter_mut().for_each(|o| *o = !*o);
}
let view = make_view(val, 0, start_offset as u32);
views.append(view);
// truncate inline string in values buffer if validate_utf8 is false
if !validate_utf8 && decoded_len <= inline_str_max_len {
values.truncate(start_offset);
}
}
*row = &row[offset..];
}
if validate_utf8 {
// the values contains all data, no matter if it is short or long
// we can validate utf8 in one go.
std::str::from_utf8(values.as_slice()).unwrap();
}
let builder = ArrayDataBuilder::new(DataType::BinaryView)
.len(len)
.null_count(null_count)
.null_bit_buffer(Some(nulls.into()))
.add_buffer(views.finish())
.add_buffer(values.into());
// SAFETY:
// Valid by construction above
unsafe { BinaryViewArray::from(builder.build_unchecked()) }
}
/// Decodes a binary view array from `rows` with the provided `options`
pub fn decode_binary_view(rows: &mut [&[u8]], options: SortOptions) -> BinaryViewArray {
decode_binary_view_inner(rows, options, false)
}
/// Decodes a string array from `rows` with the provided `options`
///
/// # Safety
///
/// The row must contain valid UTF-8 data
pub unsafe fn decode_string<I: OffsetSizeTrait>(
rows: &mut [&[u8]],
options: SortOptions,
validate_utf8: bool,
) -> GenericStringArray<I> {
let decoded = decode_binary::<I>(rows, options);
if validate_utf8 {
return GenericStringArray::from(decoded);
}
let builder = decoded
.into_data()
.into_builder()
.data_type(GenericStringArray::<I>::DATA_TYPE);
// SAFETY:
// Row data must have come from a valid UTF-8 array
GenericStringArray::from(unsafe { builder.build_unchecked() })
}
/// Decodes a string view array from `rows` with the provided `options`
///
/// # Safety
///
/// The row must contain valid UTF-8 data
pub unsafe fn decode_string_view(
rows: &mut [&[u8]],
options: SortOptions,
validate_utf8: bool,
) -> StringViewArray {
let view = decode_binary_view_inner(rows, options, validate_utf8);
unsafe { view.to_string_view_unchecked() }
}