1
0
mirror of https://github.com/tensorchord/pgvecto.rs.git synced 2025-07-30 19:23:05 +03:00

merge 'main' into 'feat/binary-vector'

Signed-off-by: Mingzhuo Yin <yinmingzhuo@gmail.com>
This commit is contained in:
Mingzhuo Yin
2024-02-20 17:02:04 +08:00
93 changed files with 1733 additions and 2809 deletions

View File

@ -54,19 +54,67 @@ jobs:
VERSION: ${{ matrix.version }} VERSION: ${{ matrix.version }}
OS: ${{ matrix.os }} OS: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/cache/restore@v3 - uses: actions/cache/restore@v4
id: cache
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
~/.cargo/registry/cache/ ~/.cargo/registry/cache/
~/.cargo/git/db/ ~/.cargo/git/db/
key: cargo-${{ matrix.os }}-pg${{ matrix.version }}-${{ hashFiles('./Cargo.lock') }} key: ${{ github.job }}-${{ matrix.version }}-${{ matrix.os }}-${{ hashFiles('./Cargo.lock') }}
restore-keys: cargo-${{ matrix.os }}-pg${{ matrix.version }} - uses: mozilla-actions/sccache-action@v0.0.4
- uses: mozilla-actions/sccache-action@v0.0.3
- name: Setup - name: Setup
shell: bash shell: bash
run: ./scripts/ci_setup.sh run: |
./scripts/ci_setup.sh
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall sqllogictest-bin -y --force
cargo install cargo-pgrx@$(grep 'pgrx = {' Cargo.toml | cut -d '"' -f 2 | head -n 1) --debug
cargo pgrx init --pg$VERSION=$(which pg_config)
- name: Install release
run: ./scripts/ci_install.sh
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Test
run: ./tests/tests.sh
- uses: actions/cache/save@v4
if: ${{ !steps.cache.outputs.cache-hit }}
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ github.job }}-${{ matrix.version }}-${{ matrix.os }}-${{ hashFiles('./Cargo.lock') }}
debug_check:
strategy:
matrix:
include:
- { version: 14, os: "ubuntu-latest" }
- { version: 15, os: "ubuntu-latest" }
- { version: 16, os: "ubuntu-latest" }
runs-on: ${{ matrix.os }}
env:
VERSION: ${{ matrix.version }}
OS: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/cache/restore@v4
id: cache
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ github.job }}-${{ matrix.version }}-${{ matrix.os }}-${{ hashFiles('./Cargo.lock') }}
- uses: mozilla-actions/sccache-action@v0.0.4
- name: Setup
shell: bash
run: |
./scripts/ci_setup.sh
cargo install cargo-pgrx@$(grep 'pgrx = {' Cargo.toml | cut -d '"' -f 2 | head -n 1) --debug
cargo pgrx init --pg$VERSION=$(which pg_config)
- name: Format check - name: Format check
run: cargo fmt --check run: cargo fmt --check
- name: Semantic check - name: Semantic check
@ -80,17 +128,11 @@ jobs:
- name: Test - name: Test
run: | run: |
cargo test --all --no-fail-fast --no-default-features --features "pg${{ matrix.version }} pg_test" --target x86_64-unknown-linux-gnu -- --nocapture cargo test --all --no-fail-fast --no-default-features --features "pg${{ matrix.version }} pg_test" --target x86_64-unknown-linux-gnu -- --nocapture
- name: Install release - uses: actions/cache/save@v4
run: ./scripts/ci_install.sh if: ${{ !steps.cache.outputs.cache-hit }}
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Test 2
run: ./tests/tests.sh
- uses: actions/cache/save@v3
with: with:
path: | path: |
~/.cargo/registry/index/ ~/.cargo/registry/index/
~/.cargo/registry/cache/ ~/.cargo/registry/cache/
~/.cargo/git/db/ ~/.cargo/git/db/
key: cargo-${{ matrix.os }}-pg${{ matrix.version }}-${{ hashFiles('./Cargo.lock') }} key: ${{ github.job }}-${{ matrix.version }}-${{ matrix.os }}-${{ hashFiles('./Cargo.lock') }}

View File

@ -55,14 +55,6 @@ jobs:
run: | run: |
sed -i "s/@CARGO_VERSION@/${{ needs.semver.outputs.version }}/g" ./vectors.control sed -i "s/@CARGO_VERSION@/${{ needs.semver.outputs.version }}/g" ./vectors.control
cat ./vectors.control cat ./vectors.control
- uses: actions/cache/restore@v3
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: cargo-${{ runner.os }}-pg${{ matrix.version }}-${{ hashFiles('./Cargo.lock') }}
restore-keys: cargo-${{ runner.os }}-pg${{ matrix.version }}
- uses: mozilla-actions/sccache-action@v0.0.3 - uses: mozilla-actions/sccache-action@v0.0.3
- name: Prepare - name: Prepare
run: | run: |

342
Cargo.lock generated
View File

@ -27,16 +27,58 @@ dependencies = [
] ]
[[package]] [[package]]
name = "anstyle" name = "anstream"
version = "1.0.5" version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220" checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.79" version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
@ -49,9 +91,6 @@ name = "arrayvec"
version = "0.7.4" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
@ -61,7 +100,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -104,6 +143,37 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base"
version = "0.0.0"
dependencies = [
"arc-swap",
"bincode",
"bitvec",
"bytemuck",
"byteorder",
"c",
"crc32fast",
"crossbeam",
"dashmap",
"detect",
"half 2.3.1",
"libc",
"log",
"memmap2",
"multiversion",
"num-traits",
"parking_lot",
"rand",
"rayon",
"rustix",
"serde",
"serde_json",
"thiserror",
"uuid",
"validator",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.7" version = "0.21.7"
@ -121,22 +191,22 @@ dependencies = [
[[package]] [[package]]
name = "bindgen" name = "bindgen"
version = "0.69.2" version = "0.69.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
dependencies = [ dependencies = [
"bitflags 2.4.2", "bitflags 2.4.2",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools",
"lazy_static", "lazy_static",
"lazycell", "lazycell",
"peeking_take_while",
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
"rustc-hash", "rustc-hash",
"shlex", "shlex",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -190,15 +260,15 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.14.0" version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.14.1" version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
@ -211,7 +281,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -283,9 +353,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.4.18" version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -303,9 +373,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.4.18" version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"clap_lex", "clap_lex",
@ -313,21 +383,27 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.4.7" version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.6.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
@ -355,9 +431,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.3.2" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -507,9 +583,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "either" name = "either"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]] [[package]]
name = "enum-map" name = "enum-map"
@ -528,20 +604,30 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
]
[[package]]
name = "env_filter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
dependencies = [
"log",
"regex",
] ]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.2" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d"
dependencies = [ dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime", "humantime",
"is-terminal",
"log", "log",
"regex",
"termcolor",
] ]
[[package]] [[package]]
@ -639,7 +725,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -757,12 +843,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
[[package]] [[package]]
name = "hmac" name = "hmac"
version = "0.12.1" version = "0.12.1"
@ -812,23 +892,29 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.2" version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
] ]
[[package]] [[package]]
name = "is-terminal" name = "interprocess_atomic_wait"
version = "0.4.10" version = "0.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [ dependencies = [
"hermit-abi", "libc",
"rustix", "ulock-sys",
"windows-sys 0.52.0", ]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
] ]
[[package]] [[package]]
@ -839,9 +925,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.67" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -929,6 +1015,15 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memfd"
version = "0.0.0"
dependencies = [
"detect",
"rand",
"rustix",
]
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.9.4" version = "0.9.4"
@ -955,9 +1050,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@ -1016,9 +1111,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.17" version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm", "libm",
@ -1074,6 +1169,12 @@ dependencies = [
"windows-targets 0.48.5", "windows-targets 0.48.5",
] ]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]] [[package]]
name = "pathsearch" name = "pathsearch"
version = "0.2.0" version = "0.2.0"
@ -1084,12 +1185,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -1098,9 +1193,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.6" version = "2.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror", "thiserror",
@ -1527,7 +1622,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [ dependencies = [
"semver 1.0.21", "semver 1.0.22",
] ]
[[package]] [[package]]
@ -1557,9 +1652,9 @@ dependencies = [
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.16" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -1593,9 +1688,9 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.21" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]] [[package]]
name = "semver-parser" name = "semver-parser"
@ -1606,6 +1701,15 @@ dependencies = [
"pest", "pest",
] ]
[[package]]
name = "send_fd"
version = "0.0.0"
dependencies = [
"libc",
"log",
"rustix",
]
[[package]] [[package]]
name = "seq-macro" name = "seq-macro"
version = "0.3.5" version = "0.3.5"
@ -1639,7 +1743,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -1667,7 +1771,7 @@ name = "service"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"arrayvec", "base",
"bincode", "bincode",
"bitvec", "bitvec",
"bytemuck", "bytemuck",
@ -1681,7 +1785,6 @@ dependencies = [
"libc", "libc",
"log", "log",
"memmap2", "memmap2",
"memoffset",
"multiversion", "multiversion",
"num-traits", "num-traits",
"parking_lot", "parking_lot",
@ -1691,7 +1794,6 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
"ulock-sys",
"uuid", "uuid",
"validator", "validator",
] ]
@ -1804,9 +1906,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.48" version = "2.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1842,44 +1944,34 @@ checksum = "cfb5fa503293557c5158bd215fdc225695e567a77e453f5d4452a50a193969bd"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.9.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"redox_syscall",
"rustix", "rustix",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.56" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.56" version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
] ]
[[package]] [[package]]
@ -1899,9 +1991,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.35.1" version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -1954,9 +2046,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.9" version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325" checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@ -1975,9 +2067,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.21.1" version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -2061,9 +2153,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.10.1" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "url" name = "url"
@ -2076,6 +2168,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.7.0" version = "1.7.0"
@ -2133,6 +2231,7 @@ name = "vectors"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"base",
"bincode", "bincode",
"bitvec", "bitvec",
"bytemuck", "bytemuck",
@ -2140,13 +2239,18 @@ dependencies = [
"detect", "detect",
"env_logger", "env_logger",
"half 2.3.1", "half 2.3.1",
"interprocess_atomic_wait",
"libc", "libc",
"log", "log",
"memfd",
"memmap2",
"num-traits", "num-traits",
"paste",
"pgrx", "pgrx",
"pgrx-tests", "pgrx-tests",
"rand", "rand",
"rustix", "rustix",
"send_fd",
"serde", "serde",
"serde_json", "serde_json",
"service", "service",
@ -2188,9 +2292,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -2198,24 +2302,24 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -2223,28 +2327,28 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.48", "syn 2.0.49",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.90" version = "0.2.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.67" version = "0.3.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -2425,9 +2529,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.36" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "818ce546a11a9986bc24f93d0cdf38a8a1a400f1473ea8c82e59f6e0ffab9249" checksum = "d90f4e0f530c4c69f62b80d839e9ef3855edc9cba471a160c4d692deed62b401"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View File

@ -14,26 +14,32 @@ pg16 = ["pgrx/pg16", "pgrx-tests/pg16"]
pg_test = [] pg_test = []
[dependencies] [dependencies]
arrayvec.workspace = true
bincode.workspace = true
bitvec.workspace = true
bytemuck.workspace = true
byteorder.workspace = true
half.workspace = true
libc.workspace = true libc.workspace = true
log.workspace = true log.workspace = true
memmap2.workspace = true
num-traits.workspace = true
paste.workspace = true
rand.workspace = true
rustix.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
validator.workspace = true
rustix.workspace = true
thiserror.workspace = true thiserror.workspace = true
byteorder.workspace = true validator.workspace = true
bincode.workspace = true base = { path = "crates/base" }
half.workspace = true
num-traits.workspace = true
rand.workspace = true
bytemuck.workspace = true
bitvec.workspace = true
service = { path = "crates/service" }
detect = { path = "crates/detect" } detect = { path = "crates/detect" }
send_fd = { path = "crates/send_fd" }
service = { path = "crates/service" }
interprocess_atomic_wait = { path = "crates/interprocess-atomic-wait" }
memfd = { path = "crates/memfd" }
pgrx = { version = "0.11.3", default-features = false, features = [] } pgrx = { version = "0.11.3", default-features = false, features = [] }
env_logger = "0.10.0" env_logger = "0.11.2"
toml = "0.8.8" toml = "0.8.10"
arrayvec = "0.7.4"
[dev-dependencies] [dev-dependencies]
pgrx-tests = "0.11.3" pgrx-tests = "0.11.3"
@ -59,25 +65,30 @@ version = "0.0.0"
edition = "2021" edition = "2021"
[workspace.dependencies] [workspace.dependencies]
libc = "~0.2" arrayvec = "~0.7"
log = "~0.4"
serde = "~1.0"
serde_json = "1"
thiserror = "~1.0"
bincode = "~1.3" bincode = "~1.3"
bitvec = { version = "~1.0", features = ["serde"] } bitvec = { version = "~1.0", features = ["serde"] }
byteorder = "~1.5"
bytemuck = { version = "~1.14", features = ["extern_crate_alloc"] } bytemuck = { version = "~1.14", features = ["extern_crate_alloc"] }
byteorder = "~1.5"
half = { version = "~2.3", features = [ half = { version = "~2.3", features = [
"bytemuck", "bytemuck",
"num-traits", "num-traits",
"serde", "serde",
"use-intrinsics", "use-intrinsics",
"rand_distr",
] } ] }
libc = "~0.2"
log = "~0.4"
memmap2 = "0.9.4"
num-traits = "~0.2" num-traits = "~0.2"
validator = { version = "~0.16", features = ["derive"] } paste = "~1.0"
rand = "0.8.5"
rustix = { version = "~0.38", features = ["fs", "net", "mm"] } rustix = { version = "~0.38", features = ["fs", "net", "mm"] }
rand = "~0.8" serde = "~1.0"
serde_json = "~1.0"
thiserror = "~1.0"
uuid = { version = "1.7.0", features = ["v4", "serde"] }
validator = { version = "~0.16", features = ["derive"] }
[profile.dev] [profile.dev]
panic = "unwind" panic = "unwind"

View File

@ -45,7 +45,7 @@ docker run \
--name pgvecto-rs-demo \ --name pgvecto-rs-demo \
-e POSTGRES_PASSWORD=mysecretpassword \ -e POSTGRES_PASSWORD=mysecretpassword \
-p 5432:5432 \ -p 5432:5432 \
-d tensorchord/pgvecto-rs:pg16-v0.1.14-beta -d tensorchord/pgvecto-rs:pg16-v0.2.0
``` ```
Then you can connect to the database using the `psql` command line tool. The default username is `postgres`, and the default password is `mysecretpassword`. Then you can connect to the database using the `psql` command line tool. The default username is `postgres`, and the default password is `mysecretpassword`.

View File

@ -55,7 +55,7 @@ EXPECTED_SQRT_EUCLID_DIS = [14.0, 2030.4756, 3.0]
OP_NEG_DOT_PROD_DIS = [1, 2, 4] OP_NEG_DOT_PROD_DIS = [1, 2, 4]
EXPECTED_NEG_DOT_PROD_DIS = [-17.0, 80.64, -7.0] EXPECTED_NEG_DOT_PROD_DIS = [-17.0, 80.64, -7.0]
OP_NEG_COS_DIS = [3, 2, 1] OP_NEG_COS_DIS = [3, 2, 1]
EXPECTED_NEG_COS_DIS = [-0.7142857, 0.5199225, -0.92582005] EXPECTED_NEG_COS_DIS = [0.28571427, 1.5199225, 0.07417989]
# ==== test_delete ==== # ==== test_delete ====
LEN_AFT_DEL = 2 LEN_AFT_DEL = 2

41
crates/base/Cargo.toml Normal file
View File

@ -0,0 +1,41 @@
[package]
name = "base"
version.workspace = true
edition.workspace = true
[dependencies]
bincode.workspace = true
bitvec.workspace = true
bytemuck.workspace = true
byteorder.workspace = true
half.workspace = true
libc.workspace = true
log.workspace = true
memmap2.workspace = true
num-traits.workspace = true
rand.workspace = true
rustix.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
uuid.workspace = true
validator.workspace = true
c = { path = "../c" }
detect = { path = "../detect" }
crc32fast = "1.4.0"
crossbeam = "0.8.4"
dashmap = "5.5.3"
parking_lot = "0.12.1"
rayon = "1.8.1"
arc-swap = "1.6.0"
multiversion = "0.7.3"
[lints]
clippy.derivable_impls = "allow"
clippy.len_without_is_empty = "allow"
clippy.needless_range_loop = "allow"
clippy.too_many_arguments = "allow"
rust.internal_features = "allow"
rust.unsafe_op_in_unsafe_fn = "forbid"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"

95
crates/base/src/error.rs Normal file
View File

@ -0,0 +1,95 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
// control plane
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum CreateError {
#[error("Index of given name already exists.")]
Exist,
#[error("Invalid index options.")]
InvalidIndexOptions { reason: String },
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum DropError {
#[error("Index not found.")]
NotExist,
}
// data plane
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum FlushError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum InsertError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
#[error("Invalid vector.")]
InvalidVector,
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum DeleteError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum BasicError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
#[error("Invalid vector.")]
InvalidVector,
#[error("Invalid search options.")]
InvalidSearchOptions { reason: String },
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum VbaseError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
#[error("Invalid vector.")]
InvalidVector,
#[error("Invalid search options.")]
InvalidSearchOptions { reason: String },
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum ListError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
}
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
pub enum StatError {
#[error("Index not found.")]
NotExist,
#[error("Maintenance should be done.")]
Upgrade,
}

8
crates/base/src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
#![feature(core_intrinsics)]
#![feature(pointer_is_aligned)]
pub mod error;
pub mod scalar;
pub mod search;
pub mod sys;
pub mod vector;

View File

@ -1,4 +1,4 @@
use crate::prelude::global::FloatCast; use super::FloatCast;
use half::f16; use half::f16;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;

View File

@ -1,4 +1,4 @@
use crate::prelude::global::FloatCast; use super::FloatCast;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};

View File

@ -0,0 +1,16 @@
mod f16;
mod f32;
pub use f16::F16;
pub use f32::F32;
pub trait FloatCast: Sized {
fn from_f32(x: f32) -> Self;
fn to_f32(self) -> f32;
fn from_f(x: F32) -> Self {
Self::from_f32(x.0)
}
fn to_f(self) -> F32 {
F32(Self::to_f32(self))
}
}

View File

@ -1,4 +1,4 @@
use crate::prelude::F32; use crate::scalar::F32;
pub type Payload = u64; pub type Payload = u64;

View File

@ -1,4 +1,5 @@
use crate::prelude::*; use super::Vector;
use crate::scalar::F32;
use bitvec::{slice::BitSlice, vec::BitVec}; use bitvec::{slice::BitSlice, vec::BitVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -0,0 +1,21 @@
mod binary;
mod sparse_f32;
pub use binary::{BinaryVec, BinaryVecRef};
pub use sparse_f32::{SparseF32, SparseF32Ref};
pub trait Vector {
fn dims(&self) -> u16;
}
impl<T> Vector for Vec<T> {
fn dims(&self) -> u16 {
self.len().try_into().unwrap()
}
}
impl<'a, T> Vector for &'a [T] {
fn dims(&self) -> u16 {
self.len().try_into().unwrap()
}
}

View File

@ -1,4 +1,6 @@
use crate::prelude::*; use super::Vector;
use crate::scalar::F32;
use num_traits::Zero;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]

View File

@ -4,9 +4,9 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dev-dependencies] [dev-dependencies]
half = { version = "~2.3", features = ["use-intrinsics", "rand_distr"] } half.workspace = true
rand.workspace = true
detect = { path = "../detect" } detect = { path = "../detect" }
rand = "0.8.5"
[build-dependencies] [build-dependencies]
cc = "1.0" cc = "1.0"

View File

@ -4,5 +4,5 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
std_detect = { git = "https://github.com/tensorchord/stdarch.git", branch = "avx512fp16" }
rustix.workspace = true rustix.workspace = true
std_detect = { git = "https://github.com/tensorchord/stdarch.git", branch = "avx512fp16" }

View File

@ -0,0 +1,16 @@
[package]
name = "interprocess_atomic_wait"
version.workspace = true
edition.workspace = true
[dependencies]
libc.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
ulock-sys = "0.1.0"
[lints]
rust.internal_features = "allow"
rust.unsafe_op_in_unsafe_fn = "forbid"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"

View File

@ -0,0 +1,91 @@
use std::sync::atomic::AtomicU32;
use std::time::Duration;
#[cfg(target_os = "linux")]
#[inline(always)]
pub fn wait(futex: &AtomicU32, value: u32, timeout: Duration) {
let timeout = libc::timespec {
tv_sec: i64::try_from(timeout.as_secs()).expect("Timeout is overflow."),
tv_nsec: timeout.subsec_nanos().into(),
};
unsafe {
libc::syscall(
libc::SYS_futex,
futex.as_ptr(),
libc::FUTEX_WAIT,
value,
&timeout,
);
}
}
#[cfg(target_os = "linux")]
#[inline(always)]
pub fn wake(futex: &AtomicU32) {
unsafe {
libc::syscall(libc::SYS_futex, futex.as_ptr(), libc::FUTEX_WAKE, i32::MAX);
}
}
#[cfg(target_os = "macos")]
#[inline(always)]
pub fn wait(futex: &AtomicU32, value: u32, timeout: Duration) {
let timeout = u32::try_from(timeout.as_millis()).expect("Timeout is overflow.");
unsafe {
// https://github.com/apple-oss-distributions/xnu/blob/main/bsd/kern/sys_ulock.c#L531
ulock_sys::__ulock_wait(
ulock_sys::darwin19::UL_COMPARE_AND_WAIT_SHARED,
futex.as_ptr().cast(),
value as _,
timeout,
);
}
}
#[cfg(target_os = "macos")]
#[inline(always)]
pub fn wake(futex: &AtomicU32) {
unsafe {
ulock_sys::__ulock_wake(
ulock_sys::darwin19::UL_COMPARE_AND_WAIT_SHARED,
futex.as_ptr().cast(),
0,
);
}
}
#[cfg(target_os = "freebsd")]
#[inline(always)]
pub fn wait(futex: &AtomicU32, value: u32, timeout: Duration) {
let ptr: *const AtomicU32 = futex;
let mut timeout = libc::timespec {
tv_sec: i64::try_from(timeout.as_secs()).expect("Timeout is overflow."),
tv_nsec: timeout.subsec_nanos().into(),
};
unsafe {
// https://github.com/freebsd/freebsd-src/blob/main/sys/kern/kern_umtx.c#L3943
// https://github.com/freebsd/freebsd-src/blob/main/sys/kern/kern_umtx.c#L3836
libc::_umtx_op(
ptr as *mut libc::c_void,
libc::UMTX_OP_WAIT_UINT,
value as libc::c_ulong,
std::mem::size_of_val(&timeout) as *mut std::ffi::c_void,
std::ptr::addr_of_mut!(timeout).cast(),
);
};
}
#[cfg(target_os = "freebsd")]
#[inline(always)]
pub fn wake(futex: &AtomicU32) {
let ptr: *const AtomicU32 = futex;
unsafe {
libc::_umtx_op(
ptr as *mut libc::c_void,
libc::UMTX_OP_WAKE,
i32::MAX as libc::c_ulong,
core::ptr::null_mut(),
core::ptr::null_mut(),
);
};
}

15
crates/memfd/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "memfd"
version.workspace = true
edition.workspace = true
[dependencies]
rand.workspace = true
rustix.workspace = true
detect = { path = "../detect" }
[lints]
rust.internal_features = "allow"
rust.unsafe_op_in_unsafe_fn = "forbid"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"

70
crates/memfd/src/lib.rs Normal file
View File

@ -0,0 +1,70 @@
use std::os::fd::OwnedFd;
#[cfg(target_os = "linux")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
if detect::linux::detect_memfd() {
use rustix::fs::MemfdFlags;
Ok(rustix::fs::memfd_create(
format!(".memfd.MEMFD.{:x}", std::process::id()),
MemfdFlags::empty(),
)?)
} else {
use rustix::fs::Mode;
use rustix::fs::OFlags;
// POSIX fcntl locking do not support shmem, so we use a regular file here.
// reference: https://man7.org/linux/man-pages/man3/fcntl.3p.html
// However, Linux shmem supports fcntl locking.
let name = format!(
".shm.MEMFD.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}
}
#[cfg(target_os = "macos")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
use rustix::fs::Mode;
use rustix::fs::OFlags;
// POSIX fcntl locking do not support shmem, so we use a regular file here.
// reference: https://man7.org/linux/man-pages/man3/fcntl.3p.html
let name = format!(
".shm.MEMFD.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}
#[cfg(target_os = "freebsd")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
use rustix::fs::Mode;
use rustix::fs::OFlags;
// POSIX fcntl locking do not support shmem, so we use a regular file here.
// reference: https://man7.org/linux/man-pages/man3/fcntl.3p.html
let name = format!(
".shm.MEMFD.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}

15
crates/send_fd/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "send_fd"
version.workspace = true
edition.workspace = true
[dependencies]
libc.workspace = true
log.workspace = true
rustix.workspace = true
[lints]
rust.internal_features = "allow"
rust.unsafe_op_in_unsafe_fn = "forbid"
rust.unused_lifetimes = "warn"
rust.unused_qualifications = "warn"

View File

@ -6,12 +6,12 @@ use std::io::{IoSlice, IoSliceMut};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
#[repr(C)] #[repr(C)]
pub struct FileSocket { pub struct SendFd {
tx: OwnedFd, tx: OwnedFd,
rx: OwnedFd, rx: OwnedFd,
} }
impl FileSocket { impl SendFd {
pub fn new() -> std::io::Result<Self> { pub fn new() -> std::io::Result<Self> {
let (tx, rx) = UnixStream::pair()?; let (tx, rx) = UnixStream::pair()?;
Ok(Self { Ok(Self {
@ -47,7 +47,7 @@ fn recv_fd(rx: BorrowedFd<'_>) -> std::io::Result<OwnedFd> {
let mut control = RecvAncillaryBuffer::new(&mut buffer.0); let mut control = RecvAncillaryBuffer::new(&mut buffer.0);
let mut buffer_ios = [b'.']; let mut buffer_ios = [b'.'];
let ios = IoSliceMut::new(&mut buffer_ios); let ios = IoSliceMut::new(&mut buffer_ios);
let returned = rustix::net::recvmsg(rx, &mut [ios], &mut control, RecvFlags::CMSG_CLOEXEC)?; let returned = rustix::net::recvmsg(rx, &mut [ios], &mut control, RecvFlags::empty())?;
if returned.flags.bits() & libc::MSG_CTRUNC as u32 != 0 { if returned.flags.bits() & libc::MSG_CTRUNC as u32 != 0 {
log::warn!("Ancillary is truncated."); log::warn!("Ancillary is truncated.");
} }

View File

@ -4,37 +4,33 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
bincode.workspace = true
bitvec.workspace = true
bytemuck.workspace = true
byteorder.workspace = true
half.workspace = true
libc.workspace = true libc.workspace = true
log.workspace = true log.workspace = true
serde.workspace = true memmap2.workspace = true
serde_json.workspace = true
validator.workspace = true
rustix.workspace = true
thiserror.workspace = true
byteorder.workspace = true
bincode.workspace = true
half.workspace = true
num-traits.workspace = true num-traits.workspace = true
rand.workspace = true rand.workspace = true
bytemuck.workspace = true rustix.workspace = true
bitvec.workspace = true serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
uuid.workspace = true
validator.workspace = true
base = { path = "../base" }
c = { path = "../c" } c = { path = "../c" }
detect = { path = "../detect" } detect = { path = "../detect" }
crc32fast = "1.3.2" crc32fast = "1.4.0"
crossbeam = "0.8.2" crossbeam = "0.8.4"
dashmap = "5.4.0" dashmap = "5.5.3"
parking_lot = "0.12.1" parking_lot = "0.12.1"
memoffset = "0.9.0" rayon = "1.8.1"
arrayvec = { version = "0.7.3", features = ["serde"] }
memmap2 = "0.9.0"
rayon = "1.6.1"
uuid = { version = "1.6.1", features = ["v4", "serde"] }
arc-swap = "1.6.0" arc-swap = "1.6.0"
multiversion = "0.7.3" multiversion = "0.7.3"
[target.'cfg(target_os = "macos")'.dependencies]
ulock-sys = "0.1.0"
[lints] [lints]
clippy.derivable_impls = "allow" clippy.derivable_impls = "allow"
clippy.len_without_is_empty = "allow" clippy.len_without_is_empty = "allow"

View File

@ -1,5 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use crate::utils::vec2::Vec2; use crate::utils::vec2::Vec2;
use base::scalar::FloatCast;
use rand::rngs::StdRng; use rand::rngs::StdRng;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};

View File

@ -5,6 +5,7 @@ use crate::index::IndexOptions;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::dir_ops::sync_dir; use crate::utils::dir_ops::sync_dir;
use crate::utils::mmap_array::MmapArray; use crate::utils::mmap_array::MmapArray;
use base::scalar::FloatCast;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;

View File

@ -1,456 +0,0 @@
#![allow(unused)]
use crate::algorithms::raw::Raw;
use crate::prelude::*;
use crossbeam::atomic::AtomicCell;
use parking_lot::RwLock;
use parking_lot::RwLockReadGuard;
use parking_lot::RwLockWriteGuard;
use rand::distributions::Uniform;
use rand::prelude::SliceRandom;
use rand::Rng;
use rayon::prelude::*;
use std::cmp::Reverse;
use std::collections::{BTreeMap, BinaryHeap, HashSet};
use std::sync::Arc;
pub struct VertexWithDistance {
pub id: u32,
pub distance: Scalar,
}
impl VertexWithDistance {
pub fn new(id: u32, distance: Scalar) -> Self {
Self { id, distance }
}
}
impl PartialEq for VertexWithDistance {
fn eq(&self, other: &Self) -> bool {
self.distance.eq(&other.distance)
}
}
impl Eq for VertexWithDistance {}
impl PartialOrd for VertexWithDistance {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.distance.cmp(&other.distance))
}
}
impl Ord for VertexWithDistance {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.distance.cmp(&other.distance)
}
}
/// DiskANN search state.
pub struct SearchState {
pub visited: HashSet<u32>,
candidates: BTreeMap<Scalar, u32>,
heap: BinaryHeap<Reverse<VertexWithDistance>>,
heap_visited: HashSet<u32>,
l: usize,
/// Number of results to return.
//TODO: used during search.
k: usize,
}
impl SearchState {
/// Creates a new search state.
pub(crate) fn new(k: usize, l: usize) -> Self {
Self {
visited: HashSet::new(),
candidates: BTreeMap::new(),
heap: BinaryHeap::new(),
heap_visited: HashSet::new(),
k,
l,
}
}
/// Return the next unvisited vertex.
fn pop(&mut self) -> Option<u32> {
while let Some(vertex) = self.heap.pop() {
if !self.candidates.contains_key(&vertex.0.distance) {
// The vertex has been removed from the candidate lists,
// from [`push()`].
continue;
}
self.visited.insert(vertex.0.id);
return Some(vertex.0.id);
}
None
}
/// Push a new (unvisited) vertex into the search state.
fn push(&mut self, vertex_id: u32, distance: Scalar) {
assert!(!self.visited.contains(&vertex_id));
self.heap_visited.insert(vertex_id);
self.heap
.push(Reverse(VertexWithDistance::new(vertex_id, distance)));
self.candidates.insert(distance, vertex_id);
if self.candidates.len() > self.l {
self.candidates.pop_last();
}
}
/// Mark a vertex as visited.
fn visit(&mut self, vertex_id: u32) {
self.visited.insert(vertex_id);
}
// Returns true if the vertex has been visited.
fn is_visited(&self, vertex_id: u32) -> bool {
self.visited.contains(&vertex_id) || self.heap_visited.contains(&vertex_id)
}
}
pub struct VamanaImpl {
raw: Arc<Raw>,
/// neighbors[vertex_id*r..(vertex_id+1)*r] records r neighbors for each vertex
neighbors: Vec<AtomicCell<u32>>,
/// neighbor_size[vertex_id] records the actual number of neighbors for each vertex
/// the RwLock is for protecting both the data for size and original data
neighbor_size: Vec<RwLock<u32>>,
/// the entry for the entire graph, the closet vector to centroid
medoid: u32,
dims: u16,
r: u32,
alpha: f32,
l: usize,
d: Distance,
}
unsafe impl Send for VamanaImpl {}
unsafe impl Sync for VamanaImpl {}
impl VamanaImpl {
pub fn new(
raw: Arc<Raw>,
n: u32,
dims: u16,
r: u32,
alpha: f32,
l: usize,
d: Distance,
) -> Self {
let neighbors = {
let mut result = Vec::new();
result.resize_with(r as usize * n as usize, || AtomicCell::new(0));
result
};
let neighbor_size = unsafe {
let mut result = Vec::new();
result.resize_with(n as usize, || RwLock::new(0));
result
};
let medoid = 0;
let mut new_vamana = Self {
raw,
neighbors,
neighbor_size,
medoid,
dims,
r,
alpha,
l,
d,
};
// 1. init graph with r random neighbors for each node
let rng = rand::thread_rng();
new_vamana._init_graph(n, rng.clone());
// 2. find medoid
new_vamana.medoid = new_vamana._find_medoid(n);
// 3. iterate pass
new_vamana._one_pass(n, 1.0, r, l, rng.clone());
new_vamana._one_pass(n, alpha, r, l, rng.clone());
new_vamana
}
pub fn search<F>(&self, target: Box<[Scalar]>, k: usize, f: F) -> Vec<(Scalar, Payload)>
where
F: FnMut(Payload) -> bool,
{
// TODO: filter
let state = self._greedy_search_with_filter(0, &target, k, k * 2, f);
let mut results = BinaryHeap::<(Scalar, u32)>::new();
for (distance, row) in state.candidates {
if results.len() == k {
break;
}
results.push((distance, row));
}
let mut res_vec: Vec<(Scalar, Payload)> = results
.iter()
.map(|x| (x.0, self.raw.payload(x.1)))
.collect();
res_vec.sort();
res_vec
}
fn _greedy_search_with_filter<F>(
&self,
start: u32,
query: &[Scalar],
k: usize,
search_size: usize,
mut f: F,
) -> SearchState
where
F: FnMut(Payload) -> bool,
{
let mut state = SearchState::new(k, search_size);
let dist = self.d.distance(query, self.raw.vector(start));
state.push(start, dist);
while let Some(id) = state.pop() {
// only pop id in the search list but not visited
state.visit(id);
{
let guard = self.neighbor_size[id as usize].read();
let neighbor_ids = self._get_neighbors(id, &guard);
for neighbor_id in neighbor_ids {
let neighbor_id = neighbor_id.load();
if state.is_visited(neighbor_id) {
continue;
}
if f(self.raw.payload(neighbor_id)) {
let dist = self.d.distance(query, self.raw.vector(neighbor_id));
state.push(neighbor_id, dist); // push and retain closet l nodes
}
}
}
}
state
}
fn _init_graph(&self, n: u32, mut rng: impl Rng) {
let distribution = Uniform::new(0, n);
for i in 0..n {
let mut neighbor_ids: HashSet<u32> = HashSet::new();
if self.r < n {
while neighbor_ids.len() < self.r as usize {
let neighbor_id = rng.sample(distribution);
if neighbor_id != i {
neighbor_ids.insert(neighbor_id);
}
}
} else {
neighbor_ids = (0..n).collect();
}
{
let mut guard = self.neighbor_size[i as usize].write();
self._set_neighbors(i, &neighbor_ids, &mut guard);
}
}
}
fn _set_neighbors(
&self,
vertex_index: u32,
neighbor_ids: &HashSet<u32>,
guard: &mut RwLockWriteGuard<u32>,
) {
assert!(neighbor_ids.len() <= self.r as usize);
for (i, item) in neighbor_ids.iter().enumerate() {
self.neighbors[vertex_index as usize * self.r as usize + i].store(*item);
}
**guard = neighbor_ids.len() as u32;
}
fn _get_neighbors(
&self,
vertex_index: u32,
guard: &RwLockReadGuard<u32>,
) -> &[AtomicCell<u32>] {
//TODO: store neighbor length
let size = **guard;
&self.neighbors[(vertex_index as usize * self.r as usize)
..(vertex_index as usize * self.r as usize + size as usize)]
}
fn _get_neighbors_with_write_guard(
&self,
vertex_index: u32,
guard: &RwLockWriteGuard<u32>,
) -> &[AtomicCell<u32>] {
let size = **guard;
&self.neighbors[(vertex_index as usize * self.r as usize)
..(vertex_index as usize * self.r as usize + size as usize)]
}
fn _find_medoid(&self, n: u32) -> u32 {
let centroid = self._compute_centroid(n);
let centroid_arr: &[Scalar] = &centroid;
let mut medoid_index = 0;
let mut min_dis = Scalar::INFINITY;
for i in 0..n {
let dis = self.d.distance(centroid_arr, self.raw.vector(i));
if dis < min_dis {
min_dis = dis;
medoid_index = i;
}
}
medoid_index
}
fn _compute_centroid(&self, n: u32) -> Vec<Scalar> {
let dim = self.dims as usize;
let mut sum = vec![0_f64; dim]; // change to f32 to avoid overflow
for i in 0..n {
let vec = self.raw.vector(i);
for j in 0..dim {
sum[j] += f32::from(vec[j]) as f64;
}
}
let collection: Vec<Scalar> = sum
.iter()
.map(|v| Scalar::from((*v / n as f64) as f32))
.collect();
collection
}
// r and l leave here for multiple pass extension
fn _one_pass(&self, n: u32, alpha: f32, r: u32, l: usize, mut rng: impl Rng) {
let mut ids = (0..n).collect::<Vec<_>>();
ids.shuffle(&mut rng);
ids.into_par_iter()
.for_each(|id| self.search_and_prune_for_one_vertex(id, alpha, r, l));
}
fn search_and_prune_for_one_vertex(&self, id: u32, alpha: f32, r: u32, l: usize) {
let query = self.raw.vector(id);
let mut state = self._greedy_search(self.medoid, query, 1, l);
state.visited.remove(&id); // in case visited has id itself
let mut new_neighbor_ids: HashSet<u32> = HashSet::new();
{
let mut guard = self.neighbor_size[id as usize].write();
let neighbor_ids = self._get_neighbors_with_write_guard(id, &guard);
state.visited.extend(neighbor_ids.iter().map(|x| x.load()));
let neighbor_ids = self._robust_prune(id, state.visited, alpha, r);
let neighbor_ids: HashSet<u32> = neighbor_ids.into_iter().collect();
self._set_neighbors(id, &neighbor_ids, &mut guard);
new_neighbor_ids = neighbor_ids;
}
for &neighbor_id in new_neighbor_ids.iter() {
{
let mut guard = self.neighbor_size[neighbor_id as usize].write();
let old_neighbors = self._get_neighbors_with_write_guard(neighbor_id, &guard);
let mut old_neighbors: HashSet<u32> =
old_neighbors.iter().map(|x| x.load()).collect();
old_neighbors.insert(id);
if old_neighbors.len() > r as usize {
// need robust prune
let new_neighbors = self._robust_prune(neighbor_id, old_neighbors, alpha, r);
let new_neighbors: HashSet<u32> = new_neighbors.into_iter().collect();
self._set_neighbors(neighbor_id, &new_neighbors, &mut guard);
} else {
self._set_neighbors(neighbor_id, &old_neighbors, &mut guard);
}
}
}
}
fn _greedy_search(
&self,
start: u32,
query: &[Scalar],
k: usize,
search_size: usize,
) -> SearchState {
let mut state = SearchState::new(k, search_size);
let dist = self.d.distance(query, self.raw.vector(start));
state.push(start, dist);
while let Some(id) = state.pop() {
// only pop id in the search list but not visited
state.visit(id);
{
let guard = self.neighbor_size[id as usize].read();
let neighbor_ids = self._get_neighbors(id, &guard);
for neighbor_id in neighbor_ids {
let neighbor_id = neighbor_id.load();
if state.is_visited(neighbor_id) {
continue;
}
let dist = self.d.distance(query, self.raw.vector(neighbor_id));
state.push(neighbor_id, dist); // push and retain closet l nodes
}
}
}
state
}
fn _robust_prune(&self, id: u32, mut visited: HashSet<u32>, alpha: f32, r: u32) -> Vec<u32> {
let mut heap: BinaryHeap<VertexWithDistance> = visited
.iter()
.map(|v| {
let dist = self.d.distance(self.raw.vector(id), self.raw.vector(*v));
VertexWithDistance {
id: *v,
distance: dist,
}
})
.collect();
let mut new_neighbor_ids: Vec<u32> = vec![];
while !visited.is_empty() {
if let Some(mut p) = heap.pop() {
while !visited.contains(&p.id) {
match heap.pop() {
Some(value) => {
p = value;
}
None => {
return new_neighbor_ids;
}
}
}
new_neighbor_ids.push(p.id);
if new_neighbor_ids.len() >= r as usize {
break;
}
let mut to_remove: HashSet<u32> = HashSet::new();
for pv in visited.iter() {
let dist_prime = self.d.distance(self.raw.vector(p.id), self.raw.vector(*pv));
let dist_query = self.d.distance(self.raw.vector(id), self.raw.vector(*pv));
if Scalar::from(alpha) * dist_prime <= dist_query {
to_remove.insert(*pv);
}
}
for pv in to_remove.iter() {
visited.remove(pv);
}
} else {
return new_neighbor_ids;
}
}
new_neighbor_ids
}
}

View File

@ -92,13 +92,10 @@ pub struct SegmentStat {
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum IndexStat { pub struct IndexStat {
Normal { pub indexing: bool,
indexing: bool, pub segments: Vec<SegmentStat>,
segments: Vec<SegmentStat>, pub options: IndexOptions,
options: IndexOptions,
},
Upgrade,
} }
pub struct Index<S: G> { pub struct Index<S: G> {
@ -113,10 +110,10 @@ pub struct Index<S: G> {
} }
impl<S: G> Index<S> { impl<S: G> Index<S> {
pub fn create(path: PathBuf, options: IndexOptions) -> Result<Arc<Self>, ServiceError> { pub fn create(path: PathBuf, options: IndexOptions) -> Result<Arc<Self>, CreateError> {
if let Err(err) = options.validate() { if let Err(err) = options.validate() {
return Err(ServiceError::BadOption { return Err(CreateError::InvalidIndexOptions {
validation: err.to_string(), reason: err.to_string(),
}); });
} }
std::fs::create_dir(&path).unwrap(); std::fs::create_dir(&path).unwrap();
@ -277,7 +274,7 @@ impl<S: G> Index<S> {
} }
pub fn stat(&self) -> IndexStat { pub fn stat(&self) -> IndexStat {
let view = self.view(); let view = self.view();
IndexStat::Normal { IndexStat {
indexing: self.instant_index.load() < self.instant_write.load(), indexing: self.instant_index.load() < self.instant_write.load(),
options: self.options().clone(), options: self.options().clone(),
segments: { segments: {
@ -326,9 +323,14 @@ impl<S: G> IndexView<S> {
vector: S::VectorRef<'_>, vector: S::VectorRef<'_>,
opts: &'a SearchOptions, opts: &'a SearchOptions,
filter: F, filter: F,
) -> Result<impl Iterator<Item = Pointer> + 'a, ServiceError> { ) -> Result<impl Iterator<Item = Pointer> + 'a, BasicError> {
if self.options.vector.dims != vector.dims() { if self.options.vector.dims != vector.dims() {
return Err(ServiceError::Unmatched); return Err(BasicError::InvalidVector);
}
if let Err(err) = opts.validate() {
return Err(BasicError::InvalidSearchOptions {
reason: err.to_string(),
});
} }
struct Comparer(std::collections::BinaryHeap<Reverse<Element>>); struct Comparer(std::collections::BinaryHeap<Reverse<Element>>);
@ -399,9 +401,14 @@ impl<S: G> IndexView<S> {
vector: S::VectorRef<'a>, vector: S::VectorRef<'a>,
opts: &'a SearchOptions, opts: &'a SearchOptions,
filter: F, filter: F,
) -> Result<impl Iterator<Item = Pointer> + 'a, ServiceError> { ) -> Result<impl Iterator<Item = Pointer> + 'a, VbaseError> {
if self.options.vector.dims != vector.dims() { if self.options.vector.dims != vector.dims() {
return Err(ServiceError::Unmatched); return Err(VbaseError::InvalidVector);
}
if let Err(err) = opts.validate() {
return Err(VbaseError::InvalidSearchOptions {
reason: err.to_string(),
});
} }
struct Filtering<'a, F: 'a> { struct Filtering<'a, F: 'a> {
@ -463,7 +470,7 @@ impl<S: G> IndexView<S> {
} }
})) }))
} }
pub fn list(&self) -> impl Iterator<Item = Pointer> + '_ { pub fn list(&self) -> Result<impl Iterator<Item = Pointer> + '_, ListError> {
let sealed = self let sealed = self
.sealed .sealed
.values() .values()
@ -477,18 +484,19 @@ impl<S: G> IndexView<S> {
.iter() .iter()
.map(|(_, x)| x) .map(|(_, x)| x)
.flat_map(|x| (0..x.len()).map(|i| x.payload(i))); .flat_map(|x| (0..x.len()).map(|i| x.payload(i)));
sealed let iter = sealed
.chain(growing) .chain(growing)
.chain(write) .chain(write)
.filter_map(|p| self.delete.check(p)) .filter_map(|p| self.delete.check(p));
Ok(iter)
} }
pub fn insert( pub fn insert(
&self, &self,
vector: S::VectorOwned, vector: S::VectorOwned,
pointer: Pointer, pointer: Pointer,
) -> Result<Result<(), OutdatedError>, ServiceError> { ) -> Result<Result<(), OutdatedError>, InsertError> {
if self.options.vector.dims != vector.dims() { if self.options.vector.dims != vector.dims() {
return Err(ServiceError::Unmatched); return Err(InsertError::InvalidVector);
} }
let payload = (pointer.as_u48() << 16) | self.delete.version(pointer) as Payload; let payload = (pointer.as_u48() << 16) | self.delete.version(pointer) as Payload;
@ -502,14 +510,16 @@ impl<S: G> IndexView<S> {
Ok(Err(OutdatedError)) Ok(Err(OutdatedError))
} }
} }
pub fn delete(&self, p: Pointer) { pub fn delete(&self, p: Pointer) -> Result<(), DeleteError> {
self.delete.delete(p); self.delete.delete(p);
Ok(())
} }
pub fn flush(&self) { pub fn flush(&self) -> Result<(), FlushError> {
self.delete.flush(); self.delete.flush();
if let Some((_, write)) = &self.write { if let Some((_, write)) = &self.write {
write.flush(); write.flush();
} }
Ok(())
} }
} }

View File

@ -14,9 +14,9 @@ pub struct OptimizingOptions {
#[serde(default = "OptimizingOptions::default_sealing_size")] #[serde(default = "OptimizingOptions::default_sealing_size")]
#[validate(range(min = 1, max = 4_000_000_000))] #[validate(range(min = 1, max = 4_000_000_000))]
pub sealing_size: u32, pub sealing_size: u32,
#[serde(default = "OptimizingOptions::default_deleted_threshold", skip)] #[serde(default = "OptimizingOptions::default_delete_threshold")]
#[validate(range(min = 0.01, max = 1.00))] #[validate(range(min = 0.01, max = 1.00))]
pub deleted_threshold: f64, pub delete_threshold: f64,
#[serde(default = "OptimizingOptions::default_optimizing_threads")] #[serde(default = "OptimizingOptions::default_optimizing_threads")]
#[validate(range(min = 1, max = 65535))] #[validate(range(min = 1, max = 65535))]
pub optimizing_threads: usize, pub optimizing_threads: usize,
@ -29,7 +29,7 @@ impl OptimizingOptions {
fn default_sealing_size() -> u32 { fn default_sealing_size() -> u32 {
1 1
} }
fn default_deleted_threshold() -> f64 { fn default_delete_threshold() -> f64 {
0.2 0.2
} }
fn default_optimizing_threads() -> usize { fn default_optimizing_threads() -> usize {
@ -45,7 +45,7 @@ impl Default for OptimizingOptions {
Self { Self {
sealing_secs: Self::default_sealing_secs(), sealing_secs: Self::default_sealing_secs(),
sealing_size: Self::default_sealing_size(), sealing_size: Self::default_sealing_size(),
deleted_threshold: Self::default_deleted_threshold(), delete_threshold: Self::default_delete_threshold(),
optimizing_threads: Self::default_optimizing_threads(), optimizing_threads: Self::default_optimizing_threads(),
} }
} }

View File

@ -10,6 +10,22 @@ use crate::prelude::*;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
pub trait InstanceViewOperations {
fn basic<'a, F: Fn(Pointer) -> bool + Clone + 'a>(
&'a self,
vector: &'a DynamicVector,
opts: &'a SearchOptions,
filter: F,
) -> Result<Box<dyn Iterator<Item = Pointer> + 'a>, BasicError>;
fn vbase<'a, F: FnMut(Pointer) -> bool + Clone + 'a>(
&'a self,
vector: &'a DynamicVector,
opts: &'a SearchOptions,
filter: F,
) -> Result<Box<dyn Iterator<Item = Pointer> + 'a>, VbaseError>;
fn list(&self) -> Result<Box<dyn Iterator<Item = Pointer> + '_>, ListError>;
}
#[derive(Clone)] #[derive(Clone)]
pub enum Instance { pub enum Instance {
F32Cos(Arc<Index<F32Cos>>), F32Cos(Arc<Index<F32Cos>>),
@ -28,7 +44,7 @@ pub enum Instance {
} }
impl Instance { impl Instance {
pub fn create(path: PathBuf, options: IndexOptions) -> Result<Self, ServiceError> { pub fn create(path: PathBuf, options: IndexOptions) -> Result<Self, CreateError> {
match (options.vector.d, options.vector.k) { match (options.vector.d, options.vector.k) {
(Distance::Cos, Kind::F32) => { (Distance::Cos, Kind::F32) => {
let index = Index::create(path.clone(), options)?; let index = Index::create(path.clone(), options)?;
@ -148,21 +164,21 @@ impl Instance {
Instance::Upgrade => None, Instance::Upgrade => None,
} }
} }
pub fn stat(&self) -> IndexStat { pub fn stat(&self) -> Option<IndexStat> {
match self { match self {
Instance::F32Cos(x) => x.stat(), Instance::F32Cos(x) => Some(x.stat()),
Instance::F32Dot(x) => x.stat(), Instance::F32Dot(x) => Some(x.stat()),
Instance::F32L2(x) => x.stat(), Instance::F32L2(x) => Some(x.stat()),
Instance::F16Cos(x) => x.stat(), Instance::F16Cos(x) => Some(x.stat()),
Instance::F16Dot(x) => x.stat(), Instance::F16Dot(x) => Some(x.stat()),
Instance::F16L2(x) => x.stat(), Instance::F16L2(x) => Some(x.stat()),
Instance::SparseF32L2(x) => x.stat(), Instance::SparseF32L2(x) => Some(x.stat()),
Instance::SparseF32Cos(x) => x.stat(), Instance::SparseF32Cos(x) => Some(x.stat()),
Instance::SparseF32Dot(x) => x.stat(), Instance::SparseF32Dot(x) => Some(x.stat()),
Instance::BinaryCos(x) => x.stat(), Instance::BinaryCos(x) => Some(x.stat()),
Instance::BinaryDot(x) => x.stat(), Instance::BinaryDot(x) => Some(x.stat()),
Instance::BinaryL2(x) => x.stat(), Instance::BinaryL2(x) => Some(x.stat()),
Instance::Upgrade => IndexStat::Upgrade, Instance::Upgrade => None,
} }
} }
} }
@ -182,13 +198,13 @@ pub enum InstanceView {
BinaryL2(Arc<IndexView<BinaryL2>>), BinaryL2(Arc<IndexView<BinaryL2>>),
} }
impl InstanceView { impl InstanceViewOperations for InstanceView {
pub fn basic<'a, F: Fn(Pointer) -> bool + Clone + 'a>( fn basic<'a, F: Fn(Pointer) -> bool + Clone + 'a>(
&'a self, &'a self,
vector: &'a DynamicVector, vector: &'a DynamicVector,
opts: &'a SearchOptions, opts: &'a SearchOptions,
filter: F, filter: F,
) -> Result<impl Iterator<Item = Pointer> + 'a, ServiceError> { ) -> Result<Box<dyn Iterator<Item = Pointer> + 'a>, BasicError> {
match (self, vector) { match (self, vector) {
(InstanceView::F32Cos(x), DynamicVector::F32(vector)) => { (InstanceView::F32Cos(x), DynamicVector::F32(vector)) => {
Ok(Box::new(x.basic(vector, opts, filter)?) as Box<dyn Iterator<Item = Pointer>>) Ok(Box::new(x.basic(vector, opts, filter)?) as Box<dyn Iterator<Item = Pointer>>)
@ -226,15 +242,15 @@ impl InstanceView {
(InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => { (InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => {
Ok(Box::new(x.basic(vector.into(), opts, filter)?)) Ok(Box::new(x.basic(vector.into(), opts, filter)?))
} }
_ => Err(ServiceError::Unmatched), _ => Err(BasicError::InvalidVector),
} }
} }
pub fn vbase<'a, F: FnMut(Pointer) -> bool + Clone + 'a>( fn vbase<'a, F: FnMut(Pointer) -> bool + Clone + 'a>(
&'a self, &'a self,
vector: &'a DynamicVector, vector: &'a DynamicVector,
opts: &'a SearchOptions, opts: &'a SearchOptions,
filter: F, filter: F,
) -> Result<impl Iterator<Item = Pointer> + '_, ServiceError> { ) -> Result<Box<dyn Iterator<Item = Pointer> + 'a>, VbaseError> {
match (self, vector) { match (self, vector) {
(InstanceView::F32Cos(x), DynamicVector::F32(vector)) => { (InstanceView::F32Cos(x), DynamicVector::F32(vector)) => {
Ok(Box::new(x.vbase(vector, opts, filter)?) as Box<dyn Iterator<Item = Pointer>>) Ok(Box::new(x.vbase(vector, opts, filter)?) as Box<dyn Iterator<Item = Pointer>>)
@ -272,30 +288,33 @@ impl InstanceView {
(InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => { (InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => {
Ok(Box::new(x.vbase(vector.into(), opts, filter)?)) Ok(Box::new(x.vbase(vector.into(), opts, filter)?))
} }
_ => Err(ServiceError::Unmatched), _ => Err(VbaseError::InvalidVector),
} }
} }
pub fn list(&self) -> impl Iterator<Item = Pointer> + '_ { fn list(&self) -> Result<Box<dyn Iterator<Item = Pointer> + '_>, ListError> {
match self { match self {
InstanceView::F32Cos(x) => Box::new(x.list()) as Box<dyn Iterator<Item = Pointer>>, InstanceView::F32Cos(x) => Ok(Box::new(x.list()?) as Box<dyn Iterator<Item = Pointer>>),
InstanceView::F32Dot(x) => Box::new(x.list()), InstanceView::F32Dot(x) => Ok(Box::new(x.list()?)),
InstanceView::F32L2(x) => Box::new(x.list()), InstanceView::F32L2(x) => Ok(Box::new(x.list()?)),
InstanceView::F16Cos(x) => Box::new(x.list()), InstanceView::F16Cos(x) => Ok(Box::new(x.list()?)),
InstanceView::F16Dot(x) => Box::new(x.list()), InstanceView::F16Dot(x) => Ok(Box::new(x.list()?)),
InstanceView::F16L2(x) => Box::new(x.list()), InstanceView::F16L2(x) => Ok(Box::new(x.list()?)),
InstanceView::SparseF32Cos(x) => Box::new(x.list()), InstanceView::SparseF32Cos(x) => Ok(Box::new(x.list()?)),
InstanceView::SparseF32Dot(x) => Box::new(x.list()), InstanceView::SparseF32Dot(x) => Ok(Box::new(x.list()?)),
InstanceView::SparseF32L2(x) => Box::new(x.list()), InstanceView::SparseF32L2(x) => Ok(Box::new(x.list()?)),
InstanceView::BinaryCos(x) => Box::new(x.list()), InstanceView::BinaryCos(x) => Ok(Box::new(x.list()?)),
InstanceView::BinaryDot(x) => Box::new(x.list()), InstanceView::BinaryDot(x) => Ok(Box::new(x.list()?)),
InstanceView::BinaryL2(x) => Box::new(x.list()), InstanceView::BinaryL2(x) => Ok(Box::new(x.list()?)),
} }
} }
}
impl InstanceView {
pub fn insert( pub fn insert(
&self, &self,
vector: DynamicVector, vector: DynamicVector,
pointer: Pointer, pointer: Pointer,
) -> Result<Result<(), OutdatedError>, ServiceError> { ) -> Result<Result<(), OutdatedError>, InsertError> {
match (self, vector) { match (self, vector) {
(InstanceView::F32Cos(x), DynamicVector::F32(vector)) => x.insert(vector, pointer), (InstanceView::F32Cos(x), DynamicVector::F32(vector)) => x.insert(vector, pointer),
(InstanceView::F32Dot(x), DynamicVector::F32(vector)) => x.insert(vector, pointer), (InstanceView::F32Dot(x), DynamicVector::F32(vector)) => x.insert(vector, pointer),
@ -319,10 +338,10 @@ impl InstanceView {
x.insert(vector, pointer) x.insert(vector, pointer)
} }
(InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => x.insert(vector, pointer), (InstanceView::BinaryL2(x), DynamicVector::Binary(vector)) => x.insert(vector, pointer),
_ => Err(ServiceError::Unmatched), _ => Err(InsertError::InvalidVector),
} }
} }
pub fn delete(&self, pointer: Pointer) { pub fn delete(&self, pointer: Pointer) -> Result<(), DeleteError> {
match self { match self {
InstanceView::F32Cos(x) => x.delete(pointer), InstanceView::F32Cos(x) => x.delete(pointer),
InstanceView::F32Dot(x) => x.delete(pointer), InstanceView::F32Dot(x) => x.delete(pointer),
@ -338,7 +357,7 @@ impl InstanceView {
InstanceView::BinaryL2(x) => x.delete(pointer), InstanceView::BinaryL2(x) => x.delete(pointer),
} }
} }
pub fn flush(&self) { pub fn flush(&self) -> Result<(), FlushError> {
match self { match self {
InstanceView::F32Cos(x) => x.flush(), InstanceView::F32Cos(x) => x.flush(),
InstanceView::F32Dot(x) => x.flush(), InstanceView::F32Dot(x) => x.flush(),

View File

@ -1,6 +1,5 @@
#![feature(core_intrinsics)] #![feature(core_intrinsics)]
#![feature(avx512_target_feature)] #![feature(avx512_target_feature)]
#![feature(pointer_is_aligned)]
pub mod algorithms; pub mod algorithms;
pub mod index; pub mod index;

View File

@ -1,37 +0,0 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[must_use]
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
#[rustfmt::skip]
pub enum ServiceError {
#[error("\
The given index option is invalid.
INFORMATION: reason = {validation:?}\
")]
BadOption { validation: String },
#[error("\
The index is not existing in the background worker.
ADVICE: Drop or rebuild the index.\
")]
UnknownIndex,
#[error("\
The index is already existing in the background worker.\
")]
KnownIndex,
#[error("\
The given vector is invalid for input.
ADVICE: Check if dimensions and scalar type of the vector is matched with the index.\
")]
Unmatched,
#[error("\
The extension is upgraded so all index files are outdated.
ADVICE: Delete all index files. Please read `https://docs.pgvecto.rs/admin/upgrading.html`.\
")]
Upgrade,
#[error("\
The extension is upgraded so this index is outdated.
ADVICE: Rebuild the index. Please read `https://docs.pgvecto.rs/admin/upgrading.html`.\
")]
Upgrade2,
}

View File

@ -1,4 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
pub fn cosine(lhs: &[F16], rhs: &[F16]) -> F32 { pub fn cosine(lhs: &[F16], rhs: &[F16]) -> F32 {
#[inline(always)] #[inline(always)]

View File

@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
use std::borrow::Cow;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum F16Cos {} pub enum F16Cos {}

View File

@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
use std::borrow::Cow;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum F16Dot {} pub enum F16Dot {}

View File

@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
use std::borrow::Cow;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum F16L2 {} pub enum F16L2 {}

View File

@ -1,6 +1,5 @@
use std::borrow::Cow;
use crate::prelude::*; use crate::prelude::*;
use std::borrow::Cow;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum F32L2 {} pub enum F32L2 {}

View File

@ -15,6 +15,9 @@ mod sparse_f32_cos;
mod sparse_f32_dot; mod sparse_f32_dot;
mod sparse_f32_l2; mod sparse_f32_l2;
pub use binary_cos::BinaryCos;
pub use binary_dot::BinaryDot;
pub use binary_l2::BinaryL2;
pub use f16_cos::F16Cos; pub use f16_cos::F16Cos;
pub use f16_dot::F16Dot; pub use f16_dot::F16Dot;
pub use f16_l2::F16L2; pub use f16_l2::F16L2;
@ -24,9 +27,6 @@ pub use f32_l2::F32L2;
pub use sparse_f32_cos::SparseF32Cos; pub use sparse_f32_cos::SparseF32Cos;
pub use sparse_f32_dot::SparseF32Dot; pub use sparse_f32_dot::SparseF32Dot;
pub use sparse_f32_l2::SparseF32L2; pub use sparse_f32_l2::SparseF32L2;
pub use binary_cos::BinaryCos;
pub use binary_dot::BinaryDot;
pub use binary_l2::BinaryL2;
use crate::prelude::*; use crate::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -50,7 +50,7 @@ pub trait G: Copy + Debug + 'static {
+ Zero + Zero
+ num_traits::NumOps + num_traits::NumOps
+ num_traits::NumAssignOps + num_traits::NumAssignOps
+ FloatCast; + base::scalar::FloatCast;
type Storage: for<'a> Storage<VectorRef<'a> = Self::VectorRef<'a>>; type Storage: for<'a> Storage<VectorRef<'a> = Self::VectorRef<'a>>;
type L2: for<'a> G<Scalar = Self::Scalar, VectorRef<'a> = &'a [Self::Scalar]>; type L2: for<'a> G<Scalar = Self::Scalar, VectorRef<'a> = &'a [Self::Scalar]>;
type VectorOwned: Vector + Clone + Serialize + for<'a> Deserialize<'a>; type VectorOwned: Vector + Clone + Serialize + for<'a> Deserialize<'a>;
@ -126,33 +126,6 @@ pub trait G: Copy + Debug + 'static {
} }
} }
pub trait FloatCast: Sized {
fn from_f32(x: f32) -> Self;
fn to_f32(self) -> f32;
fn from_f(x: F32) -> Self {
Self::from_f32(x.0)
}
fn to_f(self) -> F32 {
F32(Self::to_f32(self))
}
}
pub trait Vector {
fn dims(&self) -> u16;
}
impl<T> Vector for Vec<T> {
fn dims(&self) -> u16 {
self.len().try_into().unwrap()
}
}
impl<'a, T> Vector for &'a [T] {
fn dims(&self) -> u16 {
self.len().try_into().unwrap()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DynamicVector { pub enum DynamicVector {
F32(Vec<F32>), F32(Vec<F32>),

View File

@ -47,6 +47,6 @@ impl G for SparseF32Cos {
} }
fn elkan_k_means_distance2(lhs: &SparseF32, rhs: &[Self::Scalar]) -> F32 { fn elkan_k_means_distance2(lhs: &SparseF32, rhs: &[Self::Scalar]) -> F32 {
super::sparse_f32::dot_2( SparseF32Ref::from(lhs), rhs).acos() super::sparse_f32::dot_2(SparseF32Ref::from(lhs), rhs).acos()
} }
} }

View File

@ -1,15 +1,13 @@
mod error;
mod global; mod global;
mod scalar;
mod search;
mod storage; mod storage;
mod sys;
pub use self::error::ServiceError;
pub use self::global::*; pub use self::global::*;
pub use self::scalar::{BinaryVec, BinaryVecRef, SparseF32, SparseF32Ref, F16, F32}; pub use self::storage::{BinaryMmap, DenseMmap, SparseMmap, Storage};
pub use self::search::{Element, Filter, Payload};
pub use self::storage::{DenseMmap, SparseMmap, Storage, BinaryMmap}; pub use base::error::*;
pub use self::sys::{Handle, Pointer}; pub use base::scalar::{F16, F32};
pub use base::search::{Element, Filter, Payload};
pub use base::sys::{Handle, Pointer};
pub use base::vector::{BinaryVec, BinaryVecRef, SparseF32, SparseF32Ref, Vector};
pub use num_traits::{Float, Zero}; pub use num_traits::{Float, Zero};

View File

@ -1,9 +0,0 @@
mod binary;
mod f16;
mod f32;
mod sparse_f32;
pub use binary::{BinaryVec, BinaryVecRef};
pub use f16::F16;
pub use f32::F32;
pub use sparse_f32::{SparseF32, SparseF32Ref};

View File

@ -53,7 +53,9 @@ impl Storage for BinaryMmap {
ram: RawRam<S>, ram: RawRam<S>,
) -> Self { ) -> Self {
let n = ram.len(); let n = ram.len();
let vectors_iter = (0..n).flat_map(|i| ram.vector(i).as_bytes().iter()).copied(); let vectors_iter = (0..n)
.flat_map(|i| ram.vector(i).as_bytes().iter())
.copied();
let payload_iter = (0..n).map(|i| ram.payload(i)); let payload_iter = (0..n).map(|i| ram.payload(i));
let vectors = MmapArray::create(&path.join("vectors"), vectors_iter); let vectors = MmapArray::create(&path.join("vectors"), vectors_iter);
let payload = MmapArray::create(&path.join("payload"), payload_iter); let payload = MmapArray::create(&path.join("payload"), payload_iter);

View File

@ -1,10 +1,10 @@
mod binary;
mod dense; mod dense;
mod sparse; mod sparse;
mod binary;
pub use binary::BinaryMmap;
pub use dense::DenseMmap; pub use dense::DenseMmap;
pub use sparse::SparseMmap; pub use sparse::SparseMmap;
pub use binary::BinaryMmap;
use crate::algorithms::raw::RawRam; use crate::algorithms::raw::RawRam;
use crate::index::IndexOptions; use crate::index::IndexOptions;

View File

@ -1,7 +1,7 @@
pub mod metadata; pub mod metadata;
use crate::index::IndexOptions; use crate::index::{IndexOptions, IndexStat};
use crate::instance::Instance; use crate::instance::{Instance, InstanceView, InstanceViewOperations};
use crate::prelude::*; use crate::prelude::*;
use crate::utils::clean::clean; use crate::utils::clean::clean;
use crate::utils::dir_ops::sync_dir; use crate::utils::dir_ops::sync_dir;
@ -13,6 +13,25 @@ use std::collections::{HashMap, HashSet};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
pub trait WorkerOperations {
type InstanceView: InstanceViewOperations;
fn create(&self, handle: Handle, options: IndexOptions) -> Result<(), CreateError>;
fn drop(&self, handle: Handle) -> Result<(), DropError>;
fn flush(&self, handle: Handle) -> Result<(), FlushError>;
fn insert(
&self,
handle: Handle,
vector: DynamicVector,
pointer: Pointer,
) -> Result<(), InsertError>;
fn delete(&self, handle: Handle, pointer: Pointer) -> Result<(), DeleteError>;
fn basic_view(&self, handle: Handle) -> Result<Self::InstanceView, BasicError>;
fn vbase_view(&self, handle: Handle) -> Result<Self::InstanceView, VbaseError>;
fn list_view(&self, handle: Handle) -> Result<Self::InstanceView, ListError>;
fn stat(&self, handle: Handle) -> Result<IndexStat, StatError>;
}
pub struct Worker { pub struct Worker {
path: PathBuf, path: PathBuf,
protect: Mutex<WorkerProtect>, protect: Mutex<WorkerProtect>,
@ -65,11 +84,12 @@ impl Worker {
pub fn view(&self) -> Arc<WorkerView> { pub fn view(&self) -> Arc<WorkerView> {
self.view.load_full() self.view.load_full()
} }
pub fn instance_create( }
&self,
handle: Handle, impl WorkerOperations for Worker {
options: IndexOptions, type InstanceView = InstanceView;
) -> Result<(), ServiceError> {
fn create(&self, handle: Handle, options: IndexOptions) -> Result<(), CreateError> {
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
let mut protect = self.protect.lock(); let mut protect = self.protect.lock();
match protect.indexes.entry(handle) { match protect.indexes.entry(handle) {
@ -80,15 +100,70 @@ impl Worker {
protect.maintain(&self.view); protect.maintain(&self.view);
Ok(()) Ok(())
} }
Entry::Occupied(_) => Err(ServiceError::KnownIndex), Entry::Occupied(_) => Err(CreateError::Exist),
} }
} }
pub fn instance_destroy(&self, handle: Handle) { fn drop(&self, handle: Handle) -> Result<(), DropError> {
let mut protect = self.protect.lock(); let mut protect = self.protect.lock();
if protect.indexes.remove(&handle).is_some() { if protect.indexes.remove(&handle).is_some() {
protect.maintain(&self.view); protect.maintain(&self.view);
Ok(())
} else {
Err(DropError::NotExist)
} }
} }
fn flush(&self, handle: Handle) -> Result<(), FlushError> {
let view = self.view();
let instance = view.get(handle).ok_or(FlushError::NotExist)?;
let view = instance.view().ok_or(FlushError::Upgrade)?;
view.flush()?;
Ok(())
}
fn insert(
&self,
handle: Handle,
vector: DynamicVector,
pointer: Pointer,
) -> Result<(), InsertError> {
let view = self.view();
let instance = view.get(handle).ok_or(InsertError::NotExist)?;
loop {
let view = instance.view().ok_or(InsertError::Upgrade)?;
match view.insert(vector.clone(), pointer)? {
Ok(()) => break,
Err(_) => instance.refresh(),
}
}
Ok(())
}
fn delete(&self, handle: Handle, pointer: Pointer) -> Result<(), DeleteError> {
let view = self.view();
let instance = view.get(handle).ok_or(DeleteError::NotExist)?;
let view = instance.view().ok_or(DeleteError::Upgrade)?;
view.delete(pointer)?;
Ok(())
}
fn basic_view(&self, handle: Handle) -> Result<InstanceView, BasicError> {
let view = self.view();
let instance = view.get(handle).ok_or(BasicError::NotExist)?;
instance.view().ok_or(BasicError::Upgrade)
}
fn vbase_view(&self, handle: Handle) -> Result<InstanceView, VbaseError> {
let view = self.view();
let instance = view.get(handle).ok_or(VbaseError::NotExist)?;
instance.view().ok_or(VbaseError::Upgrade)
}
fn list_view(&self, handle: Handle) -> Result<InstanceView, ListError> {
let view = self.view();
let instance = view.get(handle).ok_or(ListError::NotExist)?;
instance.view().ok_or(ListError::Upgrade)
}
fn stat(&self, handle: Handle) -> Result<IndexStat, StatError> {
let view = self.view();
let instance = view.get(handle).ok_or(StatError::NotExist)?;
let stat = instance.stat().ok_or(StatError::Upgrade)?;
Ok(stat)
}
} }
pub struct WorkerView { pub struct WorkerView {

View File

@ -10,6 +10,3 @@ if [ "$OS" == "ubuntu-latest" ]; then
sudo systemctl restart postgresql sudo systemctl restart postgresql
pg_lsclusters pg_lsclusters
fi fi
if [ "$OS" == "macos-latest" ]; then
brew services restart postgresql@$VERSION
fi

View File

@ -25,21 +25,6 @@ if [ "$OS" == "ubuntu-latest" ]; then
sudo -iu postgres createuser -s -r runner sudo -iu postgres createuser -s -r runner
createdb createdb
fi fi
if [ "$OS" == "macos-latest" ]; then
brew uninstall postgresql
brew install postgresql@$VERSION
export PATH="$PATH:$(brew --prefix postgresql@$VERSION)/bin"
echo "$(brew --prefix postgresql@$VERSION)/bin" >> $GITHUB_PATH
brew services start postgresql@$VERSION
sleep 30
createdb
fi
sudo chmod -R 777 `pg_config --pkglibdir` sudo chmod -R 777 `pg_config --pkglibdir`
sudo chmod -R 777 `pg_config --sharedir`/extension sudo chmod -R 777 `pg_config --sharedir`/extension
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
cargo binstall sqllogictest-bin -y --force
cargo install cargo-pgrx@$(grep 'pgrx = {' Cargo.toml | cut -d '"' -f 2 | head -n 1) --debug
cargo pgrx init --pg$VERSION=$(which pg_config)

View File

@ -1,26 +1,35 @@
pub mod normal; pub mod normal;
pub mod upgrade;
use std::sync::atomic::{AtomicBool, Ordering};
static STARTED: AtomicBool = AtomicBool::new(false);
pub unsafe fn init() { pub unsafe fn init() {
use pgrx::bgworkers::BackgroundWorkerBuilder; use service::worker::Worker;
use pgrx::bgworkers::BgWorkerStartTime; let path = std::path::Path::new("pg_vectors");
use std::time::Duration; if !path.try_exists().unwrap() || Worker::check(path.to_owned()) {
BackgroundWorkerBuilder::new("vectors") use pgrx::bgworkers::BackgroundWorkerBuilder;
.set_library("vectors") use pgrx::bgworkers::BgWorkerStartTime;
.set_function("_vectors_main") use std::time::Duration;
.set_argument(None) BackgroundWorkerBuilder::new("vectors")
.enable_shmem_access(None) .set_library("vectors")
.set_start_time(BgWorkerStartTime::PostmasterStart) .set_function("_vectors_main")
.set_restart_time(Some(Duration::from_secs(1))) .set_argument(None)
.load(); .enable_shmem_access(None)
.set_start_time(BgWorkerStartTime::PostmasterStart)
.set_restart_time(Some(Duration::from_secs(15)))
.load();
STARTED.store(true, Ordering::Relaxed);
}
} }
pub fn is_started() -> bool {
STARTED.load(Ordering::Relaxed)
}
#[pgrx::pg_guard]
#[no_mangle] #[no_mangle]
extern "C" fn _vectors_main(_arg: pgrx::pg_sys::Datum) { extern "C" fn _vectors_main(_arg: pgrx::pg_sys::Datum) {
let _ = std::panic::catch_unwind(main);
}
fn main() {
pub struct AllocErrorPanicPayload { pub struct AllocErrorPanicPayload {
pub layout: std::alloc::Layout, pub layout: std::alloc::Layout,
} }
@ -60,12 +69,8 @@ fn main() {
use std::path::Path; use std::path::Path;
let path = Path::new("pg_vectors"); let path = Path::new("pg_vectors");
if path.try_exists().unwrap() { if path.try_exists().unwrap() {
if Worker::check(path.to_owned()) { let worker = Worker::open(path.to_owned());
let worker = Worker::open(path.to_owned()); self::normal::normal(worker);
self::normal::normal(worker);
} else {
self::upgrade::upgrade();
}
} else { } else {
let worker = Worker::create(path.to_owned()); let worker = Worker::create(path.to_owned());
self::normal::normal(worker); self::normal::normal(worker);

View File

@ -1,7 +1,5 @@
use crate::ipc::server::RpcHandler;
use crate::ipc::ConnectionError; use crate::ipc::ConnectionError;
use service::index::OutdatedError; use crate::ipc::ServerRpcHandler;
use service::prelude::ServiceError;
use service::worker::Worker; use service::worker::Worker;
use std::sync::Arc; use std::sync::Arc;
@ -59,167 +57,128 @@ pub fn normal(worker: Arc<Worker>) {
}); });
} }
fn session(worker: Arc<Worker>, handler: RpcHandler) -> Result<!, ConnectionError> { fn session(worker: Arc<Worker>, handler: ServerRpcHandler) -> Result<!, ConnectionError> {
use crate::ipc::server::RpcHandle; use crate::ipc::ServerRpcHandle;
use service::instance::InstanceViewOperations;
use service::worker::WorkerOperations;
let mut handler = handler; let mut handler = handler;
loop { loop {
match handler.handle()? { match handler.handle()? {
// transaction // control plane
RpcHandle::Flush { handle, x } => { ServerRpcHandle::Create { handle, options, x } => {
let view = worker.view(); handler = x.leave(WorkerOperations::create(worker.as_ref(), handle, options))?;
if let Some(instance) = view.get(handle) {
if let Some(view) = instance.view() {
view.flush();
}
}
handler = x.leave()?;
} }
RpcHandle::Drop { handle, x } => { ServerRpcHandle::Drop { handle, x } => {
worker.instance_destroy(handle); handler = x.leave(WorkerOperations::drop(worker.as_ref(), handle))?;
handler = x.leave()?;
} }
RpcHandle::Create { handle, options, x } => { // data plane
match worker.instance_create(handle, options) { ServerRpcHandle::Flush { handle, x } => {
Ok(()) => (), handler = x.leave(worker.flush(handle))?;
Err(e) => x.reset(e)?,
};
handler = x.leave()?;
} }
// instance ServerRpcHandle::Insert {
RpcHandle::Insert {
handle, handle,
vector, vector,
pointer, pointer,
x, x,
} => { } => {
let view = worker.view(); handler = x.leave(worker.insert(handle, vector, pointer))?;
let Some(instance) = view.get(handle) else {
x.reset(ServiceError::UnknownIndex)?;
};
loop {
let instance_view = match instance.view() {
Some(x) => x,
None => x.reset(ServiceError::Upgrade2)?,
};
match instance_view.insert(vector.clone(), pointer) {
Ok(Ok(())) => break,
Ok(Err(OutdatedError)) => instance.refresh(),
Err(e) => x.reset(e)?,
}
}
handler = x.leave()?;
} }
RpcHandle::Delete { handle, pointer, x } => { ServerRpcHandle::Delete { handle, pointer, x } => {
let view = worker.view(); handler = x.leave(worker.delete(handle, pointer))?;
let Some(instance) = view.get(handle) else {
x.reset(ServiceError::UnknownIndex)?;
};
let instance_view = match instance.view() {
Some(x) => x,
None => x.reset(ServiceError::Upgrade2)?,
};
instance_view.delete(pointer);
handler = x.leave()?;
} }
RpcHandle::Stat { handle, x } => { ServerRpcHandle::Stat { handle, x } => {
let view = worker.view(); handler = x.leave(worker.stat(handle))?;
let Some(instance) = view.get(handle) else {
x.reset(ServiceError::UnknownIndex)?;
};
let r = instance.stat();
handler = x.leave(r)?
} }
RpcHandle::Basic { ServerRpcHandle::Basic {
handle, handle,
vector, vector,
opts, opts,
x, x,
} => { } => {
use crate::ipc::server::BasicHandle::*; let v = match worker.basic_view(handle) {
let view = worker.view();
let Some(instance) = view.get(handle) else {
x.reset(ServiceError::UnknownIndex)?;
};
let view = match instance.view() {
Some(x) => x,
None => x.reset(ServiceError::Upgrade2)?,
};
let mut it = match view.basic(&vector, &opts, |_| true) {
Ok(x) => x, Ok(x) => x,
Err(e) => x.reset(e)?, Err(e) => {
handler = x.error_err(e)?;
continue;
}
}; };
let mut x = x.error()?; match v.basic(&vector, &opts, |_| true) {
loop { Ok(mut iter) => {
match x.handle()? { use crate::ipc::ServerBasicHandle;
Next { x: y } => { let mut x = x.error_ok()?;
x = y.leave(it.next())?; loop {
} match x.handle()? {
Leave { x } => { ServerBasicHandle::Next { x: y } => {
handler = x; x = y.leave(iter.next())?;
break; }
ServerBasicHandle::Leave { x } => {
handler = x;
break;
}
}
} }
} }
} Err(e) => handler = x.error_err(e)?,
};
} }
RpcHandle::Vbase { ServerRpcHandle::Vbase {
handle, handle,
vector, vector,
opts, opts,
x, x,
} => { } => {
use crate::ipc::server::VbaseHandle::*; let v = match worker.vbase_view(handle) {
let view = worker.view();
let Some(instance) = view.get(handle) else {
x.reset(ServiceError::UnknownIndex)?;
};
let view = match instance.view() {
Some(x) => x,
None => x.reset(ServiceError::Upgrade2)?,
};
let mut it = match view.vbase(&vector, &opts, |_| true) {
Ok(x) => x, Ok(x) => x,
Err(e) => x.reset(e)?, Err(e) => {
handler = x.error_err(e)?;
continue;
}
}; };
let mut x = x.error()?; match v.vbase(&vector, &opts, |_| true) {
loop { Ok(mut iter) => {
match x.handle()? { use crate::ipc::ServerVbaseHandle;
Next { x: y } => { let mut x = x.error_ok()?;
x = y.leave(it.next())?; loop {
} match x.handle()? {
Leave { x } => { ServerVbaseHandle::Next { x: y } => {
handler = x; x = y.leave(iter.next())?;
break; }
ServerVbaseHandle::Leave { x } => {
handler = x;
break;
}
}
} }
} }
} Err(e) => handler = x.error_err(e)?,
};
} }
RpcHandle::List { handle, x } => { ServerRpcHandle::List { handle, x } => {
use crate::ipc::server::ListHandle::*; let v = match worker.list_view(handle) {
let view = worker.view(); Ok(x) => x,
let Some(instance) = view.get(handle) else { Err(e) => {
x.reset(ServiceError::UnknownIndex)?; handler = x.error_err(e)?;
continue;
}
}; };
let view = match instance.view() { match v.list() {
Some(x) => x, Ok(mut iter) => {
None => x.reset(ServiceError::Upgrade2)?, use crate::ipc::ServerListHandle;
}; let mut x = x.error_ok()?;
let mut it = view.list(); loop {
let mut x = x.error()?; match x.handle()? {
loop { ServerListHandle::Next { x: y } => {
match x.handle()? { x = y.leave(iter.next())?;
Next { x: y } => { }
x = y.leave(it.next())?; ServerListHandle::Leave { x } => {
} handler = x;
Leave { x } => { break;
handler = x; }
break; }
} }
} }
} Err(e) => handler = x.error_err(e)?,
} };
// admin
RpcHandle::Upgrade { x } => {
handler = x.leave()?;
} }
} }
} }

View File

@ -1,78 +0,0 @@
use crate::ipc::server::RpcHandler;
use crate::ipc::ConnectionError;
use service::prelude::*;
pub fn upgrade() {
std::thread::scope(|scope| {
scope.spawn({
move || {
for rpc_handler in crate::ipc::listen_unix() {
std::thread::spawn({
move || {
log::trace!("Session established.");
let _ = session(rpc_handler);
log::trace!("Session closed.");
}
});
}
}
});
scope.spawn({
move || {
for rpc_handler in crate::ipc::listen_mmap() {
std::thread::spawn({
move || {
log::trace!("Session established.");
let _ = session(rpc_handler);
log::trace!("Session closed.");
}
});
}
}
});
loop {
let mut sig: i32 = 0;
unsafe {
let mut set: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset(&mut set);
libc::sigaddset(&mut set, libc::SIGHUP);
libc::sigaddset(&mut set, libc::SIGTERM);
libc::sigwait(&set, &mut sig);
}
match sig {
libc::SIGHUP => {
std::process::exit(0);
}
libc::SIGTERM => {
std::process::exit(0);
}
_ => (),
}
}
});
}
fn session(handler: RpcHandler) -> Result<(), ConnectionError> {
use crate::ipc::server::RpcHandle;
let mut handler = handler;
loop {
match handler.handle()? {
RpcHandle::Drop { x, .. } => {
// false drop
handler = x.leave()?;
}
RpcHandle::Flush { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Create { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Insert { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Delete { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Stat { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Basic { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Vbase { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::List { x, .. } => x.reset(ServiceError::Upgrade)?,
RpcHandle::Upgrade { x } => {
let _ = std::fs::remove_dir_all("./pg_vectors");
handler = x.leave()?;
}
}
}
}

View File

@ -45,7 +45,7 @@ impl BVector {
pub fn new_in_postgres(vector: BinaryVecRef<'_>) -> BVectorOutput { pub fn new_in_postgres(vector: BinaryVecRef<'_>) -> BVectorOutput {
unsafe { unsafe {
let dims = vector.values.len(); let dims = vector.values.len();
assert!(1 <= dims && dims <= 65535); assert!((1..=65535).contains(&dims));
let layout = BVector::layout(dims); let layout = BVector::layout(dims);
let ptr = pgrx::pg_sys::palloc(layout.size()) as *mut BVector; let ptr = pgrx::pg_sys::palloc(layout.size()) as *mut BVector;
ptr.cast::<u8>().add(layout.size() - 8).write_bytes(0, 8); ptr.cast::<u8>().add(layout.size() - 8).write_bytes(0, 8);
@ -215,7 +215,11 @@ unsafe impl SqlTranslatable for BVectorOutput {
#[pgrx::pg_extern(immutable, parallel_safe, strict)] #[pgrx::pg_extern(immutable, parallel_safe, strict)]
fn _vectors_bvector_in(input: &CStr, _oid: Oid, typmod: i32) -> BVectorOutput { fn _vectors_bvector_in(input: &CStr, _oid: Oid, typmod: i32) -> BVectorOutput {
use crate::utils::parse::parse_vector; use crate::utils::parse::parse_vector;
let reserve = Typmod::parse_from_i32(typmod).unwrap().dims().unwrap_or(0); let reserve = Typmod::parse_from_i32(typmod)
.unwrap()
.dims()
.map(|x| x.get())
.unwrap_or(0);
let v = parse_vector(input.to_bytes(), reserve as usize, |s| { let v = parse_vector(input.to_bytes(), reserve as usize, |s| {
s.parse::<u8>().ok().and_then(|x| match x { s.parse::<u8>().ok().and_then(|x| match x {
0 => Some(false), 0 => Some(false),
@ -225,15 +229,10 @@ fn _vectors_bvector_in(input: &CStr, _oid: Oid, typmod: i32) -> BVectorOutput {
}); });
match v { match v {
Err(e) => { Err(e) => {
SessionError::BadLiteral { bad_literal(&e.to_string());
hint: e.to_string(),
}
.friendly();
} }
Ok(vector) => { Ok(vector) => {
if vector.is_empty() || vector.len() > 65535 { check_value_dimensions(vector.len());
SessionError::BadValueDimensions.friendly();
}
let mut values = bitvec![0; vector.len()]; let mut values = bitvec![0; vector.len()];
for (i, x) in vector.iter().enumerate() { for (i, x) in vector.iter().enumerate() {
values.set(i, *x); values.set(i, *x);
@ -420,9 +419,7 @@ fn _vectors_bvector_subscript(_fcinfo: pgrx::pg_sys::FunctionCallInfo) -> Datum
} }
let mut values = bitvec![0; end as usize - start as usize]; let mut values = bitvec![0; end as usize - start as usize];
values.copy_from_bitslice(&input.data().values[start as usize..end as usize]); values.copy_from_bitslice(&input.data().values[start as usize..end as usize]);
let output = BVector::new_in_postgres(BinaryVecRef { let output = BVector::new_in_postgres(BinaryVecRef { values: &values });
values: &values,
});
(*op).resvalue.write(output.into_datum().unwrap()); (*op).resvalue.write(output.into_datum().unwrap());
(*op).resnull.write(false); (*op).resnull.write(false);
} }

View File

@ -2,7 +2,8 @@ use crate::datatype::bvector::{BVector, BVectorOutput};
use crate::datatype::svecf32::{SVecf32, SVecf32Input, SVecf32Output}; use crate::datatype::svecf32::{SVecf32, SVecf32Input, SVecf32Output};
use crate::datatype::vecf16::{Vecf16, Vecf16Input, Vecf16Output}; use crate::datatype::vecf16::{Vecf16, Vecf16Input, Vecf16Output};
use crate::datatype::vecf32::{Vecf32, Vecf32Input, Vecf32Output}; use crate::datatype::vecf32::{Vecf32, Vecf32Input, Vecf32Output};
use crate::prelude::{FriendlyError, SessionError}; use crate::prelude::*;
use base::scalar::FloatCast;
use bitvec::bitvec; use bitvec::bitvec;
use service::prelude::*; use service::prelude::*;
@ -14,9 +15,7 @@ fn _vectors_cast_array_to_vecf32(
_typmod: i32, _typmod: i32,
_explicit: bool, _explicit: bool,
) -> Vecf32Output { ) -> Vecf32Output {
if array.is_empty() || array.len() > 65535 { check_value_dimensions(array.len());
SessionError::BadValueDimensions.friendly();
}
let mut data = vec![F32::zero(); array.len()]; let mut data = vec![F32::zero(); array.len()];
for (i, x) in array.iter().enumerate() { for (i, x) in array.iter().enumerate() {
data[i] = F32(x.unwrap_or(f32::NAN)); data[i] = F32(x.unwrap_or(f32::NAN));
@ -101,10 +100,7 @@ fn _vectors_cast_vecf32_to_bvector(
match x.to_f32() { match x.to_f32() {
x if x == 0. => {} x if x == 0. => {}
x if x == 1. => values.set(i, true), x if x == 1. => values.set(i, true),
_ => SessionError::BadLiteral { _ => bad_literal("The vector contains a non-binary value."),
hint: "The vector contains a non-binary value.".to_string(),
}
.friendly(),
} }
} }

View File

@ -1,165 +1,80 @@
use crate::{ use crate::datatype::bvector::{BVector, BVectorInput, BVectorOutput};
datatype::bvector::{BVector, BVectorInput, BVectorOutput}, use crate::prelude::*;
prelude::{FriendlyError, SessionError}, use base::scalar::FloatCast;
};
use service::prelude::*; use service::prelude::*;
use std::ops::Deref; use std::ops::Deref;
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_and(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput { fn _vectors_bvector_operator_and(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
let results = lhs.data().values.to_bitvec() & rhs.data().values; let results = lhs.data().values.to_bitvec() & rhs.data().values;
BVector::new_in_postgres(BinaryVecRef { values: &results }) BVector::new_in_postgres(BinaryVecRef { values: &results })
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_or(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput { fn _vectors_bvector_operator_or(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
let results = lhs.data().values.to_bitvec() | rhs.data().values; let results = lhs.data().values.to_bitvec() | rhs.data().values;
BVector::new_in_postgres(BinaryVecRef { values: &results }) BVector::new_in_postgres(BinaryVecRef { values: &results })
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_xor(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput { fn _vectors_bvector_operator_xor(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> BVectorOutput {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
let results = lhs.data().values.to_bitvec() ^ rhs.data().values; let results = lhs.data().values.to_bitvec() ^ rhs.data().values;
BVector::new_in_postgres(BinaryVecRef { values: &results }) BVector::new_in_postgres(BinaryVecRef { values: &results })
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_lt(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_lt(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() < rhs.deref() lhs.deref() < rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_lte(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_lte(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() <= rhs.deref() lhs.deref() <= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_gt(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_gt(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() > rhs.deref() lhs.deref() > rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_gte(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_gte(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() >= rhs.deref() lhs.deref() >= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_eq(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_eq(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() == rhs.deref() lhs.deref() == rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_neq(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool { fn _vectors_bvector_operator_neq(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() != rhs.deref() lhs.deref() != rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_cosine(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 { fn _vectors_bvector_operator_cosine(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
BinaryCos::distance(lhs.data(), rhs.data()).to_f32() BinaryCos::distance(lhs.data(), rhs.data()).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_dot(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 { fn _vectors_bvector_operator_dot(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
BinaryDot::distance(lhs.data(), rhs.data()).to_f32() BinaryDot::distance(lhs.data(), rhs.data()).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_bvector_operator_l2(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 { fn _vectors_bvector_operator_l2(lhs: BVectorInput<'_>, rhs: BVectorInput<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
BinaryL2::distance(lhs.data(), rhs.data()).to_f32() BinaryL2::distance(lhs.data(), rhs.data()).to_f32()
} }

View File

@ -1,19 +1,12 @@
use crate::{ use crate::datatype::svecf32::{SVecf32, SVecf32Input, SVecf32Output};
datatype::svecf32::{SVecf32, SVecf32Input, SVecf32Output}, use crate::prelude::*;
prelude::{FriendlyError, SessionError}, use base::scalar::FloatCast;
};
use service::prelude::*; use service::prelude::*;
use std::ops::Deref; use std::ops::Deref;
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_add(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> SVecf32Output { fn _vectors_svecf32_operator_add(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> SVecf32Output {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
let size1 = lhs.len(); let size1 = lhs.len();
let size2 = rhs.len(); let size2 = rhs.len();
@ -58,13 +51,7 @@ fn _vectors_svecf32_operator_add(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_minus(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> SVecf32Output { fn _vectors_svecf32_operator_minus(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> SVecf32Output {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
let size1 = lhs.len(); let size1 = lhs.len();
let size2 = rhs.len(); let size2 = rhs.len();
@ -109,117 +96,54 @@ fn _vectors_svecf32_operator_minus(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>)
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_lt(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_lt(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() < rhs.deref() lhs.deref() < rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_lte(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_lte(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() <= rhs.deref() lhs.deref() <= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_gt(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_gt(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() > rhs.deref() lhs.deref() > rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_gte(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_gte(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() >= rhs.deref() lhs.deref() >= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_eq(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_eq(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() == rhs.deref() lhs.deref() == rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_neq(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool { fn _vectors_svecf32_operator_neq(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> bool {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
lhs.deref() != rhs.deref() lhs.deref() != rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_cosine(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 { fn _vectors_svecf32_operator_cosine(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
SparseF32Cos::distance(lhs.data(), rhs.data()).to_f32() SparseF32Cos::distance(lhs.data(), rhs.data()).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_dot(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 { fn _vectors_svecf32_operator_dot(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
SparseF32Dot::distance(lhs.data(), rhs.data()).to_f32() SparseF32Dot::distance(lhs.data(), rhs.data()).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_svecf32_operator_l2(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 { fn _vectors_svecf32_operator_l2(lhs: SVecf32Input<'_>, rhs: SVecf32Input<'_>) -> f32 {
if lhs.dims() != rhs.dims() { check_matched_dimensions(lhs.dims() as _, rhs.dims() as _);
SessionError::Unmatched {
left_dimensions: lhs.dims() as _,
right_dimensions: rhs.dims() as _,
}
.friendly();
}
SparseF32L2::distance(lhs.data(), rhs.data()).to_f32() SparseF32L2::distance(lhs.data(), rhs.data()).to_f32()
} }

View File

@ -1,18 +1,12 @@
use crate::datatype::vecf16::{Vecf16, Vecf16Input, Vecf16Output}; use crate::datatype::vecf16::{Vecf16, Vecf16Input, Vecf16Output};
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
use service::prelude::*; use service::prelude::*;
use std::ops::Deref; use std::ops::Deref;
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_add(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> Vecf16Output { fn _vectors_vecf16_operator_add(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> Vecf16Output {
if lhs.len() != rhs.len() { let n = check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
let n = lhs.len();
let mut v = vec![F16::zero(); n]; let mut v = vec![F16::zero(); n];
for i in 0..n { for i in 0..n {
v[i] = lhs[i] + rhs[i]; v[i] = lhs[i] + rhs[i];
@ -22,14 +16,7 @@ fn _vectors_vecf16_operator_add(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> V
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_minus(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> Vecf16Output { fn _vectors_vecf16_operator_minus(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> Vecf16Output {
if lhs.len() != rhs.len() { let n = check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
let n = lhs.len();
let mut v = vec![F16::zero(); n]; let mut v = vec![F16::zero(); n];
for i in 0..n { for i in 0..n {
v[i] = lhs[i] - rhs[i]; v[i] = lhs[i] - rhs[i];
@ -39,108 +26,54 @@ fn _vectors_vecf16_operator_minus(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) ->
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_lt(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_lt(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() < rhs.deref() lhs.deref() < rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_lte(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_lte(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() <= rhs.deref() lhs.deref() <= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_gt(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_gt(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() > rhs.deref() lhs.deref() > rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_gte(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_gte(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() >= rhs.deref() lhs.deref() >= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_eq(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_eq(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() == rhs.deref() lhs.deref() == rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_neq(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool { fn _vectors_vecf16_operator_neq(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() != rhs.deref() lhs.deref() != rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_cosine(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 { fn _vectors_vecf16_operator_cosine(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F16Cos::distance(&lhs, &rhs).to_f32() F16Cos::distance(&lhs, &rhs).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_dot(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 { fn _vectors_vecf16_operator_dot(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F16Dot::distance(&lhs, &rhs).to_f32() F16Dot::distance(&lhs, &rhs).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf16_operator_l2(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 { fn _vectors_vecf16_operator_l2(lhs: Vecf16Input<'_>, rhs: Vecf16Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F16L2::distance(&lhs, &rhs).to_f32() F16L2::distance(&lhs, &rhs).to_f32()
} }

View File

@ -1,18 +1,12 @@
use crate::datatype::vecf32::{Vecf32, Vecf32Input, Vecf32Output}; use crate::datatype::vecf32::{Vecf32, Vecf32Input, Vecf32Output};
use crate::prelude::*; use crate::prelude::*;
use base::scalar::FloatCast;
use service::prelude::*; use service::prelude::*;
use std::ops::Deref; use std::ops::Deref;
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_add(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> Vecf32Output { fn _vectors_vecf32_operator_add(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> Vecf32Output {
if lhs.len() != rhs.len() { let n = check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
let n = lhs.len();
let mut v = vec![F32::zero(); n]; let mut v = vec![F32::zero(); n];
for i in 0..n { for i in 0..n {
v[i] = lhs[i] + rhs[i]; v[i] = lhs[i] + rhs[i];
@ -22,14 +16,7 @@ fn _vectors_vecf32_operator_add(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> V
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_minus(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> Vecf32Output { fn _vectors_vecf32_operator_minus(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> Vecf32Output {
if lhs.len() != rhs.len() { let n = check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
let n = lhs.len();
let mut v = vec![F32::zero(); n]; let mut v = vec![F32::zero(); n];
for i in 0..n { for i in 0..n {
v[i] = lhs[i] - rhs[i]; v[i] = lhs[i] - rhs[i];
@ -39,108 +26,54 @@ fn _vectors_vecf32_operator_minus(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) ->
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_lt(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_lt(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() < rhs.deref() lhs.deref() < rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_lte(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_lte(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() <= rhs.deref() lhs.deref() <= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_gt(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_gt(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() > rhs.deref() lhs.deref() > rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_gte(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_gte(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() >= rhs.deref() lhs.deref() >= rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_eq(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_eq(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() == rhs.deref() lhs.deref() == rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_neq(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool { fn _vectors_vecf32_operator_neq(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> bool {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
lhs.deref() != rhs.deref() lhs.deref() != rhs.deref()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_cosine(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 { fn _vectors_vecf32_operator_cosine(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F32Cos::distance(&lhs, &rhs).to_f32() F32Cos::distance(&lhs, &rhs).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_dot(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 { fn _vectors_vecf32_operator_dot(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F32Dot::distance(&lhs, &rhs).to_f32() F32Dot::distance(&lhs, &rhs).to_f32()
} }
#[pgrx::pg_extern(immutable, parallel_safe)] #[pgrx::pg_extern(immutable, parallel_safe)]
fn _vectors_vecf32_operator_l2(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 { fn _vectors_vecf32_operator_l2(lhs: Vecf32Input<'_>, rhs: Vecf32Input<'_>) -> f32 {
if lhs.len() != rhs.len() { check_matched_dimensions(lhs.len(), rhs.len());
SessionError::Unmatched {
left_dimensions: lhs.len() as _,
right_dimensions: rhs.len() as _,
}
.friendly();
}
F32L2::distance(&lhs, &rhs).to_f32() F32L2::distance(&lhs, &rhs).to_f32()
} }

View File

@ -275,10 +275,7 @@ fn _vectors_svecf32_in(input: &CStr, _oid: Oid, _typmod: i32) -> SVecf32Output {
if let Some(x) = option { if let Some(x) = option {
x x
} else { } else {
SessionError::BadLiteral { bad_literal(hint);
hint: hint.to_string(),
}
.friendly()
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -312,7 +309,7 @@ fn _vectors_svecf32_in(input: &CStr, _oid: Oid, _typmod: i32) -> SVecf32Output {
} }
index = match index.checked_add(1) { index = match index.checked_add(1) {
Some(x) => x, Some(x) => x,
None => SessionError::BadValueDimensions.friendly(), None => check_value_dimensions(65536).get(),
}; };
} }
(Reading, b']') => { (Reading, b']') => {
@ -324,31 +321,22 @@ fn _vectors_svecf32_in(input: &CStr, _oid: Oid, _typmod: i32) -> SVecf32Output {
} }
index = match index.checked_add(1) { index = match index.checked_add(1) {
Some(x) => x, Some(x) => x,
None => SessionError::BadValueDimensions.friendly(), None => check_value_dimensions(65536).get(),
}; };
} }
state = MatchedRight; state = MatchedRight;
} }
(_, b' ') => {} (_, b' ') => {}
_ => { _ => {
SessionError::BadLiteral { bad_literal(&format!("Bad character with ascii {:#x}.", c));
hint: format!("Bad character with ascii {:#x}.", c),
}
.friendly();
} }
} }
} }
if state != MatchedRight { if state != MatchedRight {
SessionError::BadLiteral { bad_literal("Bad sequence");
hint: "Bad sequence.".to_string(),
}
.friendly();
}
if index == 0 {
SessionError::BadValueDimensions.friendly();
} }
SVecf32::new_in_postgres(SparseF32Ref { SVecf32::new_in_postgres(SparseF32Ref {
dims: index, dims: check_value_dimensions(index as usize).get(),
indexes: &indexes, indexes: &indexes,
values: &values, values: &values,
}) })
@ -616,15 +604,12 @@ fn _vectors_svecf32_recv(internal: pgrx::Internal, _oid: Oid, _typmod: i32) -> S
if len > 1 { if len > 1 {
for i in 0..len as usize - 1 { for i in 0..len as usize - 1 {
if indexes[i] >= indexes[i + 1] { if indexes[i] >= indexes[i + 1] {
SessionError::BadLiteral { pgrx::error!("data corruption is detected");
hint: "Indexes are not sorted or duplicated.".to_string(),
}
.friendly();
} }
} }
} }
if indexes[len as usize - 1] >= dims { if indexes[len as usize - 1] >= dims {
SessionError::BadValueDimensions.friendly(); pgrx::error!("data corruption is detected");
} }
output.dims = dims; output.dims = dims;
@ -643,33 +628,24 @@ fn _vectors_svecf32_recv(internal: pgrx::Internal, _oid: Oid, _typmod: i32) -> S
} }
#[pgrx::pg_extern(immutable, parallel_safe, strict)] #[pgrx::pg_extern(immutable, parallel_safe, strict)]
fn _vectors_svector_from_array( fn _vectors_to_svector(
dims: i32, dims: i32,
index: pgrx::Array<i32>, index: pgrx::Array<i32>,
value: pgrx::Array<f32>, value: pgrx::Array<f32>,
) -> SVecf32Output { ) -> SVecf32Output {
let dims: u16 = match dims.try_into() { let dims = check_value_dimensions(dims as usize);
Ok(x) => x,
Err(_) => SessionError::BadValueDimensions.friendly(),
};
if index.len() != value.len() { if index.len() != value.len() {
SessionError::BadLiteral { bad_literal("Lengths of index and value are not matched.");
hint: "Lengths of index and value are not matched.".to_string(),
}
.friendly();
} }
if index.contains_nulls() || value.contains_nulls() { if index.contains_nulls() || value.contains_nulls() {
SessionError::BadLiteral { bad_literal("Index or value contains nulls.");
hint: "Index or value contains nulls.".to_string(),
}
.friendly();
} }
let mut vector: Vec<(u16, F32)> = index let mut vector: Vec<(u16, F32)> = index
.iter_deny_null() .iter_deny_null()
.zip(value.iter_deny_null()) .zip(value.iter_deny_null())
.map(|(index, value)| { .map(|(index, value)| {
if index < 0 || index >= dims as i32 { if index < 0 || index >= dims.get() as i32 {
SessionError::BadValueDimensions.friendly(); bad_literal("Index out of bound.");
} }
(index as u16, F32(value)) (index as u16, F32(value))
}) })
@ -678,10 +654,7 @@ fn _vectors_svector_from_array(
if vector.len() > 1 { if vector.len() > 1 {
for i in 0..vector.len() - 1 { for i in 0..vector.len() - 1 {
if vector[i].0 == vector[i + 1].0 { if vector[i].0 == vector[i + 1].0 {
SessionError::BadLiteral { bad_literal("Duplicated index.");
hint: "Duplicated index.".to_string(),
}
.friendly();
} }
} }
} }
@ -693,7 +666,7 @@ fn _vectors_svector_from_array(
values.push(x.1); values.push(x.1);
} }
SVecf32::new_in_postgres(SparseF32Ref { SVecf32::new_in_postgres(SparseF32Ref {
dims, dims: dims.get(),
indexes: &indexes, indexes: &indexes,
values: &values, values: &values,
}) })

View File

@ -11,14 +11,6 @@ pub enum Typmod {
} }
impl Typmod { impl Typmod {
pub fn parse_from_str(s: &str) -> Option<Self> {
use Typmod::*;
if let Ok(x) = s.parse::<NonZeroU16>() {
Some(Dims(x))
} else {
None
}
}
pub fn parse_from_i32(x: i32) -> Option<Self> { pub fn parse_from_i32(x: i32) -> Option<Self> {
use Typmod::*; use Typmod::*;
if x == -1 { if x == -1 {
@ -43,11 +35,11 @@ impl Typmod {
Dims(x) => i32::from(x.get()), Dims(x) => i32::from(x.get()),
} }
} }
pub fn dims(self) -> Option<u16> { pub fn dims(self) -> Option<NonZeroU16> {
use Typmod::*; use Typmod::*;
match self { match self {
Any => None, Any => None,
Dims(dims) => Some(dims.get()), Dims(dims) => Some(dims),
} }
} }
} }
@ -58,12 +50,11 @@ fn _vectors_typmod_in(list: Array<&CStr>) -> i32 {
-1 -1
} else if list.len() == 1 { } else if list.len() == 1 {
let s = list.get(0).unwrap().unwrap().to_str().unwrap(); let s = list.get(0).unwrap().unwrap().to_str().unwrap();
let typmod = Typmod::parse_from_str(s) let typmod = Typmod::Dims(check_type_dimensions(s.parse::<NonZeroU16>().ok()));
.ok_or(SessionError::BadTypeDimensions)
.friendly();
typmod.into_i32() typmod.into_i32()
} else { } else {
SessionError::BadTypeDimensions.friendly(); check_type_dimensions(None);
unreachable!()
} }
} }

View File

@ -268,19 +268,18 @@ unsafe impl SqlTranslatable for Vecf16Output {
#[pgrx::pg_extern(immutable, parallel_safe, strict)] #[pgrx::pg_extern(immutable, parallel_safe, strict)]
fn _vectors_vecf16_in(input: &CStr, _oid: Oid, typmod: i32) -> Vecf16Output { fn _vectors_vecf16_in(input: &CStr, _oid: Oid, typmod: i32) -> Vecf16Output {
use crate::utils::parse::parse_vector; use crate::utils::parse::parse_vector;
let reserve = Typmod::parse_from_i32(typmod).unwrap().dims().unwrap_or(0); let reserve = Typmod::parse_from_i32(typmod)
.unwrap()
.dims()
.map(|x| x.get())
.unwrap_or(0);
let v = parse_vector(input.to_bytes(), reserve as usize, |s| s.parse().ok()); let v = parse_vector(input.to_bytes(), reserve as usize, |s| s.parse().ok());
match v { match v {
Err(e) => { Err(e) => {
SessionError::BadLiteral { bad_literal(&e.to_string());
hint: e.to_string(),
}
.friendly();
} }
Ok(vector) => { Ok(vector) => {
if vector.is_empty() || vector.len() > 65535 { check_value_dimensions(vector.len());
SessionError::BadValueDimensions.friendly();
}
Vecf16::new_in_postgres(&vector) Vecf16::new_in_postgres(&vector)
} }
} }

View File

@ -268,19 +268,18 @@ unsafe impl SqlTranslatable for Vecf32Output {
#[pgrx::pg_extern(immutable, parallel_safe, strict)] #[pgrx::pg_extern(immutable, parallel_safe, strict)]
fn _vectors_vecf32_in(input: &CStr, _oid: Oid, typmod: i32) -> Vecf32Output { fn _vectors_vecf32_in(input: &CStr, _oid: Oid, typmod: i32) -> Vecf32Output {
use crate::utils::parse::parse_vector; use crate::utils::parse::parse_vector;
let reserve = Typmod::parse_from_i32(typmod).unwrap().dims().unwrap_or(0); let reserve = Typmod::parse_from_i32(typmod)
.unwrap()
.dims()
.map(|x| x.get())
.unwrap_or(0);
let v = parse_vector(input.to_bytes(), reserve as usize, |s| s.parse().ok()); let v = parse_vector(input.to_bytes(), reserve as usize, |s| s.parse().ok());
match v { match v {
Err(e) => { Err(e) => {
SessionError::BadLiteral { bad_literal(&e.to_string());
hint: e.to_string(),
}
.friendly();
} }
Ok(vector) => { Ok(vector) => {
if vector.is_empty() || vector.len() > 65535 { check_value_dimensions(vector.len());
SessionError::BadValueDimensions.friendly();
}
Vecf32::new_in_postgres(&vector) Vecf32::new_in_postgres(&vector)
} }
} }

View File

@ -1,6 +1,5 @@
use pgrx::{GucContext, GucFlags, GucRegistry, GucSetting}; use pgrx::{GucContext, GucFlags, GucRegistry, GucSetting};
use service::index::SearchOptions; use service::index::SearchOptions;
use validator::Validate;
static ENABLE_PREFILTER: GucSetting<bool> = GucSetting::<bool>::new(true); static ENABLE_PREFILTER: GucSetting<bool> = GucSetting::<bool>::new(true);
@ -40,11 +39,9 @@ pub unsafe fn init() {
} }
pub fn search_options() -> SearchOptions { pub fn search_options() -> SearchOptions {
let options = SearchOptions { SearchOptions {
prefilter_enable: ENABLE_PREFILTER.get(), prefilter_enable: ENABLE_PREFILTER.get(),
hnsw_ef_search: HNSW_EF_SEARCH.get() as usize, hnsw_ef_search: HNSW_EF_SEARCH.get() as usize,
ivf_nprobe: IVF_NPROBE.get() as u32, ivf_nprobe: IVF_NPROBE.get() as u32,
}; }
assert!(options.validate().is_ok());
options
} }

View File

@ -1,14 +1,14 @@
#![allow(unsafe_op_in_unsafe_fn)] #![allow(unsafe_op_in_unsafe_fn)]
use crate::index::am_setup::options;
use crate::index::utils::from_datum; use crate::index::utils::from_datum;
use crate::ipc::client::ClientGuard; use crate::ipc::ClientRpc;
use crate::prelude::*; use crate::prelude::*;
use crate::{index::am_setup::options, ipc::client::Rpc};
use pgrx::pg_sys::{IndexBuildResult, IndexInfo, RelationData}; use pgrx::pg_sys::{IndexBuildResult, IndexInfo, RelationData};
use service::prelude::*; use service::prelude::*;
pub struct Builder { pub struct Builder {
pub rpc: ClientGuard<Rpc>, pub rpc: ClientRpc,
pub heap_relation: *mut RelationData, pub heap_relation: *mut RelationData,
pub index_info: *mut IndexInfo, pub index_info: *mut IndexInfo,
pub result: *mut IndexBuildResult, pub result: *mut IndexBuildResult,
@ -24,8 +24,14 @@ pub unsafe fn build(
let oid = (*index).rd_locator.relNumber; let oid = (*index).rd_locator.relNumber;
let id = Handle::from_sys(oid); let id = Handle::from_sys(oid);
let options = options(index); let options = options(index);
let mut rpc = crate::ipc::client::borrow_mut(); let mut rpc = check_client(crate::ipc::client());
rpc.create(id, options); match rpc.create(id, options) {
Ok(()) => (),
Err(CreateError::Exist) => bad_service_exists(),
Err(CreateError::InvalidIndexOptions { reason }) => {
bad_service_invalid_index_options(&reason)
}
}
if let Some((heap_relation, index_info, result)) = data { if let Some((heap_relation, index_info, result)) = data {
let mut builder = Builder { let mut builder = Builder {
rpc, rpc,
@ -60,7 +66,12 @@ unsafe extern "C" fn callback(
let state = &mut *(state as *mut Builder); let state = &mut *(state as *mut Builder);
let vector = from_datum(*values.add(0)); let vector = from_datum(*values.add(0));
let pointer = Pointer::from_sys(*ctid); let pointer = Pointer::from_sys(*ctid);
state.rpc.insert(id, vector, pointer); match state.rpc.insert(id, vector, pointer) {
Ok(()) => (),
Err(InsertError::NotExist) => bad_service_not_exist(),
Err(InsertError::Upgrade) => bad_service_upgrade(),
Err(InsertError::InvalidVector) => bad_service_invalid_vector(),
}
(*state.result).heap_tuples += 1.0; (*state.result).heap_tuples += 1.0;
(*state.result).index_tuples += 1.0; (*state.result).index_tuples += 1.0;
} }

View File

@ -4,8 +4,7 @@ use crate::gucs::executing::search_options;
use crate::gucs::planning::Mode; use crate::gucs::planning::Mode;
use crate::gucs::planning::SEARCH_MODE; use crate::gucs::planning::SEARCH_MODE;
use crate::index::utils::from_datum; use crate::index::utils::from_datum;
use crate::ipc::client::ClientGuard; use crate::ipc::{ClientBasic, ClientVbase};
use crate::ipc::client::{Basic, Vbase};
use crate::prelude::*; use crate::prelude::*;
use pgrx::FromDatum; use pgrx::FromDatum;
use service::prelude::*; use service::prelude::*;
@ -17,11 +16,11 @@ pub enum Scanner {
}, },
Basic { Basic {
node: *mut pgrx::pg_sys::IndexScanState, node: *mut pgrx::pg_sys::IndexScanState,
basic: ClientGuard<Basic>, basic: ClientBasic,
}, },
Vbase { Vbase {
node: *mut pgrx::pg_sys::IndexScanState, node: *mut pgrx::pg_sys::IndexScanState,
vbase: ClientGuard<Vbase>, vbase: ClientVbase,
}, },
} }
@ -97,17 +96,29 @@ pub unsafe fn next_scan(scan: pgrx::pg_sys::IndexScanDesc) -> bool {
let oid = (*(*scan).indexRelation).rd_locator.relNumber; let oid = (*(*scan).indexRelation).rd_locator.relNumber;
let id = Handle::from_sys(oid); let id = Handle::from_sys(oid);
let rpc = crate::ipc::client::borrow_mut(); let rpc = check_client(crate::ipc::client());
match SEARCH_MODE.get() { match SEARCH_MODE.get() {
Mode::basic => { Mode::basic => {
let opts = search_options(); let opts = search_options();
let basic = rpc.basic(id, vector.clone(), opts); let basic = match rpc.basic(id, vector.clone(), opts) {
Ok(x) => x,
Err((_, BasicError::NotExist)) => bad_service_not_exist(),
Err((_, BasicError::Upgrade)) => bad_service_upgrade(),
Err((_, BasicError::InvalidVector)) => bad_service_invalid_vector(),
Err((_, BasicError::InvalidSearchOptions { reason: _ })) => unreachable!(),
};
*scanner = Scanner::Basic { node, basic }; *scanner = Scanner::Basic { node, basic };
} }
Mode::vbase => { Mode::vbase => {
let opts = search_options(); let opts = search_options();
let vbase = rpc.vbase(id, vector.clone(), opts); let vbase = match rpc.vbase(id, vector.clone(), opts) {
Ok(x) => x,
Err((_, VbaseError::NotExist)) => bad_service_not_exist(),
Err((_, VbaseError::Upgrade)) => bad_service_upgrade(),
Err((_, VbaseError::InvalidVector)) => bad_service_invalid_vector(),
Err((_, VbaseError::InvalidSearchOptions { reason: _ })) => unreachable!(),
};
*scanner = Scanner::Vbase { node, vbase }; *scanner = Scanner::Vbase { node, vbase };
} }
} }

View File

@ -80,7 +80,7 @@ pub unsafe fn convert_opfamily_to_distance(opfamily: pgrx::pg_sys::Oid) -> (Dist
} else if operator == regoperatorin("vectors.<=>(vectors.bvector,vectors.bvector)") { } else if operator == regoperatorin("vectors.<=>(vectors.bvector,vectors.bvector)") {
result = (Distance::Cos, Kind::Binary); result = (Distance::Cos, Kind::Binary);
} else { } else {
SessionError::BadOptions2.friendly(); bad_opclass();
}; };
pgrx::pg_sys::ReleaseCatCacheList(list); pgrx::pg_sys::ReleaseCatCacheList(list);
pgrx::pg_sys::ReleaseSysCache(tuple); pgrx::pg_sys::ReleaseSysCache(tuple);
@ -97,7 +97,7 @@ pub unsafe fn options(index_relation: pgrx::pg_sys::Relation) -> IndexOptions {
let attrs = (*(*index_relation).rd_att).attrs.as_slice(1); let attrs = (*(*index_relation).rd_att).attrs.as_slice(1);
let attr = &attrs[0]; let attr = &attrs[0];
let typmod = Typmod::parse_from_i32(attr.type_mod()).unwrap(); let typmod = Typmod::parse_from_i32(attr.type_mod()).unwrap();
let dims = typmod.dims().ok_or(SessionError::BadOption1).friendly(); let dims = check_column_dimensions(typmod.dims()).get();
// get other options // get other options
let parsed = get_parsed_from_varlena((*index_relation).rd_options); let parsed = get_parsed_from_varlena((*index_relation).rd_options);
IndexOptions { IndexOptions {

View File

@ -6,18 +6,32 @@ pub fn update_insert(handle: Handle, vector: DynamicVector, tid: pgrx::pg_sys::I
callback_dirty(handle); callback_dirty(handle);
let pointer = Pointer::from_sys(tid); let pointer = Pointer::from_sys(tid);
let mut rpc = crate::ipc::client::borrow_mut(); let mut rpc = check_client(crate::ipc::client());
rpc.insert(handle, vector, pointer);
match rpc.insert(handle, vector, pointer) {
Ok(()) => (),
Err(InsertError::NotExist) => bad_service_not_exist(),
Err(InsertError::Upgrade) => bad_service_upgrade(),
Err(InsertError::InvalidVector) => bad_service_invalid_vector(),
}
} }
pub fn update_delete(handle: Handle, f: impl Fn(Pointer) -> bool) { pub fn update_delete(handle: Handle, f: impl Fn(Pointer) -> bool) {
callback_dirty(handle); callback_dirty(handle);
let mut rpc_list = crate::ipc::client::borrow_mut().list(handle); let mut rpc_list = match check_client(crate::ipc::client()).list(handle) {
let mut rpc = crate::ipc::client::borrow_mut(); Ok(x) => x,
Err((_, ListError::NotExist)) => bad_service_not_exist(),
Err((_, ListError::Upgrade)) => bad_service_upgrade(),
};
let mut rpc = check_client(crate::ipc::client());
while let Some(p) = rpc_list.next() { while let Some(p) = rpc_list.next() {
if f(p) { if f(p) {
rpc.delete(handle, p); match rpc.delete(handle, p) {
Ok(()) => (),
Err(DeleteError::NotExist) => (),
Err(DeleteError::Upgrade) => (),
}
} }
} }
rpc_list.leave(); rpc_list.leave();

View File

@ -1,8 +1,4 @@
use crate::ipc::client; #[pgrx::pg_extern(volatile, strict)]
#[pgrx::pg_extern(immutable, parallel_safe, strict)]
fn _vectors_pgvectors_upgrade() { fn _vectors_pgvectors_upgrade() {
let mut client = client::borrow_mut(); let _ = std::fs::remove_dir_all("pg_vectors");
client.upgrade();
pgrx::warning!("pgvecto.rs is upgraded. Restart PostgreSQL to take effects.");
} }

View File

@ -16,12 +16,14 @@ pub fn commit() {
if pending_deletes.is_empty() && pending_dirty.is_empty() { if pending_deletes.is_empty() && pending_dirty.is_empty() {
return; return;
} }
let mut rpc = crate::ipc::client::borrow_mut(); let Some(mut rpc) = crate::ipc::client() else {
return;
};
for handle in pending_dirty { for handle in pending_dirty {
rpc.flush(handle); let _ = rpc.flush(handle);
} }
for handle in pending_deletes { for handle in pending_deletes {
rpc.drop(handle); let _ = rpc.drop(handle);
} }
} }
@ -31,9 +33,11 @@ pub fn abort() {
if pending_deletes.is_empty() { if pending_deletes.is_empty() {
return; return;
} }
let mut rpc = crate::ipc::client::borrow_mut(); let Some(mut rpc) = crate::ipc::client() else {
return;
};
for handle in pending_deletes { for handle in pending_deletes {
rpc.drop(handle); let _ = rpc.drop(handle);
} }
} }

View File

@ -9,14 +9,14 @@ fn _vectors_index_stat(
use service::index::IndexStat; use service::index::IndexStat;
let id = Handle::from_sys(oid); let id = Handle::from_sys(oid);
let mut res = PgHeapTuple::new_composite_type("vectors.vector_index_stat").unwrap(); let mut res = PgHeapTuple::new_composite_type("vectors.vector_index_stat").unwrap();
let mut rpc = crate::ipc::client::borrow_mut(); let mut rpc = check_client(crate::ipc::client());
let stat = rpc.stat(id); let stat = rpc.stat(id);
match stat { match stat {
IndexStat::Normal { Ok(IndexStat {
indexing, indexing,
options, options,
segments, segments,
} => { }) => {
res.set_by_name("idx_status", "NORMAL").unwrap(); res.set_by_name("idx_status", "NORMAL").unwrap();
res.set_by_name("idx_indexing", indexing).unwrap(); res.set_by_name("idx_indexing", indexing).unwrap();
res.set_by_name( res.set_by_name(
@ -60,7 +60,10 @@ fn _vectors_index_stat(
.unwrap(); .unwrap();
res res
} }
IndexStat::Upgrade => { Err(StatError::NotExist) => {
bad_service_not_exist();
}
Err(StatError::Upgrade) => {
res.set_by_name("idx_status", "UPGRADE").unwrap(); res.set_by_name("idx_status", "UPGRADE").unwrap();
res res
} }

View File

@ -1,269 +0,0 @@
use super::packet::*;
use super::transport::ClientSocket;
use crate::gucs::internal::{Transport, TRANSPORT};
use crate::prelude::*;
use crate::utils::cells::PgRefCell;
use service::index::IndexOptions;
use service::index::IndexStat;
use service::index::SearchOptions;
use service::prelude::*;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ops::DerefMut;
pub trait ClientLike: 'static {
fn from_socket(socket: ClientSocket) -> Self;
fn to_socket(self) -> ClientSocket;
}
pub struct ClientGuard<T: ClientLike>(pub ManuallyDrop<T>);
impl<T: ClientLike> ClientGuard<T> {
fn map<U: ClientLike>(mut self) -> ClientGuard<U> {
unsafe {
let t = ManuallyDrop::take(&mut self.0);
std::mem::forget(self);
ClientGuard::new(U::from_socket(t.to_socket()))
}
}
}
impl<T: ClientLike> Deref for ClientGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T: ClientLike> DerefMut for ClientGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub struct Rpc {
socket: ClientSocket,
}
impl Rpc {
pub fn new(socket: ClientSocket) -> Self {
Self { socket }
}
}
impl ClientGuard<Rpc> {
pub fn flush(&mut self, handle: Handle) {
let packet = RpcPacket::Flush { handle };
self.socket.ok(packet).friendly();
let flush::FlushPacket::Leave {} = self.socket.recv().friendly();
}
pub fn drop(&mut self, handle: Handle) {
let packet = RpcPacket::Drop { handle };
self.socket.ok(packet).friendly();
let drop::DropPacket::Leave {} = self.socket.recv().friendly();
}
pub fn create(&mut self, handle: Handle, options: IndexOptions) {
let packet = RpcPacket::Create { handle, options };
self.socket.ok(packet).friendly();
let create::CreatePacket::Leave {} = self.socket.recv().friendly();
}
pub fn basic(
mut self,
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
) -> ClientGuard<Basic> {
let packet = RpcPacket::Basic {
handle,
vector,
opts,
};
self.socket.ok(packet).friendly();
let basic::BasicErrorPacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
pub fn delete(&mut self, handle: Handle, pointer: Pointer) {
let packet = RpcPacket::Delete { handle, pointer };
self.socket.ok(packet).friendly();
let delete::DeletePacket::Leave {} = self.socket.recv().friendly();
}
pub fn insert(&mut self, handle: Handle, vector: DynamicVector, pointer: Pointer) {
let packet = RpcPacket::Insert {
handle,
vector,
pointer,
};
self.socket.ok(packet).friendly();
let insert::InsertPacket::Leave {} = self.socket.recv().friendly();
}
pub fn stat(&mut self, handle: Handle) -> IndexStat {
let packet = RpcPacket::Stat { handle };
self.socket.ok(packet).friendly();
let stat::StatPacket::Leave { result } = self.socket.recv().friendly();
result
}
pub fn vbase(
mut self,
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
) -> ClientGuard<Vbase> {
let packet = RpcPacket::Vbase {
handle,
vector,
opts,
};
self.socket.ok(packet).friendly();
let vbase::VbaseErrorPacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
pub fn list(mut self, handle: Handle) -> ClientGuard<List> {
let packet = RpcPacket::List { handle };
self.socket.ok(packet).friendly();
let list::ListErrorPacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
pub fn upgrade(&mut self) {
let packet = RpcPacket::Upgrade {};
self.socket.ok(packet).friendly();
let upgrade::UpgradePacket::Leave {} = self.socket.recv().friendly();
}
}
impl ClientLike for Rpc {
fn from_socket(socket: ClientSocket) -> Self {
Self { socket }
}
fn to_socket(self) -> ClientSocket {
self.socket
}
}
pub struct Vbase {
socket: ClientSocket,
}
impl Vbase {
pub fn next(&mut self) -> Option<Pointer> {
let packet = vbase::VbasePacket::Next {};
self.socket.ok(packet).friendly();
let vbase::VbaseNextPacket { p } = self.socket.recv().friendly();
p
}
}
impl ClientGuard<Vbase> {
pub fn leave(mut self) -> ClientGuard<Rpc> {
let packet = vbase::VbasePacket::Leave {};
self.socket.ok(packet).friendly();
let vbase::VbaseLeavePacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
}
impl ClientLike for Vbase {
fn from_socket(socket: ClientSocket) -> Self {
Self { socket }
}
fn to_socket(self) -> ClientSocket {
self.socket
}
}
pub struct Basic {
socket: ClientSocket,
}
impl Basic {
pub fn next(&mut self) -> Option<Pointer> {
let packet = basic::BasicPacket::Next {};
self.socket.ok(packet).friendly();
let basic::BasicNextPacket { p } = self.socket.recv().friendly();
p
}
}
impl ClientGuard<Basic> {
pub fn leave(mut self) -> ClientGuard<Rpc> {
let packet = basic::BasicPacket::Leave {};
self.socket.ok(packet).friendly();
let basic::BasicLeavePacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
}
impl ClientLike for Basic {
fn from_socket(socket: ClientSocket) -> Self {
Self { socket }
}
fn to_socket(self) -> ClientSocket {
self.socket
}
}
pub struct List {
socket: ClientSocket,
}
impl List {
pub fn next(&mut self) -> Option<Pointer> {
let packet = list::ListPacket::Next {};
self.socket.ok(packet).friendly();
let list::ListNextPacket { p } = self.socket.recv().friendly();
p
}
}
impl ClientGuard<List> {
pub fn leave(mut self) -> ClientGuard<Rpc> {
let packet = list::ListPacket::Leave {};
self.socket.ok(packet).friendly();
let list::ListLeavePacket {} = self.socket.recv().friendly();
ClientGuard::map(self)
}
}
impl ClientLike for List {
fn from_socket(socket: ClientSocket) -> Self {
Self { socket }
}
fn to_socket(self) -> ClientSocket {
self.socket
}
}
static CLIENTS: PgRefCell<Vec<ClientSocket>> = unsafe { PgRefCell::new(Vec::new()) };
pub fn borrow_mut() -> ClientGuard<Rpc> {
let mut x = CLIENTS.borrow_mut();
if let Some(socket) = x.pop() {
return ClientGuard::new(Rpc::new(socket));
}
let socket = match TRANSPORT.get() {
Transport::unix => crate::ipc::connect_unix(),
Transport::mmap => crate::ipc::connect_mmap(),
};
ClientGuard::new(Rpc::new(socket))
}
impl<T: ClientLike> ClientGuard<T> {
pub fn new(t: T) -> Self {
Self(ManuallyDrop::new(t))
}
}
impl<T: ClientLike> Drop for ClientGuard<T> {
fn drop(&mut self) {
let socket = unsafe { ManuallyDrop::take(&mut self.0).to_socket() };
if !std::thread::panicking() && std::any::TypeId::of::<T>() == std::any::TypeId::of::<Rpc>()
{
let mut x = CLIENTS.borrow_mut();
x.push(socket);
}
}
}

View File

@ -1,55 +1,43 @@
pub mod client;
mod packet;
pub mod server;
pub mod transport; pub mod transport;
use self::server::RpcHandler; use self::transport::ClientSocket;
use self::transport::ServerSocket;
use crate::gucs::internal::{Transport, TRANSPORT};
use crate::ipc::transport::Packet;
use crate::prelude::*; use crate::prelude::*;
use crate::utils::cells::PgRefCell;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use service::prelude::ServiceError; use service::index::IndexOptions;
use thiserror::Error; use service::index::IndexStat;
use service::index::SearchOptions;
use service::prelude::*;
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone)]
pub enum ConnectionError { pub enum ConnectionError {
#[error("\ ClosedConnection,
IPC connection is closed unexpected. BadSerialization,
ADVICE: The error is raisen by background worker errors. \ BadDeserialization,
Please check the full PostgreSQL log to get more information. Please read `https://docs.pgvecto.rs/admin/configuration.html`.\
")]
Unexpected,
#[error(transparent)]
Service(#[from] ServiceError),
#[error(transparent)]
Grace(#[from] GraceError),
} }
impl FriendlyError for ConnectionError {} pub fn listen_unix() -> impl Iterator<Item = ServerRpcHandler> {
#[derive(Debug, Clone, Error, Serialize, Deserialize)]
#[error("Client performs a graceful shutdown.")]
pub struct GraceError;
impl FriendlyError for GraceError {}
pub fn listen_unix() -> impl Iterator<Item = RpcHandler> {
std::iter::from_fn(move || { std::iter::from_fn(move || {
let socket = self::transport::ServerSocket::Unix(self::transport::unix::accept()); let socket = self::transport::ServerSocket::Unix(self::transport::unix::accept());
Some(self::server::RpcHandler::new(socket)) Some(self::ServerRpcHandler::new(socket))
}) })
} }
pub fn listen_mmap() -> impl Iterator<Item = RpcHandler> { pub fn listen_mmap() -> impl Iterator<Item = ServerRpcHandler> {
std::iter::from_fn(move || { std::iter::from_fn(move || {
let socket = self::transport::ServerSocket::Mmap(self::transport::mmap::accept()); let socket = self::transport::ServerSocket::Mmap(self::transport::mmap::accept());
Some(self::server::RpcHandler::new(socket)) Some(self::ServerRpcHandler::new(socket))
}) })
} }
pub fn connect_unix() -> self::transport::ClientSocket { pub fn connect_unix() -> ClientSocket {
self::transport::ClientSocket::Unix(self::transport::unix::connect()) self::transport::ClientSocket::Unix(self::transport::unix::connect())
} }
pub fn connect_mmap() -> self::transport::ClientSocket { pub fn connect_mmap() -> ClientSocket {
self::transport::ClientSocket::Mmap(self::transport::mmap::connect()) self::transport::ClientSocket::Mmap(self::transport::mmap::connect())
} }
@ -57,3 +45,288 @@ pub fn init() {
self::transport::mmap::init(); self::transport::mmap::init();
self::transport::unix::init(); self::transport::unix::init();
} }
impl Drop for ClientRpc {
fn drop(&mut self) {
let socket = self.socket.take();
if let Some(socket) = socket {
if !std::thread::panicking() {
let mut x = CLIENTS.borrow_mut();
x.push(socket);
}
}
}
}
pub struct ClientRpc {
pub socket: Option<ClientSocket>,
}
impl ClientRpc {
fn new(socket: ClientSocket) -> Self {
Self {
socket: Some(socket),
}
}
fn _ok<U: Packet>(&mut self, packet: U) -> Result<(), ConnectionError> {
self.socket.as_mut().unwrap().ok(packet)
}
fn _recv<U: Packet>(&mut self) -> Result<U, ConnectionError> {
self.socket.as_mut().unwrap().recv()
}
}
static CLIENTS: PgRefCell<Vec<ClientSocket>> = unsafe { PgRefCell::new(Vec::new()) };
pub fn client() -> Option<ClientRpc> {
if !crate::bgworker::is_started() {
return None;
}
let mut x = CLIENTS.borrow_mut();
if let Some(socket) = x.pop() {
return Some(ClientRpc::new(socket));
}
let socket = match TRANSPORT.get() {
Transport::unix => connect_unix(),
Transport::mmap => connect_mmap(),
};
Some(ClientRpc::new(socket))
}
pub struct ServerRpcHandler {
socket: ServerSocket,
}
impl ServerRpcHandler {
pub(super) fn new(socket: ServerSocket) -> Self {
Self { socket }
}
}
macro_rules! define_packets {
(unary $name:ident($($p_name:ident: $p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
#[derive(Debug, Serialize, Deserialize)]
pub struct [<Packet $name:camel>] {
pub result: Result<$r, [< $name:camel Error >]>,
}
}
};
(stream $name:ident($($p_name:ident: $p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
#[derive(Debug, Serialize, Deserialize)]
pub struct [<Packet $name:camel 0>] {
pub result: Result<(), [< $name:camel Error >]>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum [<Packet $name:camel>] {
Next {},
Leave {},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct [<Packet $name:camel 1>] {
pub p: Option<$r>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct [<Packet $name:camel 2>] {}
}
};
}
macro_rules! define_client_stuffs {
(unary $name:ident($($p_name:ident:$p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
impl ClientRpc {
pub fn $name(&mut self, $($p_name:$p_ty),*) -> Result<$r, [< $name:camel Error >]> {
let packet = PacketRpc::[< $name:camel >] { $($p_name),* };
check_connection(self._ok(packet));
let [<Packet $name:camel>] { result } = check_connection(self._recv());
result
}
}
}
};
(stream $name:ident($($p_name:ident:$p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
impl ClientRpc {
pub fn $name(mut self, $($p_name:$p_ty),*) -> Result<[<Client $name:camel>], (Self, [< $name:camel Error >])> {
let packet = PacketRpc::[<$name:camel>] { $($p_name),* };
check_connection(self._ok(packet));
let [<Packet $name:camel 0>] { result } = check_connection(self._recv());
if let Err(e) = result {
Err((self, e))
} else {
Ok([<Client $name:camel>] {
socket: self.socket.take()
})
}
}
}
pub struct [<Client $name:camel>] {
socket: Option<ClientSocket>,
}
impl [<Client $name:camel>] {
fn _ok<U: Packet>(&mut self, packet: U) -> Result<(), ConnectionError> {
self.socket.as_mut().unwrap().ok(packet)
}
fn _recv<U: Packet>(&mut self) -> Result<U, ConnectionError> {
self.socket.as_mut().unwrap().recv()
}
}
impl [<Client $name:camel>] {
pub fn next(&mut self) -> Option<$r> {
let packet = [<Packet $name:camel>]::Next {};
check_connection(self._ok(packet));
let [<Packet $name:camel 1>] { p } = check_connection(self._recv());
p
}
pub fn leave(mut self) -> ClientRpc {
let packet = [<Packet $name:camel>]::Leave {};
check_connection(self._ok(packet));
let [<Packet $name:camel 2>] {} = check_connection(self._recv());
ClientRpc { socket: self.socket.take() }
}
}
}
};
}
macro_rules! define_server_stuffs {
(unary $name:ident($($p_name:ident:$p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
pub struct [<Server $name:camel>] {
socket: ServerSocket,
}
impl [<Server $name:camel>] {
pub fn leave(mut self, result: Result<$r, [<$name:camel Error>]>) -> Result<ServerRpcHandler, ConnectionError> {
let packet = [<Packet $name:camel>] { result };
self.socket.ok(packet)?;
Ok(ServerRpcHandler {
socket: self.socket,
})
}
}
}
};
(stream $name:ident($($p_name:ident:$p_ty:ty),*) -> $r:ty;) => {
paste::paste! {
pub struct [<Server $name:camel>] {
socket: ServerSocket,
}
impl [<Server $name:camel>] {
pub fn error_ok(mut self) -> Result<[<Server $name:camel Handler>], ConnectionError> {
self.socket.ok([<Packet $name:camel 0>] { result: Ok(()) })?;
Ok([<Server $name:camel Handler>] {
socket: self.socket,
})
}
pub fn error_err(mut self, err: [<$name:camel Error>]) -> Result<ServerRpcHandler, ConnectionError> {
self.socket.ok([<Packet $name:camel 0>] { result: Err(err) })?;
Ok(ServerRpcHandler {
socket: self.socket,
})
}
}
pub struct [<Server $name:camel Handler>] {
socket: ServerSocket,
}
impl [<Server $name:camel Handler>] {
pub fn handle(mut self) -> Result<[<Server $name:camel Handle>], ConnectionError> {
Ok(match self.socket.recv::<[<Packet $name:camel>]>()? {
[<Packet $name:camel>]::Next {} => [<Server $name:camel Handle>]::Next {
x: [<Server $name:camel Next>] {
socket: self.socket,
},
},
[<Packet $name:camel>]::Leave {} => {
self.socket.ok([<Packet $name:camel 2>] {})?;
[<Server $name:camel Handle>]::Leave {
x: ServerRpcHandler {
socket: self.socket,
},
}
}
})
}
}
pub enum [<Server $name:camel Handle>] {
Next { x: [<Server $name:camel Next>] },
Leave { x: ServerRpcHandler },
}
pub struct [<Server $name:camel Next>] {
socket: ServerSocket,
}
impl [<Server $name:camel Next>] {
pub fn leave(mut self, p: Option<$r>) -> Result<[<Server $name:camel Handler>], ConnectionError> {
let packet = [<Packet $name:camel 1>] { p };
self.socket.ok(packet)?;
Ok([<Server $name:camel Handler>] {
socket: self.socket,
})
}
}
}
};
}
macro_rules! defines {
(
$($kind:ident $name:ident($($p_name:ident:$p_ty:ty),*) -> $r:ty;)*
) => {
$(define_packets!($kind $name($($p_name:$p_ty),*) -> $r;);)*
$(define_client_stuffs!($kind $name($($p_name:$p_ty),*) -> $r;);)*
$(define_server_stuffs!($kind $name($($p_name:$p_ty),*) -> $r;);)*
paste::paste! {
#[derive(Debug, Serialize, Deserialize)]
pub enum PacketRpc {
$([<$name:camel>]{$($p_name:$p_ty),*},)*
}
impl ServerRpcHandler {
pub fn handle(mut self) -> Result<ServerRpcHandle, ConnectionError> {
Ok(match self.socket.recv::<PacketRpc>()? {
$(PacketRpc::[<$name:camel>] { $($p_name),* } => ServerRpcHandle::[<$name:camel>] {
$($p_name),*,
x: [<Server $name:camel>] {
socket: self.socket,
},
},)*
})
}
}
pub enum ServerRpcHandle {
$([<$name:camel>] {
$($p_name:$p_ty),*,
x: [< Server $name:camel >],
}),*
}
}
};
}
defines! {
unary create(handle: Handle, options: IndexOptions) -> ();
unary drop(handle: Handle) -> ();
unary flush(handle: Handle) -> ();
unary insert(handle: Handle, vector: DynamicVector, pointer: Pointer) -> ();
unary delete(handle: Handle, pointer: Pointer) -> ();
stream basic(handle: Handle, vector: DynamicVector, opts: SearchOptions) -> Pointer;
stream vbase(handle: Handle, vector: DynamicVector, opts: SearchOptions) -> Pointer;
stream list(handle: Handle) -> Pointer;
unary stat(handle: Handle) -> IndexStat;
}

View File

@ -1,19 +0,0 @@
use serde::{Deserialize, Serialize};
use service::prelude::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct BasicErrorPacket {}
#[derive(Debug, Serialize, Deserialize)]
pub enum BasicPacket {
Next {},
Leave {},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BasicNextPacket {
pub p: Option<Pointer>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BasicLeavePacket {}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum CreatePacket {
Leave {},
}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum DeletePacket {
Leave {},
}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum DropPacket {
Leave {},
}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum FlushPacket {
Leave {},
}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum InsertPacket {
Leave {},
}

View File

@ -1,19 +0,0 @@
use serde::{Deserialize, Serialize};
use service::prelude::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct ListErrorPacket {}
#[derive(Debug, Serialize, Deserialize)]
pub enum ListPacket {
Next {},
Leave {},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListNextPacket {
pub p: Option<Pointer>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListLeavePacket {}

View File

@ -1,58 +0,0 @@
pub mod basic;
pub mod create;
pub mod delete;
pub mod drop;
pub mod flush;
pub mod insert;
pub mod list;
pub mod stat;
pub mod upgrade;
pub mod vbase;
use serde::{Deserialize, Serialize};
use service::index::IndexOptions;
use service::index::SearchOptions;
use service::prelude::*;
#[derive(Debug, Serialize, Deserialize)]
pub enum RpcPacket {
// transaction
Flush {
handle: Handle,
},
Drop {
handle: Handle,
},
Create {
handle: Handle,
options: IndexOptions,
},
// instance
Insert {
handle: Handle,
vector: DynamicVector,
pointer: Pointer,
},
Delete {
handle: Handle,
pointer: Pointer,
},
Stat {
handle: Handle,
},
Basic {
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
},
Vbase {
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
},
List {
handle: Handle,
},
// admin
Upgrade {},
}

View File

@ -1,7 +0,0 @@
use serde::{Deserialize, Serialize};
use service::index::IndexStat;
#[derive(Debug, Serialize, Deserialize)]
pub enum StatPacket {
Leave { result: IndexStat },
}

View File

@ -1,6 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum UpgradePacket {
Leave {},
}

View File

@ -1,19 +0,0 @@
use serde::{Deserialize, Serialize};
use service::prelude::*;
#[derive(Debug, Serialize, Deserialize)]
pub struct VbaseErrorPacket {}
#[derive(Debug, Serialize, Deserialize)]
pub enum VbasePacket {
Next {},
Leave {},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VbaseNextPacket {
pub p: Option<Pointer>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VbaseLeavePacket {}

View File

@ -1,449 +0,0 @@
use super::packet::*;
use super::transport::ServerSocket;
use super::ConnectionError;
use service::index::IndexOptions;
use service::index::IndexStat;
use service::index::SearchOptions;
use service::prelude::*;
pub struct RpcHandler {
socket: ServerSocket,
}
impl RpcHandler {
pub(super) fn new(socket: ServerSocket) -> Self {
Self { socket }
}
pub fn handle(mut self) -> Result<RpcHandle, ConnectionError> {
Ok(match self.socket.recv::<RpcPacket>()? {
RpcPacket::Flush { handle } => RpcHandle::Flush {
handle,
x: Flush {
socket: self.socket,
},
},
RpcPacket::Drop { handle } => RpcHandle::Drop {
handle,
x: Drop {
socket: self.socket,
},
},
RpcPacket::Create { handle, options } => RpcHandle::Create {
handle,
options,
x: Create {
socket: self.socket,
},
},
RpcPacket::Insert {
handle,
vector,
pointer,
} => RpcHandle::Insert {
handle,
vector,
pointer,
x: Insert {
socket: self.socket,
},
},
RpcPacket::Delete { handle, pointer } => RpcHandle::Delete {
handle,
pointer,
x: Delete {
socket: self.socket,
},
},
RpcPacket::Basic {
handle,
vector,
opts,
} => RpcHandle::Basic {
handle,
vector,
opts,
x: Basic {
socket: self.socket,
},
},
RpcPacket::Stat { handle } => RpcHandle::Stat {
handle,
x: Stat {
socket: self.socket,
},
},
RpcPacket::Vbase {
handle,
vector,
opts,
} => RpcHandle::Vbase {
handle,
vector,
opts,
x: Vbase {
socket: self.socket,
},
},
RpcPacket::List { handle } => RpcHandle::List {
handle,
x: List {
socket: self.socket,
},
},
RpcPacket::Upgrade {} => RpcHandle::Upgrade {
x: Upgrade {
socket: self.socket,
},
},
})
}
}
pub enum RpcHandle {
Flush {
handle: Handle,
x: Flush,
},
Drop {
handle: Handle,
x: Drop,
},
Create {
handle: Handle,
options: IndexOptions,
x: Create,
},
Basic {
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
x: Basic,
},
Insert {
handle: Handle,
vector: DynamicVector,
pointer: Pointer,
x: Insert,
},
Delete {
handle: Handle,
pointer: Pointer,
x: Delete,
},
Stat {
handle: Handle,
x: Stat,
},
Vbase {
handle: Handle,
vector: DynamicVector,
opts: SearchOptions,
x: Vbase,
},
List {
handle: Handle,
x: List,
},
Upgrade {
x: Upgrade,
},
}
pub struct Flush {
socket: ServerSocket,
}
impl Flush {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = flush::FlushPacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
#[allow(dead_code)]
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Drop {
socket: ServerSocket,
}
impl Drop {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = drop::DropPacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
#[allow(dead_code)]
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Create {
socket: ServerSocket,
}
impl Create {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = create::CreatePacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Insert {
socket: ServerSocket,
}
impl Insert {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = insert::InsertPacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Delete {
socket: ServerSocket,
}
impl Delete {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = delete::DeletePacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Basic {
socket: ServerSocket,
}
impl Basic {
pub fn error(mut self) -> Result<BasicHandler, ConnectionError> {
self.socket.ok(basic::BasicErrorPacket {})?;
Ok(BasicHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct BasicHandler {
socket: ServerSocket,
}
impl BasicHandler {
pub fn handle(mut self) -> Result<BasicHandle, ConnectionError> {
Ok(match self.socket.recv::<basic::BasicPacket>()? {
basic::BasicPacket::Next {} => BasicHandle::Next {
x: BasicNext {
socket: self.socket,
},
},
basic::BasicPacket::Leave {} => {
self.socket.ok(basic::BasicLeavePacket {})?;
BasicHandle::Leave {
x: RpcHandler {
socket: self.socket,
},
}
}
})
}
}
pub enum BasicHandle {
Next { x: BasicNext },
Leave { x: RpcHandler },
}
pub struct BasicNext {
socket: ServerSocket,
}
impl BasicNext {
pub fn leave(mut self, p: Option<Pointer>) -> Result<BasicHandler, ConnectionError> {
let packet = basic::BasicNextPacket { p };
self.socket.ok(packet)?;
Ok(BasicHandler {
socket: self.socket,
})
}
}
pub struct Stat {
socket: ServerSocket,
}
impl Stat {
pub fn leave(mut self, result: IndexStat) -> Result<RpcHandler, ConnectionError> {
let packet = stat::StatPacket::Leave { result };
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct Vbase {
socket: ServerSocket,
}
impl Vbase {
pub fn error(mut self) -> Result<VbaseHandler, ConnectionError> {
self.socket.ok(vbase::VbaseErrorPacket {})?;
Ok(VbaseHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct VbaseHandler {
socket: ServerSocket,
}
impl VbaseHandler {
pub fn handle(mut self) -> Result<VbaseHandle, ConnectionError> {
Ok(match self.socket.recv::<vbase::VbasePacket>()? {
vbase::VbasePacket::Next {} => VbaseHandle::Next {
x: VbaseNext {
socket: self.socket,
},
},
vbase::VbasePacket::Leave {} => {
self.socket.ok(vbase::VbaseLeavePacket {})?;
VbaseHandle::Leave {
x: RpcHandler {
socket: self.socket,
},
}
}
})
}
}
pub enum VbaseHandle {
Next { x: VbaseNext },
Leave { x: RpcHandler },
}
pub struct VbaseNext {
socket: ServerSocket,
}
impl VbaseNext {
pub fn leave(mut self, p: Option<Pointer>) -> Result<VbaseHandler, ConnectionError> {
let packet = vbase::VbaseNextPacket { p };
self.socket.ok(packet)?;
Ok(VbaseHandler {
socket: self.socket,
})
}
}
pub struct List {
socket: ServerSocket,
}
impl List {
pub fn error(mut self) -> Result<ListHandler, ConnectionError> {
self.socket.ok(list::ListErrorPacket {})?;
Ok(ListHandler {
socket: self.socket,
})
}
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}
pub struct ListHandler {
socket: ServerSocket,
}
impl ListHandler {
pub fn handle(mut self) -> Result<ListHandle, ConnectionError> {
Ok(match self.socket.recv::<list::ListPacket>()? {
list::ListPacket::Next {} => ListHandle::Next {
x: ListNext {
socket: self.socket,
},
},
list::ListPacket::Leave {} => {
self.socket.ok(list::ListLeavePacket {})?;
ListHandle::Leave {
x: RpcHandler {
socket: self.socket,
},
}
}
})
}
}
pub enum ListHandle {
Next { x: ListNext },
Leave { x: RpcHandler },
}
pub struct ListNext {
socket: ServerSocket,
}
impl ListNext {
pub fn leave(mut self, p: Option<Pointer>) -> Result<ListHandler, ConnectionError> {
let packet = list::ListNextPacket { p };
self.socket.ok(packet)?;
Ok(ListHandler {
socket: self.socket,
})
}
}
pub struct Upgrade {
socket: ServerSocket,
}
impl Upgrade {
pub fn leave(mut self) -> Result<RpcHandler, ConnectionError> {
let packet = upgrade::UpgradePacket::Leave {};
self.socket.ok(packet)?;
Ok(RpcHandler {
socket: self.socket,
})
}
#[allow(dead_code)]
pub fn reset(mut self, err: ServiceError) -> Result<!, ConnectionError> {
self.socket.err(err)
}
}

View File

@ -1,43 +1,58 @@
use super::ConnectionError; use super::ConnectionError;
use crate::utils::file_socket::FileSocket;
use crate::utils::os::{futex_wait, futex_wake, memfd_create, mmap_populate};
use rustix::fd::{AsFd, OwnedFd}; use rustix::fd::{AsFd, OwnedFd};
use rustix::fs::FlockOperation; use rustix::fs::FlockOperation;
use send_fd::SendFd;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::OnceLock; use std::sync::OnceLock;
use std::time::Duration;
const BUFFER_SIZE: usize = 512 * 1024; const BUFFER_SIZE: usize = 512 * 1024;
const SPIN_LIMIT: usize = 8; const SPIN_LIMIT: usize = 8;
const TIMEOUT: Duration = Duration::from_secs(15);
static CHANNEL: OnceLock<FileSocket> = OnceLock::new(); static CHANNEL: OnceLock<SendFd> = OnceLock::new();
pub fn init() { pub fn init() {
CHANNEL.set(FileSocket::new().unwrap()).ok().unwrap(); CHANNEL.set(SendFd::new().unwrap()).ok().unwrap();
} }
pub fn accept() -> Socket { pub fn accept() -> Socket {
let memfd = CHANNEL.get().unwrap().recv().unwrap(); let memfd = CHANNEL.get().unwrap().recv().unwrap();
rustix::fs::fcntl_lock(&memfd, FlockOperation::NonBlockingLockShared).unwrap(); rustix::fs::fcntl_lock(&memfd, FlockOperation::NonBlockingLockShared).unwrap();
let addr = unsafe { mmap_populate(BUFFER_SIZE, &memfd).unwrap() }; let memmap = unsafe {
memmap2::MmapOptions::new()
.len(BUFFER_SIZE)
.populate()
.map_mut(&memfd)
.unwrap()
};
Socket { Socket {
is_server: true, is_server: true,
addr: addr as _, addr: memmap.as_ptr().cast(),
memfd, memfd,
_memmap: memmap,
} }
} }
pub fn connect() -> Socket { pub fn connect() -> Socket {
let memfd = memfd_create().unwrap(); let memfd = memfd::memfd_create().unwrap();
rustix::fs::ftruncate(&memfd, BUFFER_SIZE as u64).unwrap(); rustix::fs::ftruncate(&memfd, BUFFER_SIZE as u64).unwrap();
rustix::fs::fcntl_lock(&memfd, FlockOperation::NonBlockingLockShared).unwrap(); rustix::fs::fcntl_lock(&memfd, FlockOperation::NonBlockingLockShared).unwrap();
CHANNEL.get().unwrap().send(memfd.as_fd()).unwrap(); CHANNEL.get().unwrap().send(memfd.as_fd()).unwrap();
let addr = unsafe { mmap_populate(BUFFER_SIZE, &memfd).unwrap() }; let memmap = unsafe {
memmap2::MmapOptions::new()
.len(BUFFER_SIZE)
.populate()
.map_mut(&memfd)
.unwrap()
};
Socket { Socket {
is_server: false, is_server: false,
addr: addr as _, addr: memmap.as_ptr().cast(),
memfd, memfd,
_memmap: memmap,
} }
} }
@ -45,6 +60,7 @@ pub struct Socket {
is_server: bool, is_server: bool,
addr: *const Channel, addr: *const Channel,
memfd: OwnedFd, memfd: OwnedFd,
_memmap: memmap2::MmapMut,
} }
unsafe impl Send for Socket {} unsafe impl Send for Socket {}
@ -123,17 +139,13 @@ impl Channel {
{ {
break; break;
} }
unsafe { interprocess_atomic_wait::wait(&self.futex, Y, TIMEOUT);
futex_wait(&self.futex, Y);
}
} }
Y => { Y => {
if !test() { if !test() {
return Err(ConnectionError::Unexpected); return Err(ConnectionError::ClosedConnection);
}
unsafe {
futex_wait(&self.futex, Y);
} }
interprocess_atomic_wait::wait(&self.futex, Y, TIMEOUT);
} }
_ => unsafe { std::hint::unreachable_unchecked() }, _ => unsafe { std::hint::unreachable_unchecked() },
} }
@ -154,9 +166,7 @@ impl Channel {
(*self.bytes.get())[0..data.len()].copy_from_slice(data); (*self.bytes.get())[0..data.len()].copy_from_slice(data);
} }
if X == self.futex.swap(T, Ordering::Release) { if X == self.futex.swap(T, Ordering::Release) {
unsafe { interprocess_atomic_wait::wake(&self.futex);
futex_wake(&self.futex);
}
} }
} }
unsafe fn server_recv(&self, test: impl Fn() -> bool) -> Result<Vec<u8>, ConnectionError> { unsafe fn server_recv(&self, test: impl Fn() -> bool) -> Result<Vec<u8>, ConnectionError> {
@ -182,17 +192,13 @@ impl Channel {
{ {
break; break;
} }
unsafe { interprocess_atomic_wait::wait(&self.futex, Y, TIMEOUT);
futex_wait(&self.futex, Y);
}
} }
Y => { Y => {
if !test() { if !test() {
return Err(ConnectionError::Unexpected); return Err(ConnectionError::ClosedConnection);
}
unsafe {
futex_wait(&self.futex, Y);
} }
interprocess_atomic_wait::wait(&self.futex, Y, TIMEOUT);
} }
_ => unsafe { std::hint::unreachable_unchecked() }, _ => unsafe { std::hint::unreachable_unchecked() },
} }
@ -213,9 +219,7 @@ impl Channel {
(*self.bytes.get())[0..data.len()].copy_from_slice(data); (*self.bytes.get())[0..data.len()].copy_from_slice(data);
} }
if X == self.futex.swap(T, Ordering::Release) { if X == self.futex.swap(T, Ordering::Release) {
unsafe { interprocess_atomic_wait::wake(&self.futex);
futex_wake(&self.futex);
}
} }
} }
} }

View File

@ -1,23 +1,21 @@
pub mod mmap; pub mod mmap;
pub mod unix; pub mod unix;
use super::{ConnectionError, GraceError}; use super::ConnectionError;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use service::prelude::ServiceError;
use std::fmt::Debug;
pub trait Bincode: Debug { pub trait Packet: Sized {
fn serialize(&self) -> Vec<u8>; fn serialize(&self) -> Option<Vec<u8>>;
fn deserialize(_: &[u8]) -> Self; fn deserialize(_: &[u8]) -> Option<Self>;
} }
impl<T: Debug + Serialize + for<'a> Deserialize<'a>> Bincode for T { impl<T: Serialize + for<'a> Deserialize<'a>> Packet for T {
fn serialize(&self) -> Vec<u8> { fn serialize(&self) -> Option<Vec<u8>> {
bincode::serialize(self).unwrap() bincode::serialize(self).ok()
} }
fn deserialize(bytes: &[u8]) -> Self { fn deserialize(bytes: &[u8]) -> Option<Self> {
bincode::deserialize(bytes).unwrap() bincode::deserialize(bytes).ok()
} }
} }
@ -32,66 +30,39 @@ pub enum ClientSocket {
} }
impl ServerSocket { impl ServerSocket {
pub fn ok<T: Bincode>(&mut self, packet: T) -> Result<(), ConnectionError> { pub fn ok<T: Packet>(&mut self, packet: T) -> Result<(), ConnectionError> {
let mut buffer = vec![0u8]; let buffer = packet
buffer.extend(packet.serialize()); .serialize()
.ok_or(ConnectionError::BadSerialization)?;
match self { match self {
Self::Unix(x) => x.send(&buffer), Self::Unix(x) => x.send(&buffer),
Self::Mmap(x) => x.send(&buffer), Self::Mmap(x) => x.send(&buffer),
} }
} }
pub fn err(&mut self, packet: ServiceError) -> Result<!, ConnectionError> { pub fn recv<T: Packet>(&mut self) -> Result<T, ConnectionError> {
let mut buffer = vec![1u8];
buffer.extend(Bincode::serialize(&packet));
match self {
Self::Unix(x) => x.send(&buffer)?,
Self::Mmap(x) => x.send(&buffer)?,
}
Err(ConnectionError::Service(packet))
}
pub fn recv<T: Bincode>(&mut self) -> Result<T, ConnectionError> {
let buffer = match self { let buffer = match self {
Self::Unix(x) => x.recv()?, Self::Unix(x) => x.recv()?,
Self::Mmap(x) => x.recv()?, Self::Mmap(x) => x.recv()?,
}; };
let c = &buffer[1..]; T::deserialize(&buffer).ok_or(ConnectionError::BadDeserialization)
match buffer[0] {
0u8 => Ok(T::deserialize(c)),
1u8 => Err(ConnectionError::Grace(bincode::deserialize(c).unwrap())),
_ => unreachable!(),
}
} }
} }
impl ClientSocket { impl ClientSocket {
pub fn ok<T: Bincode>(&mut self, packet: T) -> Result<(), ConnectionError> { pub fn ok<T: Packet>(&mut self, packet: T) -> Result<(), ConnectionError> {
let mut buffer = vec![0u8]; let buffer = packet
buffer.extend(packet.serialize()); .serialize()
.ok_or(ConnectionError::BadSerialization)?;
match self { match self {
Self::Unix(x) => x.send(&buffer), Self::Unix(x) => x.send(&buffer),
Self::Mmap(x) => x.send(&buffer), Self::Mmap(x) => x.send(&buffer),
} }
} }
#[allow(unused)] pub fn recv<T: Packet>(&mut self) -> Result<T, ConnectionError> {
pub fn err(&mut self, packet: GraceError) -> Result<!, ConnectionError> {
let mut buffer = vec![1u8];
buffer.extend(Bincode::serialize(&packet));
match self {
Self::Unix(x) => x.send(&buffer)?,
Self::Mmap(x) => x.send(&buffer)?,
}
Err(ConnectionError::Grace(packet))
}
pub fn recv<T: Bincode>(&mut self) -> Result<T, ConnectionError> {
let buffer = match self { let buffer = match self {
Self::Unix(x) => x.recv()?, Self::Unix(x) => x.recv()?,
Self::Mmap(x) => x.recv()?, Self::Mmap(x) => x.recv()?,
}; };
let c = &buffer[1..]; T::deserialize(&buffer).ok_or(ConnectionError::BadDeserialization)
match buffer[0] {
0u8 => Ok(T::deserialize(c)),
1u8 => Err(ConnectionError::Service(bincode::deserialize(c).unwrap())),
_ => unreachable!(),
}
} }
} }

View File

@ -1,15 +1,15 @@
use super::ConnectionError; use super::ConnectionError;
use crate::utils::file_socket::FileSocket;
use byteorder::{ReadBytesExt, WriteBytesExt}; use byteorder::{ReadBytesExt, WriteBytesExt};
use rustix::fd::AsFd; use rustix::fd::AsFd;
use send_fd::SendFd;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::sync::OnceLock; use std::sync::OnceLock;
static CHANNEL: OnceLock<FileSocket> = OnceLock::new(); static CHANNEL: OnceLock<SendFd> = OnceLock::new();
pub fn init() { pub fn init() {
CHANNEL.set(FileSocket::new().unwrap()).ok().unwrap(); CHANNEL.set(SendFd::new().unwrap()).ok().unwrap();
} }
pub fn accept() -> Socket { pub fn accept() -> Socket {
@ -32,7 +32,7 @@ macro_rules! resolve_closed {
($t: expr) => { ($t: expr) => {
match $t { match $t {
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
return Err(ConnectionError::Unexpected) return Err(ConnectionError::ClosedConnection)
} }
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
Ok(e) => e, Ok(e) => e,

View File

@ -21,7 +21,7 @@ pgrx::extension_sql_file!("./sql/finalize.sql", finalize);
unsafe extern "C" fn _PG_init() { unsafe extern "C" fn _PG_init() {
use crate::prelude::*; use crate::prelude::*;
if unsafe { pgrx::pg_sys::IsUnderPostmaster } { if unsafe { pgrx::pg_sys::IsUnderPostmaster } {
SessionError::BadInit.friendly(); bad_init();
} }
unsafe { unsafe {
detect::initialize(); detect::initialize();

View File

@ -1,75 +1,143 @@
use service::prelude::ServiceError; use crate::ipc::{ClientRpc, ConnectionError};
use std::fmt::Display; use pgrx::error;
use thiserror::Error; use std::num::NonZeroU16;
pub trait FriendlyError: Display { pub fn bad_init() -> ! {
fn friendly(&self) -> ! { error!("\
panic!("pgvecto.rs: {}", self); pgvecto.rs: pgvecto.rs must be loaded via shared_preload_libraries.
}
}
impl FriendlyError for ServiceError {}
pub trait Friendly<T> {
fn friendly(self) -> T;
}
impl<T, E: FriendlyError> Friendly<T> for Result<T, E> {
fn friendly(self) -> T {
match self {
Ok(x) => x,
Err(e) => e.friendly(),
}
}
}
#[must_use]
#[derive(Debug, Error)]
#[rustfmt::skip]
pub enum SessionError {
#[error("\
pgvecto.rs must be loaded via shared_preload_libraries.
ADVICE: If you encounter this error for your first use of pgvecto.rs, \ ADVICE: If you encounter this error for your first use of pgvecto.rs, \
please read `https://docs.pgvecto.rs/getting-started/installation.html`. \ please read `https://docs.pgvecto.rs/getting-started/installation.html`. \
You should edit `shared_preload_libraries` in `postgresql.conf` to include `vectors.so`, \ You should edit `shared_preload_libraries` in `postgresql.conf` to include `vectors.so`, \
or simply run the command `psql -U postgres -c 'ALTER SYSTEM SET shared_preload_libraries = \"vectors.so\"'`.\ or simply run the command `psql -U postgres -c 'ALTER SYSTEM SET shared_preload_libraries = \"vectors.so\"'`.");
")] }
BadInit,
#[error("\ pub fn check_type_dimensions(dimensions: Option<NonZeroU16>) -> NonZeroU16 {
Bad literal. match dimensions {
INFORMATION: hint = {hint}\ None => {
")] error!(
BadLiteral { "\
hint: String, pgvecto.rs: Modifier of the type is invalid.
}, ADVICE: Check if modifier of the type is an integer among 1 and 65535."
#[error("\ )
Dimensions type modifier of a vector column is needed for building the index.\ }
")] Some(x) => x,
BadOption1,
#[error("\
Indexes can only be built on built-in distance functions.
ADVICE: If you want pgvecto.rs to support more distance functions, \
visit `https://github.com/tensorchord/pgvecto.rs/issues` and contribute your ideas.\
")]
BadOptions2,
#[error("\
Modifier of the type is invalid.
ADVICE: Check if modifier of the type is an integer among 1 and 65535.\
")]
BadTypeDimensions,
#[error("\
Dimensions of the vector is invalid.
ADVICE: Check if dimensions of the vector are among 1 and 65535.\
")]
BadValueDimensions,
#[error("\
Operands of the operator differs in dimensions or scalar type.
INFORMATION: left_dimensions = {left_dimensions}, right_dimensions = {right_dimensions}\
")]
Unmatched {
left_dimensions: u16,
right_dimensions: u16,
} }
} }
impl FriendlyError for SessionError {} pub fn check_value_dimensions(dimensions: usize) -> NonZeroU16 {
match u16::try_from(dimensions)
.and_then(NonZeroU16::try_from)
.ok()
{
None => {
error!(
"\
pgvecto.rs: Dimensions of the vector is invalid.
ADVICE: Check if dimensions of the vector are among 1 and 65535."
)
}
Some(x) => x,
}
}
pub fn bad_literal(hint: &str) -> ! {
error!(
"\
pgvecto.rs: Bad literal.
INFORMATION: hint = {hint}"
);
}
#[inline(always)]
pub fn check_matched_dimensions(left_dimensions: usize, right_dimensions: usize) -> usize {
if left_dimensions != right_dimensions {
error!(
"\
pgvecto.rs: Operands of the operator differs in dimensions or scalar type.
INFORMATION: left_dimensions = {left_dimensions}, right_dimensions = {right_dimensions}",
)
}
left_dimensions
}
#[inline(always)]
pub fn check_column_dimensions(dimensions: Option<NonZeroU16>) -> NonZeroU16 {
match dimensions {
None => error!(
"\
pgvecto.rs: Dimensions type modifier of a vector column is needed for building the index.",
),
Some(x) => x,
}
}
pub fn bad_opclass() -> ! {
error!(
"\
pgvecto.rs: Indexes can only be built on built-in distance functions.
ADVICE: If you want pgvecto.rs to support more distance functions, \
visit `https://github.com/tensorchord/pgvecto.rs/issues` and contribute your ideas."
);
}
pub fn bad_service_not_exist() -> ! {
error!(
"\
pgvecto.rs: The index is not existing in the background worker.
ADVICE: Drop or rebuild the index.\
"
);
}
pub fn check_connection<T>(result: Result<T, ConnectionError>) -> T {
match result {
Err(_) => error!(
"\
pgvecto.rs: Indexes can only be built on built-in distance functions.
ADVICE: If you want pgvecto.rs to support more distance functions, \
visit `https://github.com/tensorchord/pgvecto.rs/issues` and contribute your ideas."
),
Ok(x) => x,
}
}
pub fn check_client(option: Option<ClientRpc>) -> ClientRpc {
match option {
None => error!(
"\
pgvecto.rs: The extension is upgraded so all index files are outdated.
ADVICE: Delete all index files. Please read `https://docs.pgvecto.rs/admin/upgrading.html`"
),
Some(x) => x,
}
}
pub fn bad_service_upgrade() -> ! {
error!(
"\
pgvecto.rs: The extension is upgraded so this index is outdated.
ADVICE: Rebuild the index. Please read `https://docs.pgvecto.rs/admin/upgrading.html`."
)
}
pub fn bad_service_exists() -> ! {
error!(
"\
pgvecto.rs: The index is already existing in the background worker."
)
}
pub fn bad_service_invalid_index_options(reason: &str) -> ! {
error!(
"\
pgvecto.rs: The given index option is invalid.
INFORMATION: reason = {reason:?}"
)
}
pub fn bad_service_invalid_vector() -> ! {
error!(
"\
pgvecto.rs: The dimension of a vector does not matched that in a vector index column."
)
}

View File

@ -1,5 +1,5 @@
mod error; mod error;
mod sys; mod sys;
pub use error::{Friendly, FriendlyError, SessionError}; pub use error::*;
pub use sys::{FromSys, IntoSys}; pub use sys::{FromSys, IntoSys};

View File

@ -451,10 +451,10 @@ CREATE OPERATOR <=> (
-- List of functions -- List of functions
CREATE FUNCTION pgvectors_upgrade() RETURNS void CREATE FUNCTION pgvectors_upgrade() RETURNS void
IMMUTABLE STRICT PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', '_vectors_pgvectors_upgrade_wrapper'; STRICT LANGUAGE c AS 'MODULE_PATHNAME', '_vectors_pgvectors_upgrade_wrapper';
CREATE FUNCTION to_svector("dims" INT, "indices" INT[], "values" real[]) RETURNS svector CREATE FUNCTION to_svector(dims INT, indices INT[], vals real[]) RETURNS svector
IMMUTABLE STRICT PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', '_vectors_svector_from_array_wrapper'; IMMUTABLE STRICT PARALLEL SAFE LANGUAGE c AS 'MODULE_PATHNAME', '_vectors_to_svector_wrapper';
-- List of casts -- List of casts

View File

@ -1,4 +1,2 @@
pub mod cells; pub mod cells;
pub mod file_socket;
pub mod os;
pub mod parse; pub mod parse;

View File

@ -1,190 +0,0 @@
use rustix::fd::{AsFd, OwnedFd};
use rustix::mm::{MapFlags, ProtFlags};
use std::sync::atomic::AtomicU32;
#[cfg(target_os = "linux")]
pub unsafe fn futex_wait(futex: &AtomicU32, value: u32) {
const FUTEX_TIMEOUT: libc::timespec = libc::timespec {
tv_sec: 15,
tv_nsec: 0,
};
unsafe {
libc::syscall(
libc::SYS_futex,
futex.as_ptr(),
libc::FUTEX_WAIT,
value,
&FUTEX_TIMEOUT,
);
}
}
#[cfg(target_os = "linux")]
pub unsafe fn futex_wake(futex: &AtomicU32) {
unsafe {
libc::syscall(libc::SYS_futex, futex.as_ptr(), libc::FUTEX_WAKE, i32::MAX);
}
}
#[cfg(target_os = "linux")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
if detect::linux::detect_memfd() {
use rustix::fs::MemfdFlags;
Ok(rustix::fs::memfd_create(
format!(".memfd.VECTORS.{:x}", std::process::id()),
MemfdFlags::empty(),
)?)
} else {
use rustix::fs::Mode;
use rustix::fs::OFlags;
// POSIX fcntl locking do not support shmem, so we use a regular file here.
// reference: https://man7.org/linux/man-pages/man3/fcntl.3p.html
let name = format!(
".shm.VECTORS.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}
}
#[cfg(target_os = "linux")]
pub unsafe fn mmap_populate(len: usize, fd: impl AsFd) -> std::io::Result<*mut libc::c_void> {
use std::ptr::null_mut;
unsafe {
Ok(rustix::mm::mmap(
null_mut(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED | MapFlags::POPULATE,
fd,
0,
)?)
}
}
#[cfg(target_os = "macos")]
pub unsafe fn futex_wait(futex: &AtomicU32, value: u32) {
const ULOCK_TIMEOUT: u32 = 15_000_000;
unsafe {
ulock_sys::__ulock_wait(
ulock_sys::darwin19::UL_COMPARE_AND_WAIT_SHARED,
futex.as_ptr().cast(),
value as _,
ULOCK_TIMEOUT,
);
}
}
#[cfg(target_os = "macos")]
pub unsafe fn futex_wake(futex: &AtomicU32) {
unsafe {
ulock_sys::__ulock_wake(
ulock_sys::darwin19::UL_COMPARE_AND_WAIT_SHARED,
futex.as_ptr().cast(),
0,
);
}
}
#[cfg(target_os = "macos")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
use rustix::fs::Mode;
use rustix::fs::OFlags;
// POSIX fcntl locking do not support shmem, so we use a regular file here.
// reference: https://man7.org/linux/man-pages/man3/fcntl.3p.html
let name = format!(
".shm.VECTORS.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}
#[cfg(target_os = "macos")]
pub unsafe fn mmap_populate(len: usize, fd: impl AsFd) -> std::io::Result<*mut libc::c_void> {
use std::ptr::null_mut;
unsafe {
Ok(rustix::mm::mmap(
null_mut(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED,
fd,
0,
)?)
}
}
#[cfg(target_os = "freebsd")]
pub unsafe fn futex_wait(futex: &AtomicU32, value: u32) {
let ptr: *const AtomicU32 = futex;
unsafe {
libc::_umtx_op(
ptr as *mut libc::c_void,
libc::UMTX_OP_WAIT_UINT,
value as libc::c_ulong,
core::ptr::null_mut(),
core::ptr::null_mut(),
);
};
}
#[cfg(target_os = "freebsd")]
pub unsafe fn futex_wake(futex: &AtomicU32) {
let ptr: *const AtomicU32 = futex;
unsafe {
libc::_umtx_op(
ptr as *mut libc::c_void,
libc::UMTX_OP_WAKE,
i32::MAX as libc::c_ulong,
core::ptr::null_mut(),
core::ptr::null_mut(),
);
};
}
#[cfg(target_os = "freebsd")]
pub fn memfd_create() -> std::io::Result<OwnedFd> {
use rustix::fs::Mode;
use rustix::fs::OFlags;
let name = format!(
".shm.VECTORS.{:x}.{:x}",
std::process::id(),
rand::random::<u32>()
);
let fd = rustix::fs::open(
&name,
OFlags::RDWR | OFlags::CREATE | OFlags::EXCL,
Mode::RUSR | Mode::WUSR,
)?;
rustix::fs::unlink(&name)?;
Ok(fd)
}
#[cfg(target_os = "freebsd")]
pub unsafe fn mmap_populate(len: usize, fd: impl AsFd) -> std::io::Result<*mut libc::c_void> {
use std::ptr::null_mut;
unsafe {
Ok(rustix::mm::mmap(
null_mut(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED,
fd,
0,
)?)
}
}

View File

@ -7,8 +7,8 @@ CREATE TABLE t (val vector(3));
statement ok statement ok
CREATE INDEX ON t USING vectors (val vector_l2_ops); CREATE INDEX ON t USING vectors (val vector_l2_ops);
statement error The given vector is invalid for input. statement error The dimension of a vector does not matched that in a vector index column.
INSERT INTO t (val) VALUES ('[0, 1, 2, 3]'); INSERT INTO t (val) VALUES ('[0, 1, 2, 3]');
statement error The given vector is invalid for input. statement error The dimension of a vector does not matched that in a vector index column.
SELECT * FROM t ORDER BY val <-> '[0, 1, 2, 3]'; SELECT * FROM t ORDER BY val <-> '[0, 1, 2, 3]';