├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── cd.yml │ └── test.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── gradle.xml ├── misc.xml └── vcs.xml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── buildSrc └── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── resources ├── java-logo-small.svg └── java-logo.svg ├── settings.gradle └── src ├── main └── java │ └── io │ └── qdrant │ └── client │ ├── ApiKeyCredentials.java │ ├── ConditionFactory.java │ ├── ExpressionFactory.java │ ├── PointIdFactory.java │ ├── QdrantClient.java │ ├── QdrantException.java │ ├── QdrantGrpcClient.java │ ├── QueryFactory.java │ ├── ShardKeyFactory.java │ ├── ShardKeySelectorFactory.java │ ├── StartFromFactory.java │ ├── TargetVectorFactory.java │ ├── ValueFactory.java │ ├── VectorFactory.java │ ├── VectorInputFactory.java │ ├── VectorsFactory.java │ ├── VersionsCompatibilityChecker.java │ ├── WithPayloadSelectorFactory.java │ ├── WithVectorsSelectorFactory.java │ └── package-info.java └── test └── java └── io └── qdrant └── client ├── ApiKeyTest.java ├── CollectionsTest.java ├── DockerImage.java ├── HealthTest.java ├── PointsTest.java ├── QdrantClientTest.java ├── QdrantGrpcClientTest.java ├── SnapshotsTest.java ├── VersionsCompatibilityCheckerTest.java └── package-info.java /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | indent_size = 4 7 | 8 | [*.{md,markdown,json,js,xml,yml}] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.{md,markdown}] 13 | trim_trailing_whitespace = false 14 | max_line_length = 80 15 | 16 | [*.{sh,bat,ps1}] 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto eol=lf 3 | 4 | # Set default behavior for command prompt diff. 5 | # This gives output on command line taking java language constructs into consideration (e.g showing class name) 6 | *.java text diff=java 7 | 8 | # Set windows specific files explicitly to crlf line ending 9 | *.cmd eol=crlf 10 | *.bat eol=crlf 11 | *.ps1 eol=crlf -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | permissions: 10 | contents: write 11 | checks: write 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up JDK 17 23 | uses: actions/setup-java@v3 24 | with: 25 | java-version: '17' 26 | distribution: 'temurin' 27 | 28 | - name: Build 29 | uses: gradle/gradle-build-action@v2 30 | with: 31 | gradle-version: 8.5 32 | arguments: build 33 | 34 | - name: Test 35 | uses: gradle/gradle-build-action@v2 36 | with: 37 | gradle-version: 8.5 38 | arguments: test --info 39 | 40 | - name: Test Results 41 | uses: mikepenz/action-junit-report@v4 42 | if: always() 43 | with: 44 | fail_on_failure: true 45 | require_tests: true 46 | report_paths: '**/build/test-results/test/TEST-*.xml' 47 | 48 | - name: Upload Jars 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: QdrantJava 52 | path: build/libs 53 | 54 | publish: 55 | runs-on: ubuntu-latest 56 | needs: build 57 | env: 58 | ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEYID }} 59 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} 60 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} 61 | ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEUSERNAME }} 62 | ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPEPASSWORD }} 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | 67 | - name: Set up JDK 17 68 | uses: actions/setup-java@v3 69 | with: 70 | java-version: '17' 71 | distribution: 'temurin' 72 | 73 | - name: Publish package 74 | uses: gradle/gradle-build-action@v2 75 | with: 76 | gradle-version: 8.5 77 | arguments: publishToSonatype closeAndReleaseSonatypeStagingRepository 78 | 79 | - name: Deploy javadoc to Github Pages 80 | uses: dev-vince/actions-publish-javadoc@v1.0.1 81 | with: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | java-version: "17" 84 | java-distribution: "adopt" # The distributor of the target JDK. See https://github.com/actions/setup-java for more information. 85 | project: gradle # The project type. 86 | branch: "gh-pages" # The branch for the javadoc contents. 87 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | - reopened 10 | 11 | permissions: 12 | contents: write 13 | checks: write 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '17' 28 | distribution: 'temurin' 29 | 30 | - name: Build 31 | uses: gradle/gradle-build-action@v2 32 | with: 33 | gradle-version: 8.5 34 | arguments: build --info 35 | 36 | - name: Test 37 | uses: gradle/gradle-build-action@v2 38 | with: 39 | gradle-version: 8.5 40 | arguments: test --info 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | .idea/uiDesigner.xml 13 | .idea/codeStyles/codeStyleConfig.xml 14 | .idea/codeStyles/Project.xml 15 | .idea/inspectionProfiles/Project_Default.xml 16 | *.iws 17 | *.iml 18 | *.ipr 19 | out/ 20 | !**/src/main/**/out/ 21 | !**/src/test/**/out/ 22 | 23 | ### Eclipse ### 24 | .apt_generated 25 | .classpath 26 | .factorypath 27 | .project 28 | .settings 29 | .springBeans 30 | .sts4-cache 31 | bin/ 32 | !**/src/main/**/bin/ 33 | !**/src/test/**/bin/ 34 | 35 | ### NetBeans ### 36 | /nbproject/private/ 37 | /nbbuild/ 38 | /dist/ 39 | /nbdist/ 40 | /.nb-gradle/ 41 | 42 | ### VS Code ### 43 | .vscode/ 44 | 45 | ### Mac OS ### 46 | .DS_Store 47 | 48 | ### Project specific ### 49 | protos 50 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | client -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 33 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Java client for Qdrant 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | 10 | ## We Develop with GitHub 11 | 12 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | We Use [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow), so all code changes 15 | happen through Pull Requests. Pull requests are the best way to propose changes to the codebase. 16 | 17 | It's usually best to open an issue first to discuss a feature or bug before opening a pull request. 18 | Doing so can save time and help further ascertain the crux of an issue. 19 | 20 | 1. See if there is an existing issue 21 | 2. Fork the repo and create your branch from `master`. 22 | 3. If you've added code that should be tested, add tests. 23 | 4. Ensure the test suite passes. 24 | 5. Issue that pull request! 25 | 26 | ### Any contributions you make will be under the Apache License 2.0 27 | 28 | In short, when you submit code changes, your submissions are understood to be under the 29 | same [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/) that covers the project. 30 | Feel free to contact the maintainers if that's a concern. 31 | 32 | ## Report bugs using GitHub's [issues](https://github.com/qdrant/java-client/issues) 33 | 34 | We use GitHub issues to track public bugs. Report a bug by 35 | [opening a new issue](https://github.com/qdrant/java-client/issues/new); it's that easy! 36 | 37 | **Great Bug Reports** tend to have: 38 | 39 | - A quick summary and/or background 40 | - Steps to reproduce 41 | - Be specific! 42 | - Give sample code if you can. 43 | - What you expected would happen 44 | - What actually happens 45 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 46 | 47 | ## Coding Styleguide 48 | 49 | If you are modifying code, make sure it has no warnings when building. 50 | 51 | ## License 52 | 53 | By contributing, you agree that your contributions will be licensed under its Apache License 2.0. 54 | 55 | ## Preparing for a New Release 56 | 57 | The client uses generated stubs from upstream Qdrant proto definitions, which are downloaded from [qdrant/qdrant](https://github.com/qdrant/qdrant/tree/master/lib/api/src/grpc/proto). 58 | 59 | The generated files do not form part of the checked-in source code. Instead, they are generated 60 | and emitted into the `build/generated/source directory`, and included in compilation. 61 | 62 | ### Pre-requisites 63 | 64 | Ensure the following are installed and available in the `PATH`. 65 | 66 | - [Java 17](https://www.azul.com/downloads/?version=java-17-lts&package=jdk#zulu) 67 | - [Gradle](https://gradle.org/install/#with-a-package-manager). 68 | - [Docker](https://docs.docker.com/engine/install/) for tests. 69 | 70 | If you're using IntelliJ IDEA, see [this section](#intellij-idea) for steps to handle an IntelliSense issue. 71 | 72 | ### Steps 73 | 74 | 1. Update the values in [gradle.properties](https://github.com/qdrant/java-client/blob/master/gradle.properties) as follows: 75 | 76 | - `packageVersion` - Bump it to the next minor version to be released. 77 | - `qdrantVersion` - Set it to `dev` to use the `dev` Docker image for testing. 78 | - `qdrantProtosVersion` - Set it to `dev` to use the `dev` branch for fetching the proto files. 79 | 80 | 2. Download and generate the latest client stubs by running the following command from the project root: 81 | 82 | For Windows 83 | 84 | ```bash 85 | .\gradlew.bat build 86 | ``` 87 | 88 | For OSX/Linux 89 | 90 | ```bash 91 | ./gradlew build 92 | ``` 93 | 94 | This will 95 | 96 | - Pull down all the dependencies for the build process and the project. 97 | - Run the default build task. 98 | - Run the integration tests. Ensure Docker running. 99 | 100 | If a Qdrant image with static tags like `dev` or `latest` already exists on your system, the tests will use it. You can remove these images before running the tests to fetch the most up-to-date versions. 101 | 102 | 3. Implement new Qdrant methods in [`QdrantClient.java`](https://github.com/qdrant/java-client/blob/master/src/main/java/io/qdrant/client/QdrantClient.java) with associated tests in [src/test](https://github.com/qdrant/java-client/tree/master/src/test/java/io/qdrant/client). 103 | 104 | Since the API reference is published at , the docstrings have to be appropriate. 105 | 106 | 4. If there are any new complex/frequently used properties in the proto definitions, add factory classes in [`src/main`](https://github.com/qdrant/java-client/tree/master/src/main/java/io/qdrant/client) following the existing patterns. 107 | 108 | 5. Submit your pull request and get those approvals. 109 | 110 | ### Releasing a New Version 111 | 112 | Once the new Qdrant version is live: 113 | 114 | 1. Update the values in [gradle.properties](https://github.com/qdrant/java-client/blob/master/gradle.properties) as follows and build as mentioned above: 115 | 116 | - `qdrantVersion` - Set it to the released Docker image version for testing. 117 | - `qdrantProtosVersion` - Set it to the released version of the Qdrant source for fetching the proto files. 118 | 119 | 2. Merge the pull request. 120 | 121 | 3. Publish a new release at . The CI will then publish the library to [mvnrepository.com/artifact/io.qdrant/client](https://mvnrepository.com/artifact/io.qdrant/client) and the docs to . 122 | 123 | ### IntelliJ IDEA 124 | 125 | If you're using [IntelliJ IDEA](https://www.jetbrains.com/idea/), IntelliSense may fail to work correctly due to large source files generated from proto-definitions. 126 | 127 | To resolve this, you can increase the file size limit by configuring IntelliJ IDEA as follows: 128 | 129 | 1. In the top menu, navigate to `Help` -> `Edit Custom Properties`. 130 | 131 | 2. Set the `idea.max.intellisense.filesize` properly to a higher value. 132 | 133 | ![Screenshot 2024-10-02 at 11 13 06 PM](https://github.com/user-attachments/assets/7830d22c-4b63-4a03-8a8b-fbdd7acf3454) 134 | 135 | 3. After saving the changes, restart the IDE to apply the new file size limit. 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Qdrant 3 |   4 | Java 5 | 6 |

7 | 8 |

9 | Java library for the Qdrant vector search engine. 10 |

11 | 12 |

13 | Javadoc 14 | Tests 15 | Apache 2.0 License 16 | Discord 17 | Roadmap 2024 18 |

19 | 20 | # Qdrant Java Client 21 | 22 | Java client library with handy utility methods and overloads for interfacing with [Qdrant](https://qdrant.tech/). 23 | 24 | ## 📥 Installation 25 | 26 | > [!IMPORTANT] 27 | > Requires Java 8 or above. 28 | 29 | To install the library, add the following lines to your build config file. 30 | 31 | #### Maven 32 | 33 | ```xml 34 | 35 | io.qdrant 36 | client 37 | 1.14.0 38 | 39 | ``` 40 | 41 | #### SBT 42 | 43 | ```sbt 44 | libraryDependencies += "io.qdrant" % "client" % "1.14.0" 45 | ``` 46 | 47 | #### Gradle 48 | 49 | ```gradle 50 | implementation 'io.qdrant:client:1.14.0' 51 | ``` 52 | 53 | > [!NOTE] 54 | > Please make sure to include all necessary dependencies listed [here](https://central.sonatype.com/artifact/io.qdrant/client/dependencies) in your project. 55 | 56 | ## 📖 Documentation 57 | 58 | - Usage examples are available throughout the [Qdrant documentation](https://qdrant.tech/documentation/quick-start/) and [API Reference](https://api.qdrant.tech/). 59 | - [JavaDoc Reference](https://qdrant.github.io/java-client/) 60 | 61 | ## 🔌 Getting started 62 | 63 | ### Creating a client 64 | 65 | A client can be instantiated with 66 | 67 | ```java 68 | QdrantClient client = 69 | new QdrantClient(QdrantGrpcClient.newBuilder("localhost").build()); 70 | ``` 71 | 72 | which creates a client that will connect to Qdrant on . 73 | 74 | Internally, the high-level client uses a low-level gRPC client to interact with 75 | Qdrant. Additional constructor overloads provide more control over how the gRPC 76 | client is configured. The following example configures a client to use TLS, 77 | validating the certificate using the root CA to verify the server's identity 78 | instead of the system's default, and also configures API key authentication: 79 | 80 | ```java 81 | ManagedChannel channel = Grpc.newChannelBuilder( 82 | "localhost:6334", 83 | TlsChannelCredentials.newBuilder() 84 | .trustManager(new File("ssl/ca.crt")) 85 | .build()) 86 | .build(); 87 | 88 | QdrantClient client = new QdrantClient( 89 | QdrantGrpcClient.newBuilder(channel) 90 | .withApiKey("") 91 | .build()); 92 | ``` 93 | 94 | The client implements [`AutoCloseable`](https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html), 95 | though a client will typically be created once and used for the lifetime of the 96 | application. When a client is constructed by passing a `ManagedChannel`, the 97 | client does not shut down the channel on close by default. The client can be 98 | configured to shut down the channel on closing with 99 | 100 | ```java 101 | ManagedChannel channel = Grpc.newChannelBuilder( 102 | "localhost:6334", 103 | TlsChannelCredentials.create()) 104 | .build(); 105 | 106 | QdrantClient client = new QdrantClient( 107 | QdrantGrpcClient.newBuilder(channel, true) 108 | .withApiKey("") 109 | .build()); 110 | ``` 111 | 112 | All client methods return `ListenableFuture`. 113 | 114 | ### Working with collections 115 | 116 | Once a client has been created, create a new collection 117 | 118 | ```java 119 | client.createCollectionAsync("{collection_name}", 120 | VectorParams.newBuilder() 121 | .setDistance(Distance.Cosine) 122 | .setSize(4) 123 | .build()) 124 | .get(); 125 | ``` 126 | 127 | Insert vectors into a collection 128 | 129 | ```java 130 | // import static convenience methods 131 | import static io.qdrant.client.PointIdFactory.id; 132 | import static io.qdrant.client.ValueFactory.value; 133 | import static io.qdrant.client.VectorsFactory.vectors; 134 | 135 | List points = 136 | List.of( 137 | PointStruct.newBuilder() 138 | .setId(id(1)) 139 | .setVectors(vectors(0.32f, 0.52f, 0.21f, 0.52f)) 140 | .putAllPayload( 141 | Map.of( 142 | "color", value("red"), 143 | "rand_number", value(32))) 144 | .build(), 145 | PointStruct.newBuilder() 146 | .setId(id(2)) 147 | .setVectors(vectors(0.42f, 0.52f, 0.67f, 0.632f)) 148 | .putAllPayload( 149 | Map.of( 150 | "color", value("black"), 151 | "rand_number", value(53), 152 | "extra_field", value(true))) 153 | .build()); 154 | 155 | UpdateResult updateResult = client.upsertAsync("{collection_name}", points).get(); 156 | ``` 157 | 158 | Search for similar vectors 159 | 160 | ```java 161 | List points = 162 | client 163 | .searchAsync( 164 | SearchPoints.newBuilder() 165 | .setCollectionName("{collection_name}") 166 | .addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f)) 167 | .setLimit(5) 168 | .build()) 169 | .get(); 170 | ``` 171 | 172 | Search for similar vectors with filtering condition 173 | 174 | ```java 175 | // import static convenience methods 176 | import static io.qdrant.client.ConditionFactory.range; 177 | 178 | List points = client.searchAsync(SearchPoints.newBuilder() 179 | .setCollectionName("{collection_name}") 180 | .addAllVector(List.of(0.6235f, 0.123f, 0.532f, 0.123f)) 181 | .setFilter(Filter.newBuilder() 182 | .addMust(range("rand_number", Range.newBuilder().setGte(3).build())) 183 | .build()) 184 | .setLimit(5) 185 | .build() 186 | ).get(); 187 | ``` 188 | 189 | ## ⚖️ LICENSE 190 | 191 | [Apache 2.0](https://github.com/qdrant/java-client/blob/master/LICENSE) 192 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import net.ltgt.gradle.errorprone.CheckSeverity 2 | import org.ajoberstar.grgit.Grgit 3 | import org.apache.commons.compress.archivers.tar.TarArchiveEntry 4 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream 5 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream 6 | import org.apache.http.client.methods.HttpGet 7 | import org.apache.http.impl.client.HttpClients 8 | 9 | import java.nio.file.Files 10 | import java.nio.file.Paths 11 | import java.nio.file.StandardOpenOption 12 | import java.time.ZoneOffset 13 | import java.util.regex.Pattern 14 | 15 | plugins { 16 | id 'java-library' 17 | id 'idea' 18 | id 'signing' 19 | id 'maven-publish' 20 | 21 | id 'com.google.protobuf' version '0.9.4' 22 | id 'net.ltgt.errorprone' version '3.1.0' 23 | id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' 24 | id 'com.diffplug.spotless' version '6.22.0' 25 | } 26 | 27 | group = 'io.qdrant' 28 | version = packageVersion 29 | description = 'Official Java client for Qdrant vector database' 30 | 31 | repositories { 32 | // google mirror for maven 33 | maven { 34 | url 'https://maven-central.storage-download.googleapis.com/maven2/' 35 | } 36 | mavenCentral() 37 | mavenLocal() 38 | } 39 | 40 | java { 41 | sourceCompatibility = JavaVersion.VERSION_1_8 42 | targetCompatibility = JavaVersion.VERSION_1_8 43 | 44 | withJavadocJar() 45 | withSourcesJar() 46 | } 47 | 48 | tasks.withType(JavaCompile).configureEach { 49 | // exclude generated code from error prone checks 50 | options.errorprone.excludedPaths.set(".*/build/generated/.*") 51 | options.errorprone { 52 | //noinspection GroovyAssignabilityCheck 53 | check("NullAway", CheckSeverity.ERROR) 54 | option("NullAway:AnnotatedPackages", "com.uber") 55 | } 56 | } 57 | 58 | javadoc { 59 | // exclude code generated from protos 60 | exclude 'io/qdrant/client/grpc/**' 61 | exclude 'grpc/**' 62 | } 63 | 64 | sourcesJar { 65 | // exclude generated duplicate of com/qdrant/client/grpc/CollectionsGrpc.java 66 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 67 | } 68 | 69 | jar { 70 | doFirst { 71 | def git = Grgit.open(Map.of('currentDir', rootProject.rootDir)) 72 | // add qdrant version from which client is generated. 73 | jar.manifest.attributes['X-Qdrant-Version'] = qdrantProtosVersion 74 | // add git revision and commit time to jar manifest 75 | jar.manifest.attributes['X-Git-Revision'] = git.head().id 76 | jar.manifest.attributes['X-Git-Commit-Time'] = git.head().dateTime.withZoneSameLocal(ZoneOffset.UTC) 77 | jar.manifest.attributes['Implementation-Version'] = packageVersion 78 | git.close() 79 | } 80 | } 81 | 82 | def grpcVersion = '1.65.1' 83 | def protocVersion = '3.25.4' 84 | def slf4jVersion = '2.0.14' 85 | def testcontainersVersion = '1.20.1' 86 | def jUnitVersion = '5.10.2' 87 | 88 | dependencies { 89 | errorprone "com.uber.nullaway:nullaway:0.10.18" 90 | 91 | implementation "io.grpc:grpc-protobuf:${grpcVersion}" 92 | implementation "io.grpc:grpc-stub:${grpcVersion}" 93 | implementation "org.slf4j:slf4j-api:${slf4jVersion}" 94 | 95 | compileOnly "org.apache.tomcat:annotations-api:6.0.53" 96 | 97 | errorprone "com.google.errorprone:error_prone_core:2.29.2" 98 | 99 | runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" 100 | 101 | testImplementation "io.grpc:grpc-testing:${grpcVersion}" 102 | testImplementation "org.junit.jupiter:junit-jupiter-api:${jUnitVersion}" 103 | testImplementation "org.junit.jupiter:junit-jupiter-params:${jUnitVersion}" 104 | testImplementation "org.mockito:mockito-core:3.4.0" 105 | testImplementation "org.slf4j:slf4j-nop:${slf4jVersion}" 106 | testImplementation "org.testcontainers:qdrant:${testcontainersVersion}" 107 | testImplementation "org.testcontainers:junit-jupiter:${testcontainersVersion}" 108 | 109 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${jUnitVersion}" 110 | } 111 | 112 | tasks.register('downloadProtos') { 113 | // gradle detects changes to this output dir since last run to determine whether to run this task 114 | outputs.dir(new File(rootProject.rootDir, "protos/${qdrantProtosVersion}")) 115 | 116 | doLast { 117 | def outputDirectory = outputs.files.singleFile.toPath().toString() 118 | def protoFileRegex = Pattern.compile(".*?lib/api/src/grpc/proto/.*?.proto") 119 | try (def httpClient = HttpClients.createDefault()) { 120 | def url = "https://api.github.com/repos/qdrant/qdrant/tarball/${qdrantProtosVersion}" 121 | logger.debug("downloading protos from {}", url) 122 | def response = httpClient.execute(new HttpGet(url)) 123 | try (InputStream tarballStream = response.getEntity().getContent()) { 124 | try (TarArchiveInputStream tarInput = new TarArchiveInputStream(new GzipCompressorInputStream(tarballStream))) { 125 | TarArchiveEntry entry 126 | while ((entry = tarInput.getNextTarEntry()) != null) { 127 | if (!entry.isDirectory() && protoFileRegex.matcher(entry.getName()).matches()) { 128 | def lines = new ArrayList() 129 | def lineNum = -1 130 | def seenJavaPackage = false 131 | def br = new BufferedReader(new InputStreamReader(tarInput)) 132 | String line 133 | while ((line = br.readLine()) != null) { 134 | lines.add(line) 135 | if (line == "package qdrant;") { 136 | lineNum = lines.size() 137 | } else if (line.startsWith("option java_package")) { 138 | seenJavaPackage = true 139 | } 140 | } 141 | // patch in java package to qdrant protos 142 | if (!seenJavaPackage && lineNum != -1) { 143 | lines.add(lineNum, "option java_package = \"io.qdrant.client.grpc\";") 144 | } 145 | 146 | def fileName = Paths.get(entry.getName()).getFileName().toString() 147 | def dest = java.nio.file.Path.of(outputDirectory, fileName) 148 | logger.debug("writing {} to {}", fileName, dest) 149 | Files.write(dest, lines, StandardOpenOption.CREATE) 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | processResources { 159 | dependsOn downloadProtos 160 | } 161 | 162 | extractIncludeProto { 163 | dependsOn downloadProtos 164 | } 165 | 166 | protobuf { 167 | protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } 168 | plugins { 169 | grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } 170 | } 171 | generateProtoTasks { 172 | all()*.plugins { grpc {} } 173 | } 174 | } 175 | 176 | // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. 177 | sourceSets { 178 | main { 179 | proto { 180 | // include protos from outside the sourceSets 181 | //noinspection GroovyAssignabilityCheck 182 | srcDir "protos/${qdrantProtosVersion}" 183 | } 184 | java { 185 | srcDirs 'build/generated/source/proto/main/grpc' 186 | srcDirs 'build/generated/source/proto/main/java' 187 | } 188 | } 189 | } 190 | 191 | test { 192 | useJUnitPlatform() 193 | 194 | // Set system property to use as docker image version for integration tests 195 | systemProperty 'qdrantVersion', qdrantVersion 196 | } 197 | 198 | def organization = 'qdrant' 199 | def repository = 'java-client' 200 | 201 | publishing { 202 | publications { 203 | mavenJava(MavenPublication) { 204 | from components.java 205 | pom { 206 | name = "Qdrant Java Client" 207 | description = "${project.description}" 208 | url = "https://github.com/${organization}/${repository}" 209 | licenses { 210 | license { 211 | name = 'The Apache License, Version 2.0' 212 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 213 | distribution = 'repo' 214 | } 215 | } 216 | developers { 217 | developer { 218 | id = 'qdrant' 219 | name = 'Qdrant and Contributors' 220 | email = 'info@qdrant.com' 221 | } 222 | } 223 | scm { 224 | connection = "scm:git:git://github.com/${organization}/${repository}.git" 225 | developerConnection = "scm:git:ssh://github.com/${organization}/${repository}.git" 226 | url = "https://github.com/${organization}/${repository}" 227 | } 228 | } 229 | } 230 | } 231 | repositories { 232 | mavenLocal() 233 | } 234 | } 235 | 236 | nexusPublishing { 237 | repositories { 238 | sonatype { 239 | nexusUrl = uri("https://s01.oss.sonatype.org/service/local/") 240 | snapshotRepositoryUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") 241 | } 242 | } 243 | } 244 | 245 | signing { 246 | def signingKeyId = findProperty("signingKeyId") 247 | def signingKey = findProperty("signingKey") 248 | def signingPassword = findProperty("signingPassword") 249 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) 250 | sign publishing.publications.mavenJava 251 | } 252 | 253 | spotless { 254 | java { 255 | target 'src/*/java/**/*.java' 256 | importOrder() 257 | removeUnusedImports() 258 | cleanthat() 259 | googleJavaFormat() 260 | formatAnnotations() 261 | } 262 | } 263 | 264 | compileJava.dependsOn 'spotlessApply' 265 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.ajoberstar.grgit:grgit-gradle:5.0.0' 3 | implementation 'org.apache.httpcomponents:httpclient:4.5.14' 4 | implementation 'org.apache.commons:commons-compress:1.23.0' 5 | } 6 | 7 | repositories { 8 | mavenCentral() 9 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # The version of qdrant to use to download protos 2 | qdrantProtosVersion=v1.14.0 3 | 4 | # The version of qdrant docker image to run integration tests against 5 | qdrantVersion=v1.14.0 6 | 7 | # The version of the client to generate 8 | packageVersion=1.14.0 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qdrant/java-client/38fd5de62121c8e8b0f1bcb6b89adb39542abbc1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /resources/java-logo-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 56 | 59 | 61 | 62 | 64 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /resources/java-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'client' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ApiKeyCredentials.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.grpc.CallCredentials; 4 | import io.grpc.Metadata; 5 | import io.grpc.Status; 6 | import java.util.concurrent.Executor; 7 | 8 | /** API key authentication credentials */ 9 | public class ApiKeyCredentials extends CallCredentials { 10 | private final String apiKey; 11 | 12 | /** 13 | * Instantiates a new instance of {@link ApiKeyCredentials} 14 | * 15 | * @param apiKey The API key to use for authentication 16 | */ 17 | public ApiKeyCredentials(String apiKey) { 18 | this.apiKey = apiKey; 19 | } 20 | 21 | @Override 22 | public void applyRequestMetadata( 23 | RequestInfo requestInfo, Executor appExecutor, MetadataApplier applier) { 24 | appExecutor.execute( 25 | () -> { 26 | try { 27 | Metadata headers = new Metadata(); 28 | headers.put(Metadata.Key.of("api-key", Metadata.ASCII_STRING_MARSHALLER), apiKey); 29 | applier.apply(headers); 30 | } catch (Throwable e) { 31 | applier.fail(Status.UNAUTHENTICATED.withCause(e)); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ConditionFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points.Condition; 4 | import io.qdrant.client.grpc.Points.DatetimeRange; 5 | import io.qdrant.client.grpc.Points.FieldCondition; 6 | import io.qdrant.client.grpc.Points.Filter; 7 | import io.qdrant.client.grpc.Points.GeoBoundingBox; 8 | import io.qdrant.client.grpc.Points.GeoLineString; 9 | import io.qdrant.client.grpc.Points.GeoPoint; 10 | import io.qdrant.client.grpc.Points.GeoPolygon; 11 | import io.qdrant.client.grpc.Points.GeoRadius; 12 | import io.qdrant.client.grpc.Points.HasIdCondition; 13 | import io.qdrant.client.grpc.Points.HasVectorCondition; 14 | import io.qdrant.client.grpc.Points.IsEmptyCondition; 15 | import io.qdrant.client.grpc.Points.IsNullCondition; 16 | import io.qdrant.client.grpc.Points.Match; 17 | import io.qdrant.client.grpc.Points.NestedCondition; 18 | import io.qdrant.client.grpc.Points.PointId; 19 | import io.qdrant.client.grpc.Points.Range; 20 | import io.qdrant.client.grpc.Points.RepeatedIntegers; 21 | import io.qdrant.client.grpc.Points.RepeatedStrings; 22 | import io.qdrant.client.grpc.Points.ValuesCount; 23 | import java.util.List; 24 | 25 | /** Convenience methods for constructing {@link Condition} */ 26 | public final class ConditionFactory { 27 | private ConditionFactory() {} 28 | 29 | /** 30 | * Match all records with the provided id 31 | * 32 | * @param id The id to match 33 | * @return a new instance of {@link Condition} 34 | */ 35 | public static Condition hasId(PointId id) { 36 | return Condition.newBuilder() 37 | .setHasId(HasIdCondition.newBuilder().addHasId(id).build()) 38 | .build(); 39 | } 40 | 41 | /** 42 | * Match all records with the provided ids 43 | * 44 | * @param ids The ids to match 45 | * @return a new instance of {@link Condition} 46 | */ 47 | public static Condition hasId(List ids) { 48 | return Condition.newBuilder() 49 | .setHasId(HasIdCondition.newBuilder().addAllHasId(ids).build()) 50 | .build(); 51 | } 52 | 53 | /** 54 | * Match all records where the given field either does not exist, or has null or empty value. 55 | * 56 | * @param field The name of the field 57 | * @return a new instance of {@link Condition} 58 | */ 59 | public static Condition isEmpty(String field) { 60 | return Condition.newBuilder() 61 | .setIsEmpty(IsEmptyCondition.newBuilder().setKey(field).build()) 62 | .build(); 63 | } 64 | 65 | /** 66 | * Match all records where the given field is null. 67 | * 68 | * @param field The name of the field 69 | * @return a new instance of {@link Condition} 70 | */ 71 | public static Condition isNull(String field) { 72 | return Condition.newBuilder() 73 | .setIsNull(IsNullCondition.newBuilder().setKey(field).build()) 74 | .build(); 75 | } 76 | 77 | /** 78 | * Match records where the given field matches the given keyword 79 | * 80 | * @param field The name of the field 81 | * @param keyword The keyword to match 82 | * @return a new instance of {@link Condition} 83 | */ 84 | public static Condition matchKeyword(String field, String keyword) { 85 | return Condition.newBuilder() 86 | .setField( 87 | FieldCondition.newBuilder() 88 | .setKey(field) 89 | .setMatch(Match.newBuilder().setKeyword(keyword).build()) 90 | .build()) 91 | .build(); 92 | } 93 | 94 | /** 95 | * Match records where the given field matches the given text. 96 | * 97 | * @param field The name of the field 98 | * @param text The text to match 99 | * @return a new instance of {@link Condition} 100 | */ 101 | public static Condition matchText(String field, String text) { 102 | return Condition.newBuilder() 103 | .setField( 104 | FieldCondition.newBuilder() 105 | .setKey(field) 106 | .setMatch(Match.newBuilder().setText(text).build()) 107 | .build()) 108 | .build(); 109 | } 110 | 111 | /** 112 | * Match records where the given field matches the given boolean value. 113 | * 114 | * @param field The name of the field 115 | * @param value The value to match 116 | * @return a new instance of {@link Condition} 117 | */ 118 | public static Condition match(String field, boolean value) { 119 | return Condition.newBuilder() 120 | .setField( 121 | FieldCondition.newBuilder() 122 | .setKey(field) 123 | .setMatch(Match.newBuilder().setBoolean(value).build()) 124 | .build()) 125 | .build(); 126 | } 127 | 128 | /** 129 | * Match records where the given field matches the given long value. 130 | * 131 | * @param field The name of the field 132 | * @param value The value to match 133 | * @return a new instance of {@link Condition} 134 | */ 135 | public static Condition match(String field, long value) { 136 | return Condition.newBuilder() 137 | .setField( 138 | FieldCondition.newBuilder() 139 | .setKey(field) 140 | .setMatch(Match.newBuilder().setInteger(value).build()) 141 | .build()) 142 | .build(); 143 | } 144 | 145 | /** 146 | * Match records where the given field matches any of the given keywords. 147 | * 148 | * @param field The name of the field 149 | * @param keywords The keywords to match 150 | * @return a new instance of {@link Condition} 151 | */ 152 | public static Condition matchKeywords(String field, List keywords) { 153 | return Condition.newBuilder() 154 | .setField( 155 | FieldCondition.newBuilder() 156 | .setKey(field) 157 | .setMatch( 158 | Match.newBuilder() 159 | .setKeywords(RepeatedStrings.newBuilder().addAllStrings(keywords).build()) 160 | .build()) 161 | .build()) 162 | .build(); 163 | } 164 | 165 | /** 166 | * Match records where the given field matches any of the given values. 167 | * 168 | * @param field The name of the field 169 | * @param values The values to match 170 | * @return a new instance of {@link Condition} 171 | */ 172 | public static Condition matchValues(String field, List values) { 173 | return Condition.newBuilder() 174 | .setField( 175 | FieldCondition.newBuilder() 176 | .setKey(field) 177 | .setMatch( 178 | Match.newBuilder() 179 | .setIntegers(RepeatedIntegers.newBuilder().addAllIntegers(values).build()) 180 | .build()) 181 | .build()) 182 | .build(); 183 | } 184 | 185 | /** 186 | * Match records where the given field does not match any of the given keywords. 187 | * 188 | * @param field The name of the field 189 | * @param keywords The keywords not to match 190 | * @return a new instance of {@link Condition} 191 | */ 192 | public static Condition matchExceptKeywords(String field, List keywords) { 193 | return Condition.newBuilder() 194 | .setField( 195 | FieldCondition.newBuilder() 196 | .setKey(field) 197 | .setMatch( 198 | Match.newBuilder() 199 | .setExceptKeywords( 200 | RepeatedStrings.newBuilder().addAllStrings(keywords).build()) 201 | .build()) 202 | .build()) 203 | .build(); 204 | } 205 | 206 | /** 207 | * Match records where the given field does not match any of the given values. 208 | * 209 | * @param field The name of the field 210 | * @param values The values not to match 211 | * @return a new instance of {@link Condition} 212 | */ 213 | public static Condition matchExceptValues(String field, List values) { 214 | return Condition.newBuilder() 215 | .setField( 216 | FieldCondition.newBuilder() 217 | .setKey(field) 218 | .setMatch( 219 | Match.newBuilder() 220 | .setExceptIntegers( 221 | RepeatedIntegers.newBuilder().addAllIntegers(values).build()) 222 | .build()) 223 | .build()) 224 | .build(); 225 | } 226 | 227 | /** 228 | * Match records where the given nested field matches the given condition. 229 | * 230 | * @param field The name of the nested field. 231 | * @param condition The condition to match. 232 | * @return a new instance of {@link Condition} 233 | */ 234 | public static Condition nested(String field, Condition condition) { 235 | return Condition.newBuilder() 236 | .setNested( 237 | NestedCondition.newBuilder() 238 | .setKey(field) 239 | .setFilter(Filter.newBuilder().addMust(condition).build()) 240 | .build()) 241 | .build(); 242 | } 243 | 244 | /** 245 | * Match records where the given nested field matches the given filter. 246 | * 247 | * @param field The name of the nested field. 248 | * @param filter The filter to match. 249 | * @return a new instance of {@link Condition} 250 | */ 251 | public static Condition nested(String field, Filter filter) { 252 | return Condition.newBuilder() 253 | .setNested(NestedCondition.newBuilder().setKey(field).setFilter(filter)) 254 | .build(); 255 | } 256 | 257 | /** 258 | * Match records where the given field matches the given range. 259 | * 260 | * @param field The name of the nested field. 261 | * @param range The range to match. 262 | * @return a new instance of {@link Condition} 263 | */ 264 | public static Condition range(String field, Range range) { 265 | return Condition.newBuilder() 266 | .setField(FieldCondition.newBuilder().setKey(field).setRange(range).build()) 267 | .build(); 268 | } 269 | 270 | /** 271 | * Match records where the given field has values inside a circle centred at a given latitude and 272 | * longitude with a given radius. 273 | * 274 | * @param field The name of the field. 275 | * @param latitude The latitude of the center. 276 | * @param longitude The longitude of the center. 277 | * @param radius The radius in meters. 278 | * @return a new instance of {@link Condition} 279 | */ 280 | public static Condition geoRadius(String field, double latitude, double longitude, float radius) { 281 | return Condition.newBuilder() 282 | .setField( 283 | FieldCondition.newBuilder() 284 | .setKey(field) 285 | .setGeoRadius( 286 | GeoRadius.newBuilder() 287 | .setCenter(GeoPoint.newBuilder().setLat(latitude).setLon(longitude).build()) 288 | .setRadius(radius) 289 | .build()) 290 | .build()) 291 | .build(); 292 | } 293 | 294 | /** 295 | * Match records where the given field has values inside a bounding box specified by the top left 296 | * and bottom right coordinates. 297 | * 298 | * @param field The name of the field. 299 | * @param topLeftLatitude The latitude of the top left point. 300 | * @param topLeftLongitude The longitude of the top left point. 301 | * @param bottomRightLatitude The latitude of the bottom right point. 302 | * @param bottomRightLongitude The longitude of the bottom right point. 303 | * @return a new instance of {@link Condition} 304 | */ 305 | public static Condition geoBoundingBox( 306 | String field, 307 | double topLeftLatitude, 308 | double topLeftLongitude, 309 | double bottomRightLatitude, 310 | double bottomRightLongitude) { 311 | return Condition.newBuilder() 312 | .setField( 313 | FieldCondition.newBuilder() 314 | .setKey(field) 315 | .setGeoBoundingBox( 316 | GeoBoundingBox.newBuilder() 317 | .setTopLeft( 318 | GeoPoint.newBuilder() 319 | .setLat(topLeftLatitude) 320 | .setLon(topLeftLongitude) 321 | .build()) 322 | .setBottomRight( 323 | GeoPoint.newBuilder() 324 | .setLat(bottomRightLatitude) 325 | .setLon(bottomRightLongitude) 326 | .build()) 327 | .build()) 328 | .build()) 329 | .build(); 330 | } 331 | 332 | /** 333 | * Matches records where the given field has values inside the provided polygon. A polygon always 334 | * has an exterior ring and may optionally have interior rings, which represent independent areas 335 | * or holes. When defining a ring, you must pick either a clockwise or counterclockwise ordering 336 | * for your points. The first and last point of the polygon must be the same. 337 | * 338 | * @param field The name of the field. 339 | * @param exterior The exterior ring of the polygon. 340 | * @param interiors The interior rings of the polygon. 341 | * @return a new instance of {@link Condition} 342 | */ 343 | public static Condition geoPolygon( 344 | String field, GeoLineString exterior, List interiors) { 345 | GeoPolygon.Builder geoPolygonBuilder = GeoPolygon.newBuilder().setExterior(exterior); 346 | 347 | if (!interiors.isEmpty()) { 348 | geoPolygonBuilder.addAllInteriors(interiors); 349 | } 350 | 351 | return Condition.newBuilder() 352 | .setField( 353 | FieldCondition.newBuilder() 354 | .setKey(field) 355 | .setGeoPolygon(geoPolygonBuilder.build()) 356 | .build()) 357 | .build(); 358 | } 359 | 360 | /** 361 | * Matches records where the given field has a count of values within the specified count range 362 | * 363 | * @param field The name of the field. 364 | * @param valuesCount The count range to match. 365 | * @return a new instance of {@link Condition} 366 | */ 367 | public static Condition valuesCount(String field, ValuesCount valuesCount) { 368 | return Condition.newBuilder() 369 | .setField(FieldCondition.newBuilder().setKey(field).setValuesCount(valuesCount).build()) 370 | .build(); 371 | } 372 | 373 | /** 374 | * Nests a filter 375 | * 376 | * @param filter The filter to nest. 377 | * @return a new instance of {@link Condition} 378 | */ 379 | public static Condition filter(Filter filter) { 380 | return Condition.newBuilder().setFilter(filter).build(); 381 | } 382 | 383 | /** 384 | * Matches records where the given field has a datetime value within the specified range 385 | * 386 | * @param field The name of the field. 387 | * @param datetimeRange The datetime range to match. 388 | * @return a new instance of {@link Condition} 389 | */ 390 | public static Condition datetimeRange(String field, DatetimeRange datetimeRange) { 391 | return Condition.newBuilder() 392 | .setField(FieldCondition.newBuilder().setKey(field).setDatetimeRange(datetimeRange).build()) 393 | .build(); 394 | } 395 | 396 | /** 397 | * Matches records where a value for the given vector is present. 398 | * 399 | * @param vector The name of the vector. 400 | * @return a new instance of {@link Condition} 401 | */ 402 | public static Condition hasVector(String vector) { 403 | return Condition.newBuilder() 404 | .setHasVector(HasVectorCondition.newBuilder().setHasVector(vector).build()) 405 | .build(); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ExpressionFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points.Condition; 4 | import io.qdrant.client.grpc.Points.DecayParamsExpression; 5 | import io.qdrant.client.grpc.Points.DivExpression; 6 | import io.qdrant.client.grpc.Points.Expression; 7 | import io.qdrant.client.grpc.Points.GeoDistance; 8 | import io.qdrant.client.grpc.Points.MultExpression; 9 | import io.qdrant.client.grpc.Points.PowExpression; 10 | import io.qdrant.client.grpc.Points.SumExpression; 11 | 12 | /** Convenience methods for constructing {@link Expression} */ 13 | public final class ExpressionFactory { 14 | private ExpressionFactory() {} 15 | 16 | /** 17 | * Creates an {@link Expression} from a constant. 18 | * 19 | * @param constant The constant float value 20 | * @return a new instance of {@link Expression} 21 | */ 22 | public static Expression constant(float constant) { 23 | return Expression.newBuilder().setConstant(constant).build(); 24 | } 25 | 26 | /** 27 | * Creates an {@link Expression} from a variable name. 28 | * 29 | * @param variable The variable name (e.g., payload key or score reference) 30 | * @return a new instance of {@link Expression} 31 | */ 32 | public static Expression variable(String variable) { 33 | return Expression.newBuilder().setVariable(variable).build(); 34 | } 35 | 36 | /** 37 | * Creates an {@link Expression} from a {@link Condition}. 38 | * 39 | * @param condition The condition to evaluate 40 | * @return a new instance of {@link Expression} 41 | */ 42 | public static Expression condition(Condition condition) { 43 | return Expression.newBuilder().setCondition(condition).build(); 44 | } 45 | 46 | /** 47 | * Creates an {@link Expression} from a {@link GeoDistance}. 48 | * 49 | * @param geoDistance The geo distance object 50 | * @return a new instance of {@link Expression} 51 | */ 52 | public static Expression geoDistance(GeoDistance geoDistance) { 53 | return Expression.newBuilder().setGeoDistance(geoDistance).build(); 54 | } 55 | 56 | /** 57 | * Creates an {@link Expression} from a date-time constant string. 58 | * 59 | * @param datetime The date-time string 60 | * @return a new instance of {@link Expression} 61 | */ 62 | public static Expression datetime(String datetime) { 63 | return Expression.newBuilder().setDatetime(datetime).build(); 64 | } 65 | 66 | /** 67 | * Creates an {@link Expression} from a payload key referencing date-time values. 68 | * 69 | * @param datetimeKey The payload key containing date-time values 70 | * @return a new instance of {@link Expression} 71 | */ 72 | public static Expression datetimeKey(String datetimeKey) { 73 | return Expression.newBuilder().setDatetimeKey(datetimeKey).build(); 74 | } 75 | 76 | /** 77 | * Creates an {@link Expression} that multiplies values. 78 | * 79 | * @param mult The multiplication expression 80 | * @return a new instance of {@link Expression} 81 | */ 82 | public static Expression mult(MultExpression mult) { 83 | return Expression.newBuilder().setMult(mult).build(); 84 | } 85 | 86 | /** 87 | * Creates an {@link Expression} that sums values. 88 | * 89 | * @param sum The summation expression 90 | * @return a new instance of {@link Expression} 91 | */ 92 | public static Expression sum(SumExpression sum) { 93 | return Expression.newBuilder().setSum(sum).build(); 94 | } 95 | 96 | /** 97 | * Creates an {@link Expression} that divides values. 98 | * 99 | * @param div The division expression 100 | * @return a new instance of {@link Expression} 101 | */ 102 | public static Expression div(DivExpression div) { 103 | return Expression.newBuilder().setDiv(div).build(); 104 | } 105 | 106 | /** 107 | * Creates a negated {@link Expression}. 108 | * 109 | * @param expr The expression to negate 110 | * @return a new instance of {@link Expression} 111 | */ 112 | public static Expression negate(Expression expr) { 113 | return Expression.newBuilder().setNeg(expr).build(); 114 | } 115 | 116 | /** 117 | * Creates an {@link Expression} representing absolute value. 118 | * 119 | * @param expr The expression to wrap with abs() 120 | * @return a new instance of {@link Expression} 121 | */ 122 | public static Expression abs(Expression expr) { 123 | return Expression.newBuilder().setAbs(expr).build(); 124 | } 125 | 126 | /** 127 | * Creates an {@link Expression} representing square root. 128 | * 129 | * @param expr The expression to apply sqrt() to 130 | * @return a new instance of {@link Expression} 131 | */ 132 | public static Expression sqrt(Expression expr) { 133 | return Expression.newBuilder().setSqrt(expr).build(); 134 | } 135 | 136 | /** 137 | * Creates an {@link Expression} from a {@link PowExpression}. 138 | * 139 | * @param pow The power expression (base and exponent) 140 | * @return a new instance of {@link Expression} 141 | */ 142 | public static Expression pow(PowExpression pow) { 143 | return Expression.newBuilder().setPow(pow).build(); 144 | } 145 | 146 | /** 147 | * Creates an {@link Expression} representing exponential. 148 | * 149 | * @param expr The expression to apply exponential to 150 | * @return a new instance of {@link Expression} 151 | */ 152 | public static Expression exp(Expression expr) { 153 | return Expression.newBuilder().setExp(expr).build(); 154 | } 155 | 156 | /** 157 | * Creates an {@link Expression} representing base-10 logarithm. 158 | * 159 | * @param expr The expression to apply log10() to 160 | * @return a new instance of {@link Expression} 161 | */ 162 | public static Expression log10(Expression expr) { 163 | return Expression.newBuilder().setLog10(expr).build(); 164 | } 165 | 166 | /** 167 | * Creates an {@link Expression} representing natural logarithm. 168 | * 169 | * @param expr The expression to apply natural log to 170 | * @return a new instance of {@link Expression} 171 | */ 172 | public static Expression ln(Expression expr) { 173 | return Expression.newBuilder().setLn(expr).build(); 174 | } 175 | 176 | /** 177 | * Creates an {@link Expression} representing exponential decay. 178 | * 179 | * @param decay The decay parameters 180 | * @return a new instance of {@link Expression} 181 | */ 182 | public static Expression expDecay(DecayParamsExpression decay) { 183 | return Expression.newBuilder().setExpDecay(decay).build(); 184 | } 185 | 186 | /** 187 | * Creates an {@link Expression} representing Gaussian decay. 188 | * 189 | * @param decay The decay parameters 190 | * @return a new instance of {@link Expression} 191 | */ 192 | public static Expression gaussDecay(DecayParamsExpression decay) { 193 | return Expression.newBuilder().setGaussDecay(decay).build(); 194 | } 195 | 196 | /** 197 | * Creates an {@link Expression} representing linear decay. 198 | * 199 | * @param decay The decay parameters 200 | * @return a new instance of {@link Expression} 201 | */ 202 | public static Expression linDecay(DecayParamsExpression decay) { 203 | return Expression.newBuilder().setLinDecay(decay).build(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/PointIdFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points.PointId; 4 | import java.util.UUID; 5 | 6 | /** Convenience methods for constructing {@link PointId} */ 7 | public final class PointIdFactory { 8 | private PointIdFactory() {} 9 | 10 | /** 11 | * Creates a point id from a {@link long} 12 | * 13 | * @param id The id 14 | * @return a new instance of {@link PointId} 15 | */ 16 | public static PointId id(long id) { 17 | return PointId.newBuilder().setNum(id).build(); 18 | } 19 | 20 | /** 21 | * Creates a point id from a {@link UUID} 22 | * 23 | * @param id The id 24 | * @return a new instance of {@link PointId} 25 | */ 26 | public static PointId id(UUID id) { 27 | return PointId.newBuilder().setUuid(id.toString()).build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/QdrantException.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | /** An exception when interacting with qdrant */ 4 | public class QdrantException extends RuntimeException { 5 | /** 6 | * Instantiates a new instance of {@link QdrantException} 7 | * 8 | * @param message The exception message 9 | */ 10 | public QdrantException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/QdrantGrpcClient.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.grpc.CallCredentials; 4 | import io.grpc.Deadline; 5 | import io.grpc.ManagedChannel; 6 | import io.grpc.ManagedChannelBuilder; 7 | import io.qdrant.client.grpc.*; 8 | import io.qdrant.client.grpc.CollectionsGrpc.CollectionsFutureStub; 9 | import io.qdrant.client.grpc.PointsGrpc.PointsFutureStub; 10 | import io.qdrant.client.grpc.QdrantGrpc.QdrantFutureStub; 11 | import io.qdrant.client.grpc.SnapshotsGrpc.SnapshotsFutureStub; 12 | import java.time.Duration; 13 | import java.util.concurrent.TimeUnit; 14 | import javax.annotation.Nullable; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | /** Low-level gRPC client for qdrant vector database. */ 19 | public class QdrantGrpcClient implements AutoCloseable { 20 | private static final Logger logger = LoggerFactory.getLogger(QdrantGrpcClient.class); 21 | @Nullable private final CallCredentials callCredentials; 22 | private final ManagedChannel channel; 23 | private final boolean shutdownChannelOnClose; 24 | @Nullable private final Duration timeout; 25 | 26 | QdrantGrpcClient( 27 | ManagedChannel channel, 28 | boolean shutdownChannelOnClose, 29 | @Nullable CallCredentials callCredentials, 30 | @Nullable Duration timeout) { 31 | this.callCredentials = callCredentials; 32 | this.channel = channel; 33 | this.shutdownChannelOnClose = shutdownChannelOnClose; 34 | this.timeout = timeout; 35 | } 36 | 37 | /** 38 | * Creates a new builder to build a client. 39 | * 40 | * @param channel The channel for communication. This channel is not shutdown by the client and 41 | * must be managed by the caller. 42 | * @return a new instance of {@link Builder} 43 | */ 44 | public static Builder newBuilder(ManagedChannel channel) { 45 | return new Builder(channel, false, true); 46 | } 47 | 48 | /** 49 | * Creates a new builder to build a client. 50 | * 51 | * @param channel The channel for communication. 52 | * @param shutdownChannelOnClose Whether the channel is shutdown on client close. 53 | * @return a new instance of {@link Builder} 54 | */ 55 | public static Builder newBuilder(ManagedChannel channel, boolean shutdownChannelOnClose) { 56 | return new Builder(channel, shutdownChannelOnClose, true); 57 | } 58 | 59 | /** 60 | * Creates a new builder to build a client. 61 | * 62 | * @param channel The channel for communication. 63 | * @param shutdownChannelOnClose Whether the channel is shutdown on client close. 64 | * @param checkCompatibility Whether to check compatibility between client's and server's 65 | * versions. 66 | * @return a new instance of {@link Builder} 67 | */ 68 | public static Builder newBuilder( 69 | ManagedChannel channel, boolean shutdownChannelOnClose, boolean checkCompatibility) { 70 | return new Builder(channel, shutdownChannelOnClose, checkCompatibility); 71 | } 72 | 73 | /** 74 | * Creates a new builder to build a client. 75 | * 76 | * @param host The host to connect to. The default gRPC port 6334 is used. 77 | * @return a new instance of {@link Builder} 78 | */ 79 | public static Builder newBuilder(String host) { 80 | return new Builder(host, 6334, true, true); 81 | } 82 | 83 | /** 84 | * Creates a new builder to build a client. The client uses Transport Layer Security by default. 85 | * 86 | * @param host The host to connect to. 87 | * @param port The port to connect to. 88 | * @return a new instance of {@link Builder} 89 | */ 90 | public static Builder newBuilder(String host, int port) { 91 | return new Builder(host, port, true, true); 92 | } 93 | 94 | /** 95 | * Creates a new builder to build a client. 96 | * 97 | * @param host The host to connect to. 98 | * @param port The port to connect to. 99 | * @param useTransportLayerSecurity Whether the client uses Transport Layer Security (TLS) to 100 | * secure communications. Running without TLS should only be used for testing purposes. 101 | * @return a new instance of {@link Builder} 102 | */ 103 | public static Builder newBuilder(String host, int port, boolean useTransportLayerSecurity) { 104 | return new Builder(host, port, useTransportLayerSecurity, true); 105 | } 106 | 107 | /** 108 | * Creates a new builder to build a client. 109 | * 110 | * @param host The host to connect to. 111 | * @param port The port to connect to. 112 | * @param useTransportLayerSecurity Whether the client uses Transport Layer Security (TLS) to 113 | * secure communications. Running without TLS should only be used for testing purposes. 114 | * @param checkCompatibility Whether to check compatibility between client's and server's 115 | * versions. 116 | * @return a new instance of {@link Builder} 117 | */ 118 | public static Builder newBuilder( 119 | String host, int port, boolean useTransportLayerSecurity, boolean checkCompatibility) { 120 | return new Builder(host, port, useTransportLayerSecurity, checkCompatibility); 121 | } 122 | 123 | /** 124 | * Gets the channel 125 | * 126 | * @return the channel 127 | */ 128 | public ManagedChannel channel() { 129 | return channel; 130 | } 131 | 132 | /** 133 | * Gets the client for qdrant services 134 | * 135 | * @return a new instance of {@link QdrantFutureStub} 136 | */ 137 | public QdrantGrpc.QdrantFutureStub qdrant() { 138 | return QdrantGrpc.newFutureStub(channel) 139 | .withCallCredentials(callCredentials) 140 | .withDeadline( 141 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null); 142 | } 143 | 144 | /** 145 | * Gets the client for points 146 | * 147 | * @return a new instance of {@link PointsFutureStub} 148 | */ 149 | public PointsFutureStub points() { 150 | return PointsGrpc.newFutureStub(channel) 151 | .withCallCredentials(callCredentials) 152 | .withDeadline( 153 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null); 154 | } 155 | 156 | /** 157 | * Gets the client for collections 158 | * 159 | * @return a new instance of {@link CollectionsFutureStub} 160 | */ 161 | public CollectionsFutureStub collections() { 162 | return CollectionsGrpc.newFutureStub(channel) 163 | .withCallCredentials(callCredentials) 164 | .withDeadline( 165 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null); 166 | } 167 | 168 | /** 169 | * Gets the client for snapshots 170 | * 171 | * @return a new instance of {@link SnapshotsFutureStub} 172 | */ 173 | public SnapshotsFutureStub snapshots() { 174 | return SnapshotsGrpc.newFutureStub(channel) 175 | .withCallCredentials(callCredentials) 176 | .withDeadline( 177 | timeout != null ? Deadline.after(timeout.toMillis(), TimeUnit.MILLISECONDS) : null); 178 | } 179 | 180 | @Override 181 | public void close() { 182 | if (shutdownChannelOnClose && !channel.isShutdown() && !channel.isTerminated()) { 183 | try { 184 | channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); 185 | } catch (InterruptedException e) { 186 | logger.warn("exception thrown when shutting down channel", e); 187 | } 188 | } 189 | } 190 | 191 | /** builder for {@link QdrantGrpcClient} */ 192 | public static class Builder { 193 | private final ManagedChannel channel; 194 | private final boolean shutdownChannelOnClose; 195 | @Nullable private CallCredentials callCredentials; 196 | @Nullable private Duration timeout; 197 | 198 | Builder(ManagedChannel channel, boolean shutdownChannelOnClose, boolean checkCompatibility) { 199 | this.channel = channel; 200 | this.shutdownChannelOnClose = shutdownChannelOnClose; 201 | String clientVersion = Builder.class.getPackage().getImplementationVersion(); 202 | if (checkCompatibility) { 203 | checkVersionsCompatibility(clientVersion); 204 | } 205 | } 206 | 207 | Builder(String host, int port, boolean useTransportLayerSecurity, boolean checkCompatibility) { 208 | String clientVersion = Builder.class.getPackage().getImplementationVersion(); 209 | String javaVersion = System.getProperty("java.version"); 210 | String userAgent = "java-client/" + clientVersion + " java/" + javaVersion; 211 | this.channel = createChannel(host, port, useTransportLayerSecurity, userAgent); 212 | this.shutdownChannelOnClose = true; 213 | if (checkCompatibility) { 214 | checkVersionsCompatibility(clientVersion); 215 | } 216 | } 217 | 218 | /** 219 | * Sets the API key to use for authentication 220 | * 221 | * @param apiKey The API key to use. 222 | * @return this 223 | */ 224 | public Builder withApiKey(String apiKey) { 225 | this.callCredentials = new ApiKeyCredentials(apiKey); 226 | return this; 227 | } 228 | 229 | /** 230 | * Sets a default timeout for all requests. 231 | * 232 | * @param timeout The timeout. 233 | * @return this 234 | */ 235 | public Builder withTimeout(@Nullable Duration timeout) { 236 | this.timeout = timeout; 237 | return this; 238 | } 239 | 240 | /** 241 | * Sets the credential data that will be propagated to the server via request metadata for each 242 | * RPC. 243 | * 244 | * @param callCredentials The call credentials to use. 245 | * @return this 246 | */ 247 | public Builder withCallCredentials(@Nullable CallCredentials callCredentials) { 248 | this.callCredentials = callCredentials; 249 | return this; 250 | } 251 | 252 | /** 253 | * Builds a new instance of {@link QdrantGrpcClient} 254 | * 255 | * @return a new instance of {@link QdrantGrpcClient} 256 | */ 257 | public QdrantGrpcClient build() { 258 | return new QdrantGrpcClient(channel, shutdownChannelOnClose, callCredentials, timeout); 259 | } 260 | 261 | private static ManagedChannel createChannel( 262 | String host, int port, boolean useTransportLayerSecurity, String userAgent) { 263 | ManagedChannelBuilder channelBuilder = ManagedChannelBuilder.forAddress(host, port); 264 | 265 | if (useTransportLayerSecurity) { 266 | channelBuilder.useTransportSecurity(); 267 | } else { 268 | channelBuilder.usePlaintext(); 269 | } 270 | 271 | channelBuilder.userAgent(userAgent); 272 | 273 | return channelBuilder.build(); 274 | } 275 | 276 | private void checkVersionsCompatibility(String clientVersion) { 277 | try { 278 | String serverVersion = 279 | QdrantGrpc.newBlockingStub(this.channel) 280 | .healthCheck(QdrantOuterClass.HealthCheckRequest.getDefaultInstance()) 281 | .getVersion(); 282 | if (!VersionsCompatibilityChecker.isCompatible(clientVersion, serverVersion)) { 283 | String logMessage = 284 | "Qdrant client version " 285 | + clientVersion 286 | + " is incompatible with server version " 287 | + serverVersion 288 | + ". Major versions should match and minor version difference must not exceed 1. " 289 | + "Set checkCompatibility=false to skip version check."; 290 | logger.warn(logMessage); 291 | } 292 | } catch (Exception e) { 293 | logger.warn( 294 | "Failed to obtain server version. Unable to check client-server compatibility. Set checkCompatibility=false to skip version check."); 295 | } 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/QueryFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static io.qdrant.client.VectorInputFactory.multiVectorInput; 4 | import static io.qdrant.client.VectorInputFactory.vectorInput; 5 | 6 | import io.qdrant.client.grpc.Points.ContextInput; 7 | import io.qdrant.client.grpc.Points.DiscoverInput; 8 | import io.qdrant.client.grpc.Points.Formula; 9 | import io.qdrant.client.grpc.Points.Fusion; 10 | import io.qdrant.client.grpc.Points.OrderBy; 11 | import io.qdrant.client.grpc.Points.PointId; 12 | import io.qdrant.client.grpc.Points.Query; 13 | import io.qdrant.client.grpc.Points.RecommendInput; 14 | import io.qdrant.client.grpc.Points.Sample; 15 | import io.qdrant.client.grpc.Points.VectorInput; 16 | import java.util.List; 17 | import java.util.UUID; 18 | 19 | /** Convenience methods for constructing {@link Query} */ 20 | public final class QueryFactory { 21 | private QueryFactory() {} 22 | 23 | /** 24 | * Creates a {@link Query} for recommendation. 25 | * 26 | * @param input An instance of {@link RecommendInput} 27 | * @return a new instance of {@link Query} 28 | */ 29 | public static Query recommend(RecommendInput input) { 30 | return Query.newBuilder().setRecommend(input).build(); 31 | } 32 | 33 | /** 34 | * Creates a {@link Query} for discovery. 35 | * 36 | * @param input An instance of {@link DiscoverInput} 37 | * @return a new instance of {@link Query} 38 | */ 39 | public static Query discover(DiscoverInput input) { 40 | return Query.newBuilder().setDiscover(input).build(); 41 | } 42 | 43 | /** 44 | * Creates a {@link Query} for context search. 45 | * 46 | * @param input An instance of {@link ContextInput} 47 | * @return a new instance of {@link Query} 48 | */ 49 | public static Query context(ContextInput input) { 50 | return Query.newBuilder().setContext(input).build(); 51 | } 52 | 53 | /** 54 | * Creates a {@link Query} for pre-fetch results fusion. 55 | * 56 | * @param fusion An instance of {@link Fusion} 57 | * @return a new instance of {@link Query} 58 | */ 59 | public static Query fusion(Fusion fusion) { 60 | return Query.newBuilder().setFusion(fusion).build(); 61 | } 62 | 63 | /** 64 | * Creates a {@link Query} to order points by a payload field. 65 | * 66 | * @param key Name of the payload field to order by 67 | * @return a new instance of {@link Query} 68 | */ 69 | public static Query orderBy(String key) { 70 | OrderBy orderBy = OrderBy.newBuilder().setKey(key).build(); 71 | return Query.newBuilder().setOrderBy(orderBy).build(); 72 | } 73 | 74 | /** 75 | * Creates a {@link Query} to order points by a payload field. 76 | * 77 | * @param orderBy An instance of {@link OrderBy} 78 | * @return a new instance of {@link Query} 79 | */ 80 | public static Query orderBy(OrderBy orderBy) { 81 | return Query.newBuilder().setOrderBy(orderBy).build(); 82 | } 83 | 84 | /** 85 | * Creates a {@link Query} for score boosting using an arbitrary formula. 86 | * 87 | * @param formula An instance of {@link Formula} 88 | * @return a new instance of {@link Query} 89 | */ 90 | public static Query formula(Formula formula) { 91 | return Query.newBuilder().setFormula(formula).build(); 92 | } 93 | 94 | // region Nearest search queries 95 | 96 | /** 97 | * Creates a {@link Query} for nearest search. 98 | * 99 | * @param input An instance of {@link VectorInput} 100 | * @return a new instance of {@link Query} 101 | */ 102 | public static Query nearest(VectorInput input) { 103 | return Query.newBuilder().setNearest(input).build(); 104 | } 105 | 106 | /** 107 | * Creates a {@link Query} from a list of floats 108 | * 109 | * @param values A map of vector names to values 110 | * @return A new instance of {@link Query} 111 | */ 112 | public static Query nearest(List values) { 113 | return Query.newBuilder().setNearest(vectorInput(values)).build(); 114 | } 115 | 116 | /** 117 | * Creates a {@link Query} from a list of floats 118 | * 119 | * @param values A list of values 120 | * @return A new instance of {@link Query} 121 | */ 122 | public static Query nearest(float... values) { 123 | return Query.newBuilder().setNearest(vectorInput(values)).build(); 124 | } 125 | 126 | /** 127 | * Creates a {@link Query} from a list of floats and integers as indices 128 | * 129 | * @param values The list of floats representing the vector. 130 | * @param indices The list of integers representing the indices. 131 | * @return A new instance of {@link Query} 132 | */ 133 | public static Query nearest(List values, List indices) { 134 | return Query.newBuilder().setNearest(vectorInput(values, indices)).build(); 135 | } 136 | 137 | /** 138 | * Creates a {@link Query} from a nested array of floats representing a multi vector 139 | * 140 | * @param vectors The nested array of floats. 141 | * @return A new instance of {@link Query} 142 | */ 143 | public static Query nearest(float[][] vectors) { 144 | return Query.newBuilder().setNearest(multiVectorInput(vectors)).build(); 145 | } 146 | 147 | /** 148 | * Creates a {@link Query} from a {@link long} 149 | * 150 | * @param id The point id 151 | * @return a new instance of {@link Query} 152 | */ 153 | public static Query nearest(long id) { 154 | return Query.newBuilder().setNearest(vectorInput(id)).build(); 155 | } 156 | 157 | /** 158 | * Creates a {@link Query} from a {@link UUID} 159 | * 160 | * @param id The pint id 161 | * @return a new instance of {@link Query} 162 | */ 163 | public static Query nearest(UUID id) { 164 | return Query.newBuilder().setNearest(vectorInput(id)).build(); 165 | } 166 | 167 | /** 168 | * Creates a {@link Query} from a {@link PointId} 169 | * 170 | * @param id The pint id 171 | * @return a new instance of {@link Query} 172 | */ 173 | public static Query nearest(PointId id) { 174 | return Query.newBuilder().setNearest(vectorInput(id)).build(); 175 | } 176 | 177 | /** 178 | * Creates a {@link Query} from a nested list of floats representing a multi vector 179 | * 180 | * @param vectors The nested list of floats. 181 | * @return A new instance of {@link Query} 182 | */ 183 | public static Query nearestMultiVector(List> vectors) { 184 | return Query.newBuilder().setNearest(multiVectorInput(vectors)).build(); 185 | } 186 | 187 | /** 188 | * Creates a {@link Query} for sampling. 189 | * 190 | * @param sample An instance of {@link Sample} 191 | * @return A new instance of {@link Query} 192 | */ 193 | public static Query sample(Sample sample) { 194 | return Query.newBuilder().setSample(sample).build(); 195 | } 196 | 197 | // endregion 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ShardKeyFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Collections.ShardKey; 4 | 5 | /** Convenience methods for constructing {@link ShardKey} */ 6 | public final class ShardKeyFactory { 7 | private ShardKeyFactory() {} 8 | 9 | /** 10 | * Creates a {@link ShardKey} based on a keyword. 11 | * 12 | * @param keyword The keyword to create the shard key from 13 | * @return The {@link ShardKey} object 14 | */ 15 | public static ShardKey shardKey(String keyword) { 16 | return ShardKey.newBuilder().setKeyword(keyword).build(); 17 | } 18 | 19 | /** 20 | * Creates a {@link ShardKey} based on a number. 21 | * 22 | * @param number The number to create the shard key from 23 | * @return The {@link ShardKey} object 24 | */ 25 | public static ShardKey shardKey(long number) { 26 | return ShardKey.newBuilder().setNumber(number).build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ShardKeySelectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static io.qdrant.client.ShardKeyFactory.shardKey; 4 | 5 | import io.qdrant.client.grpc.Collections.ShardKey; 6 | import io.qdrant.client.grpc.Points.ShardKeySelector; 7 | import java.util.Arrays; 8 | 9 | /** Convenience methods for constructing {@link ShardKeySelector} */ 10 | public class ShardKeySelectorFactory { 11 | private ShardKeySelectorFactory() {} 12 | 13 | /** 14 | * Creates a {@link ShardKeySelector} with the given shard keys. 15 | * 16 | * @param shardKeys The shard keys to include in the selector. 17 | * @return The created {@link ShardKeySelector} object. 18 | */ 19 | public static ShardKeySelector shardKeySelector(ShardKey... shardKeys) { 20 | return ShardKeySelector.newBuilder().addAllShardKeys(Arrays.asList(shardKeys)).build(); 21 | } 22 | 23 | /** 24 | * Creates a {@link ShardKeySelector} with the given shard key keywords. 25 | * 26 | * @param keywords The shard key keywords to include in the selector. 27 | * @return The created {@link ShardKeySelector} object. 28 | */ 29 | public static ShardKeySelector shardKeySelector(String... keywords) { 30 | ShardKeySelector.Builder builder = ShardKeySelector.newBuilder(); 31 | for (String keyword : keywords) { 32 | builder.addShardKeys(shardKey(keyword)); 33 | } 34 | return builder.build(); 35 | } 36 | 37 | /** 38 | * Creates a {@link ShardKeySelector} with the given shard key numbers. 39 | * 40 | * @param numbers The shard key numbers to include in the selector. 41 | * @return The created {@link ShardKeySelector} object. 42 | */ 43 | public static ShardKeySelector shardKeySelector(long... numbers) { 44 | ShardKeySelector.Builder builder = ShardKeySelector.newBuilder(); 45 | for (long number : numbers) { 46 | builder.addShardKeys(shardKey(number)); 47 | } 48 | return builder.build(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/StartFromFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import com.google.protobuf.Timestamp; 4 | import io.qdrant.client.grpc.Points.StartFrom; 5 | import java.time.Instant; 6 | 7 | /** Convenience methods for constructing {@link StartFrom} */ 8 | public final class StartFromFactory { 9 | private StartFromFactory() {} 10 | 11 | /** 12 | * Creates a {@link StartFrom} value from a {@link float} 13 | * 14 | * @param value The value 15 | * @return a new instance of {@link StartFrom} 16 | */ 17 | public static StartFrom startFrom(float value) { 18 | return StartFrom.newBuilder().setFloat(value).build(); 19 | } 20 | 21 | /** 22 | * Creates a {@link StartFrom} value from a {@link int} 23 | * 24 | * @param value The value 25 | * @return a new instance of {@link StartFrom} 26 | */ 27 | public static StartFrom startFrom(int value) { 28 | return StartFrom.newBuilder().setInteger(value).build(); 29 | } 30 | 31 | /** 32 | * Creates a {@link StartFrom} value from a {@link String} timestamp 33 | * 34 | * @param value The value 35 | * @return a new instance of {@link StartFrom} 36 | */ 37 | public static StartFrom startFrom(String value) { 38 | return StartFrom.newBuilder().setDatetime(value).build(); 39 | } 40 | 41 | /** 42 | * Creates a {@link StartFrom} value from a {@link Timestamp} 43 | * 44 | * @param value The value 45 | * @return a new instance of {@link StartFrom} 46 | */ 47 | public static StartFrom startFrom(Timestamp value) { 48 | return StartFrom.newBuilder().setTimestamp(value).build(); 49 | } 50 | 51 | /** 52 | * Creates a {@link StartFrom} value from a {@link Timestamp} 53 | * 54 | * @param value The value 55 | * @return a new instance of {@link StartFrom} 56 | */ 57 | public static StartFrom startFrom(Instant value) { 58 | return StartFrom.newBuilder() 59 | .setTimestamp(Timestamp.newBuilder().setSeconds(value.getEpochSecond())) 60 | .build(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/TargetVectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points.PointId; 4 | import io.qdrant.client.grpc.Points.TargetVector; 5 | import io.qdrant.client.grpc.Points.Vector; 6 | import io.qdrant.client.grpc.Points.VectorExample; 7 | 8 | /** Convenience methods for constructing {@link TargetVector} */ 9 | public class TargetVectorFactory { 10 | private TargetVectorFactory() {} 11 | 12 | /** 13 | * Creates a TargetVector from a point ID 14 | * 15 | * @param id The point ID to use 16 | * @return A new instance of {@link TargetVector} 17 | */ 18 | public static TargetVector targetVector(PointId id) { 19 | return TargetVector.newBuilder().setSingle(VectorExample.newBuilder().setId(id)).build(); 20 | } 21 | 22 | /** 23 | * Creates a TargetVector from a Vector 24 | * 25 | * @param vector The Vector value to use 26 | * @return A new instance of {@link TargetVector} 27 | */ 28 | public static TargetVector targetVector(Vector vector) { 29 | return TargetVector.newBuilder() 30 | .setSingle(VectorExample.newBuilder().setVector(vector)) 31 | .build(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/ValueFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.JsonWithInt.ListValue; 4 | import io.qdrant.client.grpc.JsonWithInt.NullValue; 5 | import io.qdrant.client.grpc.JsonWithInt.Struct; 6 | import io.qdrant.client.grpc.JsonWithInt.Value; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** Convenience methods for constructing {@link Value} */ 11 | public final class ValueFactory { 12 | private ValueFactory() {} 13 | 14 | /** 15 | * Creates a value from a {@link String} 16 | * 17 | * @param value The value 18 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 19 | */ 20 | public static Value value(String value) { 21 | return Value.newBuilder().setStringValue(value).build(); 22 | } 23 | 24 | /** 25 | * Creates a value from a {@link long} 26 | * 27 | * @param value The value 28 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 29 | */ 30 | public static Value value(long value) { 31 | return Value.newBuilder().setIntegerValue(value).build(); 32 | } 33 | 34 | /** 35 | * Creates a value from a {@link double} 36 | * 37 | * @param value The value 38 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 39 | */ 40 | public static Value value(double value) { 41 | return Value.newBuilder().setDoubleValue(value).build(); 42 | } 43 | 44 | /** 45 | * Creates a value from a {@link boolean} 46 | * 47 | * @param value The value 48 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 49 | */ 50 | public static Value value(boolean value) { 51 | return Value.newBuilder().setBoolValue(value).build(); 52 | } 53 | 54 | /** 55 | * Creates a null value 56 | * 57 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 58 | */ 59 | public static Value nullValue() { 60 | return Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build(); 61 | } 62 | 63 | /** 64 | * Creates a value from a list of values 65 | * 66 | * @param values The list of values 67 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 68 | */ 69 | public static Value list(List values) { 70 | return Value.newBuilder() 71 | .setListValue(ListValue.newBuilder().addAllValues(values).build()) 72 | .build(); 73 | } 74 | 75 | /** 76 | * Creates a value from a list of values. Same as {@link #list(List)} 77 | * 78 | * @param values The list of values 79 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 80 | */ 81 | public static Value value(List values) { 82 | return list(values); 83 | } 84 | 85 | /** 86 | * Creates a value from a map of nested values 87 | * 88 | * @param values The map of values 89 | * @return a new instance of {@link io.qdrant.client.grpc.JsonWithInt.Value} 90 | */ 91 | public static Value value(Map values) { 92 | return Value.newBuilder() 93 | .setStructValue(Struct.newBuilder().putAllFields(values).build()) 94 | .build(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/VectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import com.google.common.primitives.Floats; 4 | import io.qdrant.client.grpc.Points.SparseIndices; 5 | import io.qdrant.client.grpc.Points.Vector; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | /** Convenience methods for constructing {@link Vector} */ 11 | public final class VectorFactory { 12 | private VectorFactory() {} 13 | 14 | /** 15 | * Creates a vector from a list of floats 16 | * 17 | * @param values A map of vector names to values 18 | * @return A new instance of {@link Vector} 19 | */ 20 | public static Vector vector(List values) { 21 | return Vector.newBuilder().addAllData(values).build(); 22 | } 23 | 24 | /** 25 | * Creates a vector from a list of floats 26 | * 27 | * @param values A list of values 28 | * @return A new instance of {@link Vector} 29 | */ 30 | public static Vector vector(float... values) { 31 | return Vector.newBuilder().addAllData(Floats.asList(values)).build(); 32 | } 33 | 34 | /** 35 | * Creates a sparse vector from a list of floats and integers as indices 36 | * 37 | * @param vector The list of floats representing the vector. 38 | * @param indices The list of integers representing the indices. 39 | * @return A new instance of {@link Vector} 40 | */ 41 | public static Vector vector(List vector, List indices) { 42 | return Vector.newBuilder() 43 | .addAllData(vector) 44 | .setIndices(SparseIndices.newBuilder().addAllData(indices).build()) 45 | .build(); 46 | } 47 | 48 | /** 49 | * Creates a multi vector from a nested list of floats 50 | * 51 | * @param vectors The nested list of floats representing the multi vector. 52 | * @return A new instance of {@link Vector} 53 | */ 54 | public static Vector multiVector(List> vectors) { 55 | int vectorSize = vectors.size(); 56 | List flatVector = vectors.stream().flatMap(List::stream).collect(Collectors.toList()); 57 | 58 | return Vector.newBuilder().addAllData(flatVector).setVectorsCount(vectorSize).build(); 59 | } 60 | 61 | /** 62 | * Creates a multi vector from a nested array of floats 63 | * 64 | * @param vectors The nested array of floats representing the multi vector. 65 | * @return A new instance of {@link Vector} 66 | */ 67 | public static Vector multiVector(float[][] vectors) { 68 | int vectorSize = vectors.length; 69 | 70 | List flatVector = new ArrayList<>(); 71 | for (float[] vector : vectors) { 72 | for (float value : vector) { 73 | flatVector.add(value); 74 | } 75 | } 76 | 77 | return Vector.newBuilder().addAllData(flatVector).setVectorsCount(vectorSize).build(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/VectorInputFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static io.qdrant.client.PointIdFactory.id; 4 | 5 | import com.google.common.primitives.Floats; 6 | import io.qdrant.client.grpc.Points.DenseVector; 7 | import io.qdrant.client.grpc.Points.MultiDenseVector; 8 | import io.qdrant.client.grpc.Points.PointId; 9 | import io.qdrant.client.grpc.Points.SparseVector; 10 | import io.qdrant.client.grpc.Points.VectorInput; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.UUID; 14 | import java.util.stream.Collectors; 15 | 16 | /** Convenience methods for constructing {@link VectorInput} */ 17 | public final class VectorInputFactory { 18 | private VectorInputFactory() {} 19 | 20 | /** 21 | * Creates a {@link VectorInput} from a list of floats 22 | * 23 | * @param values A map of vector names to values 24 | * @return A new instance of {@link VectorInput} 25 | */ 26 | public static VectorInput vectorInput(List values) { 27 | return VectorInput.newBuilder().setDense(DenseVector.newBuilder().addAllData(values)).build(); 28 | } 29 | 30 | /** 31 | * Creates a {@link VectorInput} from a list of floats 32 | * 33 | * @param values A list of values 34 | * @return A new instance of {@link VectorInput} 35 | */ 36 | public static VectorInput vectorInput(float... values) { 37 | return VectorInput.newBuilder() 38 | .setDense(DenseVector.newBuilder().addAllData(Floats.asList(values))) 39 | .build(); 40 | } 41 | 42 | /** 43 | * Creates a {@link VectorInput} from a list of floats and integers as indices 44 | * 45 | * @param vector The list of floats representing the vector. 46 | * @param indices The list of integers representing the indices. 47 | * @return A new instance of {@link VectorInput} 48 | */ 49 | public static VectorInput vectorInput(List vector, List indices) { 50 | return VectorInput.newBuilder() 51 | .setSparse(SparseVector.newBuilder().addAllValues(vector).addAllIndices(indices).build()) 52 | .build(); 53 | } 54 | 55 | /** 56 | * Creates a {@link VectorInput} from a nested list of floats representing a multi vector 57 | * 58 | * @param vectors The nested list of floats. 59 | * @return A new instance of {@link VectorInput} 60 | */ 61 | public static VectorInput multiVectorInput(List> vectors) { 62 | List denseVectors = 63 | vectors.stream() 64 | .map(v -> DenseVector.newBuilder().addAllData(v).build()) 65 | .collect(Collectors.toList()); 66 | return VectorInput.newBuilder() 67 | .setMultiDense(MultiDenseVector.newBuilder().addAllVectors(denseVectors).build()) 68 | .build(); 69 | } 70 | 71 | /** 72 | * Creates a {@link VectorInput} from a nested array of floats representing a multi vector 73 | * 74 | * @param vectors The nested array of floats. 75 | * @return A new instance of {@link VectorInput} 76 | */ 77 | public static VectorInput multiVectorInput(float[][] vectors) { 78 | List denseVectors = new ArrayList<>(); 79 | for (float[] vector : vectors) { 80 | denseVectors.add(DenseVector.newBuilder().addAllData(Floats.asList(vector)).build()); 81 | } 82 | return VectorInput.newBuilder() 83 | .setMultiDense(MultiDenseVector.newBuilder().addAllVectors(denseVectors).build()) 84 | .build(); 85 | } 86 | 87 | /** 88 | * Creates a {@link VectorInput} from a {@link long} 89 | * 90 | * @param id The point id 91 | * @return a new instance of {@link VectorInput} 92 | */ 93 | public static VectorInput vectorInput(long id) { 94 | return VectorInput.newBuilder().setId(id(id)).build(); 95 | } 96 | 97 | /** 98 | * Creates a {@link VectorInput} from a {@link UUID} 99 | * 100 | * @param id The point id 101 | * @return a new instance of {@link VectorInput} 102 | */ 103 | public static VectorInput vectorInput(UUID id) { 104 | return VectorInput.newBuilder().setId(id(id)).build(); 105 | } 106 | 107 | /** 108 | * Creates a {@link VectorInput} from a {@link PointId} 109 | * 110 | * @param id The point id 111 | * @return a new instance of {@link VectorInput} 112 | */ 113 | public static VectorInput vectorInput(PointId id) { 114 | return VectorInput.newBuilder().setId(id).build(); 115 | } 116 | 117 | // /** 118 | // * Creates a {@link VectorInput} from a {@link Document} 119 | // * 120 | // * @param document An instance of {@link Document} 121 | // * @return a new instance of {@link VectorInput} 122 | // */ 123 | // public static VectorInput vectorInput(Document document) { 124 | // return VectorInput.newBuilder().setDocument(document).build(); 125 | // } 126 | 127 | // /** 128 | // * Creates a {@link VectorInput} from a an {@link Image} 129 | // * 130 | // * @param image An instance of {@link Image} 131 | // * @return a new instance of {@link VectorInput} 132 | // */ 133 | // public static VectorInput vectorInput(Image image) { 134 | // return VectorInput.newBuilder().setImage(image).build(); 135 | // } 136 | 137 | // /** 138 | // * Creates a {@link VectorInput} from a {@link InferenceObject} 139 | // * 140 | // * @param object An instance of {@link InferenceObject} 141 | // * @return a new instance of {@link VectorInput} 142 | // */ 143 | // public static VectorInput vectorInput(InferenceObject object) { 144 | // return VectorInput.newBuilder().setObject(object).build(); 145 | // } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/VectorsFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static io.qdrant.client.VectorFactory.vector; 4 | 5 | import io.qdrant.client.grpc.Points.NamedVectors; 6 | import io.qdrant.client.grpc.Points.Vector; 7 | import io.qdrant.client.grpc.Points.Vectors; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** Convenience methods for constructing {@link Vectors} */ 12 | public final class VectorsFactory { 13 | private VectorsFactory() {} 14 | 15 | /** 16 | * Creates named vectors 17 | * 18 | * @param values A map of vector names to {@link Vector} 19 | * @return a new instance of {@link Vectors} 20 | */ 21 | public static Vectors namedVectors(Map values) { 22 | return Vectors.newBuilder().setVectors(NamedVectors.newBuilder().putAllVectors(values)).build(); 23 | } 24 | 25 | /** 26 | * Creates a vector 27 | * 28 | * @param values A list of values 29 | * @return a new instance of {@link Vectors} 30 | */ 31 | public static Vectors vectors(List values) { 32 | return Vectors.newBuilder().setVector(vector(values)).build(); 33 | } 34 | 35 | /** 36 | * Creates a vector 37 | * 38 | * @param values A list of values 39 | * @return a new instance of {@link Vectors} 40 | */ 41 | public static Vectors vectors(float... values) { 42 | return Vectors.newBuilder().setVector(vector(values)).build(); 43 | } 44 | 45 | /** 46 | * Creates a vector 47 | * 48 | * @param vector An instance of {@link Vector} 49 | * @return a new instance of {@link Vectors} 50 | */ 51 | public static Vectors vectors(Vector vector) { 52 | return Vectors.newBuilder().setVector(vector).build(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/VersionsCompatibilityChecker.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | class Version { 7 | private final int major; 8 | private final int minor; 9 | 10 | public Version(int major, int minor) { 11 | this.major = major; 12 | this.minor = minor; 13 | } 14 | 15 | public int getMajor() { 16 | return major; 17 | } 18 | 19 | public int getMinor() { 20 | return minor; 21 | } 22 | } 23 | 24 | /** Utility class to check compatibility between server's and client's versions. */ 25 | public class VersionsCompatibilityChecker { 26 | private static final Logger logger = LoggerFactory.getLogger(VersionsCompatibilityChecker.class); 27 | 28 | /** Default constructor. */ 29 | public VersionsCompatibilityChecker() {} 30 | 31 | private static Version parseVersion(String version) throws IllegalArgumentException { 32 | if (version.isEmpty()) { 33 | throw new IllegalArgumentException("Version is None"); 34 | } 35 | 36 | try { 37 | String[] parts = version.split("\\."); 38 | int major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0; 39 | int minor = parts.length > 1 ? Integer.parseInt(parts[1]) : 0; 40 | 41 | return new Version(major, minor); 42 | } catch (Exception e) { 43 | throw new IllegalArgumentException( 44 | "Unable to parse version, expected format: x.y[.z], found: " + version, e); 45 | } 46 | } 47 | 48 | /** 49 | * Compares server's and client's versions. 50 | * 51 | * @param clientVersion The client's version. 52 | * @param serverVersion The server's version. 53 | * @return True if the versions are compatible, false otherwise. 54 | */ 55 | public static boolean isCompatible(String clientVersion, String serverVersion) { 56 | try { 57 | Version client = parseVersion(clientVersion); 58 | Version server = parseVersion(serverVersion); 59 | 60 | if (client.getMajor() != server.getMajor()) return false; 61 | return Math.abs(client.getMinor() - server.getMinor()) <= 1; 62 | 63 | } catch (IllegalArgumentException e) { 64 | logger.warn("Version comparison failed: {}", e.getMessage()); 65 | return false; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/WithPayloadSelectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points.PayloadExcludeSelector; 4 | import io.qdrant.client.grpc.Points.PayloadIncludeSelector; 5 | import io.qdrant.client.grpc.Points.WithPayloadSelector; 6 | import java.util.List; 7 | 8 | /** Convenience methods for constructing {@link WithPayloadSelector} */ 9 | public final class WithPayloadSelectorFactory { 10 | private WithPayloadSelectorFactory() {} 11 | 12 | /** 13 | * Whether to include all payload in response. 14 | * 15 | * @param enable if true, to include all payload, if false, none. 16 | * @return a new instance of {@link WithPayloadSelector} 17 | */ 18 | public static WithPayloadSelector enable(boolean enable) { 19 | return WithPayloadSelector.newBuilder().setEnable(enable).build(); 20 | } 21 | 22 | /** 23 | * Which payload fields to include in response. 24 | * 25 | * @param fields the list of fields to include. 26 | * @return a new instance of {@link WithPayloadSelector} 27 | */ 28 | public static WithPayloadSelector include(List fields) { 29 | return WithPayloadSelector.newBuilder() 30 | .setInclude(PayloadIncludeSelector.newBuilder().addAllFields(fields).build()) 31 | .build(); 32 | } 33 | 34 | /** 35 | * Which payload fields to exclude in response. 36 | * 37 | * @param fields the list of fields to exclude. 38 | * @return a new instance of {@link WithPayloadSelector} 39 | */ 40 | public static WithPayloadSelector exclude(List fields) { 41 | return WithPayloadSelector.newBuilder() 42 | .setExclude(PayloadExcludeSelector.newBuilder().addAllFields(fields).build()) 43 | .build(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/WithVectorsSelectorFactory.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.qdrant.client.grpc.Points; 4 | import io.qdrant.client.grpc.Points.WithVectorsSelector; 5 | import java.util.List; 6 | 7 | /** Convenience methods for constructing {@link WithVectorsSelector} */ 8 | public final class WithVectorsSelectorFactory { 9 | private WithVectorsSelectorFactory() {} 10 | 11 | /** 12 | * Whether to include vectors in response. 13 | * 14 | * @param enable if true, to include vectors, if false, none. 15 | * @return a new instance of {@link WithVectorsSelector} 16 | */ 17 | public static WithVectorsSelector enable(boolean enable) { 18 | return WithVectorsSelector.newBuilder().setEnable(enable).build(); 19 | } 20 | 21 | /** 22 | * List of named vectors to include in response. 23 | * 24 | * @param names The names of vectors. 25 | * @return a new instance of {@link WithVectorsSelector} 26 | */ 27 | public static WithVectorsSelector include(List names) { 28 | return WithVectorsSelector.newBuilder() 29 | .setInclude(Points.VectorsSelector.newBuilder().addAllNames(names)) 30 | .build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/qdrant/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** package */ 2 | @ParametersAreNonnullByDefault 3 | package io.qdrant.client; 4 | 5 | import javax.annotation.ParametersAreNonnullByDefault; 6 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/ApiKeyTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | 7 | import io.grpc.Grpc; 8 | import io.grpc.InsecureChannelCredentials; 9 | import io.grpc.ManagedChannel; 10 | import io.grpc.Status; 11 | import io.grpc.StatusRuntimeException; 12 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckReply; 13 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckRequest; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.concurrent.TimeUnit; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | import org.testcontainers.junit.jupiter.Container; 20 | import org.testcontainers.junit.jupiter.Testcontainers; 21 | import org.testcontainers.qdrant.QdrantContainer; 22 | 23 | @Testcontainers 24 | public class ApiKeyTest { 25 | @Container 26 | private static final QdrantContainer QDRANT_CONTAINER = 27 | new QdrantContainer(DockerImage.QDRANT_IMAGE) 28 | .withEnv("QDRANT__SERVICE__API_KEY", "password!"); 29 | 30 | private ManagedChannel channel; 31 | 32 | @BeforeEach 33 | public void setup() { 34 | channel = 35 | Grpc.newChannelBuilder( 36 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 37 | .build(); 38 | } 39 | 40 | @AfterEach 41 | public void teardown() throws InterruptedException { 42 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 43 | } 44 | 45 | @Test 46 | public void client_with_api_key_can_connect() throws ExecutionException, InterruptedException { 47 | HealthCheckReply healthCheckReply; 48 | try (QdrantGrpcClient grpcClient = 49 | QdrantGrpcClient.newBuilder(channel).withApiKey("password!").build()) { 50 | healthCheckReply = 51 | grpcClient.qdrant().healthCheck(HealthCheckRequest.getDefaultInstance()).get(); 52 | } 53 | 54 | assertNotNull(healthCheckReply.getTitle()); 55 | assertNotNull(healthCheckReply.getVersion()); 56 | } 57 | 58 | @Test 59 | public void client_without_api_key_cannot_connect() { 60 | try (QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build()) { 61 | ExecutionException executionException = 62 | assertThrows( 63 | ExecutionException.class, 64 | () -> grpcClient.qdrant().healthCheck(HealthCheckRequest.getDefaultInstance()).get()); 65 | Throwable cause = executionException.getCause(); 66 | assertEquals(StatusRuntimeException.class, cause.getClass()); 67 | StatusRuntimeException statusRuntimeException = (StatusRuntimeException) cause; 68 | assertEquals(Status.Code.UNAUTHENTICATED, statusRuntimeException.getStatus().getCode()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/CollectionsTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertThrows; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | import io.grpc.Grpc; 9 | import io.grpc.InsecureChannelCredentials; 10 | import io.grpc.ManagedChannel; 11 | import io.grpc.Status; 12 | import io.grpc.StatusRuntimeException; 13 | import io.qdrant.client.grpc.Collections; 14 | import io.qdrant.client.grpc.Collections.AliasDescription; 15 | import io.qdrant.client.grpc.Collections.CollectionInfo; 16 | import io.qdrant.client.grpc.Collections.CollectionStatus; 17 | import io.qdrant.client.grpc.Collections.CreateCollection; 18 | import io.qdrant.client.grpc.Collections.Distance; 19 | import io.qdrant.client.grpc.Collections.VectorParams; 20 | import io.qdrant.client.grpc.Collections.VectorsConfig; 21 | import java.util.Comparator; 22 | import java.util.List; 23 | import java.util.concurrent.ExecutionException; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.stream.Collectors; 26 | import org.junit.jupiter.api.AfterEach; 27 | import org.junit.jupiter.api.BeforeEach; 28 | import org.junit.jupiter.api.Test; 29 | import org.junit.jupiter.api.TestInfo; 30 | import org.testcontainers.junit.jupiter.Container; 31 | import org.testcontainers.junit.jupiter.Testcontainers; 32 | import org.testcontainers.qdrant.QdrantContainer; 33 | 34 | @Testcontainers 35 | class CollectionsTest { 36 | @Container 37 | private static final QdrantContainer QDRANT_CONTAINER = 38 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 39 | 40 | private QdrantClient client; 41 | private ManagedChannel channel; 42 | private String testName; 43 | 44 | private static CreateCollection getCreateCollection(String collectionName) { 45 | return CreateCollection.newBuilder() 46 | .setCollectionName(collectionName) 47 | .setVectorsConfig( 48 | VectorsConfig.newBuilder() 49 | .setParams( 50 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build()) 51 | .build()) 52 | .build(); 53 | } 54 | 55 | @BeforeEach 56 | public void setup(TestInfo testInfo) { 57 | testName = testInfo.getDisplayName().replace("()", ""); 58 | channel = 59 | Grpc.newChannelBuilder( 60 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 61 | .build(); 62 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build(); 63 | client = new QdrantClient(grpcClient); 64 | } 65 | 66 | @AfterEach 67 | public void teardown() throws Exception { 68 | List collectionNames = client.listCollectionsAsync().get(); 69 | for (String collectionName : collectionNames) { 70 | client.deleteCollectionAsync(collectionName).get(); 71 | } 72 | client.close(); 73 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 74 | } 75 | 76 | @Test 77 | void createCollection() throws ExecutionException, InterruptedException { 78 | client 79 | .createCollectionAsync( 80 | testName, VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build()) 81 | .get(); 82 | 83 | List collections = client.listCollectionsAsync().get(); 84 | assertTrue(collections.contains(testName)); 85 | } 86 | 87 | @Test 88 | void createCollection_for_existing_collection() throws ExecutionException, InterruptedException { 89 | CreateCollection createCollection = getCreateCollection(testName); 90 | client.createCollectionAsync(createCollection).get(); 91 | 92 | ExecutionException exception = 93 | assertThrows( 94 | ExecutionException.class, () -> client.createCollectionAsync(createCollection).get()); 95 | assertEquals(StatusRuntimeException.class, exception.getCause().getClass()); 96 | } 97 | 98 | @Test 99 | void createCollection_with_no_collection_name() { 100 | CreateCollection createCollection = 101 | CreateCollection.newBuilder() 102 | .setVectorsConfig( 103 | VectorsConfig.newBuilder() 104 | .setParams( 105 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(4).build()) 106 | .build()) 107 | .build(); 108 | 109 | assertThrows( 110 | IllegalArgumentException.class, () -> client.createCollectionAsync(createCollection).get()); 111 | } 112 | 113 | @Test 114 | public void recreateCollection() throws ExecutionException, InterruptedException { 115 | CreateCollection createCollection = getCreateCollection(testName); 116 | client.createCollectionAsync(createCollection).get(); 117 | client.recreateCollectionAsync(createCollection).get(); 118 | 119 | List collections = client.listCollectionsAsync().get(); 120 | assertTrue(collections.contains(testName)); 121 | } 122 | 123 | @Test 124 | public void updateCollection() throws ExecutionException, InterruptedException { 125 | CreateCollection createCollection = getCreateCollection(testName); 126 | client.createCollectionAsync(createCollection).get(); 127 | client 128 | .updateCollectionAsync( 129 | Collections.UpdateCollection.newBuilder() 130 | .setCollectionName(testName) 131 | .setVectorsConfig( 132 | Collections.VectorsConfigDiff.newBuilder() 133 | .setParams( 134 | Collections.VectorParamsDiff.newBuilder().setOnDisk(false).build()) 135 | .build()) 136 | .build()) 137 | .get(); 138 | } 139 | 140 | @Test 141 | public void updateCollection_with_no_changes() throws ExecutionException, InterruptedException { 142 | CreateCollection createCollection = getCreateCollection(testName); 143 | client.createCollectionAsync(createCollection).get(); 144 | client 145 | .updateCollectionAsync( 146 | Collections.UpdateCollection.newBuilder().setCollectionName(testName).build()) 147 | .get(); 148 | } 149 | 150 | @Test 151 | public void deleteCollection() throws ExecutionException, InterruptedException { 152 | CreateCollection createCollection = getCreateCollection(testName); 153 | client.createCollectionAsync(createCollection).get(); 154 | client.deleteCollectionAsync(testName).get(); 155 | 156 | List collections = client.listCollectionsAsync().get(); 157 | assertTrue(collections.isEmpty()); 158 | } 159 | 160 | @Test 161 | public void deleteCollection_with_missing_collection() { 162 | ExecutionException exception = 163 | assertThrows(ExecutionException.class, () -> client.deleteCollectionAsync(testName).get()); 164 | Throwable cause = exception.getCause(); 165 | assertEquals(QdrantException.class, cause.getClass()); 166 | } 167 | 168 | @Test 169 | public void getCollectionInfo() throws ExecutionException, InterruptedException { 170 | CreateCollection createCollection = getCreateCollection(testName); 171 | client.createCollectionAsync(createCollection).get(); 172 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); 173 | assertEquals(CollectionStatus.Green, collectionInfo.getStatus()); 174 | } 175 | 176 | @Test 177 | public void getCollectionInfo_with_missing_collection() { 178 | ExecutionException exception = 179 | assertThrows(ExecutionException.class, () -> client.getCollectionInfoAsync(testName).get()); 180 | Throwable cause = exception.getCause(); 181 | assertEquals(StatusRuntimeException.class, cause.getClass()); 182 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause; 183 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode()); 184 | } 185 | 186 | @Test 187 | public void collectionExists() throws ExecutionException, InterruptedException { 188 | assertFalse(client.collectionExistsAsync(testName).get()); 189 | 190 | CreateCollection createCollection = getCreateCollection(testName); 191 | client.createCollectionAsync(createCollection).get(); 192 | assertTrue(client.collectionExistsAsync(testName).get()); 193 | 194 | client.deleteCollectionAsync(testName).get(); 195 | assertFalse(client.collectionExistsAsync(testName).get()); 196 | } 197 | 198 | @Test 199 | public void createAlias() throws ExecutionException, InterruptedException { 200 | CreateCollection createCollection = getCreateCollection(testName); 201 | client.createCollectionAsync(createCollection).get(); 202 | client.createAliasAsync("alias_1", testName).get(); 203 | } 204 | 205 | @Test 206 | public void listAliases() throws ExecutionException, InterruptedException { 207 | CreateCollection createCollection = getCreateCollection(testName); 208 | client.createCollectionAsync(createCollection).get(); 209 | client.createAliasAsync("alias_1", testName).get(); 210 | client.createAliasAsync("alias_2", testName).get(); 211 | 212 | List aliasDescriptions = client.listAliasesAsync().get(); 213 | 214 | List sorted = 215 | aliasDescriptions.stream() 216 | .sorted(Comparator.comparing(AliasDescription::getAliasName)) 217 | .collect(Collectors.toList()); 218 | 219 | assertEquals(2, sorted.size()); 220 | assertEquals("alias_1", sorted.get(0).getAliasName()); 221 | assertEquals(testName, sorted.get(0).getCollectionName()); 222 | assertEquals("alias_2", sorted.get(1).getAliasName()); 223 | assertEquals(testName, sorted.get(1).getCollectionName()); 224 | } 225 | 226 | @Test 227 | public void listCollectionAliases() throws ExecutionException, InterruptedException { 228 | CreateCollection createCollection = getCreateCollection(testName); 229 | client.createCollectionAsync(createCollection).get(); 230 | client.createAliasAsync("alias_1", testName).get(); 231 | client.createAliasAsync("alias_2", testName).get(); 232 | 233 | List aliases = client.listCollectionAliasesAsync(testName).get(); 234 | 235 | List sorted = aliases.stream().sorted().collect(Collectors.toList()); 236 | 237 | assertEquals(2, sorted.size()); 238 | assertEquals("alias_1", sorted.get(0)); 239 | assertEquals("alias_2", sorted.get(1)); 240 | } 241 | 242 | @Test 243 | public void renameAlias() throws ExecutionException, InterruptedException { 244 | CreateCollection createCollection = getCreateCollection(testName); 245 | client.createCollectionAsync(createCollection).get(); 246 | client.createAliasAsync("alias_1", testName).get(); 247 | client.renameAliasAsync("alias_1", "alias_2").get(); 248 | 249 | List aliases = client.listCollectionAliasesAsync(testName).get(); 250 | 251 | List sorted = aliases.stream().sorted().collect(Collectors.toList()); 252 | 253 | assertEquals(1, sorted.size()); 254 | assertEquals("alias_2", sorted.get(0)); 255 | } 256 | 257 | @Test 258 | public void deleteAlias() throws ExecutionException, InterruptedException { 259 | CreateCollection createCollection = getCreateCollection(testName); 260 | client.createCollectionAsync(createCollection).get(); 261 | client.createAliasAsync("alias_1", testName).get(); 262 | client.deleteAliasAsync("alias_1").get(); 263 | 264 | List aliases = client.listCollectionAliasesAsync(testName).get(); 265 | assertTrue(aliases.isEmpty()); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/DockerImage.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | public class DockerImage { 4 | 5 | public static final String QDRANT_IMAGE = "qdrant/qdrant:" + System.getProperty("qdrantVersion"); 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/HealthTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import io.grpc.Grpc; 6 | import io.grpc.InsecureChannelCredentials; 7 | import io.grpc.ManagedChannel; 8 | import io.qdrant.client.grpc.QdrantOuterClass.HealthCheckReply; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.concurrent.TimeUnit; 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | import org.testcontainers.junit.jupiter.Container; 15 | import org.testcontainers.junit.jupiter.Testcontainers; 16 | import org.testcontainers.qdrant.QdrantContainer; 17 | 18 | @Testcontainers 19 | class HealthTest { 20 | @Container 21 | private static final QdrantContainer QDRANT_CONTAINER = 22 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 23 | 24 | private QdrantClient client; 25 | private ManagedChannel channel; 26 | 27 | @BeforeEach 28 | public void setup() { 29 | channel = 30 | Grpc.newChannelBuilder( 31 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 32 | .build(); 33 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build(); 34 | client = new QdrantClient(grpcClient); 35 | } 36 | 37 | @AfterEach 38 | public void teardown() throws Exception { 39 | client.close(); 40 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 41 | } 42 | 43 | @Test 44 | public void healthCheck() throws ExecutionException, InterruptedException { 45 | HealthCheckReply healthCheckReply = client.healthCheckAsync().get(); 46 | assertNotNull(healthCheckReply.getTitle()); 47 | assertNotNull(healthCheckReply.getVersion()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/PointsTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static io.qdrant.client.ConditionFactory.hasId; 4 | import static io.qdrant.client.ConditionFactory.matchKeyword; 5 | import static io.qdrant.client.PointIdFactory.id; 6 | import static io.qdrant.client.QueryFactory.fusion; 7 | import static io.qdrant.client.QueryFactory.nearest; 8 | import static io.qdrant.client.QueryFactory.orderBy; 9 | import static io.qdrant.client.QueryFactory.sample; 10 | import static io.qdrant.client.TargetVectorFactory.targetVector; 11 | import static io.qdrant.client.ValueFactory.value; 12 | import static io.qdrant.client.VectorFactory.vector; 13 | import static io.qdrant.client.VectorsFactory.vectors; 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | import static org.junit.jupiter.api.Assertions.assertFalse; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | import io.grpc.Grpc; 19 | import io.grpc.InsecureChannelCredentials; 20 | import io.grpc.ManagedChannel; 21 | import io.qdrant.client.grpc.Collections; 22 | import io.qdrant.client.grpc.Collections.CollectionInfo; 23 | import io.qdrant.client.grpc.Collections.CreateCollection; 24 | import io.qdrant.client.grpc.Collections.Distance; 25 | import io.qdrant.client.grpc.Collections.PayloadSchemaType; 26 | import io.qdrant.client.grpc.Collections.VectorParams; 27 | import io.qdrant.client.grpc.Collections.VectorsConfig; 28 | import io.qdrant.client.grpc.Points; 29 | import io.qdrant.client.grpc.Points.BatchResult; 30 | import io.qdrant.client.grpc.Points.DiscoverPoints; 31 | import io.qdrant.client.grpc.Points.Filter; 32 | import io.qdrant.client.grpc.Points.Fusion; 33 | import io.qdrant.client.grpc.Points.PointGroup; 34 | import io.qdrant.client.grpc.Points.PointStruct; 35 | import io.qdrant.client.grpc.Points.PointVectors; 36 | import io.qdrant.client.grpc.Points.PointsIdsList; 37 | import io.qdrant.client.grpc.Points.PointsSelector; 38 | import io.qdrant.client.grpc.Points.PointsUpdateOperation; 39 | import io.qdrant.client.grpc.Points.PointsUpdateOperation.ClearPayload; 40 | import io.qdrant.client.grpc.Points.PointsUpdateOperation.UpdateVectors; 41 | import io.qdrant.client.grpc.Points.PrefetchQuery; 42 | import io.qdrant.client.grpc.Points.QueryPointGroups; 43 | import io.qdrant.client.grpc.Points.QueryPoints; 44 | import io.qdrant.client.grpc.Points.RecommendPointGroups; 45 | import io.qdrant.client.grpc.Points.RecommendPoints; 46 | import io.qdrant.client.grpc.Points.RetrievedPoint; 47 | import io.qdrant.client.grpc.Points.Sample; 48 | import io.qdrant.client.grpc.Points.ScoredPoint; 49 | import io.qdrant.client.grpc.Points.ScrollPoints; 50 | import io.qdrant.client.grpc.Points.ScrollResponse; 51 | import io.qdrant.client.grpc.Points.SearchPointGroups; 52 | import io.qdrant.client.grpc.Points.SearchPoints; 53 | import io.qdrant.client.grpc.Points.UpdateResult; 54 | import io.qdrant.client.grpc.Points.UpdateStatus; 55 | import io.qdrant.client.grpc.Points.VectorsOutput; 56 | import java.util.Arrays; 57 | import java.util.List; 58 | import java.util.concurrent.ExecutionException; 59 | import java.util.concurrent.TimeUnit; 60 | import org.junit.jupiter.api.AfterEach; 61 | import org.junit.jupiter.api.BeforeEach; 62 | import org.junit.jupiter.api.Test; 63 | import org.junit.jupiter.api.TestInfo; 64 | import org.testcontainers.junit.jupiter.Container; 65 | import org.testcontainers.junit.jupiter.Testcontainers; 66 | import org.testcontainers.qdrant.QdrantContainer; 67 | import org.testcontainers.shaded.com.google.common.collect.ImmutableList; 68 | import org.testcontainers.shaded.com.google.common.collect.ImmutableMap; 69 | import org.testcontainers.shaded.com.google.common.collect.ImmutableSet; 70 | 71 | @Testcontainers 72 | class PointsTest { 73 | @Container 74 | private static final QdrantContainer QDRANT_CONTAINER = 75 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 76 | 77 | private QdrantClient client; 78 | private ManagedChannel channel; 79 | private String testName; 80 | 81 | @BeforeEach 82 | public void setup(TestInfo testInfo) { 83 | testName = testInfo.getDisplayName().replace("()", ""); 84 | channel = 85 | Grpc.newChannelBuilder( 86 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 87 | .build(); 88 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build(); 89 | client = new QdrantClient(grpcClient); 90 | } 91 | 92 | @AfterEach 93 | public void teardown() throws Exception { 94 | List collectionNames = client.listCollectionsAsync().get(); 95 | for (String collectionName : collectionNames) { 96 | client.deleteCollectionAsync(collectionName).get(); 97 | } 98 | client.close(); 99 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 100 | } 101 | 102 | @Test 103 | public void retrieve() throws ExecutionException, InterruptedException { 104 | createAndSeedCollection(testName); 105 | 106 | List points = client.retrieveAsync(testName, id(9), null).get(); 107 | 108 | assertEquals(1, points.size()); 109 | RetrievedPoint point = points.get(0); 110 | assertEquals(id(9), point.getId()); 111 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); 112 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); 113 | assertEquals(value(2), point.getPayloadMap().get("bar")); 114 | assertEquals(VectorsOutput.getDefaultInstance(), point.getVectors()); 115 | } 116 | 117 | @Test 118 | public void retrieve_with_vector_without_payload() 119 | throws ExecutionException, InterruptedException { 120 | createAndSeedCollection(testName); 121 | 122 | List points = client.retrieveAsync(testName, id(8), false, true, null).get(); 123 | 124 | assertEquals(1, points.size()); 125 | RetrievedPoint point = points.get(0); 126 | assertEquals(id(8), point.getId()); 127 | assertTrue(point.getPayloadMap().isEmpty()); 128 | assertEquals( 129 | VectorsOutput.VectorsOptionsCase.VECTOR, point.getVectors().getVectorsOptionsCase()); 130 | } 131 | 132 | @Test 133 | public void setPayload() throws ExecutionException, InterruptedException { 134 | createAndSeedCollection(testName); 135 | 136 | client 137 | .setPayloadAsync( 138 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null) 139 | .get(); 140 | 141 | List points = client.retrieveAsync(testName, id(9), null).get(); 142 | 143 | assertEquals(1, points.size()); 144 | RetrievedPoint point = points.get(0); 145 | assertEquals(id(9), point.getId()); 146 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); 147 | assertEquals(value("some bar"), point.getPayloadMap().get("bar")); 148 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); 149 | } 150 | 151 | @Test 152 | public void overwritePayload() throws ExecutionException, InterruptedException { 153 | createAndSeedCollection(testName); 154 | 155 | client 156 | .overwritePayloadAsync( 157 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null) 158 | .get(); 159 | 160 | List points = client.retrieveAsync(testName, id(9), null).get(); 161 | 162 | assertEquals(1, points.size()); 163 | RetrievedPoint point = points.get(0); 164 | assertEquals(id(9), point.getId()); 165 | assertEquals(ImmutableSet.of("bar"), point.getPayloadMap().keySet()); 166 | assertEquals(value("some bar"), point.getPayloadMap().get("bar")); 167 | } 168 | 169 | @Test 170 | public void deletePayload() throws ExecutionException, InterruptedException { 171 | createAndSeedCollection(testName); 172 | 173 | client 174 | .setPayloadAsync( 175 | testName, ImmutableMap.of("bar", value("some bar")), id(9), null, null, null) 176 | .get(); 177 | 178 | client.deletePayloadAsync(testName, ImmutableList.of("foo"), id(9), null, null, null).get(); 179 | 180 | List points = client.retrieveAsync(testName, id(9), null).get(); 181 | 182 | assertEquals(1, points.size()); 183 | RetrievedPoint point = points.get(0); 184 | assertEquals(id(9), point.getId()); 185 | assertEquals(ImmutableSet.of("bar", "date"), point.getPayloadMap().keySet()); 186 | assertEquals(value("some bar"), point.getPayloadMap().get("bar")); 187 | } 188 | 189 | @Test 190 | public void clearPayload() throws ExecutionException, InterruptedException { 191 | createAndSeedCollection(testName); 192 | 193 | client.clearPayloadAsync(testName, id(9), true, null, null).get(); 194 | 195 | List points = client.retrieveAsync(testName, id(9), null).get(); 196 | 197 | assertEquals(1, points.size()); 198 | RetrievedPoint point = points.get(0); 199 | assertEquals(id(9), point.getId()); 200 | assertTrue(point.getPayloadMap().isEmpty()); 201 | } 202 | 203 | @Test 204 | public void createFieldIndex() throws ExecutionException, InterruptedException { 205 | createAndSeedCollection(testName); 206 | 207 | UpdateResult result = 208 | client 209 | .createPayloadIndexAsync( 210 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null) 211 | .get(); 212 | 213 | assertEquals(UpdateStatus.Completed, result.getStatus()); 214 | 215 | result = 216 | client 217 | .createPayloadIndexAsync( 218 | testName, "bar", PayloadSchemaType.Integer, null, null, null, null) 219 | .get(); 220 | 221 | assertEquals(UpdateStatus.Completed, result.getStatus()); 222 | 223 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); 224 | assertEquals(ImmutableSet.of("foo", "bar"), collectionInfo.getPayloadSchemaMap().keySet()); 225 | assertEquals( 226 | PayloadSchemaType.Keyword, collectionInfo.getPayloadSchemaMap().get("foo").getDataType()); 227 | assertEquals( 228 | PayloadSchemaType.Integer, collectionInfo.getPayloadSchemaMap().get("bar").getDataType()); 229 | } 230 | 231 | @Test 232 | public void createDatetimeFieldIndex() throws ExecutionException, InterruptedException { 233 | createAndSeedCollection(testName); 234 | 235 | UpdateResult result = 236 | client 237 | .createPayloadIndexAsync( 238 | testName, "date", PayloadSchemaType.Datetime, null, null, null, null) 239 | .get(); 240 | 241 | assertEquals(UpdateStatus.Completed, result.getStatus()); 242 | 243 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); 244 | assertEquals(ImmutableSet.of("date"), collectionInfo.getPayloadSchemaMap().keySet()); 245 | assertEquals( 246 | PayloadSchemaType.Datetime, collectionInfo.getPayloadSchemaMap().get("date").getDataType()); 247 | } 248 | 249 | @Test 250 | public void deleteFieldIndex() throws ExecutionException, InterruptedException { 251 | createAndSeedCollection(testName); 252 | 253 | UpdateResult result = 254 | client 255 | .createPayloadIndexAsync( 256 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null) 257 | .get(); 258 | assertEquals(UpdateStatus.Completed, result.getStatus()); 259 | 260 | result = client.deletePayloadIndexAsync(testName, "foo", null, null, null).get(); 261 | assertEquals(UpdateStatus.Completed, result.getStatus()); 262 | } 263 | 264 | @Test 265 | public void search() throws ExecutionException, InterruptedException { 266 | createAndSeedCollection(testName); 267 | 268 | List points = 269 | client 270 | .searchAsync( 271 | SearchPoints.newBuilder() 272 | .setCollectionName(testName) 273 | .setWithPayload(WithPayloadSelectorFactory.enable(true)) 274 | .addAllVector(ImmutableList.of(10.4f, 11.4f)) 275 | .setLimit(1) 276 | .build()) 277 | .get(); 278 | 279 | assertEquals(1, points.size()); 280 | ScoredPoint point = points.get(0); 281 | assertEquals(id(9), point.getId()); 282 | assertEquals(ImmutableSet.of("foo", "bar", "date"), point.getPayloadMap().keySet()); 283 | assertEquals(value("goodbye"), point.getPayloadMap().get("foo")); 284 | assertEquals(value(2), point.getPayloadMap().get("bar")); 285 | assertFalse(point.getVectors().hasVector()); 286 | } 287 | 288 | @Test 289 | public void searchBatch() throws ExecutionException, InterruptedException { 290 | createAndSeedCollection(testName); 291 | 292 | List batchResults = 293 | client 294 | .searchBatchAsync( 295 | testName, 296 | ImmutableList.of( 297 | SearchPoints.newBuilder() 298 | .addAllVector(ImmutableList.of(10.4f, 11.4f)) 299 | .setLimit(1) 300 | .build(), 301 | SearchPoints.newBuilder() 302 | .addAllVector(ImmutableList.of(3.4f, 4.4f)) 303 | .setLimit(1) 304 | .build()), 305 | null) 306 | .get(); 307 | 308 | assertEquals(2, batchResults.size()); 309 | BatchResult result = batchResults.get(0); 310 | assertEquals(1, result.getResultCount()); 311 | assertEquals(id(9), result.getResult(0).getId()); 312 | result = batchResults.get(1); 313 | assertEquals(1, result.getResultCount()); 314 | assertEquals(id(8), result.getResult(0).getId()); 315 | } 316 | 317 | @Test 318 | public void searchGroups() throws ExecutionException, InterruptedException { 319 | createAndSeedCollection(testName); 320 | 321 | client 322 | .upsertAsync( 323 | testName, 324 | ImmutableList.of( 325 | PointStruct.newBuilder() 326 | .setId(id(10)) 327 | .setVectors(VectorsFactory.vectors(30f, 31f)) 328 | .putAllPayload(ImmutableMap.of("foo", value("hello"))) 329 | .build())) 330 | .get(); 331 | 332 | List groups = 333 | client 334 | .searchGroupsAsync( 335 | SearchPointGroups.newBuilder() 336 | .setCollectionName(testName) 337 | .addAllVector(ImmutableList.of(10.4f, 11.4f)) 338 | .setGroupBy("foo") 339 | .setGroupSize(2) 340 | .setLimit(10) 341 | .build()) 342 | .get(); 343 | 344 | assertEquals(2, groups.size()); 345 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 2).count()); 346 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 1).count()); 347 | } 348 | 349 | @Test 350 | public void scroll() throws ExecutionException, InterruptedException { 351 | createAndSeedCollection(testName); 352 | 353 | ScrollResponse scrollResponse = 354 | client 355 | .scrollAsync(ScrollPoints.newBuilder().setCollectionName(testName).setLimit(1).build()) 356 | .get(); 357 | 358 | assertEquals(1, scrollResponse.getResultCount()); 359 | assertTrue(scrollResponse.hasNextPageOffset()); 360 | 361 | scrollResponse = 362 | client 363 | .scrollAsync( 364 | ScrollPoints.newBuilder() 365 | .setCollectionName(testName) 366 | .setLimit(1) 367 | .setOffset(scrollResponse.getNextPageOffset()) 368 | .build()) 369 | .get(); 370 | 371 | assertEquals(1, scrollResponse.getResultCount()); 372 | assertFalse(scrollResponse.hasNextPageOffset()); 373 | } 374 | 375 | @Test 376 | public void scrollWithOrdering() throws ExecutionException, InterruptedException { 377 | createAndSeedCollection(testName); 378 | 379 | Collections.PayloadIndexParams params = 380 | Collections.PayloadIndexParams.newBuilder() 381 | .setIntegerIndexParams( 382 | Collections.IntegerIndexParams.newBuilder().setLookup(false).setRange(true).build()) 383 | .build(); 384 | 385 | UpdateResult resultIndex = 386 | client 387 | .createPayloadIndexAsync( 388 | testName, "bar", PayloadSchemaType.Integer, params, true, null, null) 389 | .get(); 390 | 391 | assertEquals(UpdateStatus.Completed, resultIndex.getStatus()); 392 | 393 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); 394 | assertEquals(ImmutableSet.of("bar"), collectionInfo.getPayloadSchemaMap().keySet()); 395 | 396 | ScrollResponse scrollResponse = 397 | client 398 | .scrollAsync( 399 | ScrollPoints.newBuilder() 400 | .setCollectionName(testName) 401 | .setLimit(1) 402 | .setOrderBy( 403 | Points.OrderBy.newBuilder() 404 | .setDirection(Points.Direction.Desc) 405 | .setKey("bar") 406 | .build()) 407 | .build()) 408 | .get(); 409 | 410 | assertEquals(1, scrollResponse.getResultCount()); 411 | assertFalse(scrollResponse.hasNextPageOffset()); 412 | assertEquals(scrollResponse.getResult(0).getId(), id(9)); 413 | } 414 | 415 | @Test 416 | public void recommend() throws ExecutionException, InterruptedException { 417 | createAndSeedCollection(testName); 418 | 419 | List points = 420 | client 421 | .recommendAsync( 422 | RecommendPoints.newBuilder() 423 | .setCollectionName(testName) 424 | .addPositive(id(8)) 425 | .setLimit(1) 426 | .build()) 427 | .get(); 428 | 429 | assertEquals(1, points.size()); 430 | assertEquals(id(9), points.get(0).getId()); 431 | } 432 | 433 | @Test 434 | public void recommendBatch() throws ExecutionException, InterruptedException { 435 | createAndSeedCollection(testName); 436 | 437 | List batchResults = 438 | client 439 | .recommendBatchAsync( 440 | testName, 441 | ImmutableList.of( 442 | RecommendPoints.newBuilder() 443 | .setCollectionName(testName) 444 | .addPositive(id(8)) 445 | .setLimit(1) 446 | .build(), 447 | RecommendPoints.newBuilder() 448 | .setCollectionName(testName) 449 | .addPositive(id(9)) 450 | .setLimit(1) 451 | .build()), 452 | null) 453 | .get(); 454 | 455 | assertEquals(2, batchResults.size()); 456 | BatchResult result = batchResults.get(0); 457 | assertEquals(1, result.getResultCount()); 458 | assertEquals(id(9), result.getResult(0).getId()); 459 | result = batchResults.get(1); 460 | assertEquals(1, result.getResultCount()); 461 | assertEquals(id(8), result.getResult(0).getId()); 462 | } 463 | 464 | @Test 465 | public void recommendGroups() throws ExecutionException, InterruptedException { 466 | createAndSeedCollection(testName); 467 | 468 | client 469 | .upsertAsync( 470 | testName, 471 | ImmutableList.of( 472 | PointStruct.newBuilder() 473 | .setId(id(10)) 474 | .setVectors(VectorsFactory.vectors(30f, 31f)) 475 | .putAllPayload(ImmutableMap.of("foo", value("hello"))) 476 | .build())) 477 | .get(); 478 | 479 | List groups = 480 | client 481 | .recommendGroupsAsync( 482 | RecommendPointGroups.newBuilder() 483 | .setCollectionName(testName) 484 | .setGroupBy("foo") 485 | .addPositive(id(9)) 486 | .setGroupSize(2) 487 | .setLimit(10) 488 | .build()) 489 | .get(); 490 | 491 | assertEquals(1, groups.size()); 492 | assertEquals(2, groups.get(0).getHitsCount()); 493 | } 494 | 495 | @Test 496 | public void discover() throws ExecutionException, InterruptedException { 497 | createAndSeedCollection(testName); 498 | 499 | List points = 500 | client 501 | .discoverAsync( 502 | DiscoverPoints.newBuilder() 503 | .setCollectionName(testName) 504 | .setTarget(targetVector(vector(ImmutableList.of(10.4f, 11.4f)))) 505 | .setLimit(1) 506 | .build()) 507 | .get(); 508 | 509 | assertEquals(1, points.size()); 510 | assertEquals(id(9), points.get(0).getId()); 511 | } 512 | 513 | @Test 514 | public void discoverBatch() throws ExecutionException, InterruptedException { 515 | createAndSeedCollection(testName); 516 | 517 | List batchResults = 518 | client 519 | .discoverBatchAsync( 520 | testName, 521 | ImmutableList.of( 522 | DiscoverPoints.newBuilder() 523 | .setCollectionName(testName) 524 | .setTarget(targetVector(vector(ImmutableList.of(10.4f, 11.4f)))) 525 | .setLimit(1) 526 | .build(), 527 | DiscoverPoints.newBuilder() 528 | .setCollectionName(testName) 529 | .setTarget(targetVector(vector(ImmutableList.of(3.5f, 4.5f)))) 530 | .setLimit(1) 531 | .build()), 532 | null) 533 | .get(); 534 | 535 | assertEquals(2, batchResults.size()); 536 | BatchResult result = batchResults.get(0); 537 | assertEquals(1, result.getResultCount()); 538 | assertEquals(id(9), result.getResult(0).getId()); 539 | result = batchResults.get(1); 540 | assertEquals(1, result.getResultCount()); 541 | assertEquals(id(8), result.getResult(0).getId()); 542 | } 543 | 544 | @Test 545 | public void count() throws ExecutionException, InterruptedException { 546 | createAndSeedCollection(testName); 547 | Long count = client.countAsync(testName).get(); 548 | assertEquals(2, count); 549 | } 550 | 551 | @Test 552 | public void count_with_filter() throws ExecutionException, InterruptedException { 553 | createAndSeedCollection(testName); 554 | Long count = 555 | client 556 | .countAsync( 557 | testName, 558 | Filter.newBuilder() 559 | .addMust(hasId(id(9))) 560 | .addMust(matchKeyword("foo", "goodbye")) 561 | .build(), 562 | null) 563 | .get(); 564 | assertEquals(1, count); 565 | } 566 | 567 | @Test 568 | public void delete_by_id() throws ExecutionException, InterruptedException { 569 | createAndSeedCollection(testName); 570 | 571 | List points = client.retrieveAsync(testName, id(8), false, false, null).get(); 572 | 573 | assertEquals(1, points.size()); 574 | 575 | client.deleteAsync(testName, ImmutableList.of(id(8))).get(); 576 | 577 | points = client.retrieveAsync(testName, id(8), false, false, null).get(); 578 | 579 | assertEquals(0, points.size()); 580 | } 581 | 582 | @Test 583 | public void delete_by_filter() throws ExecutionException, InterruptedException { 584 | createAndSeedCollection(testName); 585 | 586 | List points = client.retrieveAsync(testName, id(8), false, false, null).get(); 587 | 588 | assertEquals(1, points.size()); 589 | 590 | client 591 | .deleteAsync(testName, Filter.newBuilder().addMust(matchKeyword("foo", "hello")).build()) 592 | .get(); 593 | 594 | points = client.retrieveAsync(testName, id(8), false, false, null).get(); 595 | 596 | assertEquals(0, points.size()); 597 | } 598 | 599 | @Test 600 | public void batchPointUpdate() throws ExecutionException, InterruptedException { 601 | createAndSeedCollection(testName); 602 | 603 | List operations = 604 | Arrays.asList( 605 | PointsUpdateOperation.newBuilder() 606 | .setClearPayload( 607 | ClearPayload.newBuilder() 608 | .setPoints( 609 | PointsSelector.newBuilder() 610 | .setPoints(PointsIdsList.newBuilder().addIds(id(9)))) 611 | .build()) 612 | .build(), 613 | PointsUpdateOperation.newBuilder() 614 | .setUpdateVectors( 615 | UpdateVectors.newBuilder() 616 | .addPoints( 617 | PointVectors.newBuilder().setId(id(9)).setVectors(vectors(0.6f, 0.7f)))) 618 | .build()); 619 | 620 | List response = client.batchUpdateAsync(testName, operations).get(); 621 | 622 | response.forEach(result -> assertEquals(UpdateStatus.Completed, result.getStatus())); 623 | } 624 | 625 | @Test 626 | public void query() throws ExecutionException, InterruptedException { 627 | createAndSeedCollection(testName); 628 | 629 | List points = 630 | client.queryAsync(QueryPoints.newBuilder().setCollectionName(testName).build()).get(); 631 | 632 | assertEquals(2, points.size()); 633 | assertEquals(points.get(0).getId(), id(8)); 634 | assertEquals(points.get(1).getId(), id(9)); 635 | 636 | points = 637 | client 638 | .queryAsync(QueryPoints.newBuilder().setCollectionName(testName).setLimit(1).build()) 639 | .get(); 640 | 641 | assertEquals(1, points.size()); 642 | assertEquals(id(8), points.get(0).getId()); 643 | } 644 | 645 | @Test 646 | public void queryWithFilter() throws ExecutionException, InterruptedException { 647 | createAndSeedCollection(testName); 648 | 649 | List points = 650 | client 651 | .queryAsync( 652 | QueryPoints.newBuilder() 653 | .setCollectionName(testName) 654 | .setFilter(Filter.newBuilder().addMust(matchKeyword("foo", "hello")).build()) 655 | .build()) 656 | .get(); 657 | 658 | assertEquals(1, points.size()); 659 | assertEquals(id(8), points.get(0).getId()); 660 | } 661 | 662 | @Test 663 | public void queryNearestWithID() throws ExecutionException, InterruptedException { 664 | createAndSeedCollection(testName); 665 | 666 | List points = 667 | client 668 | .queryAsync( 669 | QueryPoints.newBuilder().setCollectionName(testName).setQuery(nearest(8)).build()) 670 | .get(); 671 | 672 | assertEquals(1, points.size()); 673 | assertEquals(id(9), points.get(0).getId()); 674 | } 675 | 676 | @Test 677 | public void queryNearestWithVector() throws ExecutionException, InterruptedException { 678 | createAndSeedCollection(testName); 679 | 680 | List points = 681 | client 682 | .queryAsync( 683 | QueryPoints.newBuilder() 684 | .setCollectionName(testName) 685 | .setQuery(nearest(10.5f, 11.5f)) 686 | .build()) 687 | .get(); 688 | 689 | assertEquals(2, points.size()); 690 | assertEquals(id(9), points.get(0).getId()); 691 | } 692 | 693 | @Test 694 | public void queryOrderBy() throws ExecutionException, InterruptedException { 695 | createAndSeedCollection(testName); 696 | 697 | Collections.PayloadIndexParams params = 698 | Collections.PayloadIndexParams.newBuilder() 699 | .setIntegerIndexParams( 700 | Collections.IntegerIndexParams.newBuilder().setLookup(false).setRange(true).build()) 701 | .build(); 702 | 703 | UpdateResult resultIndex = 704 | client 705 | .createPayloadIndexAsync( 706 | testName, "bar", PayloadSchemaType.Integer, params, true, null, null) 707 | .get(); 708 | 709 | assertEquals(UpdateStatus.Completed, resultIndex.getStatus()); 710 | 711 | CollectionInfo collectionInfo = client.getCollectionInfoAsync(testName).get(); 712 | assertEquals(ImmutableSet.of("bar"), collectionInfo.getPayloadSchemaMap().keySet()); 713 | assertEquals( 714 | PayloadSchemaType.Integer, collectionInfo.getPayloadSchemaMap().get("bar").getDataType()); 715 | 716 | List points = 717 | client 718 | .queryAsync( 719 | QueryPoints.newBuilder() 720 | .setCollectionName(testName) 721 | .setLimit(1) 722 | .setQuery(orderBy("bar")) 723 | .build()) 724 | .get(); 725 | 726 | assertEquals(1, points.size()); 727 | assertEquals(id(8), points.get(0).getId()); 728 | } 729 | 730 | @Test 731 | public void queryWithPrefetchLimit() throws ExecutionException, InterruptedException { 732 | createAndSeedCollection(testName); 733 | 734 | List points = 735 | client 736 | .queryAsync( 737 | QueryPoints.newBuilder() 738 | .addPrefetch(PrefetchQuery.newBuilder().setLimit(1).build()) 739 | .setCollectionName(testName) 740 | .setQuery(nearest(10.5f, 11.5f)) 741 | .build()) 742 | .get(); 743 | 744 | assertEquals(1, points.size()); 745 | } 746 | 747 | @Test 748 | public void queryWithPrefetchAndFusion() throws ExecutionException, InterruptedException { 749 | createAndSeedCollection(testName); 750 | 751 | List points = 752 | client 753 | .queryAsync( 754 | QueryPoints.newBuilder() 755 | .addPrefetch(PrefetchQuery.newBuilder().setQuery(nearest(10.5f, 11.5f)).build()) 756 | .addPrefetch(PrefetchQuery.newBuilder().setQuery(nearest(3.5f, 4.5f)).build()) 757 | .setCollectionName(testName) 758 | .setQuery(fusion(Fusion.RRF)) 759 | .build()) 760 | .get(); 761 | 762 | assertEquals(2, points.size()); 763 | } 764 | 765 | @Test 766 | public void queryWithSampling() throws ExecutionException, InterruptedException { 767 | createAndSeedCollection(testName); 768 | 769 | List points = 770 | client 771 | .queryAsync( 772 | QueryPoints.newBuilder() 773 | .setCollectionName(testName) 774 | .setQuery(sample(Sample.Random)) 775 | .setLimit(1) 776 | .build()) 777 | .get(); 778 | 779 | assertEquals(1, points.size()); 780 | } 781 | 782 | @Test 783 | public void queryGroups() throws ExecutionException, InterruptedException { 784 | createAndSeedCollection(testName); 785 | 786 | client 787 | .upsertAsync( 788 | testName, 789 | ImmutableList.of( 790 | PointStruct.newBuilder() 791 | .setId(id(10)) 792 | .setVectors(VectorsFactory.vectors(30f, 31f)) 793 | .putAllPayload(ImmutableMap.of("foo", value("hello"))) 794 | .build())) 795 | .get(); 796 | // 3 points in total, 2 with "foo" = "hello" and 1 with "foo" = "goodbye" 797 | 798 | List groups = 799 | client 800 | .queryGroupsAsync( 801 | QueryPointGroups.newBuilder() 802 | .setCollectionName(testName) 803 | .setQuery(nearest(ImmutableList.of(10.4f, 11.4f))) 804 | .setGroupBy("foo") 805 | .setGroupSize(2) 806 | .setLimit(10) 807 | .build()) 808 | .get(); 809 | 810 | assertEquals(2, groups.size()); 811 | // A group with 2 hits because of 2 points with "foo" = "hello" 812 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 2).count()); 813 | // A group with 1 hit because of 1 point with "foo" = "goodbye" 814 | assertEquals(1, groups.stream().filter(g -> g.getHitsCount() == 1).count()); 815 | } 816 | 817 | @Test 818 | public void searchMatrixOffsets() throws ExecutionException, InterruptedException { 819 | createAndSeedCollection(testName); 820 | 821 | Points.SearchMatrixOffsets offsets = 822 | client 823 | .searchMatrixOffsetsAsync( 824 | Points.SearchMatrixPoints.newBuilder() 825 | .setCollectionName(testName) 826 | .setSample(3) 827 | .setLimit(2) 828 | .build()) 829 | .get(); 830 | 831 | // Number of ids matches the limit 832 | assertEquals(2, offsets.getIdsCount()); 833 | } 834 | 835 | @Test 836 | public void searchMatrixPairs() throws ExecutionException, InterruptedException { 837 | createAndSeedCollection(testName); 838 | 839 | Points.SearchMatrixPairs pairs = 840 | client 841 | .searchMatrixPairsAsync( 842 | Points.SearchMatrixPoints.newBuilder() 843 | .setCollectionName(testName) 844 | .setSample(3) 845 | .setLimit(2) 846 | .build()) 847 | .get(); 848 | 849 | // Number of ids matches the limit 850 | assertEquals(2, pairs.getPairsCount()); 851 | } 852 | 853 | @Test 854 | public void facets() throws ExecutionException, InterruptedException { 855 | createAndSeedCollection(testName); 856 | 857 | // create payload index for "foo" field 858 | UpdateResult result = 859 | client 860 | .createPayloadIndexAsync( 861 | testName, "foo", PayloadSchemaType.Keyword, null, null, null, null) 862 | .get(); 863 | 864 | assertEquals(UpdateStatus.Completed, result.getStatus()); 865 | 866 | List facets = 867 | client 868 | .facetAsync( 869 | Points.FacetCounts.newBuilder() 870 | .setCollectionName(testName) 871 | .setKey("foo") 872 | .setLimit(2) 873 | .build()) 874 | .get(); 875 | 876 | // Number of facets matches the limit 877 | assertEquals(2, facets.size()); 878 | // validate hits 879 | assertEquals( 880 | 1, 881 | facets.stream() 882 | .filter(f -> f.getValue().getStringValue().equals("hello") && f.getCount() == 1) 883 | .count()); 884 | assertEquals( 885 | 1, 886 | facets.stream() 887 | .filter(f -> f.getValue().getStringValue().equals("goodbye") && f.getCount() == 1) 888 | .count()); 889 | } 890 | 891 | private void createAndSeedCollection(String collectionName) 892 | throws ExecutionException, InterruptedException { 893 | CreateCollection request = 894 | CreateCollection.newBuilder() 895 | .setCollectionName(collectionName) 896 | .setVectorsConfig( 897 | VectorsConfig.newBuilder() 898 | .setParams( 899 | VectorParams.newBuilder().setDistance(Distance.Cosine).setSize(2).build()) 900 | .build()) 901 | .build(); 902 | 903 | client.createCollectionAsync(request).get(); 904 | 905 | UpdateResult result = 906 | client 907 | .upsertAsync( 908 | collectionName, 909 | ImmutableList.of( 910 | PointStruct.newBuilder() 911 | .setId(id(8)) 912 | .setVectors(VectorsFactory.vectors(ImmutableList.of(3.5f, 4.5f))) 913 | .putAllPayload( 914 | ImmutableMap.of( 915 | "foo", value("hello"), 916 | "bar", value(1), 917 | "date", value("2021-01-01T00:00:00Z"))) 918 | .build(), 919 | PointStruct.newBuilder() 920 | .setId(id(9)) 921 | .setVectors(VectorsFactory.vectors(ImmutableList.of(10.5f, 11.5f))) 922 | .putAllPayload( 923 | ImmutableMap.of( 924 | "foo", value("goodbye"), 925 | "bar", value(2), 926 | "date", value("2024-01-02T00:00:00Z"))) 927 | .build())) 928 | .get(); 929 | assertEquals(UpdateStatus.Completed, result.getStatus()); 930 | } 931 | } 932 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/QdrantClientTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import io.grpc.Grpc; 4 | import io.grpc.InsecureChannelCredentials; 5 | import io.grpc.ManagedChannel; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.junit.jupiter.api.Assertions; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.testcontainers.junit.jupiter.Container; 11 | import org.testcontainers.junit.jupiter.Testcontainers; 12 | import org.testcontainers.qdrant.QdrantContainer; 13 | 14 | @Testcontainers 15 | class QdrantClientTest { 16 | 17 | @Container 18 | private static final QdrantContainer QDRANT_CONTAINER = 19 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 20 | 21 | private QdrantClient client; 22 | 23 | @BeforeEach 24 | public void setup() { 25 | ManagedChannel channel = 26 | Grpc.newChannelBuilder( 27 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 28 | .build(); 29 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel, true).build(); 30 | client = new QdrantClient(grpcClient); 31 | } 32 | 33 | @AfterEach 34 | public void teardown() { 35 | client.close(); 36 | } 37 | 38 | @Test 39 | void canAccessChannelOnGrpcClient() { 40 | Assertions.assertTrue(client.grpcClient().channel().authority().startsWith("localhost")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/QdrantGrpcClientTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import io.grpc.Grpc; 6 | import io.grpc.InsecureChannelCredentials; 7 | import io.qdrant.client.grpc.QdrantOuterClass; 8 | import java.util.concurrent.ExecutionException; 9 | import org.junit.jupiter.api.AfterEach; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.testcontainers.junit.jupiter.Container; 13 | import org.testcontainers.junit.jupiter.Testcontainers; 14 | import org.testcontainers.qdrant.QdrantContainer; 15 | 16 | @Testcontainers 17 | class QdrantGrpcClientTest { 18 | 19 | @Container 20 | private static final QdrantContainer QDRANT_CONTAINER = 21 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 22 | 23 | private QdrantGrpcClient client; 24 | 25 | @BeforeEach 26 | public void setup() { 27 | client = 28 | QdrantGrpcClient.newBuilder( 29 | Grpc.newChannelBuilder( 30 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 31 | .build()) 32 | .build(); 33 | } 34 | 35 | @AfterEach 36 | public void teardown() { 37 | client.close(); 38 | } 39 | 40 | @Test 41 | void healthCheck() throws ExecutionException, InterruptedException { 42 | QdrantOuterClass.HealthCheckReply healthCheckReply = 43 | client.qdrant().healthCheck(QdrantOuterClass.HealthCheckRequest.getDefaultInstance()).get(); 44 | 45 | assertNotNull(healthCheckReply.getTitle()); 46 | assertNotNull(healthCheckReply.getVersion()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/SnapshotsTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import io.grpc.Grpc; 7 | import io.grpc.InsecureChannelCredentials; 8 | import io.grpc.ManagedChannel; 9 | import io.grpc.Status; 10 | import io.grpc.StatusRuntimeException; 11 | import io.qdrant.client.grpc.Collections; 12 | import io.qdrant.client.grpc.SnapshotsService.SnapshotDescription; 13 | import java.util.List; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.concurrent.TimeUnit; 16 | import org.junit.jupiter.api.AfterEach; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | import org.junit.jupiter.api.TestInfo; 20 | import org.testcontainers.junit.jupiter.Container; 21 | import org.testcontainers.junit.jupiter.Testcontainers; 22 | import org.testcontainers.qdrant.QdrantContainer; 23 | 24 | @Testcontainers 25 | class SnapshotsTest { 26 | @Container 27 | private static final QdrantContainer QDRANT_CONTAINER = 28 | new QdrantContainer(DockerImage.QDRANT_IMAGE); 29 | 30 | private QdrantClient client; 31 | private ManagedChannel channel; 32 | private String testName; 33 | 34 | @BeforeEach 35 | public void setup(TestInfo testInfo) { 36 | testName = testInfo.getDisplayName().replace("()", ""); 37 | channel = 38 | Grpc.newChannelBuilder( 39 | QDRANT_CONTAINER.getGrpcHostAddress(), InsecureChannelCredentials.create()) 40 | .build(); 41 | QdrantGrpcClient grpcClient = QdrantGrpcClient.newBuilder(channel).build(); 42 | client = new QdrantClient(grpcClient); 43 | } 44 | 45 | @AfterEach 46 | public void teardown() throws Exception { 47 | List collectionNames = client.listCollectionsAsync().get(); 48 | for (String collectionName : collectionNames) { 49 | List snapshots = client.listSnapshotAsync(collectionName).get(); 50 | for (SnapshotDescription snapshot : snapshots) { 51 | client.deleteSnapshotAsync(collectionName, snapshot.getName()).get(); 52 | } 53 | client.deleteCollectionAsync(collectionName).get(); 54 | } 55 | 56 | List snapshots = client.listFullSnapshotAsync().get(); 57 | for (SnapshotDescription snapshot : snapshots) { 58 | client.deleteFullSnapshotAsync(snapshot.getName()).get(); 59 | } 60 | 61 | client.close(); 62 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 63 | } 64 | 65 | @Test 66 | public void createSnapshot() throws ExecutionException, InterruptedException { 67 | createCollection(testName); 68 | client.createSnapshotAsync(testName).get(); 69 | } 70 | 71 | @Test 72 | public void deleteSnapshot() throws ExecutionException, InterruptedException { 73 | createCollection(testName); 74 | SnapshotDescription snapshotDescription = client.createSnapshotAsync(testName).get(); 75 | client.deleteSnapshotAsync(testName, snapshotDescription.getName()).get(); 76 | } 77 | 78 | @Test 79 | public void deleteSnapshot_with_missing_snapshot() { 80 | ExecutionException exception = 81 | assertThrows( 82 | ExecutionException.class, 83 | () -> client.deleteSnapshotAsync(testName, "snapshot_1").get()); 84 | Throwable cause = exception.getCause(); 85 | assertEquals(StatusRuntimeException.class, cause.getClass()); 86 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause; 87 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode()); 88 | } 89 | 90 | @Test 91 | public void listSnapshots() throws ExecutionException, InterruptedException { 92 | createCollection(testName); 93 | client.createSnapshotAsync(testName).get(); 94 | // snapshots are timestamped named to second precision. Wait more than 1 second to ensure we get 95 | // 2 snapshots 96 | Thread.sleep(2000); 97 | client.createSnapshotAsync(testName).get(); 98 | 99 | List snapshotDescriptions = client.listSnapshotAsync(testName).get(); 100 | assertEquals(2, snapshotDescriptions.size()); 101 | } 102 | 103 | @Test 104 | public void createFullSnapshot() throws ExecutionException, InterruptedException { 105 | createCollection(testName); 106 | createCollection(testName + "2"); 107 | client.createFullSnapshotAsync().get(); 108 | } 109 | 110 | @Test 111 | public void deleteFullSnapshot() throws ExecutionException, InterruptedException { 112 | createCollection(testName); 113 | createCollection(testName + "2"); 114 | SnapshotDescription snapshotDescription = client.createFullSnapshotAsync().get(); 115 | client.deleteFullSnapshotAsync(snapshotDescription.getName()).get(); 116 | } 117 | 118 | @Test 119 | public void deleteFullSnapshot_with_missing_snapshot() { 120 | ExecutionException exception = 121 | assertThrows( 122 | ExecutionException.class, () -> client.deleteFullSnapshotAsync("snapshot_1").get()); 123 | Throwable cause = exception.getCause(); 124 | assertEquals(StatusRuntimeException.class, cause.getClass()); 125 | StatusRuntimeException underlyingException = (StatusRuntimeException) cause; 126 | assertEquals(Status.Code.NOT_FOUND, underlyingException.getStatus().getCode()); 127 | } 128 | 129 | @Test 130 | public void listFullSnapshots() throws ExecutionException, InterruptedException { 131 | createCollection(testName); 132 | createCollection(testName + 2); 133 | client.createFullSnapshotAsync().get(); 134 | // snapshots are timestamped named to second precision. Wait more than 1 second to ensure we get 135 | // 2 snapshots 136 | Thread.sleep(2000); 137 | client.createFullSnapshotAsync().get(); 138 | 139 | List snapshotDescriptions = client.listFullSnapshotAsync().get(); 140 | assertEquals(2, snapshotDescriptions.size()); 141 | } 142 | 143 | private void createCollection(String collectionName) 144 | throws ExecutionException, InterruptedException { 145 | Collections.CreateCollection request = 146 | Collections.CreateCollection.newBuilder() 147 | .setCollectionName(collectionName) 148 | .setVectorsConfig( 149 | Collections.VectorsConfig.newBuilder() 150 | .setParams( 151 | Collections.VectorParams.newBuilder() 152 | .setDistance(Collections.Distance.Cosine) 153 | .setSize(4) 154 | .build()) 155 | .build()) 156 | .build(); 157 | 158 | client.createCollectionAsync(request).get(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/VersionsCompatibilityCheckerTest.java: -------------------------------------------------------------------------------- 1 | package io.qdrant.client; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertThrows; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.stream.Stream; 9 | import org.junit.jupiter.params.ParameterizedTest; 10 | import org.junit.jupiter.params.provider.MethodSource; 11 | 12 | public class VersionsCompatibilityCheckerTest { 13 | private static Stream validVersionProvider() { 14 | return Stream.of( 15 | new Object[] {"1.2.3", 1, 2}, 16 | new Object[] {"1.2.3-alpha", 1, 2}, 17 | new Object[] {"1.2", 1, 2}, 18 | new Object[] {"1", 1, 0}, 19 | new Object[] {"1.", 1, 0}); 20 | } 21 | 22 | @ParameterizedTest 23 | @MethodSource("validVersionProvider") 24 | public void testParseVersion_validVersion(String versionStr, int expectedMajor, int expectedMinor) 25 | throws Exception { 26 | Method method = 27 | VersionsCompatibilityChecker.class.getDeclaredMethod("parseVersion", String.class); 28 | method.setAccessible(true); 29 | Version version = (Version) method.invoke(null, versionStr); 30 | assertEquals(expectedMajor, version.getMajor()); 31 | assertEquals(expectedMinor, version.getMinor()); 32 | } 33 | 34 | private static Stream invalidVersionProvider() { 35 | return Stream.of("v1.12.0", "", ".1", ".1.", "1.null.1", "null.0.1", null); 36 | } 37 | 38 | @ParameterizedTest 39 | @MethodSource("invalidVersionProvider") 40 | public void testParseVersion_invalidVersion(String versionStr) throws Exception { 41 | Method method = 42 | VersionsCompatibilityChecker.class.getDeclaredMethod("parseVersion", String.class); 43 | method.setAccessible(true); 44 | assertThrows(InvocationTargetException.class, () -> method.invoke(null, versionStr)); 45 | } 46 | 47 | private static Stream versionCompatibilityProvider() { 48 | return Stream.of( 49 | new Object[] {"1.9.3.dev0", "2.8.1.dev12-something", false}, 50 | new Object[] {"1.9", "2.8", false}, 51 | new Object[] {"1", "2", false}, 52 | new Object[] {"1.9.0", "2.9.0", false}, 53 | new Object[] {"1.1.0", "1.2.9", true}, 54 | new Object[] {"1.2.7", "1.1.8.dev0", true}, 55 | new Object[] {"1.2.1", "1.2.29", true}, 56 | new Object[] {"1.2.0", "1.2.0", true}, 57 | new Object[] {"1.2.0", "1.4.0", false}, 58 | new Object[] {"1.4.0", "1.2.0", false}, 59 | new Object[] {"1.9.0", "3.7.0", false}, 60 | new Object[] {"3.0.0", "1.0.0", false}, 61 | new Object[] {"", "1.0.0", false}, 62 | new Object[] {"1.0.0", "", false}, 63 | new Object[] {"", "", false}); 64 | } 65 | 66 | @ParameterizedTest 67 | @MethodSource("versionCompatibilityProvider") 68 | public void testIsCompatible(String clientVersion, String serverVersion, boolean expected) { 69 | assertEquals(expected, VersionsCompatibilityChecker.isCompatible(clientVersion, serverVersion)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/qdrant/client/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package io.qdrant.client; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | --------------------------------------------------------------------------------