diff --git a/java/lance-jni/Cargo.lock b/java/lance-jni/Cargo.lock index 62754776f90..ab12f689519 100644 --- a/java/lance-jni/Cargo.lock +++ b/java/lance-jni/Cargo.lock @@ -332,7 +332,7 @@ version = "58.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21ca356ad6425cecb6eb7b28e4f659f1ee7880fbb1a16127de7dd62901efee9e" dependencies = [ - "bitflags", + "bitflags 2.13.0", "serde_core", "serde_json", ] @@ -918,6 +918,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.0" @@ -1012,6 +1018,35 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytecheck" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26333eeac754f0ad8a6bcd0eb0ac012156302e4e16b852b72ee399aea4f12c29" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "bytemuck" version = "1.25.2" @@ -1075,6 +1110,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cedarwood" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0524a528a6a0288df1863c3c20fe92c301875b4941e7b6c4b394ab08c5a4c55" +dependencies = [ + "smallvec", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -1131,7 +1175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" dependencies = [ "chrono", - "phf", + "phf 0.12.1", ] [[package]] @@ -1519,14 +1563,44 @@ dependencies = [ "cmov", ] +[[package]] +name = "daachorse" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db756b5eb7d81d31f31f660f4132f8cf5698de52fca144c143d0ae0cbb5f2e06" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", ] [[package]] @@ -1542,13 +1616,24 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.118", +] + [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core", + "darling_core 0.23.0", "quote", "syn 2.0.118", ] @@ -2215,6 +2300,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.118", +] + [[package]] name = "digest" version = "0.10.7" @@ -2332,6 +2448,15 @@ dependencies = [ "cfg-if 1.0.4", ] +[[package]] +name = "encoding_rs_io" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba3fe847045ecff794b9c138293a80db914678c453ad63fbf0c6a9eb6e00b22" +dependencies = [ + "encoding_rs", +] + [[package]] name = "env_filter" version = "2.0.0" @@ -2427,7 +2552,7 @@ version = "25.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3" dependencies = [ - "bitflags", + "bitflags 2.13.0", "rustc_version", ] @@ -3463,7 +3588,7 @@ version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9080b15e63775b9a2ac7dca720f7050a8b955e092ea0f6020a4a80f69998cdc0" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if 1.0.4", "libc", ] @@ -3504,6 +3629,29 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jieba-macros" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34904340bc65749a9e9a02fcc7f3368e675427c18447b9bbe02df52c15c9a36a" +dependencies = [ + "phf_codegen", +] + +[[package]] +name = "jieba-rs" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5bdea4dc241d589e179f39d2a778f31490f3370aa2f626223dbd930ebc5c9d" +dependencies = [ + "bytecount", + "cedarwood", + "jieba-macros", + "phf 0.13.1", + "regex", + "rustc-hash", +] + [[package]] name = "jiff" version = "0.2.28" @@ -3662,6 +3810,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "kanaria" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f9d9652540055ac4fded998a73aca97d965899077ab1212587437da44196ff" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "konst" version = "0.4.3" @@ -3988,6 +4145,7 @@ dependencies = [ "geoarrow-schema", "half", "itertools 0.14.0", + "jieba-rs", "jsonb", "lance-arrow", "lance-arrow-stats", @@ -4257,6 +4415,8 @@ version = "12.0.0-beta.4" dependencies = [ "frostem", "icu_segmenter", + "jieba-rs", + "lindera", "serde", "stop-words", "unicode-normalization", @@ -4358,6 +4518,60 @@ dependencies = [ "rayon", ] +[[package]] +name = "lindera" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74cda79d7161e99b414e4d292ff673cc3f8d22f070d8be3b6185c033363a9216" +dependencies = [ + "anyhow", + "byteorder", + "csv", + "daachorse", + "kanaria", + "lindera-dictionary", + "log", + "once_cell", + "percent-encoding", + "regex", + "serde", + "serde_json", + "serde_yaml_ng", + "strum", + "strum_macros", + "unicode-blocks", + "unicode-normalization", + "unicode-segmentation", + "url", +] + +[[package]] +name = "lindera-dictionary" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2385456ca9fe87c29072c5f156b52fdd5e28d5b5738ddfb3979501dbd736530" +dependencies = [ + "anyhow", + "byteorder", + "csv", + "daachorse", + "derive_builder", + "encoding_rs", + "encoding_rs_io", + "glob", + "log", + "memmap2", + "num_cpus", + "once_cell", + "regex", + "rkyv", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror 2.0.18", +] + [[package]] name = "link-section" version = "0.18.2" @@ -4619,6 +4833,26 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -4770,7 +5004,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -5338,7 +5572,37 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" dependencies = [ - "phf_shared", + "phf_shared 0.12.1", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", ] [[package]] @@ -5350,6 +5614,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -5546,6 +5819,26 @@ dependencies = [ "prost", ] +[[package]] +name = "ptr_meta" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + [[package]] name = "quick-xml" version = "0.39.4" @@ -5662,6 +5955,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rancor" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.6" @@ -5821,7 +6123,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.0", ] [[package]] @@ -5900,6 +6202,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqsign-aliyun-oss" version = "3.1.2" @@ -6155,6 +6466,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + [[package]] name = "roaring" version = "0.11.4" @@ -6234,7 +6575,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", @@ -6439,7 +6780,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6599,7 +6940,7 @@ version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" dependencies = [ - "darling", + "darling 0.23.0", "proc-macro2", "quote", "syn 2.0.118", @@ -6618,6 +6959,19 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.11.0" @@ -6888,6 +7242,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "substrait" version = "0.63.0" @@ -6988,7 +7363,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7321,7 +7696,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags", + "bitflags 2.13.0", "bytes", "http 1.4.2", "http-body 1.0.1", @@ -7339,7 +7714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", - "bitflags", + "bitflags 2.13.0", "bytes", "futures-core", "futures-util", @@ -7535,6 +7910,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-blocks" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328edfd6d0bb91a22e592bed3a3b54c7b1f5c92a517f5a7aca24a2caef10c363" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/java/lance-jni/Cargo.toml b/java/lance-jni/Cargo.toml index d7df8aa32ff..c34c78fb6c8 100644 --- a/java/lance-jni/Cargo.toml +++ b/java/lance-jni/Cargo.toml @@ -27,7 +27,7 @@ lance = { path = "../../rust/lance", features = ["substrait", "metrics"] } lance-datafusion = { path = "../../rust/lance-datafusion" } lance-encoding = { path = "../../rust/lance-encoding" } lance-linalg = { path = "../../rust/lance-linalg" } -lance-index = { path = "../../rust/lance-index" } +lance-index = { path = "../../rust/lance-index", features = ["tokenizer-jieba", "tokenizer-lindera"] } lance-io = { path = "../../rust/lance-io", features = ["metrics"] } lance-namespace = { path = "../../rust/lance-namespace" } lance-namespace-impls = { path = "../../rust/lance-namespace-impls", features = ["rest", "rest-adapter", "dir-goosefs"] } diff --git a/java/pom.xml b/java/pom.xml index 25abe78fa2b..3c066909ac2 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -490,6 +490,9 @@ false + + ${project.basedir}/src/test/resources/language_models + diff --git a/java/src/main/java/org/lance/index/scalar/InvertedIndexParams.java b/java/src/main/java/org/lance/index/scalar/InvertedIndexParams.java index 6928a78c4d9..6597fdc0841 100755 --- a/java/src/main/java/org/lance/index/scalar/InvertedIndexParams.java +++ b/java/src/main/java/org/lance/index/scalar/InvertedIndexParams.java @@ -42,10 +42,12 @@ public static Builder builder() { /** Builder for inverted scalar index parameters. */ public static final class Builder { + private String analyzer; + private String lanceTokenizer; private String baseTokenizer; private String language; private Boolean withPosition; - private Integer maxTokenLength; + private Integer maxTokenLength = 40; private Boolean lowerCase; private Boolean stem; private Boolean removeStopWords; @@ -55,10 +57,52 @@ public static final class Builder { private Integer maxNgramLength; private Boolean prefixOnly; private Integer blockSize = 128; - private Boolean skipMerge; + private Boolean splitIdentifiers; + private Boolean splitOnNumerics; + private Boolean preserveOriginal; + private Boolean indexOperators; + private Long memoryLimit; + private Integer numWorkers; private Integer formatVersion; private DocumentGranularity documentGranularity = DocumentGranularity.ROW; + /** + * Configure the analyzer preset. + * + *

Supported values are {@code "text"} and {@code "code"}. The code analyzer selects the code + * tokenizer defaults and requires FTS format v3. If unset, the analyzer is inferred from {@link + * #baseTokenizer(String)}. + * + * @param analyzer analyzer preset + * @return this builder + */ + public Builder analyzer(String analyzer) { + Objects.requireNonNull(analyzer, "analyzer must not be null"); + if (analyzer.isEmpty()) { + throw new IllegalArgumentException("analyzer must not be empty"); + } + this.analyzer = analyzer; + return this; + } + + /** + * Configure the document-level tokenizer used before lexical tokenization. + * + *

Supported values are {@code "text"} for plain strings and {@code "json"} for JSON strings. + * If unset, Lance infers the document tokenizer from the Arrow field type. + * + * @param lanceTokenizer document-level tokenizer + * @return this builder + */ + public Builder lanceTokenizer(String lanceTokenizer) { + Objects.requireNonNull(lanceTokenizer, "lanceTokenizer must not be null"); + if (lanceTokenizer.isEmpty()) { + throw new IllegalArgumentException("lanceTokenizer must not be empty"); + } + this.lanceTokenizer = lanceTokenizer; + return this; + } + /** * Configure the base tokenizer. * @@ -76,6 +120,11 @@ public static final class Builder { *

  • {@code "jieba/*"}: Jieba tokenizer * * + *

    Lindera and Jieba tokenizers load their language models from the directory configured by + * {@code LANCE_LANGUAGE_MODEL_HOME}, or from Lance's platform-specific default language model + * directory. The tokenizer suffix selects a model directory, for example {@code jieba/default}. + * The {@code code} tokenizer requires FTS format v3. + * * @param baseTokenizer tokenizer identifier string * @return this builder * @throws IllegalArgumentException @@ -119,12 +168,16 @@ public Builder withPosition(boolean withPosition) { /** * Configure the maximum token length. * - * @param maxTokenLength maximum token length, must be positive + *

    The default is {@code 40}. Set this to {@code null} to disable the maximum token length + * filter. + * + * @param maxTokenLength maximum token length, or {@code null} for no limit; non-null values + * must be positive * @return this builder - * @throws IllegalArgumentException + * @throws IllegalArgumentException if {@code maxTokenLength} is not null and is not positive */ public Builder maxTokenLength(Integer maxTokenLength) { - if (maxTokenLength == null || maxTokenLength <= 0) { + if (maxTokenLength != null && maxTokenLength <= 0) { throw new IllegalArgumentException("maxTokenLength must be positive when specified"); } this.maxTokenLength = maxTokenLength; @@ -252,15 +305,105 @@ public Builder blockSize(int blockSize) { } /** - * Configure whether to skip the partition merge stage after indexing. If true, skip the - * partition merge stage after indexing. This can be useful for distributed indexing where merge - * is handled separately. + * Configure whether code identifiers are split into subwords. + * + *

    This option is valid only with the {@code code} analyzer. + * + * @param splitIdentifiers whether to split identifiers + * @return this builder + */ + public Builder splitIdentifiers(boolean splitIdentifiers) { + this.splitIdentifiers = splitIdentifiers; + return this; + } + + /** + * Configure whether code identifier subwords are split at letter-number boundaries. + * + *

    This option is valid only with the {@code code} analyzer. + * + * @param splitOnNumerics whether to split at numeric boundaries + * @return this builder + */ + public Builder splitOnNumerics(boolean splitOnNumerics) { + this.splitOnNumerics = splitOnNumerics; + return this; + } + + /** + * Configure whether complete code identifiers are indexed alongside their subwords. + * + *

    This option is valid only with the {@code code} analyzer. + * + * @param preserveOriginal whether to preserve complete identifiers + * @return this builder + */ + public Builder preserveOriginal(boolean preserveOriginal) { + this.preserveOriginal = preserveOriginal; + return this; + } + + /** + * Configure whether code operators such as {@code ::}, {@code ->}, and {@code !=} are indexed. + * + *

    This option is valid only with the {@code code} analyzer. + * + * @param indexOperators whether to index operators + * @return this builder + */ + public Builder indexOperators(boolean indexOperators) { + this.indexOperators = indexOperators; + return this; + } + + /** + * Configure the total memory limit in MiB for the build stage. + * + *

    The limit is split evenly across FTS workers and is not persisted with the index. If + * unset, each worker uses a 2 GiB build-time limit. + * + *

    A value of {@code 0} is passed through to Rust. + * + * @param memoryLimit total memory limit in MiB, must be non-negative + * @return this builder + * @throws IllegalArgumentException if {@code memoryLimit} is negative + */ + public Builder memoryLimit(long memoryLimit) { + if (memoryLimit < 0) { + throw new IllegalArgumentException("memoryLimit must be non-negative"); + } + this.memoryLimit = memoryLimit; + return this; + } + + /** + * Configure the number of workers used for the build stage. + * + *

    The effective value is capped at the available compute-intensive CPU count and is not + * persisted with the index. Rust clamps a value of {@code 0} to one worker. + * + * @param numWorkers requested worker count, must be non-negative + * @return this builder + * @throws IllegalArgumentException if {@code numWorkers} is negative + */ + public Builder numWorkers(int numWorkers) { + if (numWorkers < 0) { + throw new IllegalArgumentException("numWorkers must be non-negative"); + } + this.numWorkers = numWorkers; + return this; + } + + /** + * This option has no effect because the Rust inverted-index builder does not support skipping + * the partition merge stage. * * @param skipMerge whether to skip partition merge * @return this builder + * @deprecated this option has no effect and will be removed in a future release */ + @Deprecated public Builder skipMerge(boolean skipMerge) { - this.skipMerge = skipMerge; return this; } @@ -268,9 +411,10 @@ public Builder skipMerge(boolean skipMerge) { * Configure the on-disk FTS format version to write when creating a new index. * *

    If unset, Lance uses {@code LANCE_FTS_FORMAT_VERSION} when present and otherwise selects - * v3 for the code analyzer or {@code blockSize = 256}, and v2 for other indexes. Format v3 - * supports both posting block sizes. Formats v1 and v2 support only {@code blockSize = 128} and - * cannot be used with the code analyzer. + * v3 for the code analyzer, {@code baseTokenizer = "code"}, or {@code blockSize = 256}, and v2 + * for other indexes. Format v3 supports both posting block sizes. Formats v1 and v2 support + * only {@code blockSize = 128} and cannot be used with the code analyzer or code base + * tokenizer. * * @param formatVersion FTS format version, must be 1, 2, or 3 * @return this builder @@ -305,10 +449,16 @@ public ScalarIndexParams build() { Preconditions.checkArgument( formatVersion == 3 || blockSize == 128, "formatVersion 1 and 2 require blockSize 128"); Preconditions.checkArgument( - !"code".equals(baseTokenizer) || formatVersion == 3, - "baseTokenizer 'code' requires formatVersion 3"); + (!"code".equals(analyzer) && !"code".equals(baseTokenizer)) || formatVersion == 3, + "code analyzer and baseTokenizer 'code' require formatVersion 3"); } Map params = new HashMap<>(); + if (analyzer != null) { + params.put("analyzer", analyzer); + } + if (lanceTokenizer != null) { + params.put("lance_tokenizer", lanceTokenizer); + } if (baseTokenizer != null) { params.put("base_tokenizer", baseTokenizer); } @@ -318,9 +468,7 @@ public ScalarIndexParams build() { if (withPosition != null) { params.put("with_position", withPosition); } - if (maxTokenLength != null) { - params.put("max_token_length", maxTokenLength); - } + params.put("max_token_length", maxTokenLength); if (lowerCase != null) { params.put("lower_case", lowerCase); } @@ -353,8 +501,23 @@ public ScalarIndexParams build() { if (blockSize != null) { params.put("block_size", blockSize); } - if (skipMerge != null) { - params.put("skip_merge", skipMerge); + if (splitIdentifiers != null) { + params.put("split_identifiers", splitIdentifiers); + } + if (splitOnNumerics != null) { + params.put("split_on_numerics", splitOnNumerics); + } + if (preserveOriginal != null) { + params.put("preserve_original", preserveOriginal); + } + if (indexOperators != null) { + params.put("index_operators", indexOperators); + } + if (memoryLimit != null) { + params.put("memory_limit", memoryLimit); + } + if (numWorkers != null) { + params.put("num_workers", numWorkers); } if (formatVersion != null) { params.put("format_version", formatVersion); diff --git a/java/src/test/java/org/lance/index/scalar/InvertedIndexParamsTest.java b/java/src/test/java/org/lance/index/scalar/InvertedIndexParamsTest.java index 9fb8de4375b..59ef98db9e1 100644 --- a/java/src/test/java/org/lance/index/scalar/InvertedIndexParamsTest.java +++ b/java/src/test/java/org/lance/index/scalar/InvertedIndexParamsTest.java @@ -21,6 +21,7 @@ import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -42,6 +43,7 @@ void defaultBlockSizeIsSerialized() { Map json = JsonUtils.fromJson(params.getJsonParams().orElseThrow()); assertEquals(128, ((Number) json.get("block_size")).intValue()); assertEquals("row", json.get("document_granularity")); + assertEquals(40, ((Number) json.get("max_token_length")).intValue()); } @Test @@ -101,5 +103,63 @@ void codeAnalyzerRequiresFormatVersionThreeWhenExplicit() { assertThrows( IllegalArgumentException.class, () -> InvertedIndexParams.builder().baseTokenizer("code").formatVersion(2).build()); + assertThrows( + IllegalArgumentException.class, + () -> InvertedIndexParams.builder().analyzer("code").formatVersion(1).build()); + assertThrows( + IllegalArgumentException.class, + () -> InvertedIndexParams.builder().analyzer("code").formatVersion(2).build()); + } + + @Test + void additionalBuildParametersAreSerialized() { + ScalarIndexParams params = + InvertedIndexParams.builder() + .analyzer("code") + .lanceTokenizer("text") + .splitIdentifiers(true) + .splitOnNumerics(false) + .preserveOriginal(true) + .indexOperators(true) + .memoryLimit(4096) + .numWorkers(4) + .formatVersion(3) + .build(); + + Map json = JsonUtils.fromJson(params.getJsonParams().orElseThrow()); + assertEquals("code", json.get("analyzer")); + assertEquals("text", json.get("lance_tokenizer")); + assertEquals(true, json.get("split_identifiers")); + assertEquals(false, json.get("split_on_numerics")); + assertEquals(true, json.get("preserve_original")); + assertEquals(true, json.get("index_operators")); + assertEquals(4096L, ((Number) json.get("memory_limit")).longValue()); + assertEquals(4, ((Number) json.get("num_workers")).intValue()); + } + + @Test + void nullMaxTokenLengthIsSerializedAsNull() { + ScalarIndexParams params = InvertedIndexParams.builder().maxTokenLength(null).build(); + + Map json = JsonUtils.fromJson(params.getJsonParams().orElseThrow()); + assertTrue(json.containsKey("max_token_length")); + assertNull(json.get("max_token_length")); + } + + @Test + void zeroResourceParametersAreSerialized() { + ScalarIndexParams params = InvertedIndexParams.builder().memoryLimit(0).numWorkers(0).build(); + + Map json = JsonUtils.fromJson(params.getJsonParams().orElseThrow()); + assertEquals(0L, ((Number) json.get("memory_limit")).longValue()); + assertEquals(0, ((Number) json.get("num_workers")).intValue()); + } + + @Test + void negativeResourceParametersAreRejected() { + assertThrows( + IllegalArgumentException.class, () -> InvertedIndexParams.builder().memoryLimit(-1)); + assertThrows( + IllegalArgumentException.class, () -> InvertedIndexParams.builder().numWorkers(-1)); } } diff --git a/java/src/test/java/org/lance/ipc/LanceScannerFullTextSearchTest.java b/java/src/test/java/org/lance/ipc/LanceScannerFullTextSearchTest.java index 9098084ac9f..1468f52256b 100755 --- a/java/src/test/java/org/lance/ipc/LanceScannerFullTextSearchTest.java +++ b/java/src/test/java/org/lance/ipc/LanceScannerFullTextSearchTest.java @@ -19,6 +19,7 @@ import org.lance.index.IndexOptions; import org.lance.index.IndexParams; import org.lance.index.IndexType; +import org.lance.index.scalar.InvertedIndexParams; import org.lance.index.scalar.ScalarIndexParams; import org.apache.arrow.c.ArrowArrayStream; @@ -40,6 +41,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -55,6 +57,23 @@ void testMatchQuery() throws Exception { 2L); } + @Test + void testJiebaTokenizer() throws Exception { + ScalarIndexParams indexParams = + InvertedIndexParams.builder() + .baseTokenizer("jieba/default") + .stem(false) + .removeStopWords(false) + .build(); + + runFtsQuery( + "memory://fts_java_jieba", + FullTextQuery.match("我们", "doc"), + 1L, + Arrays.asList("我们都有光明的前途", "光明的前途"), + indexParams); + } + @Test void testExplicitListElementGranularityReachesRustRouting() { RuntimeException error = @@ -108,6 +127,25 @@ void testBooleanQuery() throws Exception { } private void runFtsQuery(String uri, FullTextQuery query, long expectedTotal) throws Exception { + ScalarIndexParams indexParams = + ScalarIndexParams.create( + "inverted", + "{\"base_tokenizer\":\"simple\",\"language\":\"English\",\"with_position\":true}"); + runFtsQuery( + uri, + query, + expectedTotal, + Arrays.asList("hello world", "hello lance", "other text"), + indexParams); + } + + private void runFtsQuery( + String uri, + FullTextQuery query, + long expectedTotal, + List documents, + ScalarIndexParams scalarParams) + throws Exception { Schema schema = new Schema( @@ -122,16 +160,13 @@ private void runFtsQuery(String uri, FullTextQuery query, long expectedTotal) th VarCharVector titleVector = (VarCharVector) root.getVector("title"); docVector.allocateNew(); - docVector.setSafe(0, "hello world".getBytes(StandardCharsets.UTF_8)); - docVector.setSafe(1, "hello lance".getBytes(StandardCharsets.UTF_8)); - docVector.setSafe(2, "other text".getBytes(StandardCharsets.UTF_8)); - titleVector.allocateNew(); - titleVector.setSafe(0, "bye world".getBytes(StandardCharsets.UTF_8)); - titleVector.setSafe(1, "bye lance".getBytes(StandardCharsets.UTF_8)); - titleVector.setSafe(2, "say hello".getBytes(StandardCharsets.UTF_8)); - - root.setRowCount(3); + List titles = Arrays.asList("bye world", "bye lance", "say hello"); + for (int i = 0; i < documents.size(); i++) { + docVector.setSafe(i, documents.get(i).getBytes(StandardCharsets.UTF_8)); + titleVector.setSafe(i, titles.get(i).getBytes(StandardCharsets.UTF_8)); + } + root.setRowCount(documents.size()); ByteArrayOutputStream out = new ByteArrayOutputStream(); try (ArrowStreamWriter writer = new ArrowStreamWriter(root, null, out)) { @@ -150,10 +185,6 @@ private void runFtsQuery(String uri, FullTextQuery query, long expectedTotal) th new WriteParams.Builder().withMode(WriteParams.WriteMode.CREATE).build(); try (Dataset dataset = Dataset.create(allocator, stream, uri, writeParams)) { - ScalarIndexParams scalarParams = - ScalarIndexParams.create( - "inverted", - "{\"base_tokenizer\":\"simple\",\"language\":\"English\",\"with_position\":true}"); IndexParams indexParams = IndexParams.builder().setScalarIndexParams(scalarParams).build(); diff --git a/java/src/test/resources/language_models/jieba/default/dict.txt b/java/src/test/resources/language_models/jieba/default/dict.txt new file mode 100644 index 00000000000..d127091108a --- /dev/null +++ b/java/src/test/resources/language_models/jieba/default/dict.txt @@ -0,0 +1,4 @@ +我们 1000 r +都有 1000 v +光明 1000 a +前途 1000 n