├── .env.example
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .rspec
├── .tool-versions
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── docker-compose.yml
├── lib
├── milvus.rb
└── milvus
│ ├── aliases.rb
│ ├── base.rb
│ ├── client.rb
│ ├── collections.rb
│ ├── constants.rb
│ ├── entities.rb
│ ├── error.rb
│ ├── indexes.rb
│ ├── partitions.rb
│ ├── roles.rb
│ ├── users.rb
│ └── version.rb
├── milvus.gemspec
├── sig
└── milvus.rbs
└── spec
├── fixtures
├── aliases
│ ├── alter.json
│ ├── create.json
│ ├── describe.json
│ ├── drop.json
│ └── list.json
├── collections
│ ├── create.json
│ ├── describe.json
│ ├── drop.json
│ ├── get_load_state.json
│ ├── get_stats.json
│ ├── has.json
│ ├── list.json
│ ├── load.json
│ ├── release.json
│ ├── rename.json
│ └── rename_error.json
├── entities
│ ├── delete.json
│ ├── get.json
│ ├── hybrid_search.json
│ ├── insert.json
│ ├── query.json
│ ├── search.json
│ └── upsert.json
├── indexes
│ ├── create.json
│ ├── describe.json
│ ├── drop.json
│ └── list.json
├── partitions
│ ├── create.json
│ ├── drop.json
│ ├── get_stats.json
│ ├── has.json
│ ├── list.json
│ ├── load.json
│ └── release.json
├── roles
│ ├── describe.json
│ └── list.json
└── users
│ ├── create.json
│ ├── describe.json
│ ├── drop.json
│ ├── grant_role.json
│ ├── list.json
│ ├── revoke_role.json
│ └── update_password.json
├── milvus
├── aliases_spec.rb
├── client_spec.rb
├── collections_spec.rb
├── entities_spec.rb
├── indexes_spec.rb
├── partitions_spec.rb
├── roles_spec.rb
└── users_spec.rb
├── milvus_spec.rb
└── spec_helper.rb
/.env.example:
--------------------------------------------------------------------------------
1 | MILVUS_URL=http://localhost:19530
2 | MILVUS_API_KEY=
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - "*"
7 | push:
8 | branches:
9 | - master
10 | jobs:
11 | tests:
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | ruby: ["2.7", "3.0", "3.1", "3.2"]
16 |
17 | steps:
18 | - uses: actions/checkout@master
19 |
20 | - name: Set up Ruby
21 | uses: ruby/setup-ruby@v1
22 | with:
23 | ruby-version: ${{ matrix.ruby }}
24 | bundler: default
25 | bundler-cache: true
26 |
27 | - name: StandardRb check
28 | run: bundle exec standardrb
29 |
30 | - name: Run tests
31 | run: |
32 | bundle exec rspec
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 |
10 | # rspec failure tracking
11 | .rspec_status
12 |
13 | /volumes
14 |
15 | .env
16 | .idea
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | ruby 3.3.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [Unreleased]
2 |
3 | ## [0.10.4] - 2025-03-31
4 | - Add missing grouping params to search operation
5 |
6 | ## [0.10.4] - 2024-11-14
7 | - `client.entities.search()` now accepts `search_params: {}`
8 |
9 | ## [0.10.3] - 2024-10-01
10 | - Milvus::Client constructor accepts customer logger: to be passed in
11 | - Added Alias management
12 |
13 | ## [0.10.2] - 2024-07-28
14 | - Added Roles management
15 | - Added Users management
16 | - Added docker-compose.yml with directions to run Milvus in Docker
17 |
18 | ## [0.10.1] - 2024-07-04 🇺🇸
19 | - Fixes and improvements to the gem.
20 |
21 | ## [0.10.0] - 2024-07-04 🇺🇸
22 | - BREAKING: Switched the gem to use newer V2 API with different endpoints and corresponding endpoints.
23 |
24 | ## [0.9.3] - 2023-07-01
25 |
26 | ## [0.9.2] - 2023-08-10
27 |
28 | ## [0.9.1] - 2023-05-10
29 |
30 | ## [0.9.0] - 2023-04-21
31 | - Initial release
32 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | # Specify your gem's dependencies in milvus.gemspec
6 | gemspec
7 |
8 | gem "rake", "~> 13.0"
9 |
10 | gem "rspec", "~> 3.0"
11 | gem "standard", "~> 1.27.0"
12 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | milvus (0.10.5)
5 | faraday (>= 2.0.1, < 3)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | ast (2.4.2)
11 | byebug (11.1.3)
12 | coderay (1.1.3)
13 | diff-lcs (1.5.0)
14 | faraday (2.7.10)
15 | faraday-net_http (>= 2.0, < 3.1)
16 | ruby2_keywords (>= 0.0.4)
17 | faraday-net_http (3.0.2)
18 | json (2.6.3)
19 | language_server-protocol (3.17.0.3)
20 | method_source (1.0.0)
21 | parallel (1.23.0)
22 | parser (3.2.2.0)
23 | ast (~> 2.4.1)
24 | pry (0.14.2)
25 | coderay (~> 1.1)
26 | method_source (~> 1.0)
27 | pry-byebug (3.10.1)
28 | byebug (~> 11.0)
29 | pry (>= 0.13, < 0.15)
30 | rainbow (3.1.1)
31 | rake (13.0.6)
32 | regexp_parser (2.8.0)
33 | rexml (3.2.5)
34 | rspec (3.12.0)
35 | rspec-core (~> 3.12.0)
36 | rspec-expectations (~> 3.12.0)
37 | rspec-mocks (~> 3.12.0)
38 | rspec-core (3.12.1)
39 | rspec-support (~> 3.12.0)
40 | rspec-expectations (3.12.2)
41 | diff-lcs (>= 1.2.0, < 2.0)
42 | rspec-support (~> 3.12.0)
43 | rspec-mocks (3.12.5)
44 | diff-lcs (>= 1.2.0, < 2.0)
45 | rspec-support (~> 3.12.0)
46 | rspec-support (3.12.0)
47 | rubocop (1.50.2)
48 | json (~> 2.3)
49 | parallel (~> 1.10)
50 | parser (>= 3.2.0.0)
51 | rainbow (>= 2.2.2, < 4.0)
52 | regexp_parser (>= 1.8, < 3.0)
53 | rexml (>= 3.2.5, < 4.0)
54 | rubocop-ast (>= 1.28.0, < 2.0)
55 | ruby-progressbar (~> 1.7)
56 | unicode-display_width (>= 2.4.0, < 3.0)
57 | rubocop-ast (1.28.0)
58 | parser (>= 3.2.1.0)
59 | rubocop-performance (1.16.0)
60 | rubocop (>= 1.7.0, < 2.0)
61 | rubocop-ast (>= 0.4.0)
62 | ruby-progressbar (1.13.0)
63 | ruby2_keywords (0.0.5)
64 | standard (1.27.0)
65 | language_server-protocol (~> 3.17.0.2)
66 | rubocop (~> 1.50.2)
67 | rubocop-performance (~> 1.16.0)
68 | unicode-display_width (2.4.2)
69 |
70 | PLATFORMS
71 | ruby
72 |
73 | DEPENDENCIES
74 | milvus!
75 | pry-byebug (~> 3.10.0)
76 | rake (~> 13.0)
77 | rspec (~> 3.0)
78 | standard (~> 1.27.0)
79 |
80 | BUNDLED WITH
81 | 2.5.14
82 |
--------------------------------------------------------------------------------
/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 | # Milvus
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Ruby wrapper for the Milvus vector search database API.
10 |
11 | Part of the [Langchain.rb](https://github.com/andreibondarev/langchainrb) stack.
12 |
13 | Available for paid consulting engagements! [Email me](mailto:andrei@sourcelabs.io).
14 |
15 | 
16 | [](https://badge.fury.io/rb/milvus)
17 | [](http://rubydoc.info/gems/milvus)
18 | [](https://github.com/andreibondarev/milvus/blob/main/LICENSE.txt)
19 | [](https://discord.gg/WDARp7J2n8)
20 | [](https://twitter.com/rushing_andrei)
21 |
22 | ## API Docs
23 | https://docs.zilliz.com/reference/restful/data-plane-v2
24 |
25 | ## Installation
26 |
27 | Install the gem and add to the application's Gemfile by executing:
28 |
29 | $ bundle add milvus
30 |
31 | If bundler is not being used to manage dependencies, install the gem by executing:
32 |
33 | $ gem install milvus
34 |
35 | ## Usage
36 |
37 | ### Instantiating API client
38 |
39 | ```ruby
40 | require 'milvus'
41 |
42 | client = Milvus::Client.new(
43 | url: 'http://localhost:19530'
44 | )
45 | ```
46 |
47 | ### Using the Collections endpoints
48 | ```ruby
49 | # Check if the collection exists.
50 | client.collections.has(collection_name: "example_collection")
51 | ```
52 | ```ruby
53 | # Rename a collection.
54 | client.collections.rename(collection_name: "example_collection", new_collection_name: "example_collection")
55 | ```
56 | ```ruby
57 | # Get collection stats
58 | client.collections.get_stats(collection_name: "example_collection")
59 | ```
60 |
61 | ```ruby
62 | # Data types: https://github.com/patterns-ai-core/milvus/blob/main/lib/milvus/constants.rb
63 |
64 | # Creating a new collection schema
65 | client.collections.create(
66 | collection_name: "example_collection",
67 | auto_id: true,
68 | fields: [
69 | {
70 | fieldName: "book_id",
71 | isPrimary: true,
72 | autoID: false,
73 | dataType: "Int64"
74 | },
75 | {
76 | fieldName: "content",
77 | dataType: "VarChar",
78 | elementTypeParams: {
79 | max_length: "512"
80 | }
81 | },
82 | {
83 | fieldName: "vector",
84 | dataType: "FloatVector",
85 | elementTypeParams: {
86 | dim: 1536
87 | }
88 | }
89 | ]
90 | )
91 | ```
92 | ```ruby
93 | # Descrbie the collection
94 | client.collections.describe(collection_name: "example_collection")
95 | ```
96 | ```ruby
97 | # Drop the collection
98 | client.collections.drop(collection_name: "example_collection")
99 | ```
100 | ```ruby
101 | # Load the collection to memory before a search or a query
102 | client.collections.load(collection_name: "example_collection")
103 | ```
104 | ```ruby
105 | # Load status of a specific collection.
106 | client.collections.get_load_state(collection_name: "example_collection")
107 | ```
108 | ```ruby
109 | # List all collections in the specified database.
110 | client.collections.list
111 | ```
112 | ```ruby
113 | # Release a collection from memory after a search or a query to reduce memory usage
114 | client.collections.release(collection_name: "example_collection")
115 | ```
116 |
117 | ### Inserting Data
118 | ```ruby
119 | client.entities.insert(
120 | collection_name: "example_collection",
121 | data: [
122 | { id: 1, content: "The quick brown fox jumps over the lazy dog", vector: ([0.1]*1536) },
123 | { id: 2, content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit", vector: ([0.2]*1536) },
124 | { id: 3, content: "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", vector: ([0.3]*1536) }
125 | ]
126 | )
127 | ```
128 | ```ruby
129 | # Delete the entities with the boolean expression you created
130 | client.entities.delete(
131 | collection_name: "example_collection",
132 | filter: "book_id in [0,1]"
133 | )
134 | ```
135 | ```ruby
136 | # Inserts new records into the database or updates existing ones.
137 | client.entities.upsert()
138 | ```
139 | ```ruby
140 | # Get specific entities by their IDs
141 | client.entities.get()
142 | ```
143 |
144 | ### Indexes
145 | ```ruby
146 | # Create an index
147 | index_params = [
148 | {
149 | metricType: "L2",
150 | fieldName: "vector",
151 | indexName: "vector_idx",
152 | indexConfig: {
153 | index_type: "AUTOINDEX"
154 | }
155 | }
156 | ]
157 |
158 | client.indexes.create(
159 | collection_name: "example_collection",
160 | index_params: index_params
161 | )
162 | ```
163 | ```ruby
164 | # Describe an index
165 | client.indexes.describe(
166 | collection_name: "example_collection",
167 | index_name: "example_index"
168 | )
169 | ```
170 | ```ruby
171 | # List indexes
172 | client.indexes.list(
173 | collection_name: "example_collection"
174 | )
175 | ```
176 | ```ruby
177 | # Drop an index
178 | client.indexes.drop(
179 | collection_name: "example_collection",
180 | index_name: "example_index"
181 | )
182 | ```
183 |
184 | ### Search, Querying & Hybrid Search
185 | ```ruby
186 | client.entities.search(
187 | collection_name: "example_collection",
188 | anns_field: "vectors",
189 | data: [embedding],
190 | # filter: "id in [450847466900987454]",
191 | search_params: {
192 | # Other accepted values: "COSINE" or "IP"
193 | # NOTE: metric_type must be the same as metric type used when index was created
194 | metric_type: "L2",
195 | params: {
196 | radius: 0.1, range_filter: 0.8
197 | }
198 | },
199 | )
200 | ```
201 | ```ruby
202 | client.entities.query(
203 | collection_name: "example_collection",
204 | filter: "id in [450847466900987455, 450847466900987454]"
205 | )
206 | ```
207 | ```ruby
208 | client.entities.hybrid_search(
209 | collection_name: "example_collection",
210 | search: [{
211 | filter: "id in [450847466900987455]",
212 | data: [embedding],
213 | annsField: "vectors",
214 | limit: 10,
215 | outputFields: ["content", "id"]
216 | }],
217 | rerank: {
218 | "strategy": "rrf",
219 | "params": {
220 | "k": 10
221 | }
222 | },
223 | limit: 10,
224 | output_fields: ["content", "id"]
225 | )
226 | ```
227 |
228 | ### Partitions
229 | ```ruby
230 | # List partitions
231 | client.partitions.list(
232 | collection_name: "example_collection"
233 | )
234 | ```
235 | ```ruby
236 | # Create a partition
237 | client.partitions.create(
238 | collection_name: "example_collection",
239 | partition_name: "example_partition"
240 | )
241 | ```
242 | ```ruby
243 | # Check if a partition exists
244 | client.partitions.has(
245 | collection_name: "example_collection",
246 | partition_name: "example_partition"
247 | )
248 | ```
249 | ```ruby
250 | # Load partition data into memory
251 | client.partitions.load(
252 | collection_name: "example_collection",
253 | partition_names: ["example_partition"]
254 | )
255 | ```
256 | ```ruby
257 | # Release partition data from memory
258 | client.partitions.release(
259 | collection_name: "example_collection",
260 | partition_names: ["example_partition"]
261 | )
262 | ```
263 | ```ruby
264 | # Get statistics of a partition
265 | client.partitions.get_stats(
266 | collection_name: "example_collection",
267 | partition_name: "example_partition"
268 | )
269 | ```
270 | ```ruby
271 | # Drop a partition
272 | client.partitions.drop(
273 | collection_name: "example_collection",
274 | partition_name: "example_partition"
275 | )
276 | ```
277 |
278 | ### Roles
279 | ```ruby
280 | # List roles available on the server
281 | client.roles.list
282 | ```
283 | ```ruby
284 | # Describe the role
285 | client.roles.describe(role_name: 'public')
286 | ```
287 |
288 | ### Users
289 | ```ruby
290 | # Create new user
291 | client.users.create(user_name: 'user_name', password: 'password')
292 | ```
293 | ```ruby
294 | # List of roles assigned to the user
295 | client.users.describe(user_name: 'user_name')
296 | ```
297 | ```ruby
298 | # List all users in the specified database.
299 | client.users.list
300 | ```
301 | ```ruby
302 | # Drop existing user
303 | client.users.drop(user_name: 'user_name')
304 | ```
305 | ```ruby
306 | # Update password for the user
307 | client.users.update_password(user_name: 'user_name', password: 'old_password', new_password: 'new_password')
308 | ```
309 | ```ruby
310 | # Grant role to the user
311 | client.users.grant_role(user_name: 'user_name', role_name: 'admin')
312 | ```
313 | ```ruby
314 | # Revoke role from the user
315 | client.users.revoke_role(user_name: 'user_name', role_name: 'admin')
316 | ```
317 | ### Aliases
318 | ```ruby
319 | # Lists all existing collection aliases in the specified database
320 | client.aliases.list
321 | ```
322 | ```ruby
323 | # Describes the details of a specific alias
324 | client.aliases.describe
325 | ```
326 | ```ruby
327 | # Reassigns the alias of one collection to another.
328 | client.aliases.alter
329 | ```
330 | ```ruby
331 | # Drops a specified alias
332 | client.aliases.drop
333 | ```
334 | ```ruby
335 | # Creates an alias for an existing collection
336 | client.aliases.create
337 | ```
338 |
339 | ## Development
340 |
341 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
342 |
343 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
344 |
345 | ## Development with Docker
346 |
347 | Run `docker compose run --rm ruby_app bash` and install required gems (`bundle install`). It will give you a fully working development environment with Milvus services and gem's code.
348 |
349 | For example inside docker container run `bin/console` and inside the ruby console:
350 | ```ruby
351 | client = Milvus::Client.new(url: ENV["MILVUS_URL"])
352 | client.collections.list
353 | ```
354 |
355 | ## Contributing
356 |
357 | Bug reports and pull requests are welcome on GitHub at https://github.com/patterns-ai-core/milvus.
358 |
359 | ## License
360 |
361 | `milvus` is licensed under the Apache License, Version 2.0. View a copy of the License file.
362 |
363 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require "rspec/core/rake_task"
5 |
6 | RSpec::Core::RakeTask.new(:spec)
7 |
8 | task default: :spec
9 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "milvus"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | # (If you use this, don't forget to add pry to your Gemfile!)
11 | # require "pry"
12 | # Pry.start
13 |
14 | client = Milvus::Client.new(
15 | url: ENV["MILVUS_URL"],
16 | api_key: ENV["MILVUS_API_KEY"]
17 | )
18 |
19 | require "irb"
20 | IRB.start(__FILE__)
21 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | ruby_app:
3 | image: ruby:bullseye
4 | working_dir: /usr/src/app
5 | volumes:
6 | - .:/usr/src/app
7 | - bundle:/usr/local/bundle
8 | depends_on:
9 | - standalone
10 | environment:
11 | MILVUS_URL: "http://standalone:19530"
12 |
13 | etcd:
14 | container_name: milvus-etcd
15 | image: quay.io/coreos/etcd:v3.5.5
16 | environment:
17 | - ETCD_AUTO_COMPACTION_MODE=revision
18 | - ETCD_AUTO_COMPACTION_RETENTION=1000
19 | - ETCD_QUOTA_BACKEND_BYTES=4294967296
20 | - ETCD_SNAPSHOT_COUNT=50000
21 | volumes:
22 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
23 | command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
24 | healthcheck:
25 | test: ["CMD", "etcdctl", "endpoint", "health"]
26 | interval: 30s
27 | timeout: 20s
28 | retries: 3
29 |
30 | minio:
31 | container_name: milvus-minio
32 | image: minio/minio:RELEASE.2023-03-20T20-16-18Z
33 | environment:
34 | MINIO_ACCESS_KEY: minioadmin
35 | MINIO_SECRET_KEY: minioadmin
36 | ports:
37 | - "9001:9001"
38 | - "9000:9000"
39 | volumes:
40 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
41 | command: minio server /minio_data --console-address ":9001"
42 | healthcheck:
43 | test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
44 | interval: 30s
45 | timeout: 20s
46 | retries: 3
47 |
48 | standalone:
49 | container_name: milvus-standalone
50 | image: milvusdb/milvus:v2.4.5
51 | command: ["milvus", "run", "standalone"]
52 | security_opt:
53 | - seccomp:unconfined
54 | environment:
55 | ETCD_ENDPOINTS: etcd:2379
56 | MINIO_ADDRESS: minio:9000
57 | volumes:
58 | - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
59 | healthcheck:
60 | test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
61 | interval: 30s
62 | start_period: 90s
63 | timeout: 20s
64 | retries: 3
65 | ports:
66 | - "19530:19530"
67 | - "9091:9091"
68 | depends_on:
69 | - "etcd"
70 | - "minio"
71 |
72 | networks:
73 | default:
74 | name: milvus
75 |
76 | volumes:
77 | bundle:
78 |
--------------------------------------------------------------------------------
/lib/milvus.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "milvus/version"
4 | require_relative "milvus/constants"
5 |
6 | module Milvus
7 | autoload :Base, "milvus/base"
8 | autoload :Collections, "milvus/collections"
9 | autoload :Client, "milvus/client"
10 | autoload :Error, "milvus/error"
11 | autoload :Entities, "milvus/entities"
12 | autoload :Indexes, "milvus/indexes"
13 | autoload :Partitions, "milvus/partitions"
14 | autoload :Roles, "milvus/roles"
15 | autoload :Users, "milvus/users"
16 | autoload :Aliases, "milvus/aliases"
17 | end
18 |
--------------------------------------------------------------------------------
/lib/milvus/aliases.rb:
--------------------------------------------------------------------------------
1 | # https://docs.zilliz.com/reference/restful/alias-operations-v2
2 |
3 | module Milvus
4 | class Aliases < Base
5 | PATH = "aliases"
6 |
7 | # Lists available roles on the server
8 | #
9 | # @return [Hash] The response from the server
10 | def list
11 | response = client.connection.post("#{PATH}/list") do |req|
12 | req.body = {}
13 | end
14 |
15 | response.body
16 | end
17 |
18 | # Describes the details of a specific alias
19 | #
20 | # @param alias_name [String] The name of the alias to describe
21 | # @return [Hash] The response from the server
22 | def describe(alias_name:)
23 | response = client.connection.post("#{PATH}/describe") do |req|
24 | req.body = {
25 | aliasName: alias_name
26 | }
27 | end
28 |
29 | response.body
30 | end
31 |
32 | # Reassigns the alias of one collection to another
33 | #
34 | # @param alias_name [String] The alias of the collection
35 | # @param collection_name [String] The name of the target collection to reassign an alias to
36 | # @return [Hash] The response from the server
37 | def alter(alias_name:, collection_name:)
38 | response = client.connection.post("#{PATH}/alter") do |req|
39 | req.body = {
40 | aliasName: alias_name,
41 | collectionName: collection_name
42 | }
43 | end
44 |
45 | response.body
46 | end
47 |
48 | # This operation drops a specified alias
49 | #
50 | # @param alias_name [String] The alias to drop
51 | # @return [Hash] The response from the server
52 | def drop(alias_name:)
53 | response = client.connection.post("#{PATH}/drop") do |req|
54 | req.body = {
55 | aliasName: alias_name
56 | }
57 | end
58 |
59 | response.body
60 | end
61 |
62 | # This operation creates an alias for an existing collection. A collection can have multiple aliases, while an alias can be associated with only one collection.
63 | #
64 | # @param alias_name [String] The alias of the collection
65 | # @param collection_name [String] The name of the target collection to reassign an alias to
66 | # @return [Hash] The response from the server
67 | def create(alias_name:, collection_name:)
68 | response = client.connection.post("#{PATH}/create") do |req|
69 | req.body = {
70 | aliasName: alias_name,
71 | collectionName: collection_name
72 | }
73 | end
74 |
75 | response.body
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/milvus/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Base
5 | attr_reader :client
6 |
7 | def initialize(client:)
8 | @client = client
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/lib/milvus/client.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "faraday"
4 |
5 | module Milvus
6 | class Client
7 | attr_reader :url, :api_key, :adapter, :raise_error, :logger
8 |
9 | API_VERSION = "v2/vectordb"
10 |
11 | def initialize(
12 | url:,
13 | api_key: nil,
14 | adapter: Faraday.default_adapter,
15 | raise_error: false,
16 | logger: nil
17 | )
18 | @url = url
19 | @api_key = api_key
20 | @adapter = adapter
21 | @raise_error = raise_error
22 | @logger = logger || Logger.new($stdout)
23 | end
24 |
25 | def collections
26 | @schema ||= Milvus::Collections.new(client: self)
27 | end
28 |
29 | def partitions
30 | @partitions ||= Milvus::Partitions.new(client: self)
31 | end
32 |
33 | def entities
34 | @entities ||= Milvus::Entities.new(client: self)
35 | end
36 |
37 | def indexes
38 | @indexes ||= Milvus::Indexes.new(client: self)
39 | end
40 |
41 | def roles
42 | @roles ||= Milvus::Roles.new(client: self)
43 | end
44 |
45 | def users
46 | @users ||= Milvus::Users.new(client: self)
47 | end
48 |
49 | def aliases
50 | @aliases ||= Milvus::Aliases.new(client: self)
51 | end
52 |
53 | def connection
54 | @connection ||= Faraday.new(url: "#{url}/#{API_VERSION}/") do |faraday|
55 | if api_key
56 | faraday.request :authorization, :Bearer, api_key
57 | end
58 | faraday.request :json
59 | faraday.response :logger, logger, {headers: true, bodies: true, errors: true}
60 | faraday.response :raise_error if raise_error
61 | faraday.response :json, content_type: /\bjson$/
62 | faraday.adapter adapter
63 | end
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/milvus/collections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Collections < Base
5 | PATH = "collections"
6 |
7 | # This operation checks whether a collection exists.
8 | #
9 | # @param collection_name [String] The name of the collection to check.
10 | # @return [Hash] Server response
11 | def has(collection_name:)
12 | response = client.connection.post("#{PATH}/has") do |req|
13 | req.body = {
14 | collectionName: collection_name
15 | }
16 | end
17 | response.body
18 | end
19 |
20 | # This operation renames an existing collection and optionally moves the collection to a new database.
21 | #
22 | # @param collection_name [String] The name of the collection to rename.
23 | # @param new_collection_name [String] The new name of the collection.
24 | # @return [Hash] Server response
25 | def rename(collection_name:, new_collection_name:)
26 | response = client.connection.post("#{PATH}/rename") do |req|
27 | req.body = {
28 | collectionName: collection_name,
29 | newCollectionName: new_collection_name
30 | }
31 | end
32 | response.body.empty? ? true : response.body
33 | end
34 |
35 | # This operation gets the number of entities in a collection.
36 | #
37 | # @param collection_name [String] The name of the collection to get the count of.
38 | # @return [Hash] Server response
39 | def get_stats(collection_name:)
40 | response = client.connection.post("#{PATH}/get_stats") do |req|
41 | req.body = {
42 | collectionName: collection_name
43 | }
44 | end
45 | response.body
46 | end
47 |
48 | # Create a Collection
49 | #
50 | # @param collection_name [String] The name of the collection to create.
51 | # @param auto_id [Boolean] Whether to automatically generate IDs for the collection.
52 | # @param description [String] A description of the collection.
53 | # @param fields [Array] The fields of the collection.
54 | # @return [Hash] Server response
55 | def create(
56 | collection_name:,
57 | auto_id:,
58 | fields:
59 | )
60 | response = client.connection.post("#{PATH}/create") do |req|
61 | req.body = {
62 | collectionName: collection_name,
63 | schema: {
64 | autoId: auto_id,
65 | fields: fields,
66 | name: collection_name # This duplicated field is kept for historical reasons.
67 | }
68 | }
69 | end
70 | response.body.empty? ? true : response.body
71 | end
72 |
73 | # Describes the details of a collection.
74 | #
75 | # @param collection_name [String] The name of the collection to describe.
76 | # @return [Hash] Server response
77 | def describe(collection_name:)
78 | response = client.connection.post("#{PATH}/describe") do |req|
79 | req.body = {
80 | collectionName: collection_name
81 | }
82 | end
83 | response.body
84 | end
85 |
86 | # This operation lists all collections in the specified database.
87 | #
88 | # @return [Hash] Server response
89 | def list
90 | response = client.connection.post("#{PATH}/list") do |req|
91 | req.body = {}
92 | end
93 | response.body
94 | end
95 |
96 | # This operation drops the current collection and all data within the collection.
97 | #
98 | # @param collection_name [String] The name of the collection to drop.
99 | # @return [Hash] Server response
100 | def drop(collection_name:)
101 | response = client.connection.post("#{PATH}/drop") do |req|
102 | req.body = {
103 | collectionName: collection_name
104 | }
105 | end
106 | response.body.empty? ? true : response.body
107 | end
108 |
109 | # Load the collection to memory before a search or a query
110 | #
111 | # @param collection_name [String] The name of the collection to load.
112 | # @return [Hash] Server response
113 | def load(collection_name:)
114 | response = client.connection.post("#{PATH}/load") do |req|
115 | req.body = {
116 | collectionName: collection_name
117 | }
118 | end
119 | response.body.empty? ? true : response.body
120 | end
121 |
122 | # This operation returns the load status of a specific collection.
123 | #
124 | # @param collection_name [String] The name of the collection to get the load status of.
125 | # @return [Hash] Server response
126 | def get_load_state(collection_name:)
127 | response = client.connection.post("#{PATH}/get_load_state") do |req|
128 | req.body = {
129 | collectionName: collection_name
130 | }
131 | end
132 | response.body
133 | end
134 |
135 | # Release a collection from memory after a search or a query to reduce memory usage
136 | #
137 | # @param collection_name [String] The name of the collection to release.
138 | # @return [Hash] Server response
139 | def release(collection_name:)
140 | response = client.connection.post("#{PATH}/release") do |req|
141 | req.body = {
142 | collectionName: collection_name
143 | }
144 | end
145 | response.body.empty? ? true : response.body
146 | end
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/lib/milvus/constants.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | # https://milvus.io/api-reference/pymilvus/v2.4.x/MilvusClient/Collections/DataType.md
5 | DATA_TYPES = [
6 | "Boolean",
7 | "Int8",
8 | "Int16",
9 | "Int32",
10 | "Int64",
11 | "Float",
12 | "Double",
13 | "VarChar",
14 | "Array",
15 | "Json",
16 | "BinaryVector",
17 | "FloatVector",
18 | "Float16Vector",
19 | "BFloat16Vector",
20 | "SparseFloatVector"
21 | ].freeze
22 | end
23 |
--------------------------------------------------------------------------------
/lib/milvus/entities.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Entities < Base
5 | PATH = "entities"
6 |
7 | # This operation inserts data into a specific collection.
8 | #
9 | # @param collection_name [String] The name of the collection to insert data into.
10 | # @param data [Array] The data to insert.
11 | # @param partition_name [String] The name of the partition to insert the data into.
12 | #
13 | # @return [Hash] The response from the server.
14 | def insert(
15 | collection_name:,
16 | data:,
17 | partition_name: nil
18 | )
19 | response = client.connection.post("#{PATH}/insert") do |req|
20 | req.body = {
21 | collectionName: collection_name,
22 | data: data
23 | }
24 | req.body[:partitionName] = partition_name if partition_name
25 | end
26 | response.body.empty? ? true : response.body
27 | end
28 |
29 | # This operation deletes entities by their IDs or with a boolean expression.
30 | #
31 | # @param collection_name [String] The name of the collection to delete entities from.
32 | # @param filter [String] The filter to use to delete entities.
33 | # @return [Hash] The response from the server.
34 | def delete(
35 | collection_name:,
36 | filter:
37 | )
38 | response = client.connection.post("#{PATH}/delete") do |req|
39 | req.body = {
40 | collectionName: collection_name,
41 | filter: filter
42 | }
43 | end
44 | response.body.empty? ? true : response.body
45 | end
46 |
47 | # This operation conducts a filtering on the scalar field with a specified boolean expression.
48 | #
49 | # @param collection_name [String] The name of the collection to query.
50 | # @param filter [String] The filter to use to query the collection.
51 | # @param output_fields [Array] The fields to return in the results.
52 | # @param limit [Integer] The maximum number of results to return.
53 | def query(
54 | collection_name:,
55 | filter:,
56 | output_fields: [],
57 | limit: nil
58 | )
59 | response = client.connection.post("#{PATH}/query") do |req|
60 | req.body = {
61 | collectionName: collection_name,
62 | filter: filter
63 | }
64 | req.body[:outputFields] = output_fields if output_fields
65 | req.body[:limit] = limit if limit
66 | end
67 | response.body.empty? ? true : response.body
68 | end
69 |
70 | # This operation inserts new records into the database or updates existing ones.
71 | #
72 | # @param collection_name [String] The name of the collection to upsert data into.
73 | # @param data [Array] The data to upsert.
74 | # @param partition_name [String] The name of the partition to upsert the data into.
75 | # @return [Hash] The response from the server.
76 | def upsert(
77 | collection_name:,
78 | data:,
79 | partition_name: nil
80 | )
81 | response = client.connection.post("#{PATH}/upsert") do |req|
82 | req.body = {
83 | collectionName: collection_name,
84 | data: data
85 | }
86 | req.body[:partitionName] = partition_name if partition_name
87 | end
88 | response.body.empty? ? true : response.body
89 | end
90 |
91 | # This operation gets specific entities by their IDs
92 | #
93 | # @param collection_name [String] The name of the collection to get entities from.
94 | # @param id [Array] The IDs of the entities to get.
95 | # @param output_fields [Array] The fields to return in the results.
96 | # @return [Hash] The response from the server.
97 | def get(
98 | collection_name:,
99 | id:,
100 | output_fields: nil
101 | )
102 | response = client.connection.post("#{PATH}/get") do |req|
103 | req.body = {
104 | collectionName: collection_name,
105 | id: id
106 | }
107 | req.body[:outputFields] = output_fields if output_fields
108 | end
109 | response.body.empty? ? true : response.body
110 | end
111 |
112 | # This operation conducts a vector similarity search with an optional scalar filtering expression.
113 | #
114 | # @param collection_name [String] The name of the collection to search.
115 | # @param data [Array>] The data to search for.
116 | # @param anns_field [String] The field to search for.
117 | # @param limit [Integer] The maximum number of results to return.
118 | # @param output_fields [Array] The fields to return in the results.
119 | # @param offset [Integer] The offset to start from.
120 | # @param filter [String] The filter to use to search the collection.
121 | # @return [Hash] The search results.
122 | def search(
123 | collection_name:,
124 | data:,
125 | anns_field:,
126 | filter: nil,
127 | limit: nil,
128 | offset: nil,
129 | grouping_field: nil,
130 | group_size: nil,
131 | strict_group_size: nil,
132 | output_fields: [],
133 | search_params: {},
134 | partition_names: []
135 | )
136 | response = client.connection.post("#{PATH}/search") do |req|
137 | params = {
138 | collectionName: collection_name,
139 | data: data,
140 | annsField: anns_field
141 | }
142 | params[:limit] = limit if limit
143 | params[:outputFields] = output_fields if output_fields.any?
144 | params[:offset] = offset if offset
145 | params[:filter] = filter if filter
146 | params[:searchParams] = search_params if search_params.any?
147 | params[:partitionNames] = partition_names if partition_names.any?
148 | params[:groupingField] = grouping_field if grouping_field
149 | params[:groupSize] = group_size if group_size
150 | params[:strictGroupSize] = strict_group_size if strict_group_size
151 |
152 | req.body = params
153 | end
154 | response.body.empty? ? true : response.body
155 | end
156 |
157 | # Executes a hybrid search.
158 | #
159 | # @param collection_name [String] The name of the collection to search.
160 | # @param data [Array] The data to search for.
161 | # @param rerank [Hash] The rerank parameters.
162 | # @param limit [Integer] The maximum number of results to return.
163 | # @param output_fields [Array] The fields to return in the results.
164 | # @return [Hash] The search results.
165 | def hybrid_search(
166 | collection_name:,
167 | search:,
168 | rerank:,
169 | limit: nil,
170 | output_fields: []
171 | )
172 | response = client.connection.post("#{PATH}/hybrid_search") do |req|
173 | params = {
174 | collectionName: collection_name,
175 | search: search,
176 | rerank: rerank
177 | }
178 | params[:limit] = limit if limit
179 | params[:outputFields] = output_fields if output_fields.any?
180 | req.body = params
181 | end
182 | response.body.empty? ? true : response.body
183 | end
184 | end
185 | end
186 |
--------------------------------------------------------------------------------
/lib/milvus/error.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Error < StandardError
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/lib/milvus/indexes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Indexes < Base
5 | PATH = "indexes"
6 |
7 | # This creates a named index for a target field, which can either be a vector field or a scalar field.
8 | #
9 | # @param collection_name [String] The name of the collection to create the index for.
10 | # @param index_params [Hash] The parameters for the index.
11 | # @return [Hash] Server response
12 | def create(
13 | collection_name:,
14 | index_params:
15 | )
16 | response = client.connection.post("#{PATH}/create") do |req|
17 | req.body = {
18 | collectionName: collection_name,
19 | indexParams: index_params
20 | }
21 | end
22 | response.body.empty? ? true : response.body
23 | end
24 |
25 | # This operation deletes index from a specified collection.
26 | #
27 | # @param collection_name [String] The name of the collection to delete the index from.
28 | # @param index_name [String] The name of the index to delete.
29 | # @return [Hash] Server response
30 | def drop(
31 | collection_name:,
32 | index_name:
33 | )
34 | response = client.connection.post("#{PATH}/drop") do |req|
35 | req.body = {
36 | collectionName: collection_name,
37 | indexName: index_name
38 | }
39 | end
40 | response.body.empty? ? true : response.body
41 | end
42 |
43 | # This operation describes the current index.
44 | #
45 | # @param collection_name [String] The name of the collection to describe the index for.
46 | # @param index_name [String] The name of the index to describe.
47 | # @return [Hash] Server response
48 | def describe(
49 | collection_name:,
50 | index_name:
51 | )
52 | response = client.connection.post("#{PATH}/describe") do |req|
53 | req.body = {
54 | collectionName: collection_name,
55 | indexName: index_name
56 | }
57 | end
58 | response.body.empty? ? true : response.body
59 | end
60 |
61 | # This operation lists all indexes of a specific collection.
62 | #
63 | # @param collection_name [String] The name of the collection to list indexes for.
64 | # @return [Hash] Server response
65 | def list(
66 | collection_name:
67 | )
68 | response = client.connection.post("#{PATH}/list") do |req|
69 | req.body = {
70 | collectionName: collection_name
71 | }
72 | end
73 | response.body.empty? ? true : response.body
74 | end
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/milvus/partitions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Partitions < Base
5 | PATH = "partitions"
6 |
7 | # This operation lists all partitions in the database used in the current connection.
8 | #
9 | # @param collection_name [String] The name of the collection.
10 | # @return [Hash] Server response
11 | def list(collection_name:)
12 | response = client.connection.post("#{PATH}/list") do |req|
13 | req.body = {collectionName: collection_name}
14 | end
15 | response.body.empty? ? true : response.body
16 | end
17 |
18 | # This operation creates a partition in a collection.
19 | #
20 | # @param collection_name [String] The name of the collection to create the partition in.
21 | # @param partition_name [String] The name of the partition to create.
22 | def create(collection_name:, partition_name:)
23 | response = client.connection.post("#{PATH}/create") do |req|
24 | req.body = {
25 | collectionName: collection_name,
26 | partitionName: partition_name
27 | }
28 | end
29 | response.body.empty? ? true : response.body
30 | end
31 |
32 | # This operation drops the current partition. To successfully drop a partition, ensure that the partition is already released.
33 | #
34 | # @param collection_name [String] The name of the collection to drop the partition from.
35 | # @param partition_name [String] The name of the partition to drop.
36 | # @return [Hash] Server response
37 | def drop(collection_name:, partition_name:)
38 | response = client.connection.post("#{PATH}/drop") do |req|
39 | req.body = {
40 | collectionName: collection_name,
41 | partitionName: partition_name
42 | }
43 | end
44 | response.body.empty? ? true : response.body
45 | end
46 |
47 | # This operation checks whether a partition exists.
48 | #
49 | # @param collection_name [String] The name of the collection to check for the partition in.
50 | # @param partition_name [String] The name of the partition to check.
51 | # @return [Hash] Server response
52 | def has(collection_name:, partition_name:)
53 | response = client.connection.post("#{PATH}/has") do |req|
54 | req.body = {
55 | collectionName: collection_name,
56 | partitionName: partition_name
57 | }
58 | end
59 | response.body.empty? ? true : response.body
60 | end
61 |
62 | # This operation loads the data of the current partition into memory.
63 | #
64 | # @param collection_name [String] The name of the collection to load.
65 | # @param partition_names [Array] The names of the partitions to load.
66 | # @return [Hash] Server response
67 | def load(collection_name:, partition_names:)
68 | response = client.connection.post("#{PATH}/load") do |req|
69 | req.body = {
70 | collectionName: collection_name,
71 | partitionNames: partition_names
72 | }
73 | end
74 | response.body.empty? ? true : response.body
75 | end
76 |
77 | # This operation releases the data of the current partition from memory.
78 | #
79 | # @param collection_name [String] The name of the collection to release.
80 | # @param partition_name [String] The name of the partition to release.
81 | # @return [Hash] Server response
82 | def release(collection_name:, partition_names:)
83 | response = client.connection.post("#{PATH}/release") do |req|
84 | req.body = {
85 | collectionName: collection_name,
86 | partitionNames: partition_names
87 | }
88 | end
89 | response.body.empty? ? true : response.body
90 | end
91 |
92 | # This operations gets the number of entities in a partition.
93 | #
94 | # @param collection_name [String] The name of the collection to get the number of entities in.
95 | # @param partition_name [String] The name of the partition to get the number of entities in.
96 | # @return [Hash] Server response
97 | def get_stats(collection_name:, partition_name:)
98 | response = client.connection.post("#{PATH}/get_stats") do |req|
99 | req.body = {
100 | collectionName: collection_name,
101 | partitionName: partition_name
102 | }
103 | end
104 | response.body.empty? ? true : response.body
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/lib/milvus/roles.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Roles < Base
5 | PATH = "roles"
6 |
7 | # Lists available roles on the server
8 | #
9 | # @return [Hash] The response from the server.
10 | def list
11 | response = client.connection.post("#{PATH}/list") do |req|
12 | req.body = {}
13 | end
14 |
15 | response.body
16 | end
17 |
18 | # This operation inserts data into a specific collection.
19 | #
20 | # @param role_name [String] The name of the collection to insert data into.
21 | # @return [Hash] The response from the server.
22 | def describe(role_name:)
23 | response = client.connection.post("#{PATH}/describe") do |req|
24 | req.body = {
25 | roleName: role_name
26 | }
27 | end
28 |
29 | response.body
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/milvus/users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | class Users < Base
5 | PATH = "users"
6 |
7 | # Create a user
8 | #
9 | # @param user_name [String] Username for the user
10 | # @param password [String] Password for the user
11 | # @return [Hash] Server response
12 | def create(user_name:, password:)
13 | response = client.connection.post("#{PATH}/create") do |req|
14 | req.body = {
15 | userName: user_name,
16 | password: password
17 | }
18 | end
19 |
20 | response.body
21 | end
22 |
23 | # Describe a user
24 | #
25 | # @param user_name [String] Username for the user
26 | # @return [Hash] Server response
27 | def describe(user_name:)
28 | response = client.connection.post("#{PATH}/describe") do |req|
29 | req.body = {
30 | userName: user_name
31 | }
32 | end
33 |
34 | response.body
35 | end
36 |
37 | # List users
38 | #
39 | # @return [Hash] Server response
40 | def list
41 | response = client.connection.post("#{PATH}/list") do |req|
42 | req.body = {}
43 | end
44 |
45 | response.body
46 | end
47 |
48 | # Drops a user
49 | #
50 | # @param user_name [String] Username for the user
51 | # @return [Hash] Server response
52 | def drop(user_name:)
53 | response = client.connection.post("#{PATH}/drop") do |req|
54 | req.body = {
55 | userName: user_name
56 | }
57 | end
58 |
59 | response.body
60 | end
61 |
62 | def update_password(user_name:, password:, new_password:)
63 | response = client.connection.post("#{PATH}/update_password") do |req|
64 | req.body = {
65 | userName: user_name,
66 | password: password,
67 | newPassword: new_password
68 | }
69 | end
70 |
71 | response.body
72 | end
73 |
74 | def grant_role(user_name:, role_name:)
75 | response = client.connection.post("#{PATH}/grant_role") do |req|
76 | req.body = {
77 | userName: user_name,
78 | roleName: role_name
79 | }
80 | end
81 |
82 | response.body
83 | end
84 |
85 | def revoke_role(user_name:, role_name:)
86 | response = client.connection.post("#{PATH}/revoke_role") do |req|
87 | req.body = {
88 | userName: user_name,
89 | roleName: role_name
90 | }
91 | end
92 |
93 | response.body
94 | end
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/lib/milvus/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Milvus
4 | VERSION = "0.10.5"
5 | end
6 |
--------------------------------------------------------------------------------
/milvus.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative "lib/milvus/version"
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "milvus"
7 | spec.version = Milvus::VERSION
8 | spec.authors = ["Andrei Bondarev"]
9 | spec.email = ["andrei.bondarev13@gmail.com"]
10 |
11 | spec.summary = "Ruby wrapper for the Milvus vector search database API"
12 | spec.description = "Ruby wrapper for the Milvus vector search database API"
13 | spec.homepage = "https://github.com/andreibondarev/milvus"
14 | spec.license = "MIT"
15 | spec.required_ruby_version = ">= 2.6.0"
16 |
17 | spec.metadata["homepage_uri"] = spec.homepage
18 | spec.metadata["source_code_uri"] = "https://github.com/andreibondarev/milvus"
19 | spec.metadata["changelog_uri"] = "https://github.com/andreibondarev/milvus/CHANGELOG.md"
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | spec.files = Dir.chdir(__dir__) do
24 | `git ls-files -z`.split("\x0").reject do |f|
25 | (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
26 | end
27 | end
28 | spec.bindir = "exe"
29 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30 | spec.require_paths = ["lib"]
31 |
32 | spec.add_development_dependency "pry-byebug", "~> 3.10.0"
33 | spec.add_dependency "faraday", ">= 2.0.1", "< 3"
34 | end
35 |
--------------------------------------------------------------------------------
/sig/milvus.rbs:
--------------------------------------------------------------------------------
1 | module Milvus
2 | VERSION: String
3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4 | end
5 |
--------------------------------------------------------------------------------
/spec/fixtures/aliases/alter.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/aliases/create.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/aliases/describe.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"aliasName": "bob_alias", "collectionName": "example_collection_2", "dbName": "default"}}
--------------------------------------------------------------------------------
/spec/fixtures/aliases/drop.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/aliases/list.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": []}
--------------------------------------------------------------------------------
/spec/fixtures/collections/create.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/describe.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "data": {
4 | "aliases": [],
5 | "autoId": false,
6 | "collectionID": 450847466902625683,
7 | "collectionName": "book",
8 | "consistencyLevel": "Bounded",
9 | "description": "",
10 | "enableDynamicField": false,
11 | "fields": [
12 | {
13 | "autoId": false,
14 | "description": "",
15 | "id": 100,
16 | "name": "book_id",
17 | "partitionKey": false,
18 | "primaryKey": true,
19 | "type": "Int64"
20 | },
21 | {
22 | "autoId": false,
23 | "description": "",
24 | "id": 101,
25 | "name": "word_count",
26 | "partitionKey": false,
27 | "primaryKey": false,
28 | "type": "Int64"
29 | },
30 | {
31 | "autoId": false,
32 | "description": "",
33 | "id": 102,
34 | "name": "book_intro",
35 | "params": [
36 | {
37 | "key": "dim",
38 | "value": "2"
39 | }
40 | ],
41 | "partitionKey": false,
42 | "primaryKey": false,
43 | "type": "FloatVector"
44 | }
45 | ],
46 | "indexes": [],
47 | "load": "LoadStateNotLoad",
48 | "partitionsNum": 1,
49 | "properties": [],
50 | "shardsNum": 1
51 | },
52 | "message": "index not found[collection=book];"
53 | }
--------------------------------------------------------------------------------
/spec/fixtures/collections/drop.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/get_load_state.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"loadProgress": 100, "loadState": "LoadStateLoaded"}, "message": ""}
--------------------------------------------------------------------------------
/spec/fixtures/collections/get_stats.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"rowCount": 2}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/has.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"has": true}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/list.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": ["default", "test"]}
--------------------------------------------------------------------------------
/spec/fixtures/collections/load.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/release.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/rename.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/collections/rename_error.json:
--------------------------------------------------------------------------------
1 | {"code": 65535, "message": "duplicated new collection name default:recipes2 with other collection name or alias"}
--------------------------------------------------------------------------------
/spec/fixtures/entities/delete.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "cost": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/entities/get.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "cost": 0,
4 | "data": [
5 | {
6 | "content": "1",
7 | "id": 450847466900987461,
8 | "vectors": [
9 | 0.039902933,
10 | -0.042509146,
11 | -6.2022154e-05
12 | ]
13 | }
14 | ]
15 | }
--------------------------------------------------------------------------------
/spec/fixtures/entities/hybrid_search.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "cost": 0,
4 | "data": [
5 | {
6 | "content": "Lorem Ipsum",
7 | "distance": 0.09090909,
8 | "id": 450847466900987455
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/spec/fixtures/entities/insert.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "cost": 0, "data": {"insertCount": 1, "insertIds": [450847466900987457]}}
--------------------------------------------------------------------------------
/spec/fixtures/entities/query.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patterns-ai-core/milvus/574a539bfa39fb55382034f109c81ecfba9aded6/spec/fixtures/entities/query.json
--------------------------------------------------------------------------------
/spec/fixtures/entities/search.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "cost": 0, "data": [{"content": "1", "distance": 0, "id": 450847466900987457}]}
--------------------------------------------------------------------------------
/spec/fixtures/entities/upsert.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "cost": 0,
4 | "data": [
5 | {
6 | "content": "Lorem Ipsum",
7 | "distance": 0.09090909,
8 | "id": 450847466900987455
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/spec/fixtures/indexes/create.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/indexes/describe.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 0,
3 | "data": [
4 | {
5 | "failReason": "",
6 | "fieldName": "content",
7 | "indexName": "content_idx",
8 | "indexState": "Finished",
9 | "indexType": "",
10 | "indexedRows": 0,
11 | "metricType": "L2",
12 | "pendingRows": 0,
13 | "totalRows": 0
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/spec/fixtures/indexes/drop.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/indexes/list.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": ["content_idx", "vectors"]}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/create.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/drop.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/get_stats.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"rowCount": 0}}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/has.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {"has": true}}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/list.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": ["_default"]}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/load.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/partitions/release.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/roles/describe.json:
--------------------------------------------------------------------------------
1 | {"code": 0,
2 | "data": [
3 | {"dbName": "*", "grantor": "root", "objectName": "*", "objectType": "Collection", "privilege": "IndexDetail"},
4 | {"dbName": "*", "grantor": "root", "objectName": "*", "objectType": "Global", "privilege": "DescribeCollection"}
5 | ]
6 | }
--------------------------------------------------------------------------------
/spec/fixtures/roles/list.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": ["admin", "public"]}
--------------------------------------------------------------------------------
/spec/fixtures/users/create.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/users/describe.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": []}
--------------------------------------------------------------------------------
/spec/fixtures/users/drop.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": []}
--------------------------------------------------------------------------------
/spec/fixtures/users/grant_role.json:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patterns-ai-core/milvus/574a539bfa39fb55382034f109c81ecfba9aded6/spec/fixtures/users/grant_role.json
--------------------------------------------------------------------------------
/spec/fixtures/users/list.json:
--------------------------------------------------------------------------------
1 | { "code": 0, "data": ["root"] }
--------------------------------------------------------------------------------
/spec/fixtures/users/revoke_role.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/fixtures/users/update_password.json:
--------------------------------------------------------------------------------
1 | {"code": 0, "data": {}}
--------------------------------------------------------------------------------
/spec/milvus/aliases_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/aliases_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Aliases do
7 | let(:connection) { instance_double("Faraday::Connection") }
8 | let(:client) { instance_double("Client", connection: connection) }
9 | let(:aliases) { described_class.new(client: client) }
10 |
11 | describe "#list" do
12 | let(:response_body) { File.read("spec/fixtures/aliases/list.json") }
13 | let(:response) { instance_double("Faraday::Response", body: response_body) }
14 |
15 | it "lists aliases" do
16 | expect(connection).to receive(:post)
17 | .with("aliases/list")
18 | .and_yield(Faraday::Request.new)
19 | .and_return(response)
20 | result = aliases.list
21 |
22 | expect(result).to eq(response_body)
23 | end
24 | end
25 |
26 | describe "#describe" do
27 | let(:response_body) { File.read("spec/fixtures/aliases/describe.json") }
28 | let(:response) { instance_double("Faraday::Response", body: response_body) }
29 |
30 | it "describes the details of a specific alias" do
31 | expect(connection).to receive(:post)
32 | .with("aliases/describe")
33 | .and_yield(Faraday::Request.new)
34 | .and_return(response)
35 | result = aliases.describe(alias_name: "bob")
36 |
37 | expect(result).to eq(response_body)
38 | end
39 | end
40 |
41 | describe "#alter" do
42 | let(:response_body) { File.read("spec/fixtures/aliases/alter.json") }
43 | let(:response) { instance_double("Faraday::Response", body: response_body) }
44 |
45 | it "reassigns the alias of one collection to another" do
46 | expect(connection).to receive(:post)
47 | .with("aliases/alter")
48 | .and_yield(Faraday::Request.new)
49 | .and_return(response)
50 | result = aliases.alter(alias_name: "bob", collection_name: "new_collection")
51 |
52 | expect(result).to eq(response_body)
53 | end
54 | end
55 |
56 | describe "#drop" do
57 | let(:response_body) { File.read("spec/fixtures/aliases/drop.json") }
58 | let(:response) { instance_double("Faraday::Response", body: response_body) }
59 |
60 | it "drops the alias" do
61 | expect(connection).to receive(:post)
62 | .with("aliases/drop")
63 | .and_yield(Faraday::Request.new)
64 | .and_return(response)
65 | result = aliases.drop(alias_name: "bob")
66 |
67 | expect(result).to eq(response_body)
68 | end
69 | end
70 |
71 | describe "#create" do
72 | let(:response_body) { File.read("spec/fixtures/aliases/create.json") }
73 | let(:response) { instance_double("Faraday::Response", body: response_body) }
74 |
75 | it "creates alias for collection" do
76 | expect(connection).to receive(:post)
77 | .with("aliases/create")
78 | .and_yield(Faraday::Request.new)
79 | .and_return(response)
80 | result = aliases.create(alias_name: "bob", collection_name: "quick_setup")
81 |
82 | expect(result).to eq(response_body)
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/milvus/client_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "spec_helper"
4 |
5 | RSpec.describe Milvus::Client do
6 | let(:client) {
7 | described_class.new(
8 | url: "localhost:8080",
9 | api_key: "123"
10 | )
11 | }
12 |
13 | describe "#initialize" do
14 | it "creates a client" do
15 | expect(client).to be_a(Milvus::Client)
16 | end
17 |
18 | it "accepts a custom logger" do
19 | logger = Logger.new($stdout)
20 | client = Milvus::Client.new(
21 | url: "localhost:8080",
22 | api_key: "123",
23 | logger: logger
24 | )
25 | expect(client.logger).to eq(logger)
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/milvus/collections_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/collections_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Collections do
7 | let(:client) { instance_double("Client", connection: connection) }
8 | let(:connection) { instance_double("Faraday::Connection") }
9 | let(:collections) { described_class.new(client: client) }
10 |
11 | describe "#has" do
12 | let(:collection_name) { "test_collection" }
13 | let(:response_body) { File.read("spec/fixtures/collections/has.json") }
14 | let(:response) { instance_double("Faraday::Response", body: response_body) }
15 |
16 | it "checks whether a collection exists" do
17 | expect(connection).to receive(:post).with("collections/has").and_yield(Faraday::Request.new).and_return(response)
18 | result = collections.has(collection_name: collection_name)
19 | expect(result).to eq(response_body)
20 | end
21 | end
22 |
23 | describe "#rename" do
24 | let(:collection_name) { "test_collection" }
25 | let(:new_collection_name) { "new_test_collection" }
26 |
27 | context "when the rename is successful" do
28 | let(:response_body) { File.read("spec/fixtures/collections/rename.json") }
29 | let(:response) { instance_double("Faraday::Response", body: response_body) }
30 |
31 | it "renames an existing collection" do
32 | expect(connection).to receive(:post).with("collections/rename").and_return(response)
33 | result = collections.rename(collection_name: collection_name, new_collection_name: new_collection_name)
34 | expect(result).to eq(response_body)
35 | end
36 | end
37 |
38 | context "when the rename fails" do
39 | let(:response_body) { File.read("spec/fixtures/collections/rename_error.json") }
40 | let(:response) { instance_double("Faraday::Response", body: response_body) }
41 |
42 | it "returns an error message" do
43 | expect(connection).to receive(:post).with("collections/rename").and_return(response)
44 | result = collections.rename(collection_name: collection_name, new_collection_name: new_collection_name)
45 | expect(result).to eq(response_body)
46 | end
47 | end
48 | end
49 |
50 | describe "#get_stats" do
51 | let(:collection_name) { "test_collection" }
52 | let(:response_body) { File.read("spec/fixtures/collections/get_stats.json") }
53 | let(:response) { instance_double("Faraday::Response", body: response_body) }
54 |
55 | it "gets the number of entities in a collection" do
56 | expect(connection).to receive(:post).with("collections/get_stats").and_yield(Faraday::Request.new).and_return(response)
57 | result = collections.get_stats(collection_name: collection_name)
58 | expect(result).to eq(response_body)
59 | end
60 | end
61 |
62 | describe "#create" do
63 | let(:collection_name) { "test_collection" }
64 | let(:auto_id) { true }
65 | let(:fields) { [{name: "field1", type: "int64"}] }
66 | let(:response_body) { File.read("spec/fixtures/collections/create.json") }
67 | let(:response) { instance_double("Faraday::Response", body: response_body) }
68 |
69 | it "creates a collection" do
70 | expect(connection).to receive(:post).with("collections/create").and_return(response)
71 | result = collections.create(collection_name: collection_name, auto_id: auto_id, fields: fields)
72 | expect(result).to eq(response_body)
73 | end
74 | end
75 |
76 | describe "#describe" do
77 | let(:collection_name) { "test_collection" }
78 | let(:response_body) { File.read("spec/fixtures/collections/describe.json") }
79 | let(:response) { instance_double("Faraday::Response", body: response_body) }
80 |
81 | it "describes the details of a collection" do
82 | expect(connection).to receive(:post).with("collections/describe").and_yield(Faraday::Request.new).and_return(response)
83 | result = collections.describe(collection_name: collection_name)
84 | expect(result).to eq(response_body)
85 | end
86 | end
87 |
88 | describe "#list" do
89 | let(:response_body) { File.read("spec/fixtures/collections/list.json") }
90 | let(:response) { instance_double("Faraday::Response", body: response_body) }
91 |
92 | it "lists all collections in the specified database" do
93 | expect(connection).to receive(:post).with("collections/list").and_yield(Faraday::Request.new).and_return(response)
94 | result = collections.list
95 | expect(result).to eq(response_body)
96 | end
97 | end
98 |
99 | describe "#drop" do
100 | let(:collection_name) { "test_collection" }
101 | let(:response_body) { File.read("spec/fixtures/collections/drop.json") }
102 | let(:response) { instance_double("Faraday::Response", body: response_body) }
103 |
104 | it "drops the current collection and all data within the collection" do
105 | expect(connection).to receive(:post).with("collections/drop").and_return(response)
106 | result = collections.drop(collection_name: collection_name)
107 | expect(result).to eq(response_body)
108 | end
109 | end
110 |
111 | describe "#load" do
112 | let(:collection_name) { "test_collection" }
113 | let(:response_body) { File.read("spec/fixtures/collections/load.json") }
114 | let(:response) { instance_double("Faraday::Response", body: response_body) }
115 |
116 | it "loads the collection to memory" do
117 | expect(connection).to receive(:post).with("collections/load").and_return(response)
118 | result = collections.load(collection_name: collection_name)
119 | expect(result).to eq(response_body)
120 | end
121 | end
122 |
123 | describe "#get_load_state" do
124 | let(:collection_name) { "test_collection" }
125 | let(:response_body) { File.read("spec/fixtures/collections/get_load_state.json") }
126 | let(:response) { instance_double("Faraday::Response", body: response_body) }
127 |
128 | it "returns the load status of a specific collection" do
129 | expect(connection).to receive(:post).with("collections/get_load_state").and_yield(Faraday::Request.new).and_return(response)
130 | result = collections.get_load_state(collection_name: collection_name)
131 | expect(result).to eq(response_body)
132 | end
133 | end
134 |
135 | describe "#release" do
136 | let(:collection_name) { "test_collection" }
137 | let(:response_body) { File.read("spec/fixtures/collections/release.json") }
138 | let(:response) { instance_double("Faraday::Response", body: response_body) }
139 |
140 | it "releases a collection from memory" do
141 | expect(connection).to receive(:post).with("collections/release").and_return(response)
142 | result = collections.release(collection_name: collection_name)
143 | expect(result).to eq(response_body)
144 | end
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/spec/milvus/entities_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/entities_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Entities do
7 | let(:client) { instance_double("Client", connection: connection) }
8 | let(:connection) { instance_double("Faraday::Connection") }
9 | let(:entities) { described_class.new(client: client) }
10 |
11 | describe "#insert" do
12 | let(:collection_name) { "test_collection" }
13 | let(:data) { [{field1: "value1"}] }
14 | let(:response_body) { File.read("spec/fixtures/entities/insert.json") }
15 | let(:response) { instance_double("Faraday::Response", body: response_body) }
16 |
17 | it "inserts data into a specific collection" do
18 | expect(connection).to receive(:post).with("entities/insert").and_return(response)
19 | result = entities.insert(collection_name: collection_name, data: data)
20 | expect(result).to eq(response_body)
21 | end
22 | end
23 |
24 | describe "#delete" do
25 | let(:collection_name) { "test_collection" }
26 | let(:filter) { "id in [1, 2, 3]" }
27 | let(:response_body) { File.read("spec/fixtures/entities/delete.json") }
28 | let(:response) { instance_double("Faraday::Response", body: response_body) }
29 |
30 | it "deletes entities by their IDs or with a boolean expression" do
31 | expect(connection).to receive(:post).with("entities/delete").and_return(response)
32 | result = entities.delete(collection_name: collection_name, filter: filter)
33 | expect(result).to eq(response_body)
34 | end
35 | end
36 |
37 | describe "#query" do
38 | let(:collection_name) { "test_collection" }
39 | let(:filter) { "field1 > 10" }
40 | let(:output_fields) { ["field1", "field2"] }
41 | let(:limit) { 10 }
42 | let(:response_body) { File.read("spec/fixtures/entities/query.json") }
43 | let(:response) { instance_double("Faraday::Response", body: response_body) }
44 |
45 | it "conducts a filtering on the scalar field with a specified boolean expression" do
46 | expect(connection).to receive(:post).with("entities/query").and_return(response)
47 | result = entities.query(collection_name: collection_name, filter: filter, output_fields: output_fields, limit: limit)
48 | expect(result).to be true
49 | end
50 | end
51 |
52 | describe "#upsert" do
53 | let(:collection_name) { "test_collection" }
54 | let(:data) { [{field1: "value1"}] }
55 | let(:response_body) { File.read("spec/fixtures/entities/upsert.json") }
56 | let(:response) { instance_double("Faraday::Response", body: response_body) }
57 |
58 | it "inserts new records into the database or updates existing ones" do
59 | expect(connection).to receive(:post).with("entities/upsert").and_return(response)
60 | result = entities.upsert(collection_name: collection_name, data: data)
61 | expect(result).to eq(response_body)
62 | end
63 | end
64 |
65 | describe "#get" do
66 | let(:collection_name) { "test_collection" }
67 | let(:id) { [450847466900987461] }
68 | let(:output_fields) { ["field1", "field2"] }
69 | let(:response_body) { File.read("spec/fixtures/entities/get.json") }
70 | let(:response) { instance_double("Faraday::Response", body: response_body) }
71 |
72 | it "gets specific entities by their IDs" do
73 | expect(connection).to receive(:post).with("entities/get").and_return(response)
74 | result = entities.get(collection_name: collection_name, id: id, output_fields: output_fields)
75 | expect(result).to eq(response_body)
76 | end
77 | end
78 |
79 | describe "#search" do
80 | let(:collection_name) { "test_collection" }
81 | let(:search) { {query: "test_query"} }
82 | let(:rerank) { {method: "test_method"} }
83 | let(:annsField) { "test_field" }
84 | let(:limit) { 10 }
85 | let(:output_fields) { ["field1", "field2"] }
86 | let(:response_body) { File.read("spec/fixtures/entities/search.json") }
87 | let(:response) { instance_double("Faraday::Response", body: response_body) }
88 |
89 | it "executes a search" do
90 | expect(connection).to receive(:post).with("entities/search").and_return(response)
91 | result = entities.search(
92 | collection_name: collection_name,
93 | data: [[0.1, 0.2, 0.3]],
94 | output_fields: ["content"],
95 | anns_field: "vectors"
96 | )
97 | expect(result).to eq(response_body)
98 | end
99 | end
100 |
101 | describe "#hybrid_search" do
102 | let(:collection_name) { "test_collection" }
103 | let(:search) do
104 | [
105 | {
106 | filter: "id in [450847466900987455]",
107 | data: [[0.1, 0.2, 0.3]],
108 | annsField: "vectors",
109 | limit: 10,
110 | outputFields: ["content", "id"]
111 | }
112 | ]
113 | end
114 | let(:rerank) do
115 | {
116 | strategy: "rrf",
117 | params: {
118 | k: 10
119 | }
120 | }
121 | end
122 | let(:limit) { 10 }
123 | let(:output_fields) { ["field1", "field2"] }
124 | let(:response_body) { File.read("spec/fixtures/entities/hybrid_search.json") }
125 | let(:response) { instance_double("Faraday::Response", body: response_body) }
126 |
127 | it "executes a hybrid search" do
128 | expect(connection).to receive(:post).with("entities/hybrid_search").and_return(response)
129 | result = entities.hybrid_search(collection_name: collection_name, search: search, rerank: rerank, limit: limit, output_fields: output_fields)
130 | expect(result).to eq(response_body)
131 | end
132 | end
133 | end
134 |
--------------------------------------------------------------------------------
/spec/milvus/indexes_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/indexes_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Indexes do
7 | let(:client) { instance_double("Client", connection: connection) }
8 | let(:connection) { instance_double("Faraday::Connection") }
9 | let(:indexes) { described_class.new(client: client) }
10 |
11 | describe "#create" do
12 | let(:collection_name) { "test_collection" }
13 | let(:index_params) { {metricType: "L2", indexType: "IVF_FLAT", params: {nlist: 128}} }
14 | let(:response_body) { File.read("spec/fixtures/indexes/create.json") }
15 | let(:response) { instance_double("Faraday::Response", body: response_body) }
16 |
17 | it "creates a named index for a target field" do
18 | expect(connection).to receive(:post).with("indexes/create").and_return(response)
19 | result = indexes.create(collection_name: collection_name, index_params: index_params)
20 | expect(result).to eq(response_body)
21 | end
22 | end
23 |
24 | describe "#drop" do
25 | let(:collection_name) { "test_collection" }
26 | let(:index_name) { "test_index" }
27 | let(:response_body) { File.read("spec/fixtures/indexes/drop.json") }
28 | let(:response) { instance_double("Faraday::Response", body: response_body) }
29 |
30 | it "deletes index from a specified collection" do
31 | expect(connection).to receive(:post).with("indexes/drop").and_return(response)
32 | result = indexes.drop(collection_name: collection_name, index_name: index_name)
33 | expect(result).to eq(response_body)
34 | end
35 | end
36 |
37 | describe "#describe" do
38 | let(:collection_name) { "test_collection" }
39 | let(:index_name) { "test_index" }
40 | let(:response_body) { File.read("spec/fixtures/indexes/describe.json") }
41 | let(:response) { instance_double("Faraday::Response", body: response_body) }
42 |
43 | it "describes the current index" do
44 | expect(connection).to receive(:post).with("indexes/describe").and_return(response)
45 | result = indexes.describe(collection_name: collection_name, index_name: index_name)
46 | expect(result).to eq(response_body)
47 | end
48 | end
49 |
50 | describe "#list" do
51 | let(:collection_name) { "test_collection" }
52 | let(:response_body) { File.read("spec/fixtures/indexes/list.json") }
53 | let(:response) { instance_double("Faraday::Response", body: response_body) }
54 |
55 | it "lists all indexes of a specific collection" do
56 | expect(connection).to receive(:post).with("indexes/list").and_return(response)
57 | result = indexes.list(collection_name: collection_name)
58 | expect(result).to eq(response_body)
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/milvus/partitions_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/partitions_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Partitions do
7 | let(:client) { instance_double("Client", connection: connection) }
8 | let(:connection) { instance_double("Faraday::Connection") }
9 | let(:partitions) { described_class.new(client: client) }
10 |
11 | describe "#list" do
12 | let(:collection_name) { "test_collection" }
13 | let(:response_body) { File.read("spec/fixtures/partitions/list.json") }
14 | let(:response) { instance_double("Faraday::Response", body: response_body) }
15 |
16 | it "lists all partitions in the specified collection" do
17 | expect(connection).to receive(:post).with("partitions/list").and_return(response)
18 | result = partitions.list(collection_name: collection_name)
19 | expect(result).to eq(response_body)
20 | end
21 | end
22 |
23 | describe "#create" do
24 | let(:collection_name) { "test_collection" }
25 | let(:partition_name) { "test_partition" }
26 | let(:response_body) { File.read("spec/fixtures/partitions/create.json") }
27 | let(:response) { instance_double("Faraday::Response", body: response_body) }
28 |
29 | it "creates a partition in a collection" do
30 | expect(connection).to receive(:post).with("partitions/create").and_return(response)
31 | result = partitions.create(collection_name: collection_name, partition_name: partition_name)
32 | expect(result).to eq(response_body)
33 | end
34 | end
35 |
36 | describe "#drop" do
37 | let(:collection_name) { "test_collection" }
38 | let(:partition_name) { "test_partition" }
39 | let(:response_body) { File.read("spec/fixtures/partitions/drop.json") }
40 | let(:response) { instance_double("Faraday::Response", body: response_body) }
41 |
42 | it "drops the current partition from the collection" do
43 | expect(connection).to receive(:post).with("partitions/drop").and_return(response)
44 | result = partitions.drop(collection_name: collection_name, partition_name: partition_name)
45 | expect(result).to eq(response_body)
46 | end
47 | end
48 |
49 | describe "#has" do
50 | let(:collection_name) { "test_collection" }
51 | let(:partition_name) { "test_partition" }
52 | let(:response_body) { File.read("spec/fixtures/partitions/has.json") }
53 | let(:response) { instance_double("Faraday::Response", body: response_body) }
54 |
55 | it "checks whether a partition exists" do
56 | expect(connection).to receive(:post).with("partitions/has").and_return(response)
57 | result = partitions.has(collection_name: collection_name, partition_name: partition_name)
58 | expect(result).to eq(response_body)
59 | end
60 | end
61 |
62 | describe "#load" do
63 | let(:collection_name) { "test_collection" }
64 | let(:partition_names) { ["test_partition"] }
65 | let(:response_body) { File.read("spec/fixtures/partitions/load.json") }
66 | let(:response) { instance_double("Faraday::Response", body: response_body) }
67 |
68 | it "loads the data of the current partition into memory" do
69 | expect(connection).to receive(:post).with("partitions/load").and_return(response)
70 | result = partitions.load(collection_name: collection_name, partition_names: partition_names)
71 | expect(result).to eq(response_body)
72 | end
73 | end
74 |
75 | describe "#release" do
76 | let(:collection_name) { "test_collection" }
77 | let(:partition_names) { ["test_partition"] }
78 | let(:response_body) { File.read("spec/fixtures/partitions/release.json") }
79 | let(:response) { instance_double("Faraday::Response", body: response_body) }
80 |
81 | it "releases the data of the current partition from memory" do
82 | expect(connection).to receive(:post).with("partitions/release").and_return(response)
83 | result = partitions.release(collection_name: collection_name, partition_names: partition_names)
84 | expect(result).to eq(response_body)
85 | end
86 | end
87 |
88 | describe "#get_stats" do
89 | let(:collection_name) { "test_collection" }
90 | let(:partition_name) { "test_partition" }
91 | let(:response_body) { File.read("spec/fixtures/partitions/get_stats.json") }
92 | let(:response) { instance_double("Faraday::Response", body: response_body) }
93 |
94 | it "gets the number of entities in a partition" do
95 | expect(connection).to receive(:post).with("partitions/get_stats").and_return(response)
96 | result = partitions.get_stats(collection_name: collection_name, partition_name: partition_name)
97 | expect(result).to eq(response_body)
98 | end
99 | end
100 | end
101 |
--------------------------------------------------------------------------------
/spec/milvus/roles_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/roles_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Roles do
7 | let(:connection) { instance_double("Faraday::Connection") }
8 | let(:client) { instance_double("Client", connection: connection) }
9 | let(:roles) { described_class.new(client: client) }
10 |
11 | describe "#list" do
12 | let(:response_body) { File.read("spec/fixtures/roles/list.json") }
13 | let(:response) { instance_double("Faraday::Response", body: response_body) }
14 |
15 | it "lists users" do
16 | expect(connection).to receive(:post)
17 | .with("roles/list")
18 | .and_yield(Faraday::Request.new)
19 | .and_return(response)
20 | result = roles.list
21 |
22 | expect(result).to eq(response_body)
23 | end
24 | end
25 |
26 | describe "#describe" do
27 | let(:response_body) { File.read("spec/fixtures/roles/describe.json") }
28 | let(:response) { instance_double("Faraday::Response", body: response_body) }
29 |
30 | it "describes the role" do
31 | expect(connection).to receive(:post)
32 | .with("roles/describe")
33 | .and_yield(Faraday::Request.new)
34 | .and_return(response)
35 | result = roles.describe(role_name: "public")
36 |
37 | expect(result).to eq(response_body)
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/spec/milvus/users_spec.rb:
--------------------------------------------------------------------------------
1 | # spec/milvus/users_spec.rb
2 |
3 | require "spec_helper"
4 | require "faraday"
5 |
6 | RSpec.describe Milvus::Users do
7 | let(:connection) { instance_double("Faraday::Connection") }
8 | let(:client) { instance_double("Client", connection: connection) }
9 | let(:users) { described_class.new(client: client) }
10 |
11 | describe "#create" do
12 | let(:response_body) { File.read("spec/fixtures/users/create.json") }
13 | let(:response) { instance_double("Faraday::Response", body: response_body) }
14 |
15 | it "creates a user" do
16 | expect(connection).to receive(:post)
17 | .with("users/create")
18 | .and_yield(Faraday::Request.new)
19 | .and_return(response)
20 | result = users.create(user_name: "user_name", password: "password")
21 |
22 | expect(result).to eq(response_body)
23 | end
24 | end
25 |
26 | describe "#describe" do
27 | let(:response_body) { File.read("spec/fixtures/users/describe.json") }
28 | let(:response) { instance_double("Faraday::Response", body: response_body) }
29 |
30 | it "describes the user" do
31 | expect(connection).to receive(:post)
32 | .with("users/describe")
33 | .and_yield(Faraday::Request.new)
34 | .and_return(response)
35 | result = users.describe(user_name: "root")
36 |
37 | expect(result).to eq(response_body)
38 | end
39 | end
40 |
41 | describe "#list" do
42 | let(:response_body) { File.read("spec/fixtures/users/list.json") }
43 | let(:response) { instance_double("Faraday::Response", body: response_body) }
44 |
45 | it "responds with a list of users" do
46 | expect(connection).to receive(:post)
47 | .with("users/list")
48 | .and_yield(Faraday::Request.new)
49 | .and_return(response)
50 | result = users.list
51 |
52 | expect(result).to eq(response_body)
53 | end
54 | end
55 |
56 | describe "#drop" do
57 | let(:response_body) { File.read("spec/fixtures/users/drop.json") }
58 | let(:response) { instance_double("Faraday::Response", body: response_body) }
59 |
60 | it "drops the user" do
61 | expect(connection).to receive(:post)
62 | .with("users/drop")
63 | .and_yield(Faraday::Request.new)
64 | .and_return(response)
65 | result = users.drop(user_name: "root")
66 |
67 | expect(result).to eq(response_body)
68 | end
69 | end
70 |
71 | describe "#update_password" do
72 | let(:response_body) { File.read("spec/fixtures/users/update_password.json") }
73 | let(:response) { instance_double("Faraday::Response", body: response_body) }
74 |
75 | it "updates the users's password" do
76 | expect(connection).to receive(:post)
77 | .with("users/update_password")
78 | .and_yield(Faraday::Request.new)
79 | .and_return(response)
80 | result = users.update_password user_name: "username",
81 | password: "old_password",
82 | new_password: "new_password"
83 |
84 | expect(result).to eq(response_body)
85 | end
86 | end
87 |
88 | describe "#grant_role" do
89 | let(:response_body) { File.read("spec/fixtures/users/grant_role.json") }
90 | let(:response) { instance_double("Faraday::Response", body: response_body) }
91 |
92 | it "sets role to the user" do
93 | expect(connection).to receive(:post)
94 | .with("users/grant_role")
95 | .and_yield(Faraday::Request.new)
96 | .and_return(response)
97 | result = users.grant_role user_name: "user_name", role_name: "admin"
98 |
99 | expect(result).to eq(response_body)
100 | end
101 | end
102 |
103 | describe "#revoke_role" do
104 | let(:response_body) { File.read("spec/fixtures/users/revoke_role.json") }
105 | let(:response) { instance_double("Faraday::Response", body: response_body) }
106 |
107 | it "revokes the role" do
108 | expect(connection).to receive(:post)
109 | .with("users/revoke_role")
110 | .and_yield(Faraday::Request.new)
111 | .and_return(response)
112 | result = users.revoke_role user_name: "user_name", role_name: "admin"
113 |
114 | expect(result).to eq(response_body)
115 | end
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/spec/milvus_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Milvus do
4 | it "has a version number" do
5 | expect(Milvus::VERSION).not_to be nil
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "milvus"
4 | require "faraday"
5 |
6 | RSpec.configure do |config|
7 | # Enable flags like --only-failures and --next-failure
8 | config.example_status_persistence_file_path = ".rspec_status"
9 |
10 | # Disable RSpec exposing methods globally on `Module` and `main`
11 | config.disable_monkey_patching!
12 |
13 | config.expect_with :rspec do |c|
14 | c.syntax = :expect
15 | end
16 | end
17 |
--------------------------------------------------------------------------------