Spaces:
Sleeping
Sleeping
KevinStephenson
commited on
Commit
·
b110593
1
Parent(s):
9f9f982
Adding in weaviate code
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .codeclimate.yml +11 -0
- .dockerignore +39 -0
- .github/CODEOWNERS +3 -0
- .github/ISSUE_TEMPLATE/config.yml +9 -0
- .github/ISSUE_TEMPLATE/create_issue.yml +59 -0
- .github/ISSUE_TEMPLATE/feature_request.yml +29 -0
- .github/PULL_REQUEST_TEMPLATE.md +9 -0
- .github/dependabot.yml +7 -0
- .github/stale.yml +33 -0
- .github/workflows/branch.yaml +33 -0
- .github/workflows/linter.yml +29 -0
- .github/workflows/pull_requests.yaml +225 -0
- .github/workflows/release.yaml +32 -0
- .gitignore +109 -0
- .golangci.yml +62 -0
- .goreleaser.yaml +24 -0
- .pre-commit-config.yaml +13 -0
- .protolint.yaml +4 -0
- CITATION.cff +23 -0
- CODE_OF_CONDUCT.md +77 -0
- CONTRIBUTING.md +43 -0
- DockerfileHld +49 -0
- LICENSE +27 -0
- adapters/clients/client.go +96 -0
- adapters/clients/cluster_backups.go +178 -0
- adapters/clients/cluster_classifications.go +127 -0
- adapters/clients/cluster_node.go +66 -0
- adapters/clients/cluster_schema.go +164 -0
- adapters/clients/cluster_schema_test.go +441 -0
- adapters/clients/remote_index.go +839 -0
- adapters/clients/remote_index_test.go +312 -0
- adapters/clients/replication.go +311 -0
- adapters/clients/replication_test.go +592 -0
- adapters/handlers/graphql/common/fetch/filter.go +146 -0
- adapters/handlers/graphql/common/json_number.go +60 -0
- adapters/handlers/graphql/common/json_number_test.go +79 -0
- adapters/handlers/graphql/descriptions/aggregate.go +83 -0
- adapters/handlers/graphql/descriptions/explore.go +30 -0
- adapters/handlers/graphql/descriptions/fetch.go +55 -0
- adapters/handlers/graphql/descriptions/filters.go +154 -0
- adapters/handlers/graphql/descriptions/get.go +50 -0
- adapters/handlers/graphql/descriptions/getMeta.go +88 -0
- adapters/handlers/graphql/descriptions/introspect.go +30 -0
- adapters/handlers/graphql/descriptions/merge.go +31 -0
- adapters/handlers/graphql/descriptions/rootQuery.go +28 -0
- adapters/handlers/graphql/graphiql/graphiql.go +233 -0
- adapters/handlers/graphql/local/aggregate/aggregate.go +281 -0
- adapters/handlers/graphql/local/aggregate/explore_argument.go +25 -0
- adapters/handlers/graphql/local/aggregate/helpers_for_test.go +56 -0
- adapters/handlers/graphql/local/aggregate/hybrid_search.go +90 -0
.codeclimate.yml
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: "2"
|
2 |
+
exclude_pattern:
|
3 |
+
- client/
|
4 |
+
- cmd/weaviate-server/main.go
|
5 |
+
|
6 |
+
# exclude all but the configure_weaviate.go file in restapi/,
|
7 |
+
# given that the other files are automatically generated.
|
8 |
+
- restapi/
|
9 |
+
- !restapi/configure_weaviate*
|
10 |
+
|
11 |
+
- test/
|
.dockerignore
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.git
|
2 |
+
Dockerfile.*
|
3 |
+
docker-compose.yml
|
4 |
+
data*
|
5 |
+
logs/
|
6 |
+
snapshots/
|
7 |
+
snapshots-fs/
|
8 |
+
snapshots-node2/
|
9 |
+
snapshots-s3/
|
10 |
+
snapshots-gcs/
|
11 |
+
backups/
|
12 |
+
backups-fs/
|
13 |
+
backups-node2/
|
14 |
+
backups-s3/
|
15 |
+
backups-gcs/
|
16 |
+
test/
|
17 |
+
tools/
|
18 |
+
**/*.fvecs # sift data
|
19 |
+
**/*.png # profiling
|
20 |
+
**/*.out
|
21 |
+
**/*.csv
|
22 |
+
**/*.md
|
23 |
+
**/*_test.go
|
24 |
+
**/.DS_Store
|
25 |
+
**/testdata/
|
26 |
+
coverage-integration.txt
|
27 |
+
coverage-unit.txt
|
28 |
+
|
29 |
+
# wikipedia dataset when downloaded according to docs
|
30 |
+
weaviate-wikipedia-*
|
31 |
+
# the local path when extracting the wiki dataset
|
32 |
+
var/weaviate/
|
33 |
+
|
34 |
+
# IDE files
|
35 |
+
.idea
|
36 |
+
.vscode/
|
37 |
+
|
38 |
+
# ignore archives, they are most likely part of some debugging and never part of actual code
|
39 |
+
*.tar.gz
|
.github/CODEOWNERS
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
# Ci related folders
|
2 |
+
/.github/ @weaviate/core
|
3 |
+
/ci/ @weaviate/core
|
.github/ISSUE_TEMPLATE/config.yml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
blank_issues_enabled: true
|
2 |
+
contact_links:
|
3 |
+
- name: Need Help? Go to the community forum
|
4 |
+
url: https://forum.weaviate.io
|
5 |
+
about: "Community-powered knowledge repository: search, ask questions, give feedback"
|
6 |
+
|
7 |
+
- name: Want to chat? Go to the community slack
|
8 |
+
url: https://weaviate.io/slack
|
9 |
+
about: "Chat with the community about issues, questions, networking and feedback"
|
.github/ISSUE_TEMPLATE/create_issue.yml
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Found a bug?
|
2 |
+
description: Report your bug here.
|
3 |
+
labels: ["bug", "triage"]
|
4 |
+
body:
|
5 |
+
|
6 |
+
- type: markdown
|
7 |
+
attributes:
|
8 |
+
value: |
|
9 |
+
#### Before you get started
|
10 |
+
* Check to make sure someone hasn't already opened a similar [issue](https://github.com/weaviate/weaviate-io/issues).
|
11 |
+
* Check this example of a [good bug report](https://github.com/weaviate/weaviate/issues/3762).
|
12 |
+
* Read the [Contributor Guide](https://weaviate.io/developers/contributor-guide) and [Code of Conduct](https://weaviate.io/service/code-of-conduct).
|
13 |
+
|
14 |
+
- type: textarea
|
15 |
+
id: how_to_reproduce
|
16 |
+
attributes:
|
17 |
+
label: How to reproduce this bug?
|
18 |
+
description: Specify the steps here in order to reproduce this bug.
|
19 |
+
validations:
|
20 |
+
required: true
|
21 |
+
|
22 |
+
- type: textarea
|
23 |
+
id: expected_behavior
|
24 |
+
attributes:
|
25 |
+
label: What is the expected behavior?
|
26 |
+
validations:
|
27 |
+
required: true
|
28 |
+
|
29 |
+
- type: textarea
|
30 |
+
id: actual_behavior
|
31 |
+
attributes:
|
32 |
+
label: What is the actual behavior?
|
33 |
+
validations:
|
34 |
+
required: true
|
35 |
+
|
36 |
+
- type: textarea
|
37 |
+
id: suporting_information
|
38 |
+
attributes:
|
39 |
+
label: Supporting information
|
40 |
+
description: Please, paste any logs, context information (client version? environment variables?) or other details in here.
|
41 |
+
validations:
|
42 |
+
required: false
|
43 |
+
|
44 |
+
- type: input
|
45 |
+
id: server_version
|
46 |
+
attributes:
|
47 |
+
label: Server Version
|
48 |
+
description: What Weaviate Version are you running?
|
49 |
+
validations:
|
50 |
+
required: true
|
51 |
+
|
52 |
+
- type: checkboxes
|
53 |
+
id: terms
|
54 |
+
attributes:
|
55 |
+
label: Code of Conduct
|
56 |
+
description: This project has a Code of Conduct. All participants are expected to understand and follow the CoC.
|
57 |
+
options:
|
58 |
+
- label: I have read and agree to the Weaviate's [Contributor Guide](https://weaviate.io/developers/contributor-guide) and [Code of Conduct](https://weaviate.io/service/code-of-conduct)
|
59 |
+
required: true
|
.github/ISSUE_TEMPLATE/feature_request.yml
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Missing a Feature?
|
2 |
+
description: Suggest it here.
|
3 |
+
labels: ["feature request", "triage"]
|
4 |
+
body:
|
5 |
+
- type: markdown
|
6 |
+
attributes:
|
7 |
+
value: |
|
8 |
+
#### Before you get started
|
9 |
+
* Is this feature already in our [roadmap](https://weaviate.io/developers/weaviate/roadmap)?
|
10 |
+
* Check to make sure someone hasn't already [requested this feature](https://github.com/weaviate/weaviate/issues?q=is:issue+label:"feature+request"). if that's the case, give it a thumbs up 👍
|
11 |
+
* Read the [Contributor Guide](https://weaviate.io/developers/contributor-guide) and [Code of Conduct](https://weaviate.io/service/code-of-conduct).
|
12 |
+
|
13 |
+
- type: textarea
|
14 |
+
id: feature_request_body
|
15 |
+
attributes:
|
16 |
+
label: Describe your feature request
|
17 |
+
description: |
|
18 |
+
- Describe here your requested feature. Provide all necessary context, use cases and the reasoning on why this feature is a good one.
|
19 |
+
validations:
|
20 |
+
required: true
|
21 |
+
|
22 |
+
- type: checkboxes
|
23 |
+
id: terms
|
24 |
+
attributes:
|
25 |
+
label: Code of Conduct
|
26 |
+
description: This project has a Code of Conduct that all participants are expected to understand and follow.
|
27 |
+
options:
|
28 |
+
- label: I have read and agree to the Weaviate's [Contributor Guide](https://weaviate.io/developers/contributor-guide) and [Code of Conduct](https://weaviate.io/service/code-of-conduct)
|
29 |
+
required: true
|
.github/PULL_REQUEST_TEMPLATE.md
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
### What's being changed:
|
2 |
+
|
3 |
+
|
4 |
+
### Review checklist
|
5 |
+
|
6 |
+
- [ ] Documentation has been updated, if necessary. Link to changed documentation:
|
7 |
+
- [ ] Chaos pipeline run or not necessary. Link to pipeline:
|
8 |
+
- [ ] All new code is covered by tests where it is reasonable.
|
9 |
+
- [ ] Performance tests have been run or not necessary.
|
.github/dependabot.yml
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: 2
|
2 |
+
updates:
|
3 |
+
- package-ecosystem: "github-actions"
|
4 |
+
directory: "/"
|
5 |
+
schedule:
|
6 |
+
# Check for updates to GitHub Actions every week
|
7 |
+
interval: "weekly"
|
.github/stale.yml
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Number of days of inactivity before an issue becomes stale
|
2 |
+
daysUntilStale: 60
|
3 |
+
# Number of days of inactivity before a stale issue is closed
|
4 |
+
daysUntilClose: 7
|
5 |
+
# Issues with these labels will never be considered stale
|
6 |
+
exemptLabels:
|
7 |
+
- pinned
|
8 |
+
- security
|
9 |
+
- bug
|
10 |
+
- backlog
|
11 |
+
- needs-investigation
|
12 |
+
# Label to use when marking an issue as stale
|
13 |
+
staleLabel: autoclosed
|
14 |
+
# Comment to post when marking an issue as stale. Set to `false` to disable
|
15 |
+
markComment: >
|
16 |
+
Thank you for your contribution to Weaviate. This issue has not received any
|
17 |
+
activity in a while and has therefore been marked as stale. Stale issues will
|
18 |
+
eventually be autoclosed. This does not mean that we are ruling out to work
|
19 |
+
on this issue, but it most likely has not been prioritized high enough in the
|
20 |
+
last months.
|
21 |
+
|
22 |
+
If you believe that this issue should remain open, please leave a short reply.
|
23 |
+
This lets us know that the issue is not abandoned and acts as a reminder for our
|
24 |
+
team to consider prioritizing this again.
|
25 |
+
|
26 |
+
Please also consider if you can make a contribution to help with the solution
|
27 |
+
of this issue. If you are willing to contribute, but don't know where to start,
|
28 |
+
please leave a quick message and we'll try to help you.
|
29 |
+
|
30 |
+
Thank you,
|
31 |
+
The Weaviate Team
|
32 |
+
# Comment to post when closing a stale issue. Set to `false` to disable
|
33 |
+
closeComment: false
|
.github/workflows/branch.yaml
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Tests
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches:
|
6 |
+
- '**' # run on ever branch except master (is covered by PR)
|
7 |
+
- '!master'
|
8 |
+
jobs:
|
9 |
+
Unit-Tests:
|
10 |
+
name: unit-tests
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v4
|
14 |
+
- name: Set up Go
|
15 |
+
uses: actions/setup-go@v5
|
16 |
+
with:
|
17 |
+
go-version: '1.21'
|
18 |
+
cache: true
|
19 |
+
- name: Unit test
|
20 |
+
run: ./test/run.sh --unit-only
|
21 |
+
Integration-Tests:
|
22 |
+
name: integration-tests
|
23 |
+
runs-on: ubuntu-latest
|
24 |
+
steps:
|
25 |
+
- uses: actions/checkout@v4
|
26 |
+
- name: Set up Go
|
27 |
+
uses: actions/setup-go@v5
|
28 |
+
with:
|
29 |
+
go-version: '1.21'
|
30 |
+
cache: true
|
31 |
+
- name: Integration test
|
32 |
+
run: ./test/run.sh --integration-only
|
33 |
+
|
.github/workflows/linter.yml
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: golangci-lint
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches:
|
5 |
+
- master
|
6 |
+
tags:
|
7 |
+
- '**'
|
8 |
+
pull_request:
|
9 |
+
jobs:
|
10 |
+
golangci:
|
11 |
+
name: golangci
|
12 |
+
runs-on: ubuntu-latest
|
13 |
+
steps:
|
14 |
+
- uses: actions/setup-go@v5
|
15 |
+
with:
|
16 |
+
go-version: '1.21'
|
17 |
+
- uses: actions/checkout@v4
|
18 |
+
- name: golangci-lint
|
19 |
+
uses: golangci/golangci-lint-action@v3
|
20 |
+
with:
|
21 |
+
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
22 |
+
version: v1.54
|
23 |
+
args: --timeout=5m
|
24 |
+
protolint:
|
25 |
+
name: protolint
|
26 |
+
runs-on: ubuntu-latest
|
27 |
+
steps:
|
28 |
+
- uses: actions/checkout@v4
|
29 |
+
- uses: yoheimuta/action-protolint@v1
|
.github/workflows/pull_requests.yaml
ADDED
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Tests
|
2 |
+
|
3 |
+
on:
|
4 |
+
push:
|
5 |
+
branches:
|
6 |
+
- master
|
7 |
+
tags:
|
8 |
+
- '**'
|
9 |
+
pull_request:
|
10 |
+
|
11 |
+
jobs:
|
12 |
+
Run-Swagger:
|
13 |
+
name: run-swagger
|
14 |
+
runs-on: ubuntu-latest
|
15 |
+
steps:
|
16 |
+
- uses: actions/checkout@v4
|
17 |
+
- name: Set up Go
|
18 |
+
uses: actions/setup-go@v5
|
19 |
+
with:
|
20 |
+
go-version: '1.21'
|
21 |
+
cache: true
|
22 |
+
- name: Run Swagger
|
23 |
+
run: ./tools/gen-code-from-swagger.sh
|
24 |
+
- name: Error on change
|
25 |
+
run: |
|
26 |
+
# check if anything is different
|
27 |
+
CHANGED=$(git status -s | wc -l)
|
28 |
+
if [ "$CHANGED" -gt 0 ]; then
|
29 |
+
echo "Please run ./tools/gen-code-from-swagger.sh script and commit changes:"
|
30 |
+
git status -s
|
31 |
+
exit 1
|
32 |
+
else
|
33 |
+
exit 0
|
34 |
+
fi
|
35 |
+
Vulnerability-Scanning:
|
36 |
+
name: vulnerability-scanning
|
37 |
+
runs-on: ubuntu-latest
|
38 |
+
if: ${{ !github.event.pull_request.head.repo.fork }} # no PRs from fork
|
39 |
+
steps:
|
40 |
+
- uses: actions/checkout@v4
|
41 |
+
- name: Scan for Vulnerabilities in Code
|
42 |
+
uses: Templum/[email protected]
|
43 |
+
with:
|
44 |
+
go-version: '1.21'
|
45 |
+
package: ./...
|
46 |
+
fail-on-vuln: true
|
47 |
+
Unit-Tests:
|
48 |
+
name: unit-tests
|
49 |
+
runs-on: ubuntu-latest
|
50 |
+
steps:
|
51 |
+
- uses: actions/checkout@v4
|
52 |
+
- name: Set up Go
|
53 |
+
uses: actions/setup-go@v5
|
54 |
+
with:
|
55 |
+
go-version: '1.21'
|
56 |
+
cache: true
|
57 |
+
- name: Unit test
|
58 |
+
run: ./test/run.sh --unit-only
|
59 |
+
- name: Archive code coverage results
|
60 |
+
uses: actions/upload-artifact@v4
|
61 |
+
with:
|
62 |
+
name: coverage-report-unit
|
63 |
+
path: coverage-unit.txt
|
64 |
+
Integration-Tests:
|
65 |
+
name: integration-tests
|
66 |
+
runs-on: ubuntu-latest
|
67 |
+
steps:
|
68 |
+
- uses: actions/checkout@v4
|
69 |
+
- name: Set up Go
|
70 |
+
uses: actions/setup-go@v5
|
71 |
+
with:
|
72 |
+
go-version: '1.21'
|
73 |
+
cache: true
|
74 |
+
- name: Integration test
|
75 |
+
run: ./test/run.sh --integration-only
|
76 |
+
- name: Archive code coverage results
|
77 |
+
uses: actions/upload-artifact@v4
|
78 |
+
with:
|
79 |
+
name: coverage-report-integration
|
80 |
+
path: coverage-integration.txt
|
81 |
+
Modules-Acceptance-Tests:
|
82 |
+
name: modules-acceptance-tests
|
83 |
+
runs-on: ubuntu-latest-4-cores
|
84 |
+
strategy:
|
85 |
+
fail-fast: false
|
86 |
+
matrix:
|
87 |
+
test: ["--modules-backup-only", "--modules-except-backup"]
|
88 |
+
steps:
|
89 |
+
- uses: actions/checkout@v4
|
90 |
+
- name: Set up Go
|
91 |
+
uses: actions/setup-go@v5
|
92 |
+
with:
|
93 |
+
go-version: '1.21'
|
94 |
+
cache: true
|
95 |
+
- name: Acceptance tests (modules)
|
96 |
+
run: ./test/run.sh ${{ matrix.test }}
|
97 |
+
Acceptance-Tests:
|
98 |
+
name: acceptance-tests
|
99 |
+
runs-on: ubuntu-latest
|
100 |
+
steps:
|
101 |
+
- uses: actions/checkout@v4
|
102 |
+
- name: Set up Go
|
103 |
+
uses: actions/setup-go@v5
|
104 |
+
with:
|
105 |
+
go-version: '1.21'
|
106 |
+
cache: true
|
107 |
+
- name: Acceptance tests
|
108 |
+
env:
|
109 |
+
WCS_DUMMY_CI_PW: ${{ secrets.WCS_DUMMY_CI_PW }}
|
110 |
+
WCS_DUMMY_CI_PW_2: ${{ secrets.WCS_DUMMY_CI_PW_2 }}
|
111 |
+
run: ./test/run.sh --acceptance-only
|
112 |
+
Codecov:
|
113 |
+
needs: [Unit-Tests, Integration-Tests]
|
114 |
+
name: codecov
|
115 |
+
runs-on: ubuntu-latest
|
116 |
+
if: ${{ (github.ref_type == 'branch') && (github.ref_name != 'master') }}
|
117 |
+
steps:
|
118 |
+
- uses: actions/checkout@v4
|
119 |
+
- name: Download coverage artifacts integration
|
120 |
+
uses: actions/download-artifact@v4
|
121 |
+
with:
|
122 |
+
name: coverage-report-unit
|
123 |
+
- name: Download coverage unit
|
124 |
+
uses: actions/download-artifact@v4
|
125 |
+
with:
|
126 |
+
name: coverage-report-integration
|
127 |
+
- name: Codecov
|
128 |
+
uses: codecov/codecov-action@v3
|
129 |
+
with:
|
130 |
+
fail_ci_if_error: false
|
131 |
+
files: ./coverage-integration.txt, ./coverage-unit.txt
|
132 |
+
verbose: true
|
133 |
+
Compile-and-upload-binaries:
|
134 |
+
name: compile-and-upload-binaries
|
135 |
+
runs-on: ubuntu-latest-4-cores
|
136 |
+
steps:
|
137 |
+
- uses: actions/checkout@v4
|
138 |
+
- name: Set up Go
|
139 |
+
uses: actions/setup-go@v5
|
140 |
+
with:
|
141 |
+
go-version: '1.21'
|
142 |
+
cache: true
|
143 |
+
- name: goreleaser
|
144 |
+
run: |
|
145 |
+
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
|
146 |
+
sudo apt update
|
147 |
+
sudo apt install goreleaser
|
148 |
+
GIT_HASH=$(git rev-parse --short HEAD) goreleaser build --clean --snapshot
|
149 |
+
- name: Upload macos
|
150 |
+
uses: actions/upload-artifact@v4
|
151 |
+
with:
|
152 |
+
name: binaries-macos-unsigned
|
153 |
+
path: dist/weaviate_darwin_all
|
154 |
+
- name: Upload windows
|
155 |
+
uses: actions/upload-artifact@v4
|
156 |
+
with:
|
157 |
+
name: binaries-windows-amd64
|
158 |
+
path: dist/weaviate_windows_amd64_v1
|
159 |
+
- name: Upload windows
|
160 |
+
uses: actions/upload-artifact@v4
|
161 |
+
with:
|
162 |
+
name: binaries-windows-arm64
|
163 |
+
path: dist/weaviate_windows_arm64
|
164 |
+
- name: Upload linux amd64
|
165 |
+
uses: actions/upload-artifact@v4
|
166 |
+
with:
|
167 |
+
name: binaries-linux-amd64
|
168 |
+
path: dist/weaviate_linux_amd64_v1
|
169 |
+
- name: Upload linux arm64
|
170 |
+
uses: actions/upload-artifact@v4
|
171 |
+
with:
|
172 |
+
name: binaries-linux-arm64
|
173 |
+
path: dist/weaviate_linux_arm64
|
174 |
+
|
175 |
+
|
176 |
+
Acceptance-Tests-windows:
|
177 |
+
name: acceptance-tests-windows
|
178 |
+
needs: Compile-and-upload-binaries
|
179 |
+
runs-on: windows-latest
|
180 |
+
env:
|
181 |
+
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
|
182 |
+
PERSISTENCE_DATA_PATH: /tmp
|
183 |
+
QUERY_DEFAULTS_LIMIT: 20
|
184 |
+
steps:
|
185 |
+
- uses: actions/checkout@v4
|
186 |
+
- name: Download binaries
|
187 |
+
uses: actions/download-artifact@v4
|
188 |
+
with:
|
189 |
+
name: binaries-windows-amd64
|
190 |
+
- name: Set up Go
|
191 |
+
uses: actions/setup-go@v5
|
192 |
+
with:
|
193 |
+
go-version: '1.21'
|
194 |
+
cache: true
|
195 |
+
- name: start weaviate
|
196 |
+
shell: bash
|
197 |
+
# Weaviate is started without a Vectorizer as running text2vec-contextionary on GH actions is difficult:
|
198 |
+
# - docker on GHA only supports windows container - which we currently are not build
|
199 |
+
# - building those containers without a windows machine is difficult to figure out
|
200 |
+
run: ./weaviate.exe --scheme http --port 8080 &
|
201 |
+
- name: run acceptance tests
|
202 |
+
shell: bash
|
203 |
+
run: go test -count 1 -race test/acceptance/actions/*.go # tests that don't need a Vectorizer
|
204 |
+
|
205 |
+
Push-Docker:
|
206 |
+
needs: [Acceptance-Tests, Modules-Acceptance-Tests, Unit-Tests, Integration-Tests, Vulnerability-Scanning, Run-Swagger]
|
207 |
+
name: push-docker
|
208 |
+
runs-on: ubuntu-latest-8-cores
|
209 |
+
if: ${{ !github.event.pull_request.head.repo.fork }} # no PRs from fork
|
210 |
+
steps:
|
211 |
+
- uses: actions/checkout@v4
|
212 |
+
- name: Login to Docker Hub
|
213 |
+
uses: docker/login-action@v3
|
214 |
+
with:
|
215 |
+
username: ${{secrets.DOCKER_USERNAME}}
|
216 |
+
password: ${{secrets.DOCKER_PASSWORD}}
|
217 |
+
- name: Push container
|
218 |
+
id: push-container
|
219 |
+
run: ./ci/push_docker.sh
|
220 |
+
env:
|
221 |
+
PR_TITLE: "${{ github.event.pull_request.title }}"
|
222 |
+
- name: Generate Report
|
223 |
+
env:
|
224 |
+
PREVIEW_TAG: "${{ steps.push-container.outputs.PREVIEW_TAG }}"
|
225 |
+
run: ./ci/generate_docker_report.sh
|
.github/workflows/release.yaml
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Generate release assets
|
2 |
+
|
3 |
+
on:
|
4 |
+
release:
|
5 |
+
types: [published]
|
6 |
+
|
7 |
+
env:
|
8 |
+
CGO_ENABLED: 0
|
9 |
+
|
10 |
+
permissions:
|
11 |
+
contents: write
|
12 |
+
|
13 |
+
jobs:
|
14 |
+
releases-matrix:
|
15 |
+
name: Release precompiled binaries
|
16 |
+
runs-on: ubuntu-latest
|
17 |
+
strategy:
|
18 |
+
matrix:
|
19 |
+
goos: [linux]
|
20 |
+
goarch: [amd64, arm64]
|
21 |
+
steps:
|
22 |
+
- uses: actions/checkout@v4
|
23 |
+
- uses: wangyoucao577/[email protected]
|
24 |
+
with:
|
25 |
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
26 |
+
goos: ${{ matrix.goos }}
|
27 |
+
goarch: ${{ matrix.goarch }}
|
28 |
+
goversion: "1.21"
|
29 |
+
project_path: "./cmd/weaviate-server"
|
30 |
+
extra_files: LICENSE README.md
|
31 |
+
ldflags: -w -extldflags "-static" -X github.com/weaviate/weaviate/usecases/config.GitHash='"$GITHASH"'
|
32 |
+
sha256sum: true
|
.gitignore
ADDED
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# ignore release dirs
|
2 |
+
nightly/*
|
3 |
+
dist/*
|
4 |
+
dist
|
5 |
+
nightly
|
6 |
+
|
7 |
+
# ignore confectionary files
|
8 |
+
contextionary/contextionary.vocab
|
9 |
+
contextionary/contextionary.idx
|
10 |
+
contextionary/contextionary.knn
|
11 |
+
contextionary/stopwords.json
|
12 |
+
wget-log*
|
13 |
+
|
14 |
+
# ignore update files
|
15 |
+
semver
|
16 |
+
cc-test-reporter
|
17 |
+
|
18 |
+
# Ignore OLD files
|
19 |
+
*.OLD
|
20 |
+
*.OLD.go
|
21 |
+
|
22 |
+
# Do not include dist tmp
|
23 |
+
dist_tmp
|
24 |
+
|
25 |
+
# No weaviate bins
|
26 |
+
./weaviate
|
27 |
+
|
28 |
+
# Logs
|
29 |
+
logs
|
30 |
+
*.log
|
31 |
+
npm-debug.log*
|
32 |
+
|
33 |
+
# Runtime data
|
34 |
+
pids
|
35 |
+
*.pid
|
36 |
+
*.seed
|
37 |
+
|
38 |
+
# Contents of temp-directory
|
39 |
+
temp
|
40 |
+
.temp
|
41 |
+
|
42 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
43 |
+
lib-cov
|
44 |
+
|
45 |
+
# Coverage directory used by tools like istanbul
|
46 |
+
coverage
|
47 |
+
|
48 |
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
49 |
+
.grunt
|
50 |
+
|
51 |
+
# node-waf configuration
|
52 |
+
.lock-wscript
|
53 |
+
|
54 |
+
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
55 |
+
build/Release
|
56 |
+
|
57 |
+
# Dependency directory
|
58 |
+
node_modules
|
59 |
+
|
60 |
+
# Optional npm cache directory
|
61 |
+
.npm
|
62 |
+
|
63 |
+
# Optional REPL history
|
64 |
+
.node_repl_history
|
65 |
+
|
66 |
+
# MAC stuff
|
67 |
+
.DS_Store
|
68 |
+
.!
|
69 |
+
|
70 |
+
# IDE files
|
71 |
+
.idea/
|
72 |
+
.vscode/
|
73 |
+
|
74 |
+
# Local files
|
75 |
+
_local
|
76 |
+
|
77 |
+
# Remove weave files
|
78 |
+
weave-current.json
|
79 |
+
weave-current-discovery.json
|
80 |
+
|
81 |
+
# local nodes folders
|
82 |
+
graphqlapi/prototype/node_modules/
|
83 |
+
|
84 |
+
/data*/
|
85 |
+
/snapshots*/
|
86 |
+
/backups*/
|
87 |
+
docker-compose/runtime-stable/data
|
88 |
+
docker-compose/runtime-unstable/data
|
89 |
+
c.out
|
90 |
+
/cmd/weaviate-server/build_artifacts/
|
91 |
+
|
92 |
+
esbackups/
|
93 |
+
**/testdata/
|
94 |
+
|
95 |
+
# coverage files
|
96 |
+
coverage*.txt
|
97 |
+
|
98 |
+
# go mod
|
99 |
+
vendor/
|
100 |
+
|
101 |
+
test/benchmark/benchmark_results.json
|
102 |
+
# benchmark results files copied from a remote run
|
103 |
+
benchmark_results_*.json
|
104 |
+
|
105 |
+
# local credentials
|
106 |
+
creds.json
|
107 |
+
|
108 |
+
# VSCode
|
109 |
+
cmd/weaviate-server/__debug_bin*
|
.golangci.yml
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
linters:
|
2 |
+
disable:
|
3 |
+
- errcheck
|
4 |
+
enable:
|
5 |
+
- misspell
|
6 |
+
- bodyclose
|
7 |
+
- gofumpt
|
8 |
+
- exhaustive
|
9 |
+
- govet
|
10 |
+
- unused
|
11 |
+
- nolintlint
|
12 |
+
linters-settings:
|
13 |
+
exhaustive:
|
14 |
+
# Presence of "default" case in switch statements satisfies exhaustiveness,
|
15 |
+
# even if all enum members are not listed.
|
16 |
+
default-signifies-exhaustive: true
|
17 |
+
issues:
|
18 |
+
exclude-rules:
|
19 |
+
# Exclude some staticcheck messages
|
20 |
+
- linters:
|
21 |
+
- staticcheck
|
22 |
+
text: "S1034"
|
23 |
+
- linters:
|
24 |
+
- staticcheck
|
25 |
+
text: "SA1029:"
|
26 |
+
- linters:
|
27 |
+
- staticcheck
|
28 |
+
text: "SA1015:"
|
29 |
+
- linters:
|
30 |
+
- staticcheck
|
31 |
+
text: "SA5011"
|
32 |
+
- linters:
|
33 |
+
- govet
|
34 |
+
text: "composites"
|
35 |
+
- linters:
|
36 |
+
- staticcheck
|
37 |
+
path: adapters/handlers/grpc/v1/filters.go
|
38 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
39 |
+
- linters:
|
40 |
+
- staticcheck
|
41 |
+
path: adapters/handlers/grpc/v1/prepare_reply.go
|
42 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
43 |
+
- linters:
|
44 |
+
- staticcheck
|
45 |
+
path: adapters/handlers/grpc/v1/parse_search_request.go
|
46 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
47 |
+
- linters:
|
48 |
+
- staticcheck
|
49 |
+
path: adapters/handlers/grpc/v1/batch_parse_request.go
|
50 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
51 |
+
- linters:
|
52 |
+
- staticcheck
|
53 |
+
path: adapters/handlers/grpc/v1/service.go
|
54 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
55 |
+
- linters:
|
56 |
+
- staticcheck
|
57 |
+
path: test/acceptance/grpc/grpc_test_deprecated.go
|
58 |
+
text: 'SA1019' # TODO: remove this once deprecated gRPC fields are removed from API
|
59 |
+
run:
|
60 |
+
build-tags:
|
61 |
+
- integrationTest
|
62 |
+
- integrationTestSlow
|
.goreleaser.yaml
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
before:
|
2 |
+
hooks:
|
3 |
+
# You may remove this if you don't use go modules.
|
4 |
+
- go mod tidy
|
5 |
+
builds:
|
6 |
+
- env:
|
7 |
+
- CGO_ENABLED=0
|
8 |
+
goos:
|
9 |
+
- linux
|
10 |
+
- darwin
|
11 |
+
- windows
|
12 |
+
main: ./cmd/weaviate-server
|
13 |
+
goarch:
|
14 |
+
- amd64
|
15 |
+
- arm64
|
16 |
+
ldflags:
|
17 |
+
- -w
|
18 |
+
- -extldflags "-static"
|
19 |
+
- -X github.com/weaviate/weaviate/usecases/config.GitHash={{ .Env.GIT_HASH }}
|
20 |
+
|
21 |
+
# create a "fat" binary for MacOS
|
22 |
+
universal_binaries:
|
23 |
+
- replace: true
|
24 |
+
|
.pre-commit-config.yaml
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
repos:
|
2 |
+
- repo: local
|
3 |
+
hooks:
|
4 |
+
- id: gofumpt
|
5 |
+
name: gofumpt
|
6 |
+
entry: gofumpt -w
|
7 |
+
language: system
|
8 |
+
types: [ go ]
|
9 |
+
- id: swagger
|
10 |
+
name: swagger
|
11 |
+
entry: ./tools/gen-code-from-swagger.sh
|
12 |
+
language: system
|
13 |
+
types: [ go ]
|
.protolint.yaml
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
lint:
|
2 |
+
rules_option:
|
3 |
+
max_line_length:
|
4 |
+
max_chars: 120
|
CITATION.cff
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cff-version: 1.2.0
|
2 |
+
message: "If you use this software, please cite it as below."
|
3 |
+
title: "Weaviate"
|
4 |
+
url: "https://github.com/weaviate/weaviate"
|
5 |
+
authors:
|
6 |
+
- family-names: "Dilocker"
|
7 |
+
given-names: "Etienne"
|
8 |
+
- family-names: "van Luijt"
|
9 |
+
given-names: "Bob"
|
10 |
+
orcid: "https://orcid.org/0000-0002-8239-7421"
|
11 |
+
- family-names: "Voorbach"
|
12 |
+
given-names: "Byron"
|
13 |
+
- family-names: "Hasan"
|
14 |
+
given-names: "Mohd Shukri"
|
15 |
+
- family-names: "Rodriguez"
|
16 |
+
given-names: "Abdel"
|
17 |
+
- family-names: "Kulawiak"
|
18 |
+
given-names: "Dirk Alexander"
|
19 |
+
orcid: "https://orcid.org/0000-0002-9848-3818"
|
20 |
+
- family-names: "Antas"
|
21 |
+
given-names: "Marcin"
|
22 |
+
- family-names: "Duckworth"
|
23 |
+
given-names: "Parker"
|
CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Contributor Covenant Code of Conduct
|
2 |
+
|
3 |
+
## Our Pledge
|
4 |
+
|
5 |
+
In the interest of fostering an open and welcoming environment, we as
|
6 |
+
contributors and maintainers pledge to make participation in our project and
|
7 |
+
our community a harassment-free experience for everyone, regardless of age, body
|
8 |
+
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
9 |
+
level of experience, education, socio-economic status, nationality, personal
|
10 |
+
appearance, race, religion, or sexual identity and orientation.
|
11 |
+
|
12 |
+
## Our Standards
|
13 |
+
|
14 |
+
Examples of behavior that contributes to creating a positive environment
|
15 |
+
include:
|
16 |
+
|
17 |
+
* Using welcoming and inclusive language
|
18 |
+
* Being respectful of differing viewpoints and experiences
|
19 |
+
* Gracefully accepting constructive criticism
|
20 |
+
* Focusing on what is best for the community
|
21 |
+
* Showing empathy towards other community members
|
22 |
+
|
23 |
+
Examples of unacceptable behavior by participants include:
|
24 |
+
|
25 |
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26 |
+
advances
|
27 |
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28 |
+
* Public or private harassment
|
29 |
+
* Publishing others' private information, such as a physical or electronic
|
30 |
+
address, without explicit permission
|
31 |
+
* Other conduct which could reasonably be considered inappropriate in a
|
32 |
+
professional setting
|
33 |
+
|
34 |
+
## Our Responsibilities
|
35 |
+
|
36 |
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37 |
+
behavior and are expected to take appropriate and fair corrective action in
|
38 |
+
response to any instances of unacceptable behavior.
|
39 |
+
|
40 |
+
Project maintainers have the right and responsibility to remove, edit, or
|
41 |
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42 |
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43 |
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44 |
+
threatening, offensive, or harmful.
|
45 |
+
|
46 |
+
## Scope
|
47 |
+
|
48 |
+
This Code of Conduct applies within all project spaces, and it also applies when
|
49 |
+
an individual is representing the project or its community in public spaces.
|
50 |
+
Examples of representing a project or community include using an official
|
51 |
+
project e-mail address, posting via an official social media account, or acting
|
52 |
+
as an appointed representative at an online or offline event. Representation of
|
53 |
+
a project may be further defined and clarified by project maintainers.
|
54 |
+
|
55 |
+
## Enforcement
|
56 |
+
|
57 |
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58 |
+
reported by contacting the project team at [email protected]. All
|
59 |
+
complaints will be reviewed and investigated and will result in a response that
|
60 |
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61 |
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62 |
+
Further details of specific enforcement policies may be posted separately.
|
63 |
+
|
64 |
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65 |
+
faith may face temporary or permanent repercussions as determined by other
|
66 |
+
members of the project's leadership.
|
67 |
+
|
68 |
+
## Attribution
|
69 |
+
|
70 |
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71 |
+
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
72 |
+
|
73 |
+
[homepage]: https://www.contributor-covenant.org
|
74 |
+
|
75 |
+
For answers to common questions about this code of conduct, see
|
76 |
+
https://www.contributor-covenant.org/faq
|
77 |
+
|
CONTRIBUTING.md
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Thanks for looking into contributing to Weaviate!
|
2 |
+
We really appreciate that you are willing to spend some time and effort to make Weaviate better for everyone!
|
3 |
+
|
4 |
+
We have a detailed [contributor guide](https://weaviate.io/developers/contributor-guide/current/) as a part of our documentation. If you are new, we recommend reading through the [getting started guide for contributors](https://weaviate.io/developers/contributor-guide/current/getting-started/index.html) after reading this overview.
|
5 |
+
|
6 |
+
## Finding a good issue to get started
|
7 |
+
We use the `good-first-issue` labels on issues that we think are great to get started. Typically, they are issues that are isolated to a specific area of Weaviate, e.g. only the API layer or only a specific package.
|
8 |
+
|
9 |
+
## What do you need to contribute
|
10 |
+
To contribute to Weaviate Core you should have at least basic Golang skills. It is also tremendously helpful if you have successfully used Weaviate before, so that you have a rough understanding of the effects of changes etc. Any PR must include tests. We understand that not everyone is very familiar with testing strategies, and how we have setup our tests. This [guide on testing](https://weaviate.io/developers/contributor-guide/current/weaviate-core/tests.html) should be a great start. If you have completed a change, but are struggling with the test, you can also open a Draft PR and ask someone from our team for help.
|
11 |
+
|
12 |
+
## If in doubt, ask.
|
13 |
+
The Weaviate team consists of some of the nicest people on this planet. If something is unclear or you'd like a second opinion, please don't hesitate to ask. We are glad that you want to help us, so naturally we will also do our best to help you on this journey
|
14 |
+
|
15 |
+
## Development Environment
|
16 |
+
*Please note that the Weaviate team uses Linux and Mac (darwin/arm64) machines exclusively. Development on Windows may lead to unexpected issues.*
|
17 |
+
|
18 |
+
Here are some guides to get started in no time:
|
19 |
+
* [Development Setup](https://weaviate.io/developers/contributor-guide/current/weaviate-core/setup.html)
|
20 |
+
* [Code Structure](https://weaviate.io/developers/contributor-guide/current/weaviate-core/structure.html)
|
21 |
+
* [CI/CD](https://weaviate.io/developers/contributor-guide/current/weaviate-core/cicd.html)
|
22 |
+
* [Testing Philosophy](https://weaviate.io/developers/contributor-guide/current/weaviate-core/tests.html)
|
23 |
+
|
24 |
+
## Tagging your commit
|
25 |
+
Please tag your commit(s) with the appropriate GH issue that your change refers to, e.g. `gh-9001 reduce memory allocations of ACME widget`. Please also include something in your PR description to indicate which issue it will close, e.g. `fixes #9001` or `closes #9001`.
|
26 |
+
|
27 |
+
## Pull Request
|
28 |
+
If you open an external pull request our CI pipeline will get started. This external run will not have access to secrets. This prevents people from submitting a malicious PR to steal secrets. As a result the CI run will be slightly different from an internal one. For example, it will not automatically push a Docker image. If your PR is merged, a container with your changes will be built from the trunk.
|
29 |
+
|
30 |
+
## Agreements
|
31 |
+
|
32 |
+
### Code of Conduct
|
33 |
+
Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.
|
34 |
+
[](CODE_OF_CONDUCT.md)
|
35 |
+
|
36 |
+
### Contributor License Agreement
|
37 |
+
|
38 |
+
Contributions to Weaviate must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of Weaviate. Go to [this page](https://www.semi.technology/playbooks/misc/contributor-license-agreement.html) to read the current agreement.
|
39 |
+
|
40 |
+
The process works as follows:
|
41 |
+
|
42 |
+
- You contribute by opening a [pull request](#pull-request).
|
43 |
+
- If you have not contributed before, our bot will ask to agree with the CLA
|
DockerfileHld
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile for development purposes.
|
2 |
+
# Read docs/development.md for more information
|
3 |
+
# vi: ft=dockerfile
|
4 |
+
|
5 |
+
###############################################################################
|
6 |
+
# Base build image
|
7 |
+
FROM golang:1.21-alpine AS build_base
|
8 |
+
RUN apk add bash ca-certificates git gcc g++ libc-dev
|
9 |
+
WORKDIR /go/src/github.com/weaviate/weaviate
|
10 |
+
ENV GO111MODULE=on
|
11 |
+
# Populate the module cache based on the go.{mod,sum} files.
|
12 |
+
COPY go.mod .
|
13 |
+
COPY go.sum .
|
14 |
+
RUN go mod download
|
15 |
+
|
16 |
+
###############################################################################
|
17 |
+
# This image builds the weaviate server
|
18 |
+
FROM build_base AS server_builder
|
19 |
+
ARG TARGETARCH
|
20 |
+
ARG GITHASH="unknown"
|
21 |
+
ARG EXTRA_BUILD_ARGS=""
|
22 |
+
COPY . .
|
23 |
+
RUN GOOS=linux GOARCH=$TARGETARCH go build $EXTRA_BUILD_ARGS \
|
24 |
+
-ldflags '-w -extldflags "-static" -X github.com/weaviate/weaviate/usecases/config.GitHash='"$GITHASH"'' \
|
25 |
+
-o /weaviate-server ./cmd/weaviate-server
|
26 |
+
|
27 |
+
###############################################################################
|
28 |
+
# This creates an image that can be used to fake an api for telemetry acceptance test purposes
|
29 |
+
FROM build_base AS telemetry_mock_api
|
30 |
+
COPY . .
|
31 |
+
ENTRYPOINT ["./tools/dev/telemetry_mock_api.sh"]
|
32 |
+
|
33 |
+
###############################################################################
|
34 |
+
# This image gets grpc health check probe
|
35 |
+
FROM build_base AS grpc_health_probe_builder
|
36 |
+
ARG TARGETARCH
|
37 |
+
RUN GRPC_HEALTH_PROBE_VERSION=v0.4.22 && \
|
38 |
+
wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-${TARGETARCH} && \
|
39 |
+
chmod +x /bin/grpc_health_probe
|
40 |
+
|
41 |
+
###############################################################################
|
42 |
+
# Weaviate (no differentiation between dev/test/prod - 12 factor!)
|
43 |
+
FROM alpine AS weaviate
|
44 |
+
ENTRYPOINT ["/bin/weaviate"]
|
45 |
+
COPY --from=grpc_health_probe_builder /bin/grpc_health_probe /bin/
|
46 |
+
COPY --from=server_builder /weaviate-server /bin/weaviate
|
47 |
+
RUN apk add --no-cache --upgrade ca-certificates openssl
|
48 |
+
RUN mkdir ./modules
|
49 |
+
CMD [ "--host", "0.0.0.0", "--port", "8080", "--scheme", "http"]
|
LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright (c) 2020-2024, Weaviate B.V.
|
2 |
+
All rights reserved.
|
3 |
+
|
4 |
+
Redistribution and use in source and binary forms, with or without
|
5 |
+
modification, are permitted provided that the following conditions are met:
|
6 |
+
|
7 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
8 |
+
list of conditions and the following disclaimer.
|
9 |
+
|
10 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
11 |
+
this list of conditions and the following disclaimer in the documentation
|
12 |
+
and/or other materials provided with the distribution.
|
13 |
+
|
14 |
+
3. Neither the name of the copyright holder nor the names of its
|
15 |
+
contributors may be used to endorse or promote products derived from
|
16 |
+
this software without specific prior written permission.
|
17 |
+
|
18 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
19 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
22 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
24 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
25 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
26 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
adapters/clients/client.go
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"fmt"
|
18 |
+
"io"
|
19 |
+
"net/http"
|
20 |
+
"time"
|
21 |
+
)
|
22 |
+
|
23 |
+
type retryClient struct {
|
24 |
+
client *http.Client
|
25 |
+
*retryer
|
26 |
+
}
|
27 |
+
|
28 |
+
func (c *retryClient) doWithCustomMarshaller(timeout time.Duration,
|
29 |
+
req *http.Request, body []byte, decode func([]byte) error,
|
30 |
+
) (err error) {
|
31 |
+
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
32 |
+
defer cancel()
|
33 |
+
try := func(ctx context.Context) (bool, error) {
|
34 |
+
if body != nil {
|
35 |
+
req.Body = io.NopCloser(bytes.NewReader(body))
|
36 |
+
}
|
37 |
+
res, err := c.client.Do(req)
|
38 |
+
if err != nil {
|
39 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
40 |
+
}
|
41 |
+
|
42 |
+
respBody, err := io.ReadAll(res.Body)
|
43 |
+
if err != nil {
|
44 |
+
return shouldRetry(res.StatusCode), fmt.Errorf("read response: %w", err)
|
45 |
+
}
|
46 |
+
defer res.Body.Close()
|
47 |
+
|
48 |
+
if code := res.StatusCode; code != http.StatusOK {
|
49 |
+
return shouldRetry(code), fmt.Errorf("status code: %v, error: %s", code, respBody)
|
50 |
+
}
|
51 |
+
|
52 |
+
if err := decode(respBody); err != nil {
|
53 |
+
return false, fmt.Errorf("unmarshal response: %w", err)
|
54 |
+
}
|
55 |
+
|
56 |
+
return false, nil
|
57 |
+
}
|
58 |
+
return c.retry(ctx, 9, try)
|
59 |
+
}
|
60 |
+
|
61 |
+
type retryer struct {
|
62 |
+
minBackOff time.Duration
|
63 |
+
maxBackOff time.Duration
|
64 |
+
timeoutUnit time.Duration
|
65 |
+
}
|
66 |
+
|
67 |
+
func newRetryer() *retryer {
|
68 |
+
return &retryer{
|
69 |
+
minBackOff: time.Millisecond * 250,
|
70 |
+
maxBackOff: time.Second * 30,
|
71 |
+
timeoutUnit: time.Second, // used by unit tests
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
func (r *retryer) retry(ctx context.Context, n int, work func(context.Context) (bool, error)) error {
|
76 |
+
delay := r.minBackOff
|
77 |
+
for {
|
78 |
+
keepTrying, err := work(ctx)
|
79 |
+
if !keepTrying || n < 1 || err == nil {
|
80 |
+
return err
|
81 |
+
}
|
82 |
+
|
83 |
+
n--
|
84 |
+
if delay = backOff(delay); delay > r.maxBackOff {
|
85 |
+
delay = r.maxBackOff
|
86 |
+
}
|
87 |
+
timer := time.NewTimer(delay)
|
88 |
+
select {
|
89 |
+
case <-ctx.Done():
|
90 |
+
timer.Stop()
|
91 |
+
return fmt.Errorf("%v: %w", err, ctx.Err())
|
92 |
+
case <-timer.C:
|
93 |
+
}
|
94 |
+
timer.Stop()
|
95 |
+
}
|
96 |
+
}
|
adapters/clients/cluster_backups.go
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"encoding/json"
|
18 |
+
"fmt"
|
19 |
+
"io"
|
20 |
+
"net/http"
|
21 |
+
"net/url"
|
22 |
+
|
23 |
+
"github.com/weaviate/weaviate/usecases/backup"
|
24 |
+
)
|
25 |
+
|
26 |
+
const (
|
27 |
+
pathCanCommit = "/backups/can-commit"
|
28 |
+
pathCommit = "/backups/commit"
|
29 |
+
pathStatus = "/backups/status"
|
30 |
+
pathAbort = "/backups/abort"
|
31 |
+
)
|
32 |
+
|
33 |
+
type ClusterBackups struct {
|
34 |
+
client *http.Client
|
35 |
+
}
|
36 |
+
|
37 |
+
func NewClusterBackups(client *http.Client) *ClusterBackups {
|
38 |
+
return &ClusterBackups{client: client}
|
39 |
+
}
|
40 |
+
|
41 |
+
func (c *ClusterBackups) CanCommit(ctx context.Context,
|
42 |
+
host string, req *backup.Request,
|
43 |
+
) (*backup.CanCommitResponse, error) {
|
44 |
+
url := url.URL{Scheme: "http", Host: host, Path: pathCanCommit}
|
45 |
+
|
46 |
+
b, err := json.Marshal(req)
|
47 |
+
if err != nil {
|
48 |
+
return nil, fmt.Errorf("marshal can-commit request: %w", err)
|
49 |
+
}
|
50 |
+
|
51 |
+
httpReq, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b))
|
52 |
+
if err != nil {
|
53 |
+
return nil, fmt.Errorf("new can-commit request: %w", err)
|
54 |
+
}
|
55 |
+
|
56 |
+
respBody, statusCode, err := c.do(httpReq)
|
57 |
+
if err != nil {
|
58 |
+
return nil, fmt.Errorf("can-commit request: %w", err)
|
59 |
+
}
|
60 |
+
|
61 |
+
if statusCode != http.StatusOK {
|
62 |
+
return nil, fmt.Errorf("unexpected status code %d (%s)",
|
63 |
+
statusCode, respBody)
|
64 |
+
}
|
65 |
+
|
66 |
+
var resp backup.CanCommitResponse
|
67 |
+
err = json.Unmarshal(respBody, &resp)
|
68 |
+
if err != nil {
|
69 |
+
return nil, fmt.Errorf("unmarshal can-commit response: %w", err)
|
70 |
+
}
|
71 |
+
|
72 |
+
return &resp, nil
|
73 |
+
}
|
74 |
+
|
75 |
+
func (c *ClusterBackups) Commit(ctx context.Context,
|
76 |
+
host string, req *backup.StatusRequest,
|
77 |
+
) error {
|
78 |
+
url := url.URL{Scheme: "http", Host: host, Path: pathCommit}
|
79 |
+
|
80 |
+
b, err := json.Marshal(req)
|
81 |
+
if err != nil {
|
82 |
+
return fmt.Errorf("marshal commit request: %w", err)
|
83 |
+
}
|
84 |
+
|
85 |
+
httpReq, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b))
|
86 |
+
if err != nil {
|
87 |
+
return fmt.Errorf("new commit request: %w", err)
|
88 |
+
}
|
89 |
+
|
90 |
+
respBody, statusCode, err := c.do(httpReq)
|
91 |
+
if err != nil {
|
92 |
+
return fmt.Errorf("commit request: %w", err)
|
93 |
+
}
|
94 |
+
|
95 |
+
if statusCode != http.StatusCreated {
|
96 |
+
return fmt.Errorf("unexpected status code %d (%s)",
|
97 |
+
statusCode, respBody)
|
98 |
+
}
|
99 |
+
|
100 |
+
return nil
|
101 |
+
}
|
102 |
+
|
103 |
+
func (c *ClusterBackups) Status(ctx context.Context,
|
104 |
+
host string, req *backup.StatusRequest,
|
105 |
+
) (*backup.StatusResponse, error) {
|
106 |
+
url := url.URL{Scheme: "http", Host: host, Path: pathStatus}
|
107 |
+
|
108 |
+
b, err := json.Marshal(req)
|
109 |
+
if err != nil {
|
110 |
+
return nil, fmt.Errorf("marshal status request: %w", err)
|
111 |
+
}
|
112 |
+
|
113 |
+
httpReq, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b))
|
114 |
+
if err != nil {
|
115 |
+
return nil, fmt.Errorf("new status request: %w", err)
|
116 |
+
}
|
117 |
+
|
118 |
+
respBody, statusCode, err := c.do(httpReq)
|
119 |
+
if err != nil {
|
120 |
+
return nil, fmt.Errorf("status request: %w", err)
|
121 |
+
}
|
122 |
+
|
123 |
+
if statusCode != http.StatusOK {
|
124 |
+
return nil, fmt.Errorf("unexpected status code %d (%s)",
|
125 |
+
statusCode, respBody)
|
126 |
+
}
|
127 |
+
|
128 |
+
var resp backup.StatusResponse
|
129 |
+
err = json.Unmarshal(respBody, &resp)
|
130 |
+
if err != nil {
|
131 |
+
return nil, fmt.Errorf("unmarshal status response: %w", err)
|
132 |
+
}
|
133 |
+
|
134 |
+
return &resp, nil
|
135 |
+
}
|
136 |
+
|
137 |
+
func (c *ClusterBackups) Abort(_ context.Context,
|
138 |
+
host string, req *backup.AbortRequest,
|
139 |
+
) error {
|
140 |
+
url := url.URL{Scheme: "http", Host: host, Path: pathAbort}
|
141 |
+
|
142 |
+
b, err := json.Marshal(req)
|
143 |
+
if err != nil {
|
144 |
+
return fmt.Errorf("marshal abort request: %w", err)
|
145 |
+
}
|
146 |
+
|
147 |
+
httpReq, err := http.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b))
|
148 |
+
if err != nil {
|
149 |
+
return fmt.Errorf("new abort request: %w", err)
|
150 |
+
}
|
151 |
+
|
152 |
+
respBody, statusCode, err := c.do(httpReq)
|
153 |
+
if err != nil {
|
154 |
+
return fmt.Errorf("abort request: %w", err)
|
155 |
+
}
|
156 |
+
|
157 |
+
if statusCode != http.StatusNoContent {
|
158 |
+
return fmt.Errorf("unexpected status code %d (%s)",
|
159 |
+
statusCode, respBody)
|
160 |
+
}
|
161 |
+
|
162 |
+
return nil
|
163 |
+
}
|
164 |
+
|
165 |
+
func (c *ClusterBackups) do(req *http.Request) (body []byte, statusCode int, err error) {
|
166 |
+
httpResp, err := c.client.Do(req)
|
167 |
+
if err != nil {
|
168 |
+
return nil, 0, fmt.Errorf("make request: %w", err)
|
169 |
+
}
|
170 |
+
|
171 |
+
body, err = io.ReadAll(httpResp.Body)
|
172 |
+
if err != nil {
|
173 |
+
return nil, httpResp.StatusCode, fmt.Errorf("read response: %w", err)
|
174 |
+
}
|
175 |
+
defer httpResp.Body.Close()
|
176 |
+
|
177 |
+
return body, httpResp.StatusCode, nil
|
178 |
+
}
|
adapters/clients/cluster_classifications.go
ADDED
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"encoding/json"
|
18 |
+
"io"
|
19 |
+
"net/http"
|
20 |
+
"net/url"
|
21 |
+
|
22 |
+
"github.com/pkg/errors"
|
23 |
+
"github.com/weaviate/weaviate/usecases/cluster"
|
24 |
+
)
|
25 |
+
|
26 |
+
type ClusterClassifications struct {
|
27 |
+
client *http.Client
|
28 |
+
}
|
29 |
+
|
30 |
+
func NewClusterClassifications(httpClient *http.Client) *ClusterClassifications {
|
31 |
+
return &ClusterClassifications{client: httpClient}
|
32 |
+
}
|
33 |
+
|
34 |
+
func (c *ClusterClassifications) OpenTransaction(ctx context.Context, host string,
|
35 |
+
tx *cluster.Transaction,
|
36 |
+
) error {
|
37 |
+
path := "/classifications/transactions/"
|
38 |
+
method := http.MethodPost
|
39 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
40 |
+
|
41 |
+
pl := txPayload{
|
42 |
+
Type: tx.Type,
|
43 |
+
ID: tx.ID,
|
44 |
+
Payload: tx.Payload,
|
45 |
+
}
|
46 |
+
|
47 |
+
jsonBytes, err := json.Marshal(pl)
|
48 |
+
if err != nil {
|
49 |
+
return errors.Wrap(err, "marshal transaction payload")
|
50 |
+
}
|
51 |
+
|
52 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
53 |
+
bytes.NewReader(jsonBytes))
|
54 |
+
if err != nil {
|
55 |
+
return errors.Wrap(err, "open http request")
|
56 |
+
}
|
57 |
+
|
58 |
+
req.Header.Set("content-type", "application/json")
|
59 |
+
|
60 |
+
res, err := c.client.Do(req)
|
61 |
+
if err != nil {
|
62 |
+
return errors.Wrap(err, "send http request")
|
63 |
+
}
|
64 |
+
|
65 |
+
defer res.Body.Close()
|
66 |
+
if res.StatusCode != http.StatusCreated {
|
67 |
+
if res.StatusCode == http.StatusConflict {
|
68 |
+
return cluster.ErrConcurrentTransaction
|
69 |
+
}
|
70 |
+
|
71 |
+
body, _ := io.ReadAll(res.Body)
|
72 |
+
return errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
73 |
+
body)
|
74 |
+
}
|
75 |
+
|
76 |
+
return nil
|
77 |
+
}
|
78 |
+
|
79 |
+
func (c *ClusterClassifications) AbortTransaction(ctx context.Context, host string,
|
80 |
+
tx *cluster.Transaction,
|
81 |
+
) error {
|
82 |
+
path := "/classifications/transactions/" + tx.ID
|
83 |
+
method := http.MethodDelete
|
84 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
85 |
+
|
86 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
87 |
+
if err != nil {
|
88 |
+
return errors.Wrap(err, "open http request")
|
89 |
+
}
|
90 |
+
|
91 |
+
res, err := c.client.Do(req)
|
92 |
+
if err != nil {
|
93 |
+
return errors.Wrap(err, "send http request")
|
94 |
+
}
|
95 |
+
|
96 |
+
defer res.Body.Close()
|
97 |
+
if res.StatusCode != http.StatusNoContent {
|
98 |
+
return errors.Errorf("unexpected status code %d", res.StatusCode)
|
99 |
+
}
|
100 |
+
|
101 |
+
return nil
|
102 |
+
}
|
103 |
+
|
104 |
+
func (c *ClusterClassifications) CommitTransaction(ctx context.Context, host string,
|
105 |
+
tx *cluster.Transaction,
|
106 |
+
) error {
|
107 |
+
path := "/classifications/transactions/" + tx.ID + "/commit"
|
108 |
+
method := http.MethodPut
|
109 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
110 |
+
|
111 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
112 |
+
if err != nil {
|
113 |
+
return errors.Wrap(err, "open http request")
|
114 |
+
}
|
115 |
+
|
116 |
+
res, err := c.client.Do(req)
|
117 |
+
if err != nil {
|
118 |
+
return errors.Wrap(err, "send http request")
|
119 |
+
}
|
120 |
+
|
121 |
+
defer res.Body.Close()
|
122 |
+
if res.StatusCode != http.StatusNoContent {
|
123 |
+
return errors.Errorf("unexpected status code %d", res.StatusCode)
|
124 |
+
}
|
125 |
+
|
126 |
+
return nil
|
127 |
+
}
|
adapters/clients/cluster_node.go
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"context"
|
16 |
+
"encoding/json"
|
17 |
+
"io"
|
18 |
+
"net/http"
|
19 |
+
"net/url"
|
20 |
+
"path"
|
21 |
+
|
22 |
+
enterrors "github.com/weaviate/weaviate/entities/errors"
|
23 |
+
"github.com/weaviate/weaviate/entities/models"
|
24 |
+
)
|
25 |
+
|
26 |
+
type RemoteNode struct {
|
27 |
+
client *http.Client
|
28 |
+
}
|
29 |
+
|
30 |
+
func NewRemoteNode(httpClient *http.Client) *RemoteNode {
|
31 |
+
return &RemoteNode{client: httpClient}
|
32 |
+
}
|
33 |
+
|
34 |
+
func (c *RemoteNode) GetNodeStatus(ctx context.Context, hostName, className, output string) (*models.NodeStatus, error) {
|
35 |
+
p := "/nodes/status"
|
36 |
+
if className != "" {
|
37 |
+
p = path.Join(p, className)
|
38 |
+
}
|
39 |
+
method := http.MethodGet
|
40 |
+
params := url.Values{"output": []string{output}}
|
41 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: p, RawQuery: params.Encode()}
|
42 |
+
|
43 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
44 |
+
if err != nil {
|
45 |
+
return nil, enterrors.NewErrOpenHttpRequest(err)
|
46 |
+
}
|
47 |
+
|
48 |
+
res, err := c.client.Do(req)
|
49 |
+
if err != nil {
|
50 |
+
return nil, enterrors.NewErrSendHttpRequest(err)
|
51 |
+
}
|
52 |
+
|
53 |
+
defer res.Body.Close()
|
54 |
+
body, _ := io.ReadAll(res.Body)
|
55 |
+
if res.StatusCode != http.StatusOK {
|
56 |
+
return nil, enterrors.NewErrUnexpectedStatusCode(res.StatusCode, body)
|
57 |
+
}
|
58 |
+
|
59 |
+
var nodeStatus models.NodeStatus
|
60 |
+
err = json.Unmarshal(body, &nodeStatus)
|
61 |
+
if err != nil {
|
62 |
+
return nil, enterrors.NewErrUnmarshalBody(err)
|
63 |
+
}
|
64 |
+
|
65 |
+
return &nodeStatus, nil
|
66 |
+
}
|
adapters/clients/cluster_schema.go
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"encoding/json"
|
18 |
+
"fmt"
|
19 |
+
"io"
|
20 |
+
"net/http"
|
21 |
+
"net/url"
|
22 |
+
|
23 |
+
"github.com/weaviate/weaviate/usecases/cluster"
|
24 |
+
)
|
25 |
+
|
26 |
+
type ClusterSchema struct {
|
27 |
+
client *http.Client
|
28 |
+
}
|
29 |
+
|
30 |
+
func NewClusterSchema(httpClient *http.Client) *ClusterSchema {
|
31 |
+
return &ClusterSchema{client: httpClient}
|
32 |
+
}
|
33 |
+
|
34 |
+
func (c *ClusterSchema) OpenTransaction(ctx context.Context, host string,
|
35 |
+
tx *cluster.Transaction,
|
36 |
+
) error {
|
37 |
+
path := "/schema/transactions/"
|
38 |
+
method := http.MethodPost
|
39 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
40 |
+
|
41 |
+
pl := txPayload{
|
42 |
+
Type: tx.Type,
|
43 |
+
ID: tx.ID,
|
44 |
+
Payload: tx.Payload,
|
45 |
+
DeadlineMilli: tx.Deadline.UnixMilli(),
|
46 |
+
}
|
47 |
+
|
48 |
+
jsonBytes, err := json.Marshal(pl)
|
49 |
+
if err != nil {
|
50 |
+
return fmt.Errorf("marshal transaction payload: %w", err)
|
51 |
+
}
|
52 |
+
|
53 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
54 |
+
bytes.NewReader(jsonBytes))
|
55 |
+
if err != nil {
|
56 |
+
return fmt.Errorf("open http request: %w", err)
|
57 |
+
}
|
58 |
+
|
59 |
+
req.Header.Set("content-type", "application/json")
|
60 |
+
|
61 |
+
res, err := c.client.Do(req)
|
62 |
+
if err != nil {
|
63 |
+
return fmt.Errorf("send http request: %w", err)
|
64 |
+
}
|
65 |
+
|
66 |
+
defer res.Body.Close()
|
67 |
+
body, _ := io.ReadAll(res.Body)
|
68 |
+
if res.StatusCode != http.StatusCreated {
|
69 |
+
if res.StatusCode == http.StatusConflict {
|
70 |
+
return cluster.ErrConcurrentTransaction
|
71 |
+
}
|
72 |
+
|
73 |
+
return fmt.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
74 |
+
body)
|
75 |
+
}
|
76 |
+
|
77 |
+
// optional for backward-compatibility before v1.17 where only
|
78 |
+
// write-transactions where supported. They had no return value other than
|
79 |
+
// the status code. With the introduction of read-transactions it is now
|
80 |
+
// possible to return the requested value
|
81 |
+
if len(body) == 0 {
|
82 |
+
return nil
|
83 |
+
}
|
84 |
+
|
85 |
+
var txRes txResponsePayload
|
86 |
+
err = json.Unmarshal(body, &txRes)
|
87 |
+
if err != nil {
|
88 |
+
return fmt.Errorf("unexpected error unmarshalling tx response: %w", err)
|
89 |
+
}
|
90 |
+
|
91 |
+
if tx.ID != txRes.ID {
|
92 |
+
return fmt.Errorf("unexpected mismatch between outgoing and incoming tx ids:"+
|
93 |
+
"%s vs %s", tx.ID, txRes.ID)
|
94 |
+
}
|
95 |
+
|
96 |
+
tx.Payload = txRes.Payload
|
97 |
+
|
98 |
+
return nil
|
99 |
+
}
|
100 |
+
|
101 |
+
func (c *ClusterSchema) AbortTransaction(ctx context.Context, host string,
|
102 |
+
tx *cluster.Transaction,
|
103 |
+
) error {
|
104 |
+
path := "/schema/transactions/" + tx.ID
|
105 |
+
method := http.MethodDelete
|
106 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
107 |
+
|
108 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
109 |
+
if err != nil {
|
110 |
+
return fmt.Errorf("open http request: %w", err)
|
111 |
+
}
|
112 |
+
|
113 |
+
res, err := c.client.Do(req)
|
114 |
+
if err != nil {
|
115 |
+
return fmt.Errorf("send http request: %w", err)
|
116 |
+
}
|
117 |
+
|
118 |
+
defer res.Body.Close()
|
119 |
+
if res.StatusCode != http.StatusNoContent {
|
120 |
+
errBody, _ := io.ReadAll(res.Body)
|
121 |
+
return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, errBody)
|
122 |
+
}
|
123 |
+
|
124 |
+
return nil
|
125 |
+
}
|
126 |
+
|
127 |
+
func (c *ClusterSchema) CommitTransaction(ctx context.Context, host string,
|
128 |
+
tx *cluster.Transaction,
|
129 |
+
) error {
|
130 |
+
path := "/schema/transactions/" + tx.ID + "/commit"
|
131 |
+
method := http.MethodPut
|
132 |
+
url := url.URL{Scheme: "http", Host: host, Path: path}
|
133 |
+
|
134 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
135 |
+
if err != nil {
|
136 |
+
return fmt.Errorf("open http request: %w", err)
|
137 |
+
}
|
138 |
+
|
139 |
+
res, err := c.client.Do(req)
|
140 |
+
if err != nil {
|
141 |
+
return fmt.Errorf("send http request: %w", err)
|
142 |
+
}
|
143 |
+
|
144 |
+
defer res.Body.Close()
|
145 |
+
if res.StatusCode != http.StatusNoContent {
|
146 |
+
errBody, _ := io.ReadAll(res.Body)
|
147 |
+
return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, errBody)
|
148 |
+
}
|
149 |
+
|
150 |
+
return nil
|
151 |
+
}
|
152 |
+
|
153 |
+
type txPayload struct {
|
154 |
+
Type cluster.TransactionType `json:"type"`
|
155 |
+
ID string `json:"id"`
|
156 |
+
Payload interface{} `json:"payload"`
|
157 |
+
DeadlineMilli int64 `json:"deadlineMilli"`
|
158 |
+
}
|
159 |
+
|
160 |
+
type txResponsePayload struct {
|
161 |
+
Type cluster.TransactionType `json:"type"`
|
162 |
+
ID string `json:"id"`
|
163 |
+
Payload json.RawMessage `json:"payload"`
|
164 |
+
}
|
adapters/clients/cluster_schema_test.go
ADDED
@@ -0,0 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"context"
|
16 |
+
"encoding/json"
|
17 |
+
"io"
|
18 |
+
"net/http"
|
19 |
+
"net/http/httptest"
|
20 |
+
"net/url"
|
21 |
+
"testing"
|
22 |
+
"time"
|
23 |
+
|
24 |
+
"github.com/stretchr/testify/assert"
|
25 |
+
"github.com/stretchr/testify/require"
|
26 |
+
"github.com/weaviate/weaviate/usecases/cluster"
|
27 |
+
)
|
28 |
+
|
29 |
+
func TestOpenTransactionNoReturnPayload(t *testing.T) {
|
30 |
+
// The No-Return-Payload is the situation that existed prior to v1.17 where
|
31 |
+
// the only option for transactions was to broadcast updates. By keeping this
|
32 |
+
// test around, we can make sure that we are not breaking backward
|
33 |
+
// compatibility.
|
34 |
+
|
35 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
36 |
+
defer r.Body.Close()
|
37 |
+
body, err := io.ReadAll(r.Body)
|
38 |
+
require.Nil(t, err)
|
39 |
+
|
40 |
+
var pl txPayload
|
41 |
+
err = json.Unmarshal(body, &pl)
|
42 |
+
require.Nil(t, err)
|
43 |
+
|
44 |
+
assert.Equal(t, "mamma-mia-paylodia-belissima", pl.Payload.(string))
|
45 |
+
w.WriteHeader(http.StatusCreated)
|
46 |
+
}
|
47 |
+
server := httptest.NewServer(http.HandlerFunc(handler))
|
48 |
+
defer server.Close()
|
49 |
+
|
50 |
+
u, _ := url.Parse(server.URL)
|
51 |
+
|
52 |
+
c := NewClusterSchema(&http.Client{})
|
53 |
+
|
54 |
+
tx := &cluster.Transaction{
|
55 |
+
ID: "12345",
|
56 |
+
Type: "the best",
|
57 |
+
Payload: "mamma-mia-paylodia-belissima",
|
58 |
+
}
|
59 |
+
|
60 |
+
err := c.OpenTransaction(context.Background(), u.Host, tx)
|
61 |
+
assert.Nil(t, err)
|
62 |
+
}
|
63 |
+
|
64 |
+
func TestOpenTransactionWithReturnPayload(t *testing.T) {
|
65 |
+
// Newly added as part of v1.17 where read-transactions were introduced which
|
66 |
+
// are used to sync up cluster schema state when nodes join
|
67 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
68 |
+
resTx := txPayload{
|
69 |
+
Type: "my-tx",
|
70 |
+
ID: "987",
|
71 |
+
Payload: "gracie-mille-per-payload",
|
72 |
+
}
|
73 |
+
resBody, err := json.Marshal(resTx)
|
74 |
+
require.Nil(t, err)
|
75 |
+
|
76 |
+
w.WriteHeader(http.StatusCreated)
|
77 |
+
w.Write(resBody)
|
78 |
+
}
|
79 |
+
server := httptest.NewServer(http.HandlerFunc(handler))
|
80 |
+
defer server.Close()
|
81 |
+
|
82 |
+
u, _ := url.Parse(server.URL)
|
83 |
+
|
84 |
+
c := NewClusterSchema(&http.Client{})
|
85 |
+
|
86 |
+
txIn := &cluster.Transaction{
|
87 |
+
ID: "987",
|
88 |
+
Type: "my-tx",
|
89 |
+
}
|
90 |
+
|
91 |
+
err := c.OpenTransaction(context.Background(), u.Host, txIn)
|
92 |
+
assert.Nil(t, err)
|
93 |
+
|
94 |
+
expectedTxOut := &cluster.Transaction{
|
95 |
+
ID: "987",
|
96 |
+
Type: "my-tx",
|
97 |
+
Payload: json.RawMessage("\"gracie-mille-per-payload\""),
|
98 |
+
}
|
99 |
+
|
100 |
+
assert.Equal(t, expectedTxOut, txIn)
|
101 |
+
}
|
102 |
+
|
103 |
+
func TestOpenTransactionWithTTL(t *testing.T) {
|
104 |
+
deadline, err := time.Parse(time.RFC3339Nano, "2040-01-02T15:04:05.00Z")
|
105 |
+
require.Nil(t, err)
|
106 |
+
|
107 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
108 |
+
defer r.Body.Close()
|
109 |
+
body, err := io.ReadAll(r.Body)
|
110 |
+
require.Nil(t, err)
|
111 |
+
|
112 |
+
var pl txPayload
|
113 |
+
err = json.Unmarshal(body, &pl)
|
114 |
+
require.Nil(t, err)
|
115 |
+
|
116 |
+
parsedDL := time.UnixMilli(pl.DeadlineMilli)
|
117 |
+
assert.Equal(t, deadline.UnixNano(), parsedDL.UnixNano())
|
118 |
+
w.WriteHeader(http.StatusCreated)
|
119 |
+
}
|
120 |
+
server := httptest.NewServer(http.HandlerFunc(handler))
|
121 |
+
defer server.Close()
|
122 |
+
|
123 |
+
u, _ := url.Parse(server.URL)
|
124 |
+
|
125 |
+
c := NewClusterSchema(&http.Client{})
|
126 |
+
|
127 |
+
tx := &cluster.Transaction{
|
128 |
+
ID: "12345",
|
129 |
+
Type: "the best",
|
130 |
+
Payload: "mamma-mia-paylodia-belissima",
|
131 |
+
Deadline: deadline,
|
132 |
+
}
|
133 |
+
|
134 |
+
err = c.OpenTransaction(context.Background(), u.Host, tx)
|
135 |
+
assert.Nil(t, err)
|
136 |
+
}
|
137 |
+
|
138 |
+
func TestOpenTransactionUnhappyPaths(t *testing.T) {
|
139 |
+
type test struct {
|
140 |
+
name string
|
141 |
+
handler http.HandlerFunc
|
142 |
+
expectedErr error
|
143 |
+
expectedErrContains string
|
144 |
+
ctx context.Context
|
145 |
+
shutdownPrematurely bool
|
146 |
+
}
|
147 |
+
|
148 |
+
expiredCtx, cancel := context.WithCancel(context.Background())
|
149 |
+
cancel()
|
150 |
+
|
151 |
+
tests := []test{
|
152 |
+
{
|
153 |
+
name: "concurrent transaction",
|
154 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
155 |
+
defer r.Body.Close()
|
156 |
+
w.WriteHeader(http.StatusConflict)
|
157 |
+
},
|
158 |
+
expectedErr: cluster.ErrConcurrentTransaction,
|
159 |
+
},
|
160 |
+
{
|
161 |
+
name: "arbitrary 500",
|
162 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
163 |
+
defer r.Body.Close()
|
164 |
+
w.WriteHeader(http.StatusInternalServerError)
|
165 |
+
w.Write([]byte("nope!"))
|
166 |
+
},
|
167 |
+
expectedErrContains: "nope!",
|
168 |
+
},
|
169 |
+
{
|
170 |
+
name: "invalid json",
|
171 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
172 |
+
defer r.Body.Close()
|
173 |
+
w.WriteHeader(http.StatusCreated)
|
174 |
+
w.Write([]byte("<<<!@#*)!@#****@!''"))
|
175 |
+
},
|
176 |
+
expectedErrContains: "error unmarshalling",
|
177 |
+
},
|
178 |
+
{
|
179 |
+
name: "expired ctx",
|
180 |
+
ctx: expiredCtx,
|
181 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
182 |
+
},
|
183 |
+
expectedErrContains: "context",
|
184 |
+
},
|
185 |
+
{
|
186 |
+
name: "remote server shut down",
|
187 |
+
shutdownPrematurely: true,
|
188 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
189 |
+
},
|
190 |
+
expectedErrContains: "refused",
|
191 |
+
},
|
192 |
+
{
|
193 |
+
name: "tx id mismatch",
|
194 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
195 |
+
resTx := txPayload{
|
196 |
+
Type: "wrong-tx-id",
|
197 |
+
ID: "987",
|
198 |
+
Payload: "gracie-mille-per-payload",
|
199 |
+
}
|
200 |
+
resBody, err := json.Marshal(resTx)
|
201 |
+
require.Nil(t, err)
|
202 |
+
|
203 |
+
w.WriteHeader(http.StatusCreated)
|
204 |
+
w.Write(resBody)
|
205 |
+
},
|
206 |
+
expectedErrContains: "mismatch between outgoing and incoming tx ids",
|
207 |
+
},
|
208 |
+
}
|
209 |
+
|
210 |
+
for _, test := range tests {
|
211 |
+
t.Run(test.name, func(t *testing.T) {
|
212 |
+
server := httptest.NewServer(http.HandlerFunc(test.handler))
|
213 |
+
if test.shutdownPrematurely {
|
214 |
+
server.Close()
|
215 |
+
} else {
|
216 |
+
defer server.Close()
|
217 |
+
}
|
218 |
+
|
219 |
+
u, _ := url.Parse(server.URL)
|
220 |
+
|
221 |
+
c := NewClusterSchema(&http.Client{})
|
222 |
+
|
223 |
+
tx := &cluster.Transaction{
|
224 |
+
ID: "12345",
|
225 |
+
Type: "the best",
|
226 |
+
Payload: "mamma-mia-paylodia-belissima",
|
227 |
+
}
|
228 |
+
|
229 |
+
if test.ctx == nil {
|
230 |
+
test.ctx = context.Background()
|
231 |
+
}
|
232 |
+
|
233 |
+
err := c.OpenTransaction(test.ctx, u.Host, tx)
|
234 |
+
assert.NotNil(t, err)
|
235 |
+
|
236 |
+
if test.expectedErr != nil {
|
237 |
+
assert.Equal(t, test.expectedErr, err)
|
238 |
+
}
|
239 |
+
|
240 |
+
if test.expectedErrContains != "" {
|
241 |
+
assert.Contains(t, err.Error(), test.expectedErrContains)
|
242 |
+
}
|
243 |
+
})
|
244 |
+
}
|
245 |
+
}
|
246 |
+
|
247 |
+
func TestAbortTransaction(t *testing.T) {
|
248 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
249 |
+
defer r.Body.Close()
|
250 |
+
w.WriteHeader(http.StatusNoContent)
|
251 |
+
}
|
252 |
+
server := httptest.NewServer(http.HandlerFunc(handler))
|
253 |
+
defer server.Close()
|
254 |
+
|
255 |
+
u, _ := url.Parse(server.URL)
|
256 |
+
|
257 |
+
c := NewClusterSchema(&http.Client{})
|
258 |
+
|
259 |
+
tx := &cluster.Transaction{
|
260 |
+
ID: "am-i-going-to-be-cancelled",
|
261 |
+
Type: "the worst",
|
262 |
+
Payload: "",
|
263 |
+
}
|
264 |
+
|
265 |
+
err := c.AbortTransaction(context.Background(), u.Host, tx)
|
266 |
+
assert.Nil(t, err)
|
267 |
+
}
|
268 |
+
|
269 |
+
func TestAbortTransactionUnhappyPaths(t *testing.T) {
|
270 |
+
type test struct {
|
271 |
+
name string
|
272 |
+
handler http.HandlerFunc
|
273 |
+
expectedErr error
|
274 |
+
expectedErrContains string
|
275 |
+
ctx context.Context
|
276 |
+
shutdownPrematurely bool
|
277 |
+
}
|
278 |
+
|
279 |
+
expiredCtx, cancel := context.WithCancel(context.Background())
|
280 |
+
cancel()
|
281 |
+
|
282 |
+
tests := []test{
|
283 |
+
{
|
284 |
+
name: "arbitrary 500",
|
285 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
286 |
+
defer r.Body.Close()
|
287 |
+
w.WriteHeader(http.StatusInternalServerError)
|
288 |
+
w.Write([]byte("nope!"))
|
289 |
+
},
|
290 |
+
expectedErrContains: "nope!",
|
291 |
+
},
|
292 |
+
{
|
293 |
+
name: "expired ctx",
|
294 |
+
ctx: expiredCtx,
|
295 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
296 |
+
},
|
297 |
+
expectedErrContains: "context",
|
298 |
+
},
|
299 |
+
{
|
300 |
+
name: "remote server shut down",
|
301 |
+
shutdownPrematurely: true,
|
302 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
303 |
+
},
|
304 |
+
expectedErrContains: "refused",
|
305 |
+
},
|
306 |
+
}
|
307 |
+
|
308 |
+
for _, test := range tests {
|
309 |
+
t.Run(test.name, func(t *testing.T) {
|
310 |
+
server := httptest.NewServer(http.HandlerFunc(test.handler))
|
311 |
+
if test.shutdownPrematurely {
|
312 |
+
server.Close()
|
313 |
+
} else {
|
314 |
+
defer server.Close()
|
315 |
+
}
|
316 |
+
|
317 |
+
u, _ := url.Parse(server.URL)
|
318 |
+
|
319 |
+
c := NewClusterSchema(&http.Client{})
|
320 |
+
|
321 |
+
tx := &cluster.Transaction{
|
322 |
+
ID: "12345",
|
323 |
+
Type: "the best",
|
324 |
+
Payload: "mamma-mia-paylodia-belissima",
|
325 |
+
}
|
326 |
+
|
327 |
+
if test.ctx == nil {
|
328 |
+
test.ctx = context.Background()
|
329 |
+
}
|
330 |
+
|
331 |
+
err := c.AbortTransaction(test.ctx, u.Host, tx)
|
332 |
+
assert.NotNil(t, err)
|
333 |
+
|
334 |
+
if test.expectedErr != nil {
|
335 |
+
assert.Equal(t, test.expectedErr, err)
|
336 |
+
}
|
337 |
+
|
338 |
+
if test.expectedErrContains != "" {
|
339 |
+
assert.Contains(t, err.Error(), test.expectedErrContains)
|
340 |
+
}
|
341 |
+
})
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
func TestCommitTransaction(t *testing.T) {
|
346 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
347 |
+
defer r.Body.Close()
|
348 |
+
w.WriteHeader(http.StatusNoContent)
|
349 |
+
}
|
350 |
+
server := httptest.NewServer(http.HandlerFunc(handler))
|
351 |
+
defer server.Close()
|
352 |
+
|
353 |
+
u, _ := url.Parse(server.URL)
|
354 |
+
|
355 |
+
c := NewClusterSchema(&http.Client{})
|
356 |
+
|
357 |
+
tx := &cluster.Transaction{
|
358 |
+
ID: "am-i-going-to-be-cancelled",
|
359 |
+
Type: "the worst",
|
360 |
+
Payload: "",
|
361 |
+
}
|
362 |
+
|
363 |
+
err := c.CommitTransaction(context.Background(), u.Host, tx)
|
364 |
+
assert.Nil(t, err)
|
365 |
+
}
|
366 |
+
|
367 |
+
func TestCommitTransactionUnhappyPaths(t *testing.T) {
|
368 |
+
type test struct {
|
369 |
+
name string
|
370 |
+
handler http.HandlerFunc
|
371 |
+
expectedErr error
|
372 |
+
expectedErrContains string
|
373 |
+
ctx context.Context
|
374 |
+
shutdownPrematurely bool
|
375 |
+
}
|
376 |
+
|
377 |
+
expiredCtx, cancel := context.WithCancel(context.Background())
|
378 |
+
cancel()
|
379 |
+
|
380 |
+
tests := []test{
|
381 |
+
{
|
382 |
+
name: "arbitrary 500",
|
383 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
384 |
+
defer r.Body.Close()
|
385 |
+
w.WriteHeader(http.StatusInternalServerError)
|
386 |
+
w.Write([]byte("nope!"))
|
387 |
+
},
|
388 |
+
expectedErrContains: "nope!",
|
389 |
+
},
|
390 |
+
{
|
391 |
+
name: "expired ctx",
|
392 |
+
ctx: expiredCtx,
|
393 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
394 |
+
},
|
395 |
+
expectedErrContains: "context",
|
396 |
+
},
|
397 |
+
{
|
398 |
+
name: "remote server shut down",
|
399 |
+
shutdownPrematurely: true,
|
400 |
+
handler: func(w http.ResponseWriter, r *http.Request) {
|
401 |
+
},
|
402 |
+
expectedErrContains: "refused",
|
403 |
+
},
|
404 |
+
}
|
405 |
+
|
406 |
+
for _, test := range tests {
|
407 |
+
t.Run(test.name, func(t *testing.T) {
|
408 |
+
server := httptest.NewServer(http.HandlerFunc(test.handler))
|
409 |
+
if test.shutdownPrematurely {
|
410 |
+
server.Close()
|
411 |
+
} else {
|
412 |
+
defer server.Close()
|
413 |
+
}
|
414 |
+
|
415 |
+
u, _ := url.Parse(server.URL)
|
416 |
+
|
417 |
+
c := NewClusterSchema(&http.Client{})
|
418 |
+
|
419 |
+
tx := &cluster.Transaction{
|
420 |
+
ID: "12345",
|
421 |
+
Type: "the best",
|
422 |
+
Payload: "mamma-mia-paylodia-belissima",
|
423 |
+
}
|
424 |
+
|
425 |
+
if test.ctx == nil {
|
426 |
+
test.ctx = context.Background()
|
427 |
+
}
|
428 |
+
|
429 |
+
err := c.CommitTransaction(test.ctx, u.Host, tx)
|
430 |
+
assert.NotNil(t, err)
|
431 |
+
|
432 |
+
if test.expectedErr != nil {
|
433 |
+
assert.Equal(t, test.expectedErr, err)
|
434 |
+
}
|
435 |
+
|
436 |
+
if test.expectedErrContains != "" {
|
437 |
+
assert.Contains(t, err.Error(), test.expectedErrContains)
|
438 |
+
}
|
439 |
+
})
|
440 |
+
}
|
441 |
+
}
|
adapters/clients/remote_index.go
ADDED
@@ -0,0 +1,839 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"encoding/base64"
|
18 |
+
"encoding/json"
|
19 |
+
"fmt"
|
20 |
+
"io"
|
21 |
+
"net/http"
|
22 |
+
"net/url"
|
23 |
+
|
24 |
+
"github.com/go-openapi/strfmt"
|
25 |
+
"github.com/pkg/errors"
|
26 |
+
"github.com/weaviate/weaviate/adapters/handlers/rest/clusterapi"
|
27 |
+
"github.com/weaviate/weaviate/entities/additional"
|
28 |
+
"github.com/weaviate/weaviate/entities/aggregation"
|
29 |
+
"github.com/weaviate/weaviate/entities/filters"
|
30 |
+
"github.com/weaviate/weaviate/entities/search"
|
31 |
+
"github.com/weaviate/weaviate/entities/searchparams"
|
32 |
+
"github.com/weaviate/weaviate/entities/storobj"
|
33 |
+
"github.com/weaviate/weaviate/usecases/objects"
|
34 |
+
"github.com/weaviate/weaviate/usecases/scaler"
|
35 |
+
)
|
36 |
+
|
37 |
+
type RemoteIndex struct {
|
38 |
+
retryClient
|
39 |
+
}
|
40 |
+
|
41 |
+
func NewRemoteIndex(httpClient *http.Client) *RemoteIndex {
|
42 |
+
return &RemoteIndex{retryClient: retryClient{
|
43 |
+
client: httpClient,
|
44 |
+
retryer: newRetryer(),
|
45 |
+
}}
|
46 |
+
}
|
47 |
+
|
48 |
+
func (c *RemoteIndex) PutObject(ctx context.Context, hostName, indexName,
|
49 |
+
shardName string, obj *storobj.Object,
|
50 |
+
) error {
|
51 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName)
|
52 |
+
method := http.MethodPost
|
53 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
54 |
+
|
55 |
+
marshalled, err := clusterapi.IndicesPayloads.SingleObject.Marshal(obj)
|
56 |
+
if err != nil {
|
57 |
+
return errors.Wrap(err, "marshal payload")
|
58 |
+
}
|
59 |
+
|
60 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
61 |
+
bytes.NewReader(marshalled))
|
62 |
+
if err != nil {
|
63 |
+
return errors.Wrap(err, "open http request")
|
64 |
+
}
|
65 |
+
|
66 |
+
clusterapi.IndicesPayloads.SingleObject.SetContentTypeHeaderReq(req)
|
67 |
+
res, err := c.client.Do(req)
|
68 |
+
if err != nil {
|
69 |
+
return errors.Wrap(err, "send http request")
|
70 |
+
}
|
71 |
+
|
72 |
+
defer res.Body.Close()
|
73 |
+
if res.StatusCode != http.StatusNoContent {
|
74 |
+
body, _ := io.ReadAll(res.Body)
|
75 |
+
return errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
76 |
+
body)
|
77 |
+
}
|
78 |
+
|
79 |
+
return nil
|
80 |
+
}
|
81 |
+
|
82 |
+
func duplicateErr(in error, count int) []error {
|
83 |
+
out := make([]error, count)
|
84 |
+
for i := range out {
|
85 |
+
out[i] = in
|
86 |
+
}
|
87 |
+
return out
|
88 |
+
}
|
89 |
+
|
90 |
+
func (c *RemoteIndex) BatchPutObjects(ctx context.Context, hostName, indexName,
|
91 |
+
shardName string, objs []*storobj.Object, _ *additional.ReplicationProperties,
|
92 |
+
) []error {
|
93 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName)
|
94 |
+
method := http.MethodPost
|
95 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
96 |
+
|
97 |
+
marshalled, err := clusterapi.IndicesPayloads.ObjectList.Marshal(objs)
|
98 |
+
if err != nil {
|
99 |
+
return duplicateErr(errors.Wrap(err, "marshal payload"), len(objs))
|
100 |
+
}
|
101 |
+
|
102 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
103 |
+
bytes.NewReader(marshalled))
|
104 |
+
if err != nil {
|
105 |
+
return duplicateErr(errors.Wrap(err, "open http request"), len(objs))
|
106 |
+
}
|
107 |
+
|
108 |
+
clusterapi.IndicesPayloads.ObjectList.SetContentTypeHeaderReq(req)
|
109 |
+
|
110 |
+
res, err := c.client.Do(req)
|
111 |
+
if err != nil {
|
112 |
+
return duplicateErr(errors.Wrap(err, "send http request"), len(objs))
|
113 |
+
}
|
114 |
+
|
115 |
+
defer res.Body.Close()
|
116 |
+
if res.StatusCode != http.StatusOK {
|
117 |
+
body, _ := io.ReadAll(res.Body)
|
118 |
+
return duplicateErr(errors.Errorf("unexpected status code %d (%s)",
|
119 |
+
res.StatusCode, body), len(objs))
|
120 |
+
}
|
121 |
+
|
122 |
+
if ct, ok := clusterapi.IndicesPayloads.ErrorList.
|
123 |
+
CheckContentTypeHeader(res); !ok {
|
124 |
+
return duplicateErr(errors.Errorf("unexpected content type: %s",
|
125 |
+
ct), len(objs))
|
126 |
+
}
|
127 |
+
|
128 |
+
resBytes, err := io.ReadAll(res.Body)
|
129 |
+
if err != nil {
|
130 |
+
return duplicateErr(errors.Wrap(err, "ready body"), len(objs))
|
131 |
+
}
|
132 |
+
|
133 |
+
return clusterapi.IndicesPayloads.ErrorList.Unmarshal(resBytes)
|
134 |
+
}
|
135 |
+
|
136 |
+
func (c *RemoteIndex) BatchAddReferences(ctx context.Context, hostName, indexName,
|
137 |
+
shardName string, refs objects.BatchReferences,
|
138 |
+
) []error {
|
139 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/references", indexName, shardName)
|
140 |
+
method := http.MethodPost
|
141 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
142 |
+
|
143 |
+
marshalled, err := clusterapi.IndicesPayloads.ReferenceList.Marshal(refs)
|
144 |
+
if err != nil {
|
145 |
+
return duplicateErr(errors.Wrap(err, "marshal payload"), len(refs))
|
146 |
+
}
|
147 |
+
|
148 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
149 |
+
bytes.NewReader(marshalled))
|
150 |
+
if err != nil {
|
151 |
+
return duplicateErr(errors.Wrap(err, "open http request"), len(refs))
|
152 |
+
}
|
153 |
+
|
154 |
+
clusterapi.IndicesPayloads.ReferenceList.SetContentTypeHeaderReq(req)
|
155 |
+
|
156 |
+
res, err := c.client.Do(req)
|
157 |
+
if err != nil {
|
158 |
+
return duplicateErr(errors.Wrap(err, "send http request"), len(refs))
|
159 |
+
}
|
160 |
+
|
161 |
+
defer res.Body.Close()
|
162 |
+
if res.StatusCode != http.StatusOK {
|
163 |
+
body, _ := io.ReadAll(res.Body)
|
164 |
+
return duplicateErr(errors.Errorf("unexpected status code %d (%s)",
|
165 |
+
res.StatusCode, body), len(refs))
|
166 |
+
}
|
167 |
+
|
168 |
+
if ct, ok := clusterapi.IndicesPayloads.ErrorList.
|
169 |
+
CheckContentTypeHeader(res); !ok {
|
170 |
+
return duplicateErr(errors.Errorf("unexpected content type: %s",
|
171 |
+
ct), len(refs))
|
172 |
+
}
|
173 |
+
|
174 |
+
resBytes, err := io.ReadAll(res.Body)
|
175 |
+
if err != nil {
|
176 |
+
return duplicateErr(errors.Wrap(err, "ready body"), len(refs))
|
177 |
+
}
|
178 |
+
|
179 |
+
return clusterapi.IndicesPayloads.ErrorList.Unmarshal(resBytes)
|
180 |
+
}
|
181 |
+
|
182 |
+
func (c *RemoteIndex) GetObject(ctx context.Context, hostName, indexName,
|
183 |
+
shardName string, id strfmt.UUID, selectProps search.SelectProperties,
|
184 |
+
additional additional.Properties,
|
185 |
+
) (*storobj.Object, error) {
|
186 |
+
selectPropsBytes, err := json.Marshal(selectProps)
|
187 |
+
if err != nil {
|
188 |
+
return nil, errors.Wrap(err, "marshal selectProps props")
|
189 |
+
}
|
190 |
+
|
191 |
+
additionalBytes, err := json.Marshal(additional)
|
192 |
+
if err != nil {
|
193 |
+
return nil, errors.Wrap(err, "marshal additional props")
|
194 |
+
}
|
195 |
+
|
196 |
+
selectPropsEncoded := base64.StdEncoding.EncodeToString(selectPropsBytes)
|
197 |
+
additionalEncoded := base64.StdEncoding.EncodeToString(additionalBytes)
|
198 |
+
|
199 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id)
|
200 |
+
method := http.MethodGet
|
201 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
202 |
+
q := url.Query()
|
203 |
+
q.Set("additional", additionalEncoded)
|
204 |
+
q.Set("selectProperties", selectPropsEncoded)
|
205 |
+
url.RawQuery = q.Encode()
|
206 |
+
|
207 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
208 |
+
if err != nil {
|
209 |
+
return nil, errors.Wrap(err, "open http request")
|
210 |
+
}
|
211 |
+
|
212 |
+
res, err := c.client.Do(req)
|
213 |
+
if err != nil {
|
214 |
+
return nil, errors.Wrap(err, "send http request")
|
215 |
+
}
|
216 |
+
|
217 |
+
defer res.Body.Close()
|
218 |
+
if res.StatusCode == http.StatusNotFound {
|
219 |
+
// this is a legitimate case - the requested ID doesn't exist, don't try
|
220 |
+
// to unmarshal anything
|
221 |
+
return nil, nil
|
222 |
+
}
|
223 |
+
|
224 |
+
if res.StatusCode != http.StatusOK {
|
225 |
+
body, _ := io.ReadAll(res.Body)
|
226 |
+
return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
227 |
+
body)
|
228 |
+
}
|
229 |
+
|
230 |
+
ct, ok := clusterapi.IndicesPayloads.SingleObject.CheckContentTypeHeader(res)
|
231 |
+
if !ok {
|
232 |
+
return nil, errors.Errorf("unknown content type %s", ct)
|
233 |
+
}
|
234 |
+
|
235 |
+
objBytes, err := io.ReadAll(res.Body)
|
236 |
+
if err != nil {
|
237 |
+
return nil, errors.Wrap(err, "read body")
|
238 |
+
}
|
239 |
+
|
240 |
+
obj, err := clusterapi.IndicesPayloads.SingleObject.Unmarshal(objBytes)
|
241 |
+
if err != nil {
|
242 |
+
return nil, errors.Wrap(err, "unmarshal body")
|
243 |
+
}
|
244 |
+
|
245 |
+
return obj, nil
|
246 |
+
}
|
247 |
+
|
248 |
+
func (c *RemoteIndex) Exists(ctx context.Context, hostName, indexName,
|
249 |
+
shardName string, id strfmt.UUID,
|
250 |
+
) (bool, error) {
|
251 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id)
|
252 |
+
method := http.MethodGet
|
253 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
254 |
+
q := url.Query()
|
255 |
+
q.Set("check_exists", "true")
|
256 |
+
url.RawQuery = q.Encode()
|
257 |
+
|
258 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
259 |
+
if err != nil {
|
260 |
+
return false, errors.Wrap(err, "open http request")
|
261 |
+
}
|
262 |
+
|
263 |
+
res, err := c.client.Do(req)
|
264 |
+
if err != nil {
|
265 |
+
return false, errors.Wrap(err, "send http request")
|
266 |
+
}
|
267 |
+
|
268 |
+
defer res.Body.Close()
|
269 |
+
if res.StatusCode == http.StatusNotFound {
|
270 |
+
// this is a legitimate case - the requested ID doesn't exist, don't try
|
271 |
+
// to unmarshal anything
|
272 |
+
return false, nil
|
273 |
+
}
|
274 |
+
|
275 |
+
if res.StatusCode != http.StatusNoContent {
|
276 |
+
body, _ := io.ReadAll(res.Body)
|
277 |
+
return false, errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
278 |
+
body)
|
279 |
+
}
|
280 |
+
|
281 |
+
return true, nil
|
282 |
+
}
|
283 |
+
|
284 |
+
func (c *RemoteIndex) DeleteObject(ctx context.Context, hostName, indexName,
|
285 |
+
shardName string, id strfmt.UUID,
|
286 |
+
) error {
|
287 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName, id)
|
288 |
+
method := http.MethodDelete
|
289 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
290 |
+
|
291 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
292 |
+
if err != nil {
|
293 |
+
return errors.Wrap(err, "open http request")
|
294 |
+
}
|
295 |
+
|
296 |
+
res, err := c.client.Do(req)
|
297 |
+
if err != nil {
|
298 |
+
return errors.Wrap(err, "send http request")
|
299 |
+
}
|
300 |
+
|
301 |
+
defer res.Body.Close()
|
302 |
+
if res.StatusCode == http.StatusNotFound {
|
303 |
+
// this is a legitimate case - the requested ID doesn't exist, don't try
|
304 |
+
// to unmarshal anything, we can assume it was already deleted
|
305 |
+
return nil
|
306 |
+
}
|
307 |
+
|
308 |
+
if res.StatusCode != http.StatusNoContent {
|
309 |
+
body, _ := io.ReadAll(res.Body)
|
310 |
+
return errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
311 |
+
body)
|
312 |
+
}
|
313 |
+
|
314 |
+
return nil
|
315 |
+
}
|
316 |
+
|
317 |
+
func (c *RemoteIndex) MergeObject(ctx context.Context, hostName, indexName,
|
318 |
+
shardName string, mergeDoc objects.MergeDocument,
|
319 |
+
) error {
|
320 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects/%s", indexName, shardName,
|
321 |
+
mergeDoc.ID)
|
322 |
+
method := http.MethodPatch
|
323 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
324 |
+
|
325 |
+
marshalled, err := clusterapi.IndicesPayloads.MergeDoc.Marshal(mergeDoc)
|
326 |
+
if err != nil {
|
327 |
+
return errors.Wrap(err, "marshal payload")
|
328 |
+
}
|
329 |
+
|
330 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
331 |
+
bytes.NewReader(marshalled))
|
332 |
+
if err != nil {
|
333 |
+
return errors.Wrap(err, "open http request")
|
334 |
+
}
|
335 |
+
|
336 |
+
clusterapi.IndicesPayloads.MergeDoc.SetContentTypeHeaderReq(req)
|
337 |
+
res, err := c.client.Do(req)
|
338 |
+
if err != nil {
|
339 |
+
return errors.Wrap(err, "send http request")
|
340 |
+
}
|
341 |
+
|
342 |
+
defer res.Body.Close()
|
343 |
+
if res.StatusCode != http.StatusNoContent {
|
344 |
+
body, _ := io.ReadAll(res.Body)
|
345 |
+
return errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
346 |
+
body)
|
347 |
+
}
|
348 |
+
|
349 |
+
return nil
|
350 |
+
}
|
351 |
+
|
352 |
+
func (c *RemoteIndex) MultiGetObjects(ctx context.Context, hostName, indexName,
|
353 |
+
shardName string, ids []strfmt.UUID,
|
354 |
+
) ([]*storobj.Object, error) {
|
355 |
+
idsBytes, err := json.Marshal(ids)
|
356 |
+
if err != nil {
|
357 |
+
return nil, errors.Wrap(err, "marshal selectProps props")
|
358 |
+
}
|
359 |
+
|
360 |
+
idsEncoded := base64.StdEncoding.EncodeToString(idsBytes)
|
361 |
+
|
362 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName)
|
363 |
+
method := http.MethodGet
|
364 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
365 |
+
q := url.Query()
|
366 |
+
q.Set("ids", idsEncoded)
|
367 |
+
url.RawQuery = q.Encode()
|
368 |
+
|
369 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
370 |
+
if err != nil {
|
371 |
+
return nil, errors.Wrap(err, "open http request")
|
372 |
+
}
|
373 |
+
|
374 |
+
res, err := c.client.Do(req)
|
375 |
+
if err != nil {
|
376 |
+
return nil, errors.Wrap(err, "send http request")
|
377 |
+
}
|
378 |
+
|
379 |
+
defer res.Body.Close()
|
380 |
+
if res.StatusCode == http.StatusNotFound {
|
381 |
+
// this is a legitimate case - the requested ID doesn't exist, don't try
|
382 |
+
// to unmarshal anything
|
383 |
+
return nil, nil
|
384 |
+
}
|
385 |
+
|
386 |
+
if res.StatusCode != http.StatusOK {
|
387 |
+
body, _ := io.ReadAll(res.Body)
|
388 |
+
return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
389 |
+
body)
|
390 |
+
}
|
391 |
+
|
392 |
+
ct, ok := clusterapi.IndicesPayloads.ObjectList.CheckContentTypeHeader(res)
|
393 |
+
if !ok {
|
394 |
+
return nil, errors.Errorf("unexpected content type: %s", ct)
|
395 |
+
}
|
396 |
+
|
397 |
+
bodyBytes, err := io.ReadAll(res.Body)
|
398 |
+
if err != nil {
|
399 |
+
return nil, errors.Wrap(err, "read response body")
|
400 |
+
}
|
401 |
+
|
402 |
+
objs, err := clusterapi.IndicesPayloads.ObjectList.Unmarshal(bodyBytes)
|
403 |
+
if err != nil {
|
404 |
+
return nil, errors.Wrap(err, "unmarshal objects")
|
405 |
+
}
|
406 |
+
|
407 |
+
return objs, nil
|
408 |
+
}
|
409 |
+
|
410 |
+
func (c *RemoteIndex) SearchShard(ctx context.Context, host, index, shard string,
|
411 |
+
vector []float32, limit int,
|
412 |
+
filters *filters.LocalFilter,
|
413 |
+
keywordRanking *searchparams.KeywordRanking,
|
414 |
+
sort []filters.Sort,
|
415 |
+
cursor *filters.Cursor,
|
416 |
+
groupBy *searchparams.GroupBy,
|
417 |
+
additional additional.Properties,
|
418 |
+
) ([]*storobj.Object, []float32, error) {
|
419 |
+
// new request
|
420 |
+
body, err := clusterapi.IndicesPayloads.SearchParams.
|
421 |
+
Marshal(vector, limit, filters, keywordRanking, sort, cursor, groupBy, additional)
|
422 |
+
if err != nil {
|
423 |
+
return nil, nil, fmt.Errorf("marshal request payload: %w", err)
|
424 |
+
}
|
425 |
+
url := url.URL{
|
426 |
+
Scheme: "http",
|
427 |
+
Host: host,
|
428 |
+
Path: fmt.Sprintf("/indices/%s/shards/%s/objects/_search", index, shard),
|
429 |
+
}
|
430 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url.String(), bytes.NewReader(body))
|
431 |
+
if err != nil {
|
432 |
+
return nil, nil, fmt.Errorf("create http request: %w", err)
|
433 |
+
}
|
434 |
+
clusterapi.IndicesPayloads.SearchParams.SetContentTypeHeaderReq(req)
|
435 |
+
|
436 |
+
// send request
|
437 |
+
resp := &searchShardResp{}
|
438 |
+
err = c.doWithCustomMarshaller(c.timeoutUnit*20, req, body, resp.decode)
|
439 |
+
return resp.Objects, resp.Distributions, err
|
440 |
+
}
|
441 |
+
|
442 |
+
type searchShardResp struct {
|
443 |
+
Objects []*storobj.Object
|
444 |
+
Distributions []float32
|
445 |
+
}
|
446 |
+
|
447 |
+
func (r *searchShardResp) decode(data []byte) (err error) {
|
448 |
+
r.Objects, r.Distributions, err = clusterapi.IndicesPayloads.SearchResults.Unmarshal(data)
|
449 |
+
return
|
450 |
+
}
|
451 |
+
|
452 |
+
type aggregateResp struct {
|
453 |
+
Result *aggregation.Result
|
454 |
+
}
|
455 |
+
|
456 |
+
func (r *aggregateResp) decode(data []byte) (err error) {
|
457 |
+
r.Result, err = clusterapi.IndicesPayloads.AggregationResult.Unmarshal(data)
|
458 |
+
return
|
459 |
+
}
|
460 |
+
|
461 |
+
func (c *RemoteIndex) Aggregate(ctx context.Context, hostName, index,
|
462 |
+
shard string, params aggregation.Params,
|
463 |
+
) (*aggregation.Result, error) {
|
464 |
+
// create new request
|
465 |
+
body, err := clusterapi.IndicesPayloads.AggregationParams.Marshal(params)
|
466 |
+
if err != nil {
|
467 |
+
return nil, fmt.Errorf("marshal request payload: %w", err)
|
468 |
+
}
|
469 |
+
|
470 |
+
url := &url.URL{
|
471 |
+
Scheme: "http",
|
472 |
+
Host: hostName,
|
473 |
+
Path: fmt.Sprintf("/indices/%s/shards/%s/objects/_aggregations", index, shard),
|
474 |
+
}
|
475 |
+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url.String(), bytes.NewReader(body))
|
476 |
+
if err != nil {
|
477 |
+
return nil, fmt.Errorf("create http request: %w", err)
|
478 |
+
}
|
479 |
+
clusterapi.IndicesPayloads.AggregationParams.SetContentTypeHeaderReq(req)
|
480 |
+
|
481 |
+
// send request
|
482 |
+
resp := &aggregateResp{}
|
483 |
+
err = c.doWithCustomMarshaller(c.timeoutUnit*20, req, body, resp.decode)
|
484 |
+
return resp.Result, err
|
485 |
+
}
|
486 |
+
|
487 |
+
func (c *RemoteIndex) FindUUIDs(ctx context.Context, hostName, indexName,
|
488 |
+
shardName string, filters *filters.LocalFilter,
|
489 |
+
) ([]strfmt.UUID, error) {
|
490 |
+
paramsBytes, err := clusterapi.IndicesPayloads.FindUUIDsParams.Marshal(filters)
|
491 |
+
if err != nil {
|
492 |
+
return nil, errors.Wrap(err, "marshal request payload")
|
493 |
+
}
|
494 |
+
|
495 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects/_find", indexName, shardName)
|
496 |
+
method := http.MethodPost
|
497 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
498 |
+
|
499 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
500 |
+
bytes.NewReader(paramsBytes))
|
501 |
+
if err != nil {
|
502 |
+
return nil, errors.Wrap(err, "open http request")
|
503 |
+
}
|
504 |
+
|
505 |
+
clusterapi.IndicesPayloads.FindUUIDsParams.SetContentTypeHeaderReq(req)
|
506 |
+
res, err := c.client.Do(req)
|
507 |
+
if err != nil {
|
508 |
+
return nil, errors.Wrap(err, "send http request")
|
509 |
+
}
|
510 |
+
|
511 |
+
defer res.Body.Close()
|
512 |
+
if res.StatusCode != http.StatusOK {
|
513 |
+
body, _ := io.ReadAll(res.Body)
|
514 |
+
return nil, errors.Errorf("unexpected status code %d (%s)", res.StatusCode,
|
515 |
+
body)
|
516 |
+
}
|
517 |
+
|
518 |
+
resBytes, err := io.ReadAll(res.Body)
|
519 |
+
if err != nil {
|
520 |
+
return nil, errors.Wrap(err, "read body")
|
521 |
+
}
|
522 |
+
|
523 |
+
ct, ok := clusterapi.IndicesPayloads.FindUUIDsResults.CheckContentTypeHeader(res)
|
524 |
+
if !ok {
|
525 |
+
return nil, errors.Errorf("unexpected content type: %s", ct)
|
526 |
+
}
|
527 |
+
|
528 |
+
uuids, err := clusterapi.IndicesPayloads.FindUUIDsResults.Unmarshal(resBytes)
|
529 |
+
if err != nil {
|
530 |
+
return nil, errors.Wrap(err, "unmarshal body")
|
531 |
+
}
|
532 |
+
return uuids, nil
|
533 |
+
}
|
534 |
+
|
535 |
+
func (c *RemoteIndex) DeleteObjectBatch(ctx context.Context, hostName, indexName, shardName string,
|
536 |
+
uuids []strfmt.UUID, dryRun bool,
|
537 |
+
) objects.BatchSimpleObjects {
|
538 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/objects", indexName, shardName)
|
539 |
+
method := http.MethodDelete
|
540 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
541 |
+
|
542 |
+
marshalled, err := clusterapi.IndicesPayloads.BatchDeleteParams.Marshal(uuids, dryRun)
|
543 |
+
if err != nil {
|
544 |
+
err := errors.Wrap(err, "marshal payload")
|
545 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
546 |
+
}
|
547 |
+
|
548 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
549 |
+
bytes.NewReader(marshalled))
|
550 |
+
if err != nil {
|
551 |
+
err := errors.Wrap(err, "open http request")
|
552 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
553 |
+
}
|
554 |
+
|
555 |
+
clusterapi.IndicesPayloads.BatchDeleteParams.SetContentTypeHeaderReq(req)
|
556 |
+
|
557 |
+
res, err := c.client.Do(req)
|
558 |
+
if err != nil {
|
559 |
+
err := errors.Wrap(err, "send http request")
|
560 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
561 |
+
}
|
562 |
+
|
563 |
+
defer res.Body.Close()
|
564 |
+
if res.StatusCode != http.StatusOK {
|
565 |
+
body, _ := io.ReadAll(res.Body)
|
566 |
+
err := errors.Errorf("unexpected status code %d (%s)", res.StatusCode, body)
|
567 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
568 |
+
}
|
569 |
+
|
570 |
+
if ct, ok := clusterapi.IndicesPayloads.BatchDeleteResults.
|
571 |
+
CheckContentTypeHeader(res); !ok {
|
572 |
+
err := errors.Errorf("unexpected content type: %s", ct)
|
573 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
574 |
+
}
|
575 |
+
|
576 |
+
resBytes, err := io.ReadAll(res.Body)
|
577 |
+
if err != nil {
|
578 |
+
err := errors.Wrap(err, "ready body")
|
579 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
580 |
+
}
|
581 |
+
|
582 |
+
batchDeleteResults, err := clusterapi.IndicesPayloads.BatchDeleteResults.Unmarshal(resBytes)
|
583 |
+
if err != nil {
|
584 |
+
err := errors.Wrap(err, "unmarshal body")
|
585 |
+
return objects.BatchSimpleObjects{objects.BatchSimpleObject{Err: err}}
|
586 |
+
}
|
587 |
+
|
588 |
+
return batchDeleteResults
|
589 |
+
}
|
590 |
+
|
591 |
+
func (c *RemoteIndex) GetShardQueueSize(ctx context.Context,
|
592 |
+
hostName, indexName, shardName string,
|
593 |
+
) (int64, error) {
|
594 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/queuesize", indexName, shardName)
|
595 |
+
method := http.MethodGet
|
596 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
597 |
+
|
598 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
599 |
+
if err != nil {
|
600 |
+
return 0, errors.Wrap(err, "open http request")
|
601 |
+
}
|
602 |
+
var size int64
|
603 |
+
clusterapi.IndicesPayloads.GetShardQueueSizeParams.SetContentTypeHeaderReq(req)
|
604 |
+
try := func(ctx context.Context) (bool, error) {
|
605 |
+
res, err := c.client.Do(req)
|
606 |
+
if err != nil {
|
607 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
608 |
+
}
|
609 |
+
defer res.Body.Close()
|
610 |
+
|
611 |
+
if code := res.StatusCode; code != http.StatusOK {
|
612 |
+
body, _ := io.ReadAll(res.Body)
|
613 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
614 |
+
}
|
615 |
+
resBytes, err := io.ReadAll(res.Body)
|
616 |
+
if err != nil {
|
617 |
+
return false, errors.Wrap(err, "read body")
|
618 |
+
}
|
619 |
+
|
620 |
+
ct, ok := clusterapi.IndicesPayloads.GetShardQueueSizeResults.CheckContentTypeHeader(res)
|
621 |
+
if !ok {
|
622 |
+
return false, errors.Errorf("unexpected content type: %s", ct)
|
623 |
+
}
|
624 |
+
|
625 |
+
size, err = clusterapi.IndicesPayloads.GetShardQueueSizeResults.Unmarshal(resBytes)
|
626 |
+
if err != nil {
|
627 |
+
return false, errors.Wrap(err, "unmarshal body")
|
628 |
+
}
|
629 |
+
return false, nil
|
630 |
+
}
|
631 |
+
return size, c.retry(ctx, 9, try)
|
632 |
+
}
|
633 |
+
|
634 |
+
func (c *RemoteIndex) GetShardStatus(ctx context.Context,
|
635 |
+
hostName, indexName, shardName string,
|
636 |
+
) (string, error) {
|
637 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/status", indexName, shardName)
|
638 |
+
method := http.MethodGet
|
639 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
640 |
+
|
641 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
642 |
+
if err != nil {
|
643 |
+
return "", errors.Wrap(err, "open http request")
|
644 |
+
}
|
645 |
+
var status string
|
646 |
+
clusterapi.IndicesPayloads.GetShardStatusParams.SetContentTypeHeaderReq(req)
|
647 |
+
try := func(ctx context.Context) (bool, error) {
|
648 |
+
res, err := c.client.Do(req)
|
649 |
+
if err != nil {
|
650 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
651 |
+
}
|
652 |
+
defer res.Body.Close()
|
653 |
+
|
654 |
+
if code := res.StatusCode; code != http.StatusOK {
|
655 |
+
body, _ := io.ReadAll(res.Body)
|
656 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
657 |
+
}
|
658 |
+
resBytes, err := io.ReadAll(res.Body)
|
659 |
+
if err != nil {
|
660 |
+
return false, errors.Wrap(err, "read body")
|
661 |
+
}
|
662 |
+
|
663 |
+
ct, ok := clusterapi.IndicesPayloads.GetShardStatusResults.CheckContentTypeHeader(res)
|
664 |
+
if !ok {
|
665 |
+
return false, errors.Errorf("unexpected content type: %s", ct)
|
666 |
+
}
|
667 |
+
|
668 |
+
status, err = clusterapi.IndicesPayloads.GetShardStatusResults.Unmarshal(resBytes)
|
669 |
+
if err != nil {
|
670 |
+
return false, errors.Wrap(err, "unmarshal body")
|
671 |
+
}
|
672 |
+
return false, nil
|
673 |
+
}
|
674 |
+
return status, c.retry(ctx, 9, try)
|
675 |
+
}
|
676 |
+
|
677 |
+
func (c *RemoteIndex) UpdateShardStatus(ctx context.Context, hostName, indexName, shardName,
|
678 |
+
targetStatus string,
|
679 |
+
) error {
|
680 |
+
paramsBytes, err := clusterapi.IndicesPayloads.UpdateShardStatusParams.Marshal(targetStatus)
|
681 |
+
if err != nil {
|
682 |
+
return errors.Wrap(err, "marshal request payload")
|
683 |
+
}
|
684 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/status", indexName, shardName)
|
685 |
+
method := http.MethodPost
|
686 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
687 |
+
|
688 |
+
try := func(ctx context.Context) (bool, error) {
|
689 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(),
|
690 |
+
bytes.NewReader(paramsBytes))
|
691 |
+
if err != nil {
|
692 |
+
return false, fmt.Errorf("create http request: %w", err)
|
693 |
+
}
|
694 |
+
clusterapi.IndicesPayloads.UpdateShardStatusParams.SetContentTypeHeaderReq(req)
|
695 |
+
|
696 |
+
res, err := c.client.Do(req)
|
697 |
+
if err != nil {
|
698 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
699 |
+
}
|
700 |
+
defer res.Body.Close()
|
701 |
+
|
702 |
+
if code := res.StatusCode; code != http.StatusOK {
|
703 |
+
body, _ := io.ReadAll(res.Body)
|
704 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
705 |
+
}
|
706 |
+
|
707 |
+
return false, nil
|
708 |
+
}
|
709 |
+
|
710 |
+
return c.retry(ctx, 9, try)
|
711 |
+
}
|
712 |
+
|
713 |
+
func (c *RemoteIndex) PutFile(ctx context.Context, hostName, indexName,
|
714 |
+
shardName, fileName string, payload io.ReadSeekCloser,
|
715 |
+
) error {
|
716 |
+
defer payload.Close()
|
717 |
+
path := fmt.Sprintf("/indices/%s/shards/%s/files/%s",
|
718 |
+
indexName, shardName, fileName)
|
719 |
+
|
720 |
+
method := http.MethodPost
|
721 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
722 |
+
try := func(ctx context.Context) (bool, error) {
|
723 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), payload)
|
724 |
+
if err != nil {
|
725 |
+
return false, fmt.Errorf("create http request: %w", err)
|
726 |
+
}
|
727 |
+
clusterapi.IndicesPayloads.ShardFiles.SetContentTypeHeaderReq(req)
|
728 |
+
res, err := c.client.Do(req)
|
729 |
+
if err != nil {
|
730 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
731 |
+
}
|
732 |
+
defer res.Body.Close()
|
733 |
+
|
734 |
+
if code := res.StatusCode; code != http.StatusNoContent {
|
735 |
+
shouldRetry := shouldRetry(code)
|
736 |
+
if shouldRetry {
|
737 |
+
_, err := payload.Seek(0, 0)
|
738 |
+
shouldRetry = (err == nil)
|
739 |
+
}
|
740 |
+
body, _ := io.ReadAll(res.Body)
|
741 |
+
return shouldRetry, fmt.Errorf("status code: %v body: (%s)", code, body)
|
742 |
+
}
|
743 |
+
return false, nil
|
744 |
+
}
|
745 |
+
|
746 |
+
return c.retry(ctx, 12, try)
|
747 |
+
}
|
748 |
+
|
749 |
+
func (c *RemoteIndex) CreateShard(ctx context.Context,
|
750 |
+
hostName, indexName, shardName string,
|
751 |
+
) error {
|
752 |
+
path := fmt.Sprintf("/indices/%s/shards/%s", indexName, shardName)
|
753 |
+
|
754 |
+
method := http.MethodPost
|
755 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
756 |
+
|
757 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
758 |
+
if err != nil {
|
759 |
+
return fmt.Errorf("create http request: %w", err)
|
760 |
+
}
|
761 |
+
try := func(ctx context.Context) (bool, error) {
|
762 |
+
res, err := c.client.Do(req)
|
763 |
+
if err != nil {
|
764 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
765 |
+
}
|
766 |
+
defer res.Body.Close()
|
767 |
+
|
768 |
+
if code := res.StatusCode; code != http.StatusCreated {
|
769 |
+
body, _ := io.ReadAll(res.Body)
|
770 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
771 |
+
}
|
772 |
+
return false, nil
|
773 |
+
}
|
774 |
+
|
775 |
+
return c.retry(ctx, 9, try)
|
776 |
+
}
|
777 |
+
|
778 |
+
func (c *RemoteIndex) ReInitShard(ctx context.Context,
|
779 |
+
hostName, indexName, shardName string,
|
780 |
+
) error {
|
781 |
+
path := fmt.Sprintf("/indices/%s/shards/%s:reinit", indexName, shardName)
|
782 |
+
|
783 |
+
method := http.MethodPut
|
784 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
785 |
+
|
786 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), nil)
|
787 |
+
if err != nil {
|
788 |
+
return fmt.Errorf("create http request: %w", err)
|
789 |
+
}
|
790 |
+
try := func(ctx context.Context) (bool, error) {
|
791 |
+
res, err := c.client.Do(req)
|
792 |
+
if err != nil {
|
793 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
794 |
+
}
|
795 |
+
defer res.Body.Close()
|
796 |
+
|
797 |
+
if code := res.StatusCode; code != http.StatusNoContent {
|
798 |
+
body, _ := io.ReadAll(res.Body)
|
799 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
800 |
+
|
801 |
+
}
|
802 |
+
return false, nil
|
803 |
+
}
|
804 |
+
|
805 |
+
return c.retry(ctx, 9, try)
|
806 |
+
}
|
807 |
+
|
808 |
+
func (c *RemoteIndex) IncreaseReplicationFactor(ctx context.Context,
|
809 |
+
hostName, indexName string, dist scaler.ShardDist,
|
810 |
+
) error {
|
811 |
+
path := fmt.Sprintf("/replicas/indices/%s/replication-factor:increase", indexName)
|
812 |
+
|
813 |
+
method := http.MethodPut
|
814 |
+
url := url.URL{Scheme: "http", Host: hostName, Path: path}
|
815 |
+
|
816 |
+
body, err := clusterapi.IndicesPayloads.IncreaseReplicationFactor.Marshall(dist)
|
817 |
+
if err != nil {
|
818 |
+
return err
|
819 |
+
}
|
820 |
+
try := func(ctx context.Context) (bool, error) {
|
821 |
+
req, err := http.NewRequestWithContext(ctx, method, url.String(), bytes.NewReader(body))
|
822 |
+
if err != nil {
|
823 |
+
return false, fmt.Errorf("create http request: %w", err)
|
824 |
+
}
|
825 |
+
|
826 |
+
res, err := c.client.Do(req)
|
827 |
+
if err != nil {
|
828 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
829 |
+
}
|
830 |
+
defer res.Body.Close()
|
831 |
+
|
832 |
+
if code := res.StatusCode; code != http.StatusNoContent {
|
833 |
+
body, _ := io.ReadAll(res.Body)
|
834 |
+
return shouldRetry(code), fmt.Errorf("status code: %v body: (%s)", code, body)
|
835 |
+
}
|
836 |
+
return false, nil
|
837 |
+
}
|
838 |
+
return c.retry(ctx, 34, try)
|
839 |
+
}
|
adapters/clients/remote_index_test.go
ADDED
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// _ _
|
13 |
+
//
|
14 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
15 |
+
//
|
16 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
17 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
18 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
19 |
+
//
|
20 |
+
// Copyright © 2016 - 2022 SeMI Technologies B.V. All rights reserved.
|
21 |
+
//
|
22 |
+
// CONTACT: [email protected]
|
23 |
+
package clients
|
24 |
+
|
25 |
+
import (
|
26 |
+
"context"
|
27 |
+
"fmt"
|
28 |
+
"io"
|
29 |
+
"net/http"
|
30 |
+
"net/http/httptest"
|
31 |
+
"strings"
|
32 |
+
"testing"
|
33 |
+
"time"
|
34 |
+
|
35 |
+
"github.com/stretchr/testify/assert"
|
36 |
+
"github.com/weaviate/weaviate/adapters/handlers/rest/clusterapi"
|
37 |
+
)
|
38 |
+
|
39 |
+
func TestRemoteIndexIncreaseRF(t *testing.T) {
|
40 |
+
t.Parallel()
|
41 |
+
|
42 |
+
ctx := context.Background()
|
43 |
+
path := "/replicas/indices/C1/replication-factor:increase"
|
44 |
+
fs := newFakeRemoteIndexServer(t, http.MethodPut, path)
|
45 |
+
ts := fs.server(t)
|
46 |
+
defer ts.Close()
|
47 |
+
client := newRemoteIndex(ts.Client())
|
48 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
49 |
+
err := client.IncreaseReplicationFactor(ctx, "", "C1", nil)
|
50 |
+
assert.NotNil(t, err)
|
51 |
+
assert.Contains(t, err.Error(), "connect")
|
52 |
+
})
|
53 |
+
n := 0
|
54 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
55 |
+
if n == 0 {
|
56 |
+
w.WriteHeader(http.StatusInternalServerError)
|
57 |
+
} else if n == 1 {
|
58 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
59 |
+
} else {
|
60 |
+
w.WriteHeader(http.StatusNoContent)
|
61 |
+
}
|
62 |
+
n++
|
63 |
+
}
|
64 |
+
t.Run("Success", func(t *testing.T) {
|
65 |
+
err := client.IncreaseReplicationFactor(ctx, fs.host, "C1", nil)
|
66 |
+
assert.Nil(t, err)
|
67 |
+
})
|
68 |
+
}
|
69 |
+
|
70 |
+
func TestRemoteIndexReInitShardIn(t *testing.T) {
|
71 |
+
t.Parallel()
|
72 |
+
|
73 |
+
ctx := context.Background()
|
74 |
+
path := "/indices/C1/shards/S1:reinit"
|
75 |
+
fs := newFakeRemoteIndexServer(t, http.MethodPut, path)
|
76 |
+
ts := fs.server(t)
|
77 |
+
defer ts.Close()
|
78 |
+
client := newRemoteIndex(ts.Client())
|
79 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
80 |
+
err := client.ReInitShard(ctx, "", "C1", "S1")
|
81 |
+
assert.NotNil(t, err)
|
82 |
+
assert.Contains(t, err.Error(), "connect")
|
83 |
+
})
|
84 |
+
n := 0
|
85 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
86 |
+
if n == 0 {
|
87 |
+
w.WriteHeader(http.StatusInternalServerError)
|
88 |
+
} else if n == 1 {
|
89 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
90 |
+
} else {
|
91 |
+
w.WriteHeader(http.StatusNoContent)
|
92 |
+
}
|
93 |
+
n++
|
94 |
+
}
|
95 |
+
t.Run("Success", func(t *testing.T) {
|
96 |
+
err := client.ReInitShard(ctx, fs.host, "C1", "S1")
|
97 |
+
assert.Nil(t, err)
|
98 |
+
})
|
99 |
+
}
|
100 |
+
|
101 |
+
func TestRemoteIndexCreateShard(t *testing.T) {
|
102 |
+
t.Parallel()
|
103 |
+
|
104 |
+
ctx := context.Background()
|
105 |
+
path := "/indices/C1/shards/S1"
|
106 |
+
fs := newFakeRemoteIndexServer(t, http.MethodPost, path)
|
107 |
+
ts := fs.server(t)
|
108 |
+
defer ts.Close()
|
109 |
+
client := newRemoteIndex(ts.Client())
|
110 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
111 |
+
err := client.CreateShard(ctx, "", "C1", "S1")
|
112 |
+
assert.NotNil(t, err)
|
113 |
+
assert.Contains(t, err.Error(), "connect")
|
114 |
+
})
|
115 |
+
n := 0
|
116 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
117 |
+
if n == 0 {
|
118 |
+
w.WriteHeader(http.StatusInternalServerError)
|
119 |
+
} else if n == 1 {
|
120 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
121 |
+
} else {
|
122 |
+
w.WriteHeader(http.StatusCreated)
|
123 |
+
}
|
124 |
+
n++
|
125 |
+
}
|
126 |
+
t.Run("Success", func(t *testing.T) {
|
127 |
+
err := client.CreateShard(ctx, fs.host, "C1", "S1")
|
128 |
+
assert.Nil(t, err)
|
129 |
+
})
|
130 |
+
}
|
131 |
+
|
132 |
+
func TestRemoteIndexUpdateShardStatus(t *testing.T) {
|
133 |
+
t.Parallel()
|
134 |
+
|
135 |
+
ctx := context.Background()
|
136 |
+
path := "/indices/C1/shards/S1/status"
|
137 |
+
fs := newFakeRemoteIndexServer(t, http.MethodPost, path)
|
138 |
+
ts := fs.server(t)
|
139 |
+
defer ts.Close()
|
140 |
+
client := newRemoteIndex(ts.Client())
|
141 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
142 |
+
err := client.UpdateShardStatus(ctx, "", "C1", "S1", "NewStatus")
|
143 |
+
assert.NotNil(t, err)
|
144 |
+
assert.Contains(t, err.Error(), "connect")
|
145 |
+
})
|
146 |
+
n := 0
|
147 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
148 |
+
if n == 0 {
|
149 |
+
w.WriteHeader(http.StatusInternalServerError)
|
150 |
+
} else if n == 1 {
|
151 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
152 |
+
}
|
153 |
+
n++
|
154 |
+
}
|
155 |
+
t.Run("Success", func(t *testing.T) {
|
156 |
+
err := client.UpdateShardStatus(ctx, fs.host, "C1", "S1", "NewStatus")
|
157 |
+
assert.Nil(t, err)
|
158 |
+
})
|
159 |
+
}
|
160 |
+
|
161 |
+
func TestRemoteIndexShardStatus(t *testing.T) {
|
162 |
+
t.Parallel()
|
163 |
+
var (
|
164 |
+
ctx = context.Background()
|
165 |
+
path = "/indices/C1/shards/S1/status"
|
166 |
+
fs = newFakeRemoteIndexServer(t, http.MethodGet, path)
|
167 |
+
Status = "READONLY"
|
168 |
+
)
|
169 |
+
ts := fs.server(t)
|
170 |
+
defer ts.Close()
|
171 |
+
client := newRemoteIndex(ts.Client())
|
172 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
173 |
+
_, err := client.GetShardStatus(ctx, "", "C1", "S1")
|
174 |
+
assert.NotNil(t, err)
|
175 |
+
assert.Contains(t, err.Error(), "connect")
|
176 |
+
})
|
177 |
+
n := 0
|
178 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
179 |
+
if n == 0 {
|
180 |
+
w.WriteHeader(http.StatusInternalServerError)
|
181 |
+
} else if n == 1 {
|
182 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
183 |
+
} else if n == 2 {
|
184 |
+
w.Header().Set("content-type", "any")
|
185 |
+
} else if n == 3 {
|
186 |
+
clusterapi.IndicesPayloads.GetShardStatusResults.SetContentTypeHeader(w)
|
187 |
+
} else {
|
188 |
+
clusterapi.IndicesPayloads.GetShardStatusResults.SetContentTypeHeader(w)
|
189 |
+
bytes, _ := clusterapi.IndicesPayloads.GetShardStatusResults.Marshal(Status)
|
190 |
+
w.Write(bytes)
|
191 |
+
}
|
192 |
+
n++
|
193 |
+
}
|
194 |
+
|
195 |
+
t.Run("ContentType", func(t *testing.T) {
|
196 |
+
_, err := client.GetShardStatus(ctx, fs.host, "C1", "S1")
|
197 |
+
assert.NotNil(t, err)
|
198 |
+
})
|
199 |
+
t.Run("Status", func(t *testing.T) {
|
200 |
+
_, err := client.GetShardStatus(ctx, fs.host, "C1", "S1")
|
201 |
+
assert.NotNil(t, err)
|
202 |
+
})
|
203 |
+
t.Run("Success", func(t *testing.T) {
|
204 |
+
st, err := client.GetShardStatus(ctx, fs.host, "C1", "S1")
|
205 |
+
assert.Nil(t, err)
|
206 |
+
assert.Equal(t, "READONLY", st)
|
207 |
+
})
|
208 |
+
}
|
209 |
+
|
210 |
+
func TestRemoteIndexPutFile(t *testing.T) {
|
211 |
+
t.Parallel()
|
212 |
+
var (
|
213 |
+
ctx = context.Background()
|
214 |
+
path = "/indices/C1/shards/S1/files/file1"
|
215 |
+
fs = newFakeRemoteIndexServer(t, http.MethodPost, path)
|
216 |
+
)
|
217 |
+
ts := fs.server(t)
|
218 |
+
defer ts.Close()
|
219 |
+
client := newRemoteIndex(ts.Client())
|
220 |
+
|
221 |
+
rsc := struct {
|
222 |
+
*strings.Reader
|
223 |
+
io.Closer
|
224 |
+
}{
|
225 |
+
strings.NewReader("hello, world"),
|
226 |
+
io.NopCloser(nil),
|
227 |
+
}
|
228 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
229 |
+
err := client.PutFile(ctx, "", "C1", "S1", "file1", rsc)
|
230 |
+
assert.NotNil(t, err)
|
231 |
+
assert.Contains(t, err.Error(), "connect")
|
232 |
+
})
|
233 |
+
n := 0
|
234 |
+
fs.doAfter = func(w http.ResponseWriter, r *http.Request) {
|
235 |
+
if n == 0 {
|
236 |
+
w.WriteHeader(http.StatusInternalServerError)
|
237 |
+
} else if n == 1 {
|
238 |
+
w.WriteHeader(http.StatusTooManyRequests)
|
239 |
+
} else {
|
240 |
+
w.WriteHeader(http.StatusNoContent)
|
241 |
+
}
|
242 |
+
n++
|
243 |
+
}
|
244 |
+
|
245 |
+
t.Run("Success", func(t *testing.T) {
|
246 |
+
err := client.PutFile(ctx, fs.host, "C1", "S1", "file1", rsc)
|
247 |
+
assert.Nil(t, err)
|
248 |
+
})
|
249 |
+
}
|
250 |
+
|
251 |
+
func newRemoteIndex(httpClient *http.Client) *RemoteIndex {
|
252 |
+
ri := NewRemoteIndex(httpClient)
|
253 |
+
ri.minBackOff = time.Millisecond * 1
|
254 |
+
ri.maxBackOff = time.Millisecond * 10
|
255 |
+
ri.timeoutUnit = time.Millisecond * 20
|
256 |
+
return ri
|
257 |
+
}
|
258 |
+
|
259 |
+
type fakeRemoteIndexServer struct {
|
260 |
+
method string
|
261 |
+
path string
|
262 |
+
host string
|
263 |
+
doBefore func(w http.ResponseWriter, r *http.Request) error
|
264 |
+
doAfter func(w http.ResponseWriter, r *http.Request)
|
265 |
+
}
|
266 |
+
|
267 |
+
func newFakeRemoteIndexServer(t *testing.T, method, path string) *fakeRemoteIndexServer {
|
268 |
+
f := &fakeRemoteIndexServer{
|
269 |
+
method: method,
|
270 |
+
path: path,
|
271 |
+
}
|
272 |
+
f.doBefore = func(w http.ResponseWriter, r *http.Request) error {
|
273 |
+
if r.Method != f.method {
|
274 |
+
w.WriteHeader(http.StatusBadRequest)
|
275 |
+
return fmt.Errorf("method want %s got %s", method, r.Method)
|
276 |
+
}
|
277 |
+
if f.path != r.URL.Path {
|
278 |
+
w.WriteHeader(http.StatusBadRequest)
|
279 |
+
return fmt.Errorf("path want %s got %s", path, r.URL.Path)
|
280 |
+
}
|
281 |
+
return nil
|
282 |
+
}
|
283 |
+
return f
|
284 |
+
}
|
285 |
+
|
286 |
+
func (f *fakeRemoteIndexServer) server(t *testing.T) *httptest.Server {
|
287 |
+
if f.doBefore == nil {
|
288 |
+
f.doBefore = func(w http.ResponseWriter, r *http.Request) error {
|
289 |
+
if r.Method != f.method {
|
290 |
+
w.WriteHeader(http.StatusBadRequest)
|
291 |
+
return fmt.Errorf("method want %s got %s", f.method, r.Method)
|
292 |
+
}
|
293 |
+
if f.path != r.URL.Path {
|
294 |
+
w.WriteHeader(http.StatusBadRequest)
|
295 |
+
return fmt.Errorf("path want %s got %s", f.path, r.URL.Path)
|
296 |
+
}
|
297 |
+
return nil
|
298 |
+
}
|
299 |
+
}
|
300 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
301 |
+
if err := f.doBefore(w, r); err != nil {
|
302 |
+
t.Error(err)
|
303 |
+
return
|
304 |
+
}
|
305 |
+
if f.doAfter != nil {
|
306 |
+
f.doAfter(w, r)
|
307 |
+
}
|
308 |
+
}
|
309 |
+
serv := httptest.NewServer(http.HandlerFunc(handler))
|
310 |
+
f.host = serv.URL[7:]
|
311 |
+
return serv
|
312 |
+
}
|
adapters/clients/replication.go
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"bytes"
|
16 |
+
"context"
|
17 |
+
"encoding/base64"
|
18 |
+
"encoding/json"
|
19 |
+
"fmt"
|
20 |
+
"io"
|
21 |
+
"math/rand"
|
22 |
+
"net/http"
|
23 |
+
"net/url"
|
24 |
+
"time"
|
25 |
+
|
26 |
+
"github.com/go-openapi/strfmt"
|
27 |
+
"github.com/weaviate/weaviate/adapters/handlers/rest/clusterapi"
|
28 |
+
"github.com/weaviate/weaviate/entities/additional"
|
29 |
+
"github.com/weaviate/weaviate/entities/search"
|
30 |
+
"github.com/weaviate/weaviate/entities/storobj"
|
31 |
+
"github.com/weaviate/weaviate/usecases/objects"
|
32 |
+
"github.com/weaviate/weaviate/usecases/replica"
|
33 |
+
)
|
34 |
+
|
35 |
+
// ReplicationClient is to coordinate operations among replicas
|
36 |
+
|
37 |
+
type replicationClient retryClient
|
38 |
+
|
39 |
+
func NewReplicationClient(httpClient *http.Client) replica.Client {
|
40 |
+
return &replicationClient{
|
41 |
+
client: httpClient,
|
42 |
+
retryer: newRetryer(),
|
43 |
+
}
|
44 |
+
}
|
45 |
+
|
46 |
+
// FetchObject fetches one object it exits
|
47 |
+
func (c *replicationClient) FetchObject(ctx context.Context, host, index,
|
48 |
+
shard string, id strfmt.UUID, selectProps search.SelectProperties,
|
49 |
+
additional additional.Properties,
|
50 |
+
) (objects.Replica, error) {
|
51 |
+
resp := objects.Replica{}
|
52 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodGet, host, index, shard, "", id.String(), nil)
|
53 |
+
if err != nil {
|
54 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
55 |
+
}
|
56 |
+
err = c.doCustomUnmarshal(c.timeoutUnit*20, req, nil, resp.UnmarshalBinary)
|
57 |
+
return resp, err
|
58 |
+
}
|
59 |
+
|
60 |
+
func (c *replicationClient) DigestObjects(ctx context.Context,
|
61 |
+
host, index, shard string, ids []strfmt.UUID,
|
62 |
+
) (result []replica.RepairResponse, err error) {
|
63 |
+
var resp []replica.RepairResponse
|
64 |
+
body, err := json.Marshal(ids)
|
65 |
+
if err != nil {
|
66 |
+
return nil, fmt.Errorf("marshal digest objects input: %w", err)
|
67 |
+
}
|
68 |
+
req, err := newHttpReplicaRequest(
|
69 |
+
ctx, http.MethodGet, host, index, shard,
|
70 |
+
"", "_digest", bytes.NewReader(body))
|
71 |
+
if err != nil {
|
72 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
73 |
+
}
|
74 |
+
err = c.do(c.timeoutUnit*20, req, body, &resp)
|
75 |
+
return resp, err
|
76 |
+
}
|
77 |
+
|
78 |
+
func (c *replicationClient) OverwriteObjects(ctx context.Context,
|
79 |
+
host, index, shard string, vobjects []*objects.VObject,
|
80 |
+
) ([]replica.RepairResponse, error) {
|
81 |
+
var resp []replica.RepairResponse
|
82 |
+
body, err := clusterapi.IndicesPayloads.VersionedObjectList.Marshal(vobjects)
|
83 |
+
if err != nil {
|
84 |
+
return nil, fmt.Errorf("encode request: %w", err)
|
85 |
+
}
|
86 |
+
req, err := newHttpReplicaRequest(
|
87 |
+
ctx, http.MethodPut, host, index, shard,
|
88 |
+
"", "_overwrite", bytes.NewReader(body))
|
89 |
+
if err != nil {
|
90 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
91 |
+
}
|
92 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
93 |
+
return resp, err
|
94 |
+
}
|
95 |
+
|
96 |
+
func (c *replicationClient) FetchObjects(ctx context.Context, host,
|
97 |
+
index, shard string, ids []strfmt.UUID,
|
98 |
+
) ([]objects.Replica, error) {
|
99 |
+
resp := make(objects.Replicas, len(ids))
|
100 |
+
idsBytes, err := json.Marshal(ids)
|
101 |
+
if err != nil {
|
102 |
+
return nil, fmt.Errorf("marshal ids: %w", err)
|
103 |
+
}
|
104 |
+
|
105 |
+
idsEncoded := base64.StdEncoding.EncodeToString(idsBytes)
|
106 |
+
|
107 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodGet, host, index, shard, "", "", nil)
|
108 |
+
if err != nil {
|
109 |
+
return nil, fmt.Errorf("create http request: %w", err)
|
110 |
+
}
|
111 |
+
|
112 |
+
req.URL.RawQuery = url.Values{"ids": []string{idsEncoded}}.Encode()
|
113 |
+
err = c.doCustomUnmarshal(c.timeoutUnit*90, req, nil, resp.UnmarshalBinary)
|
114 |
+
return resp, err
|
115 |
+
}
|
116 |
+
|
117 |
+
func (c *replicationClient) PutObject(ctx context.Context, host, index,
|
118 |
+
shard, requestID string, obj *storobj.Object,
|
119 |
+
) (replica.SimpleResponse, error) {
|
120 |
+
var resp replica.SimpleResponse
|
121 |
+
body, err := clusterapi.IndicesPayloads.SingleObject.Marshal(obj)
|
122 |
+
if err != nil {
|
123 |
+
return resp, fmt.Errorf("encode request: %w", err)
|
124 |
+
}
|
125 |
+
|
126 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard, requestID, "", nil)
|
127 |
+
if err != nil {
|
128 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
129 |
+
}
|
130 |
+
|
131 |
+
clusterapi.IndicesPayloads.SingleObject.SetContentTypeHeaderReq(req)
|
132 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
133 |
+
return resp, err
|
134 |
+
}
|
135 |
+
|
136 |
+
func (c *replicationClient) DeleteObject(ctx context.Context, host, index,
|
137 |
+
shard, requestID string, uuid strfmt.UUID,
|
138 |
+
) (replica.SimpleResponse, error) {
|
139 |
+
var resp replica.SimpleResponse
|
140 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodDelete, host, index, shard, requestID, uuid.String(), nil)
|
141 |
+
if err != nil {
|
142 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
143 |
+
}
|
144 |
+
|
145 |
+
err = c.do(c.timeoutUnit*90, req, nil, &resp)
|
146 |
+
return resp, err
|
147 |
+
}
|
148 |
+
|
149 |
+
func (c *replicationClient) PutObjects(ctx context.Context, host, index,
|
150 |
+
shard, requestID string, objects []*storobj.Object,
|
151 |
+
) (replica.SimpleResponse, error) {
|
152 |
+
var resp replica.SimpleResponse
|
153 |
+
body, err := clusterapi.IndicesPayloads.ObjectList.Marshal(objects)
|
154 |
+
if err != nil {
|
155 |
+
return resp, fmt.Errorf("encode request: %w", err)
|
156 |
+
}
|
157 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard, requestID, "", nil)
|
158 |
+
if err != nil {
|
159 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
160 |
+
}
|
161 |
+
|
162 |
+
clusterapi.IndicesPayloads.ObjectList.SetContentTypeHeaderReq(req)
|
163 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
164 |
+
return resp, err
|
165 |
+
}
|
166 |
+
|
167 |
+
func (c *replicationClient) MergeObject(ctx context.Context, host, index, shard, requestID string,
|
168 |
+
doc *objects.MergeDocument,
|
169 |
+
) (replica.SimpleResponse, error) {
|
170 |
+
var resp replica.SimpleResponse
|
171 |
+
body, err := clusterapi.IndicesPayloads.MergeDoc.Marshal(*doc)
|
172 |
+
if err != nil {
|
173 |
+
return resp, fmt.Errorf("encode request: %w", err)
|
174 |
+
}
|
175 |
+
|
176 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodPatch, host, index, shard,
|
177 |
+
requestID, doc.ID.String(), nil)
|
178 |
+
if err != nil {
|
179 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
180 |
+
}
|
181 |
+
|
182 |
+
clusterapi.IndicesPayloads.MergeDoc.SetContentTypeHeaderReq(req)
|
183 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
184 |
+
return resp, err
|
185 |
+
}
|
186 |
+
|
187 |
+
func (c *replicationClient) AddReferences(ctx context.Context, host, index,
|
188 |
+
shard, requestID string, refs []objects.BatchReference,
|
189 |
+
) (replica.SimpleResponse, error) {
|
190 |
+
var resp replica.SimpleResponse
|
191 |
+
body, err := clusterapi.IndicesPayloads.ReferenceList.Marshal(refs)
|
192 |
+
if err != nil {
|
193 |
+
return resp, fmt.Errorf("encode request: %w", err)
|
194 |
+
}
|
195 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodPost, host, index, shard,
|
196 |
+
requestID, "references", nil)
|
197 |
+
if err != nil {
|
198 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
199 |
+
}
|
200 |
+
|
201 |
+
clusterapi.IndicesPayloads.ReferenceList.SetContentTypeHeaderReq(req)
|
202 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
203 |
+
return resp, err
|
204 |
+
}
|
205 |
+
|
206 |
+
func (c *replicationClient) DeleteObjects(ctx context.Context, host, index, shard, requestID string,
|
207 |
+
uuids []strfmt.UUID, dryRun bool,
|
208 |
+
) (resp replica.SimpleResponse, err error) {
|
209 |
+
body, err := clusterapi.IndicesPayloads.BatchDeleteParams.Marshal(uuids, dryRun)
|
210 |
+
if err != nil {
|
211 |
+
return resp, fmt.Errorf("encode request: %w", err)
|
212 |
+
}
|
213 |
+
req, err := newHttpReplicaRequest(ctx, http.MethodDelete, host, index, shard, requestID, "", nil)
|
214 |
+
if err != nil {
|
215 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
216 |
+
}
|
217 |
+
|
218 |
+
clusterapi.IndicesPayloads.BatchDeleteParams.SetContentTypeHeaderReq(req)
|
219 |
+
err = c.do(c.timeoutUnit*90, req, body, &resp)
|
220 |
+
return resp, err
|
221 |
+
}
|
222 |
+
|
223 |
+
// Commit asks a host to commit and stores the response in the value pointed to by resp
|
224 |
+
func (c *replicationClient) Commit(ctx context.Context, host, index, shard string, requestID string, resp interface{}) error {
|
225 |
+
req, err := newHttpReplicaCMD(host, "commit", index, shard, requestID, nil)
|
226 |
+
if err != nil {
|
227 |
+
return fmt.Errorf("create http request: %w", err)
|
228 |
+
}
|
229 |
+
|
230 |
+
return c.do(c.timeoutUnit*90, req, nil, resp)
|
231 |
+
}
|
232 |
+
|
233 |
+
func (c *replicationClient) Abort(ctx context.Context, host, index, shard, requestID string) (
|
234 |
+
resp replica.SimpleResponse, err error,
|
235 |
+
) {
|
236 |
+
req, err := newHttpReplicaCMD(host, "abort", index, shard, requestID, nil)
|
237 |
+
if err != nil {
|
238 |
+
return resp, fmt.Errorf("create http request: %w", err)
|
239 |
+
}
|
240 |
+
|
241 |
+
err = c.do(c.timeoutUnit*5, req, nil, &resp)
|
242 |
+
return resp, err
|
243 |
+
}
|
244 |
+
|
245 |
+
func newHttpReplicaRequest(ctx context.Context, method, host, index, shard, requestId, suffix string, body io.Reader) (*http.Request, error) {
|
246 |
+
path := fmt.Sprintf("/replicas/indices/%s/shards/%s/objects", index, shard)
|
247 |
+
if suffix != "" {
|
248 |
+
path = fmt.Sprintf("%s/%s", path, suffix)
|
249 |
+
}
|
250 |
+
u := url.URL{
|
251 |
+
Scheme: "http",
|
252 |
+
Host: host,
|
253 |
+
Path: path,
|
254 |
+
}
|
255 |
+
|
256 |
+
if requestId != "" {
|
257 |
+
u.RawQuery = url.Values{replica.RequestKey: []string{requestId}}.Encode()
|
258 |
+
}
|
259 |
+
|
260 |
+
return http.NewRequestWithContext(ctx, method, u.String(), body)
|
261 |
+
}
|
262 |
+
|
263 |
+
func newHttpReplicaCMD(host, cmd, index, shard, requestId string, body io.Reader) (*http.Request, error) {
|
264 |
+
path := fmt.Sprintf("/replicas/indices/%s/shards/%s:%s", index, shard, cmd)
|
265 |
+
q := url.Values{replica.RequestKey: []string{requestId}}.Encode()
|
266 |
+
url := url.URL{Scheme: "http", Host: host, Path: path, RawQuery: q}
|
267 |
+
return http.NewRequest(http.MethodPost, url.String(), body)
|
268 |
+
}
|
269 |
+
|
270 |
+
func (c *replicationClient) do(timeout time.Duration, req *http.Request, body []byte, resp interface{}) (err error) {
|
271 |
+
ctx, cancel := context.WithTimeout(req.Context(), timeout)
|
272 |
+
defer cancel()
|
273 |
+
try := func(ctx context.Context) (bool, error) {
|
274 |
+
if body != nil {
|
275 |
+
req.Body = io.NopCloser(bytes.NewReader(body))
|
276 |
+
}
|
277 |
+
res, err := c.client.Do(req)
|
278 |
+
if err != nil {
|
279 |
+
return ctx.Err() == nil, fmt.Errorf("connect: %w", err)
|
280 |
+
}
|
281 |
+
defer res.Body.Close()
|
282 |
+
|
283 |
+
if code := res.StatusCode; code != http.StatusOK {
|
284 |
+
b, _ := io.ReadAll(res.Body)
|
285 |
+
return shouldRetry(code), fmt.Errorf("status code: %v, error: %s", code, b)
|
286 |
+
}
|
287 |
+
if err := json.NewDecoder(res.Body).Decode(resp); err != nil {
|
288 |
+
return false, fmt.Errorf("decode response: %w", err)
|
289 |
+
}
|
290 |
+
return false, nil
|
291 |
+
}
|
292 |
+
return c.retry(ctx, 9, try)
|
293 |
+
}
|
294 |
+
|
295 |
+
func (c *replicationClient) doCustomUnmarshal(timeout time.Duration,
|
296 |
+
req *http.Request, body []byte, decode func([]byte) error,
|
297 |
+
) (err error) {
|
298 |
+
return (*retryClient)(c).doWithCustomMarshaller(timeout, req, body, decode)
|
299 |
+
}
|
300 |
+
|
301 |
+
// backOff return a new random duration in the interval [d, 3d].
|
302 |
+
// It implements truncated exponential back-off with introduced jitter.
|
303 |
+
func backOff(d time.Duration) time.Duration {
|
304 |
+
return time.Duration(float64(d.Nanoseconds()*2) * (0.5 + rand.Float64()))
|
305 |
+
}
|
306 |
+
|
307 |
+
func shouldRetry(code int) bool {
|
308 |
+
return code == http.StatusInternalServerError ||
|
309 |
+
code == http.StatusTooManyRequests ||
|
310 |
+
code == http.StatusServiceUnavailable
|
311 |
+
}
|
adapters/clients/replication_test.go
ADDED
@@ -0,0 +1,592 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package clients
|
13 |
+
|
14 |
+
import (
|
15 |
+
"context"
|
16 |
+
"encoding/json"
|
17 |
+
"net/http"
|
18 |
+
"net/http/httptest"
|
19 |
+
"testing"
|
20 |
+
"time"
|
21 |
+
|
22 |
+
"github.com/go-openapi/strfmt"
|
23 |
+
"github.com/stretchr/testify/assert"
|
24 |
+
"github.com/stretchr/testify/require"
|
25 |
+
"github.com/weaviate/weaviate/entities/additional"
|
26 |
+
"github.com/weaviate/weaviate/entities/models"
|
27 |
+
"github.com/weaviate/weaviate/entities/storobj"
|
28 |
+
"github.com/weaviate/weaviate/usecases/objects"
|
29 |
+
"github.com/weaviate/weaviate/usecases/replica"
|
30 |
+
)
|
31 |
+
|
32 |
+
const (
|
33 |
+
RequestError = "RIDNotFound"
|
34 |
+
RequestSuccess = "RIDSuccess"
|
35 |
+
RequestInternalError = "RIDInternal"
|
36 |
+
RequestMalFormedResponse = "RIDMalFormed"
|
37 |
+
)
|
38 |
+
|
39 |
+
const (
|
40 |
+
UUID1 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168241")
|
41 |
+
UUID2 = strfmt.UUID("73f2eb5f-5abf-447a-81ca-74b1dd168242")
|
42 |
+
)
|
43 |
+
|
44 |
+
type fakeServer struct {
|
45 |
+
method string
|
46 |
+
path string
|
47 |
+
RequestError replica.SimpleResponse
|
48 |
+
RequestSuccess replica.SimpleResponse
|
49 |
+
host string
|
50 |
+
}
|
51 |
+
|
52 |
+
func newFakeReplicationServer(t *testing.T, method, path string) *fakeServer {
|
53 |
+
return &fakeServer{
|
54 |
+
method: method,
|
55 |
+
path: path,
|
56 |
+
RequestError: replica.SimpleResponse{Errors: []replica.Error{{Msg: "error"}}},
|
57 |
+
RequestSuccess: replica.SimpleResponse{},
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
func (f *fakeServer) server(t *testing.T) *httptest.Server {
|
62 |
+
handler := func(w http.ResponseWriter, r *http.Request) {
|
63 |
+
if r.Method != f.method {
|
64 |
+
t.Errorf("method want %s got %s", f.method, r.Method)
|
65 |
+
w.WriteHeader(http.StatusBadRequest)
|
66 |
+
return
|
67 |
+
}
|
68 |
+
if f.path != r.URL.Path {
|
69 |
+
t.Errorf("path want %s got %s", f.path, r.URL.Path)
|
70 |
+
w.WriteHeader(http.StatusBadRequest)
|
71 |
+
return
|
72 |
+
}
|
73 |
+
requestID := r.URL.Query().Get(replica.RequestKey)
|
74 |
+
switch requestID {
|
75 |
+
case RequestInternalError:
|
76 |
+
w.WriteHeader(http.StatusInternalServerError)
|
77 |
+
case RequestError:
|
78 |
+
bytes, _ := json.Marshal(&f.RequestError)
|
79 |
+
w.Write(bytes)
|
80 |
+
case RequestSuccess:
|
81 |
+
bytes, _ := json.Marshal(&replica.SimpleResponse{})
|
82 |
+
w.Write(bytes)
|
83 |
+
case RequestMalFormedResponse:
|
84 |
+
w.Write([]byte(`mal formed`))
|
85 |
+
}
|
86 |
+
}
|
87 |
+
serv := httptest.NewServer(http.HandlerFunc(handler))
|
88 |
+
f.host = serv.URL[7:]
|
89 |
+
return serv
|
90 |
+
}
|
91 |
+
|
92 |
+
func anyObject(uuid strfmt.UUID) models.Object {
|
93 |
+
return models.Object{
|
94 |
+
Class: "C1",
|
95 |
+
CreationTimeUnix: 900000000001,
|
96 |
+
LastUpdateTimeUnix: 900000000002,
|
97 |
+
ID: uuid,
|
98 |
+
Properties: map[string]interface{}{
|
99 |
+
"stringProp": "string",
|
100 |
+
"textProp": "text",
|
101 |
+
"datePropArray": []string{"1980-01-01T00:00:00+02:00"},
|
102 |
+
},
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
+
func TestReplicationPutObject(t *testing.T) {
|
107 |
+
t.Parallel()
|
108 |
+
|
109 |
+
ctx := context.Background()
|
110 |
+
f := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects")
|
111 |
+
ts := f.server(t)
|
112 |
+
defer ts.Close()
|
113 |
+
|
114 |
+
client := newReplicationClient(ts.Client())
|
115 |
+
t.Run("EncodeRequest", func(t *testing.T) {
|
116 |
+
obj := &storobj.Object{}
|
117 |
+
_, err := client.PutObject(ctx, "Node1", "C1", "S1", "RID", obj)
|
118 |
+
assert.NotNil(t, err)
|
119 |
+
assert.Contains(t, err.Error(), "encode")
|
120 |
+
})
|
121 |
+
|
122 |
+
obj := &storobj.Object{MarshallerVersion: 1, Object: anyObject(UUID1)}
|
123 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
124 |
+
_, err := client.PutObject(ctx, "", "C1", "S1", "", obj)
|
125 |
+
assert.NotNil(t, err)
|
126 |
+
assert.Contains(t, err.Error(), "connect")
|
127 |
+
})
|
128 |
+
|
129 |
+
t.Run("Error", func(t *testing.T) {
|
130 |
+
resp, err := client.PutObject(ctx, f.host, "C1", "S1", RequestError, obj)
|
131 |
+
assert.Nil(t, err)
|
132 |
+
assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp)
|
133 |
+
})
|
134 |
+
|
135 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
136 |
+
_, err := client.PutObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, obj)
|
137 |
+
assert.NotNil(t, err)
|
138 |
+
assert.Contains(t, err.Error(), "decode response")
|
139 |
+
})
|
140 |
+
|
141 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
142 |
+
_, err := client.PutObject(ctx, f.host, "C1", "S1", RequestInternalError, obj)
|
143 |
+
assert.NotNil(t, err)
|
144 |
+
assert.Contains(t, err.Error(), "status code")
|
145 |
+
})
|
146 |
+
}
|
147 |
+
|
148 |
+
func TestReplicationDeleteObject(t *testing.T) {
|
149 |
+
t.Parallel()
|
150 |
+
|
151 |
+
ctx := context.Background()
|
152 |
+
uuid := UUID1
|
153 |
+
path := "/replicas/indices/C1/shards/S1/objects/" + uuid.String()
|
154 |
+
fs := newFakeReplicationServer(t, http.MethodDelete, path)
|
155 |
+
ts := fs.server(t)
|
156 |
+
defer ts.Close()
|
157 |
+
|
158 |
+
client := newReplicationClient(ts.Client())
|
159 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
160 |
+
_, err := client.DeleteObject(ctx, "", "C1", "S1", "", uuid)
|
161 |
+
assert.NotNil(t, err)
|
162 |
+
assert.Contains(t, err.Error(), "connect")
|
163 |
+
})
|
164 |
+
|
165 |
+
t.Run("Error", func(t *testing.T) {
|
166 |
+
resp, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestError, uuid)
|
167 |
+
assert.Nil(t, err)
|
168 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
169 |
+
})
|
170 |
+
|
171 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
172 |
+
_, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuid)
|
173 |
+
assert.NotNil(t, err)
|
174 |
+
assert.Contains(t, err.Error(), "decode response")
|
175 |
+
})
|
176 |
+
|
177 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
178 |
+
_, err := client.DeleteObject(ctx, fs.host, "C1", "S1", RequestInternalError, uuid)
|
179 |
+
assert.NotNil(t, err)
|
180 |
+
assert.Contains(t, err.Error(), "status code")
|
181 |
+
})
|
182 |
+
}
|
183 |
+
|
184 |
+
func TestReplicationPutObjects(t *testing.T) {
|
185 |
+
t.Parallel()
|
186 |
+
ctx := context.Background()
|
187 |
+
fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects")
|
188 |
+
fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
|
189 |
+
ts := fs.server(t)
|
190 |
+
defer ts.Close()
|
191 |
+
|
192 |
+
client := newReplicationClient(ts.Client())
|
193 |
+
t.Run("EncodeRequest", func(t *testing.T) {
|
194 |
+
objs := []*storobj.Object{{}}
|
195 |
+
_, err := client.PutObjects(ctx, "Node1", "C1", "S1", "RID", objs)
|
196 |
+
assert.NotNil(t, err)
|
197 |
+
assert.Contains(t, err.Error(), "encode")
|
198 |
+
})
|
199 |
+
|
200 |
+
objects := []*storobj.Object{
|
201 |
+
{MarshallerVersion: 1, Object: anyObject(UUID1)},
|
202 |
+
{MarshallerVersion: 1, Object: anyObject(UUID2)},
|
203 |
+
}
|
204 |
+
|
205 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
206 |
+
_, err := client.PutObjects(ctx, "", "C1", "S1", "", objects)
|
207 |
+
assert.NotNil(t, err)
|
208 |
+
assert.Contains(t, err.Error(), "connect")
|
209 |
+
})
|
210 |
+
|
211 |
+
t.Run("Error", func(t *testing.T) {
|
212 |
+
resp, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestError, objects)
|
213 |
+
assert.Nil(t, err)
|
214 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
215 |
+
})
|
216 |
+
|
217 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
218 |
+
_, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, objects)
|
219 |
+
assert.NotNil(t, err)
|
220 |
+
assert.Contains(t, err.Error(), "decode response")
|
221 |
+
})
|
222 |
+
|
223 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
224 |
+
_, err := client.PutObjects(ctx, fs.host, "C1", "S1", RequestInternalError, objects)
|
225 |
+
assert.NotNil(t, err)
|
226 |
+
assert.Contains(t, err.Error(), "status code")
|
227 |
+
})
|
228 |
+
}
|
229 |
+
|
230 |
+
func TestReplicationMergeObject(t *testing.T) {
|
231 |
+
t.Parallel()
|
232 |
+
ctx := context.Background()
|
233 |
+
uuid := UUID1
|
234 |
+
f := newFakeReplicationServer(t, http.MethodPatch, "/replicas/indices/C1/shards/S1/objects/"+uuid.String())
|
235 |
+
ts := f.server(t)
|
236 |
+
defer ts.Close()
|
237 |
+
|
238 |
+
client := newReplicationClient(ts.Client())
|
239 |
+
doc := &objects.MergeDocument{ID: uuid}
|
240 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
241 |
+
_, err := client.MergeObject(ctx, "", "C1", "S1", "", doc)
|
242 |
+
assert.NotNil(t, err)
|
243 |
+
assert.Contains(t, err.Error(), "connect")
|
244 |
+
})
|
245 |
+
|
246 |
+
t.Run("Error", func(t *testing.T) {
|
247 |
+
resp, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestError, doc)
|
248 |
+
assert.Nil(t, err)
|
249 |
+
assert.Equal(t, replica.SimpleResponse{Errors: f.RequestError.Errors}, resp)
|
250 |
+
})
|
251 |
+
|
252 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
253 |
+
_, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestMalFormedResponse, doc)
|
254 |
+
assert.NotNil(t, err)
|
255 |
+
assert.Contains(t, err.Error(), "decode response")
|
256 |
+
})
|
257 |
+
|
258 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
259 |
+
_, err := client.MergeObject(ctx, f.host, "C1", "S1", RequestInternalError, doc)
|
260 |
+
assert.NotNil(t, err)
|
261 |
+
assert.Contains(t, err.Error(), "status code")
|
262 |
+
})
|
263 |
+
}
|
264 |
+
|
265 |
+
func TestReplicationAddReferences(t *testing.T) {
|
266 |
+
t.Parallel()
|
267 |
+
|
268 |
+
ctx := context.Background()
|
269 |
+
fs := newFakeReplicationServer(t, http.MethodPost, "/replicas/indices/C1/shards/S1/objects/references")
|
270 |
+
fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
|
271 |
+
ts := fs.server(t)
|
272 |
+
defer ts.Close()
|
273 |
+
|
274 |
+
client := newReplicationClient(ts.Client())
|
275 |
+
refs := []objects.BatchReference{{OriginalIndex: 1}, {OriginalIndex: 2}}
|
276 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
277 |
+
_, err := client.AddReferences(ctx, "", "C1", "S1", "", refs)
|
278 |
+
assert.NotNil(t, err)
|
279 |
+
assert.Contains(t, err.Error(), "connect")
|
280 |
+
})
|
281 |
+
|
282 |
+
t.Run("Error", func(t *testing.T) {
|
283 |
+
resp, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestError, refs)
|
284 |
+
assert.Nil(t, err)
|
285 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
286 |
+
})
|
287 |
+
|
288 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
289 |
+
_, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, refs)
|
290 |
+
assert.NotNil(t, err)
|
291 |
+
assert.Contains(t, err.Error(), "decode response")
|
292 |
+
})
|
293 |
+
|
294 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
295 |
+
_, err := client.AddReferences(ctx, fs.host, "C1", "S1", RequestInternalError, refs)
|
296 |
+
assert.NotNil(t, err)
|
297 |
+
assert.Contains(t, err.Error(), "status code")
|
298 |
+
})
|
299 |
+
}
|
300 |
+
|
301 |
+
func TestReplicationDeleteObjects(t *testing.T) {
|
302 |
+
t.Parallel()
|
303 |
+
|
304 |
+
ctx := context.Background()
|
305 |
+
fs := newFakeReplicationServer(t, http.MethodDelete, "/replicas/indices/C1/shards/S1/objects")
|
306 |
+
fs.RequestError.Errors = append(fs.RequestError.Errors, replica.Error{Msg: "error2"})
|
307 |
+
ts := fs.server(t)
|
308 |
+
defer ts.Close()
|
309 |
+
client := newReplicationClient(ts.Client())
|
310 |
+
|
311 |
+
uuids := []strfmt.UUID{strfmt.UUID("1"), strfmt.UUID("2")}
|
312 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
313 |
+
_, err := client.DeleteObjects(ctx, "", "C1", "S1", "", uuids, false)
|
314 |
+
assert.NotNil(t, err)
|
315 |
+
assert.Contains(t, err.Error(), "connect")
|
316 |
+
})
|
317 |
+
|
318 |
+
t.Run("Error", func(t *testing.T) {
|
319 |
+
resp, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestError, uuids, false)
|
320 |
+
assert.Nil(t, err)
|
321 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
322 |
+
})
|
323 |
+
|
324 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
325 |
+
_, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, uuids, false)
|
326 |
+
assert.NotNil(t, err)
|
327 |
+
assert.Contains(t, err.Error(), "decode response")
|
328 |
+
})
|
329 |
+
|
330 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
331 |
+
_, err := client.DeleteObjects(ctx, fs.host, "C1", "S1", RequestInternalError, uuids, false)
|
332 |
+
assert.NotNil(t, err)
|
333 |
+
assert.Contains(t, err.Error(), "status code")
|
334 |
+
})
|
335 |
+
}
|
336 |
+
|
337 |
+
func TestReplicationAbort(t *testing.T) {
|
338 |
+
t.Parallel()
|
339 |
+
|
340 |
+
ctx := context.Background()
|
341 |
+
path := "/replicas/indices/C1/shards/S1:abort"
|
342 |
+
fs := newFakeReplicationServer(t, http.MethodPost, path)
|
343 |
+
ts := fs.server(t)
|
344 |
+
defer ts.Close()
|
345 |
+
client := newReplicationClient(ts.Client())
|
346 |
+
|
347 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
348 |
+
client := newReplicationClient(ts.Client())
|
349 |
+
client.maxBackOff = client.timeoutUnit * 20
|
350 |
+
_, err := client.Abort(ctx, "", "C1", "S1", "")
|
351 |
+
assert.NotNil(t, err)
|
352 |
+
assert.Contains(t, err.Error(), "connect")
|
353 |
+
})
|
354 |
+
|
355 |
+
t.Run("Error", func(t *testing.T) {
|
356 |
+
resp, err := client.Abort(ctx, fs.host, "C1", "S1", RequestError)
|
357 |
+
assert.Nil(t, err)
|
358 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
359 |
+
})
|
360 |
+
|
361 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
362 |
+
_, err := client.Abort(ctx, fs.host, "C1", "S1", RequestMalFormedResponse)
|
363 |
+
assert.NotNil(t, err)
|
364 |
+
assert.Contains(t, err.Error(), "decode response")
|
365 |
+
})
|
366 |
+
client.timeoutUnit = client.maxBackOff * 3
|
367 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
368 |
+
_, err := client.Abort(ctx, fs.host, "C1", "S1", RequestInternalError)
|
369 |
+
assert.NotNil(t, err)
|
370 |
+
assert.Contains(t, err.Error(), "status code")
|
371 |
+
})
|
372 |
+
}
|
373 |
+
|
374 |
+
func TestReplicationCommit(t *testing.T) {
|
375 |
+
t.Parallel()
|
376 |
+
|
377 |
+
ctx := context.Background()
|
378 |
+
path := "/replicas/indices/C1/shards/S1:commit"
|
379 |
+
fs := newFakeReplicationServer(t, http.MethodPost, path)
|
380 |
+
ts := fs.server(t)
|
381 |
+
defer ts.Close()
|
382 |
+
resp := replica.SimpleResponse{}
|
383 |
+
client := newReplicationClient(ts.Client())
|
384 |
+
|
385 |
+
t.Run("ConnectionError", func(t *testing.T) {
|
386 |
+
err := client.Commit(ctx, "", "C1", "S1", "", &resp)
|
387 |
+
assert.NotNil(t, err)
|
388 |
+
assert.Contains(t, err.Error(), "connect")
|
389 |
+
})
|
390 |
+
|
391 |
+
t.Run("Error", func(t *testing.T) {
|
392 |
+
err := client.Commit(ctx, fs.host, "C1", "S1", RequestError, &resp)
|
393 |
+
assert.Nil(t, err)
|
394 |
+
assert.Equal(t, replica.SimpleResponse{Errors: fs.RequestError.Errors}, resp)
|
395 |
+
})
|
396 |
+
|
397 |
+
t.Run("DecodeResponse", func(t *testing.T) {
|
398 |
+
err := client.Commit(ctx, fs.host, "C1", "S1", RequestMalFormedResponse, &resp)
|
399 |
+
assert.NotNil(t, err)
|
400 |
+
assert.Contains(t, err.Error(), "decode response")
|
401 |
+
})
|
402 |
+
|
403 |
+
t.Run("ServerInternalError", func(t *testing.T) {
|
404 |
+
err := client.Commit(ctx, fs.host, "C1", "S1", RequestInternalError, &resp)
|
405 |
+
assert.NotNil(t, err)
|
406 |
+
assert.Contains(t, err.Error(), "status code")
|
407 |
+
})
|
408 |
+
}
|
409 |
+
|
410 |
+
func TestReplicationFetchObject(t *testing.T) {
|
411 |
+
t.Parallel()
|
412 |
+
|
413 |
+
expected := objects.Replica{
|
414 |
+
ID: UUID1,
|
415 |
+
Object: &storobj.Object{
|
416 |
+
MarshallerVersion: 1,
|
417 |
+
Object: models.Object{
|
418 |
+
ID: UUID1,
|
419 |
+
Properties: map[string]interface{}{
|
420 |
+
"stringProp": "abc",
|
421 |
+
},
|
422 |
+
},
|
423 |
+
Vector: []float32{1, 2, 3, 4, 5},
|
424 |
+
VectorLen: 5,
|
425 |
+
},
|
426 |
+
}
|
427 |
+
|
428 |
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
429 |
+
b, _ := expected.MarshalBinary()
|
430 |
+
w.Write(b)
|
431 |
+
}))
|
432 |
+
|
433 |
+
c := newReplicationClient(server.Client())
|
434 |
+
resp, err := c.FetchObject(context.Background(), server.URL[7:],
|
435 |
+
"C1", "S1", expected.ID, nil, additional.Properties{})
|
436 |
+
require.Nil(t, err)
|
437 |
+
assert.Equal(t, expected.ID, resp.ID)
|
438 |
+
assert.Equal(t, expected.Deleted, resp.Deleted)
|
439 |
+
assert.EqualValues(t, expected.Object, resp.Object)
|
440 |
+
}
|
441 |
+
|
442 |
+
func TestReplicationFetchObjects(t *testing.T) {
|
443 |
+
t.Parallel()
|
444 |
+
expected := objects.Replicas{
|
445 |
+
{
|
446 |
+
ID: UUID1,
|
447 |
+
Object: &storobj.Object{
|
448 |
+
MarshallerVersion: 1,
|
449 |
+
Object: models.Object{
|
450 |
+
ID: UUID1,
|
451 |
+
Properties: map[string]interface{}{
|
452 |
+
"stringProp": "abc",
|
453 |
+
},
|
454 |
+
},
|
455 |
+
Vector: []float32{1, 2, 3, 4, 5},
|
456 |
+
VectorLen: 5,
|
457 |
+
},
|
458 |
+
},
|
459 |
+
{
|
460 |
+
ID: UUID2,
|
461 |
+
Object: &storobj.Object{
|
462 |
+
MarshallerVersion: 1,
|
463 |
+
Object: models.Object{
|
464 |
+
ID: UUID2,
|
465 |
+
Properties: map[string]interface{}{
|
466 |
+
"floatProp": float64(123),
|
467 |
+
},
|
468 |
+
},
|
469 |
+
Vector: []float32{10, 20, 30, 40, 50},
|
470 |
+
VectorLen: 5,
|
471 |
+
},
|
472 |
+
},
|
473 |
+
}
|
474 |
+
|
475 |
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
476 |
+
b, _ := expected.MarshalBinary()
|
477 |
+
w.Write(b)
|
478 |
+
}))
|
479 |
+
|
480 |
+
c := newReplicationClient(server.Client())
|
481 |
+
resp, err := c.FetchObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{expected[0].ID})
|
482 |
+
require.Nil(t, err)
|
483 |
+
require.Len(t, resp, 2)
|
484 |
+
assert.Equal(t, expected[0].ID, resp[0].ID)
|
485 |
+
assert.Equal(t, expected[0].Deleted, resp[0].Deleted)
|
486 |
+
assert.EqualValues(t, expected[0].Object, resp[0].Object)
|
487 |
+
assert.Equal(t, expected[1].ID, resp[1].ID)
|
488 |
+
assert.Equal(t, expected[1].Deleted, resp[1].Deleted)
|
489 |
+
assert.EqualValues(t, expected[1].Object, resp[1].Object)
|
490 |
+
}
|
491 |
+
|
492 |
+
func TestReplicationDigestObjects(t *testing.T) {
|
493 |
+
t.Parallel()
|
494 |
+
|
495 |
+
now := time.Now()
|
496 |
+
expected := []replica.RepairResponse{
|
497 |
+
{
|
498 |
+
ID: UUID1.String(),
|
499 |
+
UpdateTime: now.UnixMilli(),
|
500 |
+
Version: 1,
|
501 |
+
},
|
502 |
+
{
|
503 |
+
ID: UUID2.String(),
|
504 |
+
UpdateTime: now.UnixMilli(),
|
505 |
+
Version: 1,
|
506 |
+
},
|
507 |
+
}
|
508 |
+
|
509 |
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
510 |
+
b, _ := json.Marshal(expected)
|
511 |
+
w.Write(b)
|
512 |
+
}))
|
513 |
+
|
514 |
+
c := newReplicationClient(server.Client())
|
515 |
+
resp, err := c.DigestObjects(context.Background(), server.URL[7:], "C1", "S1", []strfmt.UUID{
|
516 |
+
strfmt.UUID(expected[0].ID),
|
517 |
+
strfmt.UUID(expected[1].ID),
|
518 |
+
})
|
519 |
+
require.Nil(t, err)
|
520 |
+
require.Len(t, resp, 2)
|
521 |
+
assert.Equal(t, expected[0].ID, resp[0].ID)
|
522 |
+
assert.Equal(t, expected[0].Deleted, resp[0].Deleted)
|
523 |
+
assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime)
|
524 |
+
assert.Equal(t, expected[0].Version, resp[0].Version)
|
525 |
+
assert.Equal(t, expected[1].ID, resp[1].ID)
|
526 |
+
assert.Equal(t, expected[1].Deleted, resp[1].Deleted)
|
527 |
+
assert.Equal(t, expected[1].UpdateTime, resp[1].UpdateTime)
|
528 |
+
assert.Equal(t, expected[1].Version, resp[1].Version)
|
529 |
+
}
|
530 |
+
|
531 |
+
func TestReplicationOverwriteObjects(t *testing.T) {
|
532 |
+
t.Parallel()
|
533 |
+
|
534 |
+
now := time.Now()
|
535 |
+
input := []*objects.VObject{
|
536 |
+
{
|
537 |
+
LatestObject: &models.Object{
|
538 |
+
ID: UUID1,
|
539 |
+
Class: "C1",
|
540 |
+
CreationTimeUnix: now.UnixMilli(),
|
541 |
+
LastUpdateTimeUnix: now.Add(time.Hour).UnixMilli(),
|
542 |
+
Properties: map[string]interface{}{
|
543 |
+
"stringProp": "abc",
|
544 |
+
},
|
545 |
+
Vector: []float32{1, 2, 3, 4, 5},
|
546 |
+
},
|
547 |
+
StaleUpdateTime: now.UnixMilli(),
|
548 |
+
Version: 0,
|
549 |
+
},
|
550 |
+
}
|
551 |
+
expected := []replica.RepairResponse{
|
552 |
+
{
|
553 |
+
ID: UUID1.String(),
|
554 |
+
Version: 1,
|
555 |
+
UpdateTime: now.Add(time.Hour).UnixMilli(),
|
556 |
+
},
|
557 |
+
}
|
558 |
+
|
559 |
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
560 |
+
b, _ := json.Marshal(expected)
|
561 |
+
w.Write(b)
|
562 |
+
}))
|
563 |
+
|
564 |
+
c := newReplicationClient(server.Client())
|
565 |
+
resp, err := c.OverwriteObjects(context.Background(), server.URL[7:], "C1", "S1", input)
|
566 |
+
require.Nil(t, err)
|
567 |
+
require.Len(t, resp, 1)
|
568 |
+
assert.Equal(t, expected[0].ID, resp[0].ID)
|
569 |
+
assert.Equal(t, expected[0].Version, resp[0].Version)
|
570 |
+
assert.Equal(t, expected[0].UpdateTime, resp[0].UpdateTime)
|
571 |
+
}
|
572 |
+
|
573 |
+
func TestExpBackOff(t *testing.T) {
|
574 |
+
N := 200
|
575 |
+
av := time.Duration(0)
|
576 |
+
delay := time.Nanosecond * 20
|
577 |
+
for i := 0; i < N; i++ {
|
578 |
+
av += backOff(delay)
|
579 |
+
}
|
580 |
+
av /= time.Duration(N)
|
581 |
+
if av < time.Nanosecond*30 || av > time.Nanosecond*50 {
|
582 |
+
t.Errorf("average time got %v", av)
|
583 |
+
}
|
584 |
+
}
|
585 |
+
|
586 |
+
func newReplicationClient(httpClient *http.Client) *replicationClient {
|
587 |
+
c := NewReplicationClient(httpClient).(*replicationClient)
|
588 |
+
c.minBackOff = time.Millisecond * 1
|
589 |
+
c.maxBackOff = time.Millisecond * 8
|
590 |
+
c.timeoutUnit = time.Millisecond * 20
|
591 |
+
return c
|
592 |
+
}
|
adapters/handlers/graphql/common/fetch/filter.go
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package fetch
|
13 |
+
|
14 |
+
import (
|
15 |
+
"fmt"
|
16 |
+
|
17 |
+
"github.com/tailor-inc/graphql"
|
18 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/descriptions"
|
19 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/local/common_filters"
|
20 |
+
)
|
21 |
+
|
22 |
+
// FilterBuilder can build where filters for both local and
|
23 |
+
type FilterBuilder struct {
|
24 |
+
prefix string
|
25 |
+
}
|
26 |
+
|
27 |
+
// NewFilterBuilder with kind and prefix
|
28 |
+
func NewFilterBuilder(prefix string) *FilterBuilder {
|
29 |
+
return &FilterBuilder{
|
30 |
+
prefix: prefix,
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
// Build a where filter ArgumentConfig
|
35 |
+
func (b *FilterBuilder) Build() *graphql.ArgumentConfig {
|
36 |
+
return &graphql.ArgumentConfig{
|
37 |
+
Description: descriptions.FetchWhereFilterFields,
|
38 |
+
Type: graphql.NewNonNull(graphql.NewInputObject(
|
39 |
+
graphql.InputObjectConfig{
|
40 |
+
Name: fmt.Sprintf("%sFetchObjectWhereInpObj", b.prefix),
|
41 |
+
Fields: b.fields(),
|
42 |
+
Description: descriptions.FetchWhereFilterFieldsInpObj,
|
43 |
+
},
|
44 |
+
)),
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
func (b *FilterBuilder) fields() graphql.InputObjectConfigFieldMap {
|
49 |
+
return graphql.InputObjectConfigFieldMap{
|
50 |
+
"class": &graphql.InputObjectFieldConfig{
|
51 |
+
Type: graphql.NewNonNull(b.class()),
|
52 |
+
Description: descriptions.WhereClass,
|
53 |
+
},
|
54 |
+
"properties": &graphql.InputObjectFieldConfig{
|
55 |
+
Type: graphql.NewNonNull(graphql.NewList(b.properties())),
|
56 |
+
Description: descriptions.WhereProperties,
|
57 |
+
},
|
58 |
+
"first": &graphql.InputObjectFieldConfig{
|
59 |
+
Type: graphql.Int,
|
60 |
+
Description: descriptions.First,
|
61 |
+
},
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
func (b *FilterBuilder) properties() *graphql.InputObject {
|
66 |
+
elements := common_filters.BuildNew(fmt.Sprintf("%sFetchObject", b.prefix))
|
67 |
+
|
68 |
+
// Remove path and operands fields as they are not required here
|
69 |
+
delete(elements, "path")
|
70 |
+
delete(elements, "operands")
|
71 |
+
|
72 |
+
// make operator required
|
73 |
+
elements["operator"].Type = graphql.NewNonNull(elements["operator"].Type)
|
74 |
+
|
75 |
+
elements["certainty"] = &graphql.InputObjectFieldConfig{
|
76 |
+
Type: graphql.NewNonNull(graphql.Float),
|
77 |
+
Description: descriptions.WhereCertainty,
|
78 |
+
}
|
79 |
+
elements["name"] = &graphql.InputObjectFieldConfig{
|
80 |
+
Type: graphql.NewNonNull(graphql.String),
|
81 |
+
Description: descriptions.WhereName,
|
82 |
+
}
|
83 |
+
elements["keywords"] = &graphql.InputObjectFieldConfig{
|
84 |
+
Type: graphql.NewList(b.keywordInpObj(fmt.Sprintf("%sFetchObjectWhereProperties", b.prefix))),
|
85 |
+
Description: descriptions.WhereKeywords,
|
86 |
+
}
|
87 |
+
|
88 |
+
networkFetchWhereInpObjPropertiesObj := graphql.NewInputObject(
|
89 |
+
graphql.InputObjectConfig{
|
90 |
+
Name: fmt.Sprintf("%sFetchObjectWhereInpObjProperties", b.prefix),
|
91 |
+
Fields: elements,
|
92 |
+
Description: descriptions.WhereProperties,
|
93 |
+
},
|
94 |
+
)
|
95 |
+
|
96 |
+
return networkFetchWhereInpObjPropertiesObj
|
97 |
+
}
|
98 |
+
|
99 |
+
func (b *FilterBuilder) keywordInpObj(prefix string) *graphql.InputObject {
|
100 |
+
return graphql.NewInputObject(
|
101 |
+
graphql.InputObjectConfig{
|
102 |
+
Name: fmt.Sprintf("%sKeywordsInpObj", prefix),
|
103 |
+
Fields: graphql.InputObjectConfigFieldMap{
|
104 |
+
"value": &graphql.InputObjectFieldConfig{
|
105 |
+
Type: graphql.String,
|
106 |
+
Description: descriptions.WhereKeywordsValue,
|
107 |
+
},
|
108 |
+
"weight": &graphql.InputObjectFieldConfig{
|
109 |
+
Type: graphql.Float,
|
110 |
+
Description: descriptions.WhereKeywordsWeight,
|
111 |
+
},
|
112 |
+
},
|
113 |
+
Description: descriptions.WhereKeywordsInpObj,
|
114 |
+
},
|
115 |
+
)
|
116 |
+
}
|
117 |
+
|
118 |
+
func (b *FilterBuilder) class() *graphql.InputObject {
|
119 |
+
filterClassElements := graphql.InputObjectConfigFieldMap{
|
120 |
+
"name": &graphql.InputObjectFieldConfig{
|
121 |
+
Type: graphql.String,
|
122 |
+
Description: descriptions.WhereName,
|
123 |
+
},
|
124 |
+
"certainty": &graphql.InputObjectFieldConfig{
|
125 |
+
Type: graphql.Float,
|
126 |
+
Description: descriptions.WhereCertainty,
|
127 |
+
},
|
128 |
+
"keywords": &graphql.InputObjectFieldConfig{
|
129 |
+
Type: graphql.NewList(b.keywordInpObj(fmt.Sprintf("%sFetchObjectWhereClass", b.prefix))),
|
130 |
+
Description: descriptions.WhereKeywords,
|
131 |
+
},
|
132 |
+
"first": &graphql.InputObjectFieldConfig{
|
133 |
+
Type: graphql.Int,
|
134 |
+
Description: descriptions.First,
|
135 |
+
},
|
136 |
+
}
|
137 |
+
|
138 |
+
networkFetchWhereInpObjClassInpObj := graphql.NewInputObject(
|
139 |
+
graphql.InputObjectConfig{
|
140 |
+
Name: fmt.Sprintf("%sFetchObjectWhereInpObjClassInpObj", b.prefix),
|
141 |
+
Fields: filterClassElements,
|
142 |
+
Description: descriptions.WhereClass,
|
143 |
+
},
|
144 |
+
)
|
145 |
+
return networkFetchWhereInpObjClassInpObj
|
146 |
+
}
|
adapters/handlers/graphql/common/json_number.go
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package common
|
13 |
+
|
14 |
+
import (
|
15 |
+
"encoding/json"
|
16 |
+
"fmt"
|
17 |
+
|
18 |
+
"github.com/tailor-inc/graphql"
|
19 |
+
"github.com/weaviate/weaviate/entities/aggregation"
|
20 |
+
)
|
21 |
+
|
22 |
+
// JSONNumberResolver turns json.Number types into number types usable by graphQL
|
23 |
+
func JSONNumberResolver(p graphql.ResolveParams) (interface{}, error) {
|
24 |
+
switch v := p.Source.(type) {
|
25 |
+
case map[string]interface{}:
|
26 |
+
field, ok := v[p.Info.FieldName]
|
27 |
+
if !ok {
|
28 |
+
return nil, nil
|
29 |
+
}
|
30 |
+
|
31 |
+
switch n := field.(type) {
|
32 |
+
case json.Number:
|
33 |
+
return n.Float64()
|
34 |
+
case int64:
|
35 |
+
return float64(n), nil
|
36 |
+
case int:
|
37 |
+
return float64(n), nil
|
38 |
+
case float64:
|
39 |
+
return n, nil
|
40 |
+
}
|
41 |
+
|
42 |
+
return nil, fmt.Errorf("unknown number type for %t", field)
|
43 |
+
|
44 |
+
case map[string]float64:
|
45 |
+
return v[p.Info.FieldName], nil
|
46 |
+
|
47 |
+
case aggregation.Text:
|
48 |
+
switch p.Info.FieldName {
|
49 |
+
// case "count":
|
50 |
+
// // TODO gh-974: Support Count in text aggregations
|
51 |
+
// return nil, nil
|
52 |
+
|
53 |
+
default:
|
54 |
+
return nil, fmt.Errorf("fieldName '%s' does not match text aggregation", p.Info.FieldName)
|
55 |
+
}
|
56 |
+
|
57 |
+
default:
|
58 |
+
return nil, fmt.Errorf("json number resolver: unusable type %T", p.Source)
|
59 |
+
}
|
60 |
+
}
|
adapters/handlers/graphql/common/json_number_test.go
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package common
|
13 |
+
|
14 |
+
import (
|
15 |
+
"encoding/json"
|
16 |
+
"fmt"
|
17 |
+
"testing"
|
18 |
+
|
19 |
+
"github.com/stretchr/testify/assert"
|
20 |
+
"github.com/tailor-inc/graphql"
|
21 |
+
)
|
22 |
+
|
23 |
+
type testCase struct {
|
24 |
+
input interface{}
|
25 |
+
expectedOutput float64
|
26 |
+
}
|
27 |
+
|
28 |
+
func TestJSONNumberResolver(t *testing.T) {
|
29 |
+
tests := []testCase{
|
30 |
+
{
|
31 |
+
input: json.Number("10"),
|
32 |
+
expectedOutput: 10.0,
|
33 |
+
},
|
34 |
+
{
|
35 |
+
input: int(10),
|
36 |
+
expectedOutput: 10.0,
|
37 |
+
},
|
38 |
+
{
|
39 |
+
input: int64(10),
|
40 |
+
expectedOutput: 10.0,
|
41 |
+
},
|
42 |
+
{
|
43 |
+
input: float64(10),
|
44 |
+
expectedOutput: 10.0,
|
45 |
+
},
|
46 |
+
}
|
47 |
+
|
48 |
+
for _, test := range tests {
|
49 |
+
name := fmt.Sprintf("%#v -> %#v", test.input, test.expectedOutput)
|
50 |
+
t.Run(name, func(t *testing.T) {
|
51 |
+
result, err := JSONNumberResolver(resolveParams(test.input))
|
52 |
+
assert.Nil(t, err, "should not error")
|
53 |
+
assert.Equal(t, test.expectedOutput, result)
|
54 |
+
})
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
func resolveParams(input interface{}) graphql.ResolveParams {
|
59 |
+
return graphql.ResolveParams{
|
60 |
+
Source: map[string]interface{}{
|
61 |
+
"myField": input,
|
62 |
+
},
|
63 |
+
Info: graphql.ResolveInfo{FieldName: "myField"},
|
64 |
+
}
|
65 |
+
}
|
66 |
+
|
67 |
+
func TestNumberFieldNotPresent(t *testing.T) {
|
68 |
+
// shouldn't return anything, but also not error. This can otherwise lead to
|
69 |
+
// odd behavior when no entries are present, yet we asked for int props and
|
70 |
+
// type, see https://github.com/weaviate/weaviate/issues/775
|
71 |
+
params := graphql.ResolveParams{
|
72 |
+
Source: map[string]interface{}{},
|
73 |
+
Info: graphql.ResolveInfo{FieldName: "myField"},
|
74 |
+
}
|
75 |
+
|
76 |
+
result, err := JSONNumberResolver(params)
|
77 |
+
assert.Nil(t, err)
|
78 |
+
assert.Equal(t, nil, result)
|
79 |
+
}
|
adapters/handlers/graphql/descriptions/aggregate.go
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// AGGREGATE
|
16 |
+
const (
|
17 |
+
AggregateProperty = "Aggregate this property"
|
18 |
+
AggregateObjects = "Aggregate Objects on a local Weaviate"
|
19 |
+
)
|
20 |
+
|
21 |
+
const GroupBy = "Specify which properties to group by"
|
22 |
+
|
23 |
+
const (
|
24 |
+
AggregatePropertyObject = "An object containing Aggregation information about this property"
|
25 |
+
)
|
26 |
+
|
27 |
+
const AggregateObjectsObj = "An object allowing Aggregation of %ss on a local Weaviate"
|
28 |
+
|
29 |
+
const (
|
30 |
+
AggregateMean = "Aggregate on the mean of numeric property values"
|
31 |
+
AggregateSum = "Aggregate on the sum of numeric property values"
|
32 |
+
AggregateMedian = "Aggregate on the median of numeric property values"
|
33 |
+
AggregateMode = "Aggregate on the mode of numeric property values"
|
34 |
+
AggregateMin = "Aggregate on the minimum of numeric property values"
|
35 |
+
AggregateMax = "Aggregate on the maximum of numeric property values"
|
36 |
+
AggregateCount = "Aggregate on the total amount of found property values"
|
37 |
+
AggregateGroupedBy = "Indicates the group of returned data"
|
38 |
+
)
|
39 |
+
|
40 |
+
const AggregateNumericObj = "An object containing the %s of numeric properties"
|
41 |
+
|
42 |
+
const AggregateCountObj = "An object containing countable properties"
|
43 |
+
|
44 |
+
const AggregateGroupedByObj = "An object containing the path and value of the grouped property"
|
45 |
+
|
46 |
+
const (
|
47 |
+
AggregateGroupedByGroupedByPath = "The path of the grouped property"
|
48 |
+
AggregateGroupedByGroupedByValue = "The value of the grouped property"
|
49 |
+
)
|
50 |
+
|
51 |
+
// NETWORK
|
52 |
+
const NetworkAggregateWeaviateObj = "An object containing Get Objects fields for network Weaviate instance: "
|
53 |
+
|
54 |
+
const NetworkAggregate = "Perform Aggregation of Objects"
|
55 |
+
|
56 |
+
const (
|
57 |
+
NetworkAggregateObj = "An object allowing Aggregation of Objects"
|
58 |
+
NetworkAggregatePropertyObject = "An object containing Aggregation information about this property"
|
59 |
+
)
|
60 |
+
|
61 |
+
const NetworkAggregateThingsActionsObj = "An object allowing Aggregation of %ss on a network Weaviate"
|
62 |
+
|
63 |
+
const (
|
64 |
+
NetworkAggregateMean = "Aggregate on the mean of numeric property values"
|
65 |
+
NetworkAggregateSum = "Aggregate on the sum of numeric property values"
|
66 |
+
NetworkAggregateMedian = "Aggregate on the median of numeric property values"
|
67 |
+
NetworkAggregateMode = "Aggregate on the mode of numeric property values"
|
68 |
+
NetworkAggregateMin = "Aggregate on the minimum of numeric property values"
|
69 |
+
NetworkAggregateMax = "Aggregate on the maximum of numeric property values"
|
70 |
+
NetworkAggregateCount = "Aggregate on the total amount of found property values"
|
71 |
+
NetworkAggregateGroupedBy = "Indicates the group of returned data"
|
72 |
+
)
|
73 |
+
|
74 |
+
const NetworkAggregateNumericObj = "An object containing the %s of numeric properties"
|
75 |
+
|
76 |
+
const NetworkAggregateCountObj = "An object containing countable properties"
|
77 |
+
|
78 |
+
const NetworkAggregateGroupedByObj = "An object containing the path and value of the grouped property"
|
79 |
+
|
80 |
+
const (
|
81 |
+
NetworkAggregateGroupedByGroupedByPath = "The path of the grouped property"
|
82 |
+
NetworkAggregateGroupedByGroupedByValue = "The value of the grouped property"
|
83 |
+
)
|
adapters/handlers/graphql/descriptions/explore.go
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
const (
|
16 |
+
LocalExplore = "Explore Concepts on a local weaviate with vector-aided search"
|
17 |
+
LocalExploreConcepts = "Explore Concepts on a local weaviate with vector-aided serach through keyword-based search terms"
|
18 |
+
VectorMovement = "Move your search term closer to or further away from another vector described by keywords"
|
19 |
+
Keywords = "Keywords are a list of search terms. Array type, e.g. [\"keyword 1\", \"keyword 2\"]"
|
20 |
+
Network = "Set to true, if the exploration should include remote peers"
|
21 |
+
Limit = "Limit the results set (usually fewer results mean faster queries)"
|
22 |
+
Offset = "Offset of the results set (usually fewer results mean faster queries)"
|
23 |
+
Certainty = "Normalized Distance between the result item and the search vector. Normalized to be between 0 (identical vectors) and 1 (perfect opposite)."
|
24 |
+
Distance = "The required degree of similarity between an object's characteristics and the provided filter values"
|
25 |
+
Vector = "Target vector to be used in kNN search"
|
26 |
+
Force = "The force to apply for a particular movements. Must be between 0 and 1 where 0 is equivalent to no movement and 1 is equivalent to largest movement possible"
|
27 |
+
ClassName = "Name of the Class"
|
28 |
+
ID = "Concept identifier in the uuid format"
|
29 |
+
Beacon = "Concept identifier in the beacon format, such as weaviate://<hostname>/<kind>/id"
|
30 |
+
)
|
adapters/handlers/graphql/descriptions/fetch.go
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// Local
|
16 |
+
const (
|
17 |
+
LocalFetch = "Fetch Beacons that are similar to a specified concept from the Objects subsets on a Weaviate network"
|
18 |
+
LocalFetchObj = "An object used to perform a Fuzzy Fetch to search for Objects and Actions similar to a specified concept on a Weaviate network"
|
19 |
+
)
|
20 |
+
|
21 |
+
const (
|
22 |
+
LocalFetchObjects = "Perform a Fuzzy Fetch to Fetch Beacons similar to a specified concept on a Weaviate network from the Objects subset"
|
23 |
+
LocalFetchFuzzy = "Perform a Fuzzy Fetch to Fetch Beacons similar to a specified concept on a Weaviate network from both the Objects subsets"
|
24 |
+
)
|
25 |
+
|
26 |
+
const (
|
27 |
+
LocalFetchBeacon = "A Beacon result from a local Weaviate Local Fetch query"
|
28 |
+
LocalFetchClassName = "The class name of the result from a local Weaviate Local Fetch query"
|
29 |
+
LocalFetchCertainty = "The degree of similarity on a scale of 0-1 between the Beacon's characteristics and the provided concept"
|
30 |
+
LocalFetchActionsObj = "An object used to Fetch Beacons from the Actions subset of the dataset"
|
31 |
+
)
|
32 |
+
|
33 |
+
const (
|
34 |
+
LocalFetchFuzzyBeacon = "A Beacon result from a local Weaviate Fetch Fuzzy query from both the Objects subsets"
|
35 |
+
LocalFetchFuzzyClassName = "Class name of the result from a local Weaviate Fetch Fuzzy query from both the Objects subsets"
|
36 |
+
LocalFetchFuzzyCertainty = "The degree of similarity on a scale of 0-1 between the Beacon's characteristics and the provided concept"
|
37 |
+
LocalFetchFuzzyObj = "An object used to Fetch Beacons from both the Objects subsets"
|
38 |
+
)
|
39 |
+
|
40 |
+
// NETWORK
|
41 |
+
const (
|
42 |
+
NetworkFetch = "Fetch Beacons that are similar to a specified concept from the Objects subsets on a Weaviate network"
|
43 |
+
NetworkFetchObj = "An object used to perform a Fuzzy Fetch to search for Objects similar to a specified concept on a Weaviate network"
|
44 |
+
)
|
45 |
+
|
46 |
+
const (
|
47 |
+
NetworkFetchFuzzy = "Perform a Fuzzy Fetch to Fetch Beacons similar to a specified concept on a Weaviate network from both the Objects subsets"
|
48 |
+
)
|
49 |
+
|
50 |
+
const (
|
51 |
+
NetworkFetchFuzzyClassName = "The class name of the result from a network Weaviate Fetch Fuzzy query from both the Objects subsets"
|
52 |
+
NetworkFetchFuzzyBeacon = "A Beacon result from a network Weaviate Fetch Fuzzy query from both the Objects subsets"
|
53 |
+
NetworkFetchFuzzyCertainty = "The degree of similarity on a scale of 0-1 between the Beacon's characteristics and the provided concept"
|
54 |
+
NetworkFetchFuzzyObj = "An object used to Fetch Beacons from both the Objects subsets"
|
55 |
+
)
|
adapters/handlers/graphql/descriptions/filters.go
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// Where filter elements
|
16 |
+
const (
|
17 |
+
GetWhere = "Filter options for a local Get query, used to convert the result to the specified filters"
|
18 |
+
GetWhereInpObj = "An object containing filter options for a local Get query, used to convert the result to the specified filters"
|
19 |
+
)
|
20 |
+
|
21 |
+
const (
|
22 |
+
LocalMetaWhere = "Filter options for a local Meta query, used to convert the result to the specified filters"
|
23 |
+
LocalMetaWhereInpObj = "An object containing filter options for a local Meta query, used to convert the result to the specified filters"
|
24 |
+
)
|
25 |
+
|
26 |
+
const (
|
27 |
+
AggregateWhere = "Filter options for a local Aggregate query, used to convert the result to the specified filters"
|
28 |
+
AggregateWhereInpObj = "An object containing filter options for a local Aggregate query, used to convert the result to the specified filters"
|
29 |
+
)
|
30 |
+
|
31 |
+
const (
|
32 |
+
NetworkGetWhere = "Filter options for a network Get query, used to convert the result to the specified filters"
|
33 |
+
NetworkGetWhereInpObj = "An object containing filter options for a network Get query, used to convert the result to the specified filters"
|
34 |
+
)
|
35 |
+
|
36 |
+
const (
|
37 |
+
NetworkMetaWhere = "Filter options for a network Meta query, used to convert the result to the specified filters"
|
38 |
+
NetworkMetaWhereInpObj = "An object containing filter options for a network Meta query, used to convert the result to the specified filters"
|
39 |
+
)
|
40 |
+
|
41 |
+
const (
|
42 |
+
NetworkAggregateWhere = "Filter options for a network Aggregate query, used to convert the result to the specified filters"
|
43 |
+
NetworkAggregateWhereInpObj = "An object containing filter options for a network Aggregate query, used to convert the result to the specified filters"
|
44 |
+
)
|
45 |
+
|
46 |
+
const (
|
47 |
+
WhereOperands = "Contains the Operands that can be applied to a 'where' filter"
|
48 |
+
WhereOperandsInpObj = "An object containing the Operands that can be applied to a 'where' filter"
|
49 |
+
)
|
50 |
+
|
51 |
+
const (
|
52 |
+
WhereOperator = "Contains the Operators that can be applied to a 'where' filter"
|
53 |
+
WhereOperatorEnum = "An object containing the Operators that can be applied to a 'where' filter"
|
54 |
+
)
|
55 |
+
|
56 |
+
const WherePath = "Specify the path from the Objects fields to the property name (e.g. ['Things', 'City', 'population'] leads to the 'population' property of a 'City' object)"
|
57 |
+
|
58 |
+
const (
|
59 |
+
WhereValueInt = "Specify an Integer value that the target property will be compared to"
|
60 |
+
WhereValueNumber = "Specify a Float value that the target property will be compared to"
|
61 |
+
WhereValueBoolean = "Specify a Boolean value that the target property will be compared to"
|
62 |
+
WhereValueString = "Specify a String value that the target property will be compared to"
|
63 |
+
WhereValueRange = "Specify both geo-coordinates (latitude and longitude as decimals) and a maximum distance from the described coordinates. The search will return any result which is located less than or equal to the specified maximum distance in km away from the specified point."
|
64 |
+
WhereValueRangeGeoCoordinates = "The geoCoordinates that form the center point of the search."
|
65 |
+
WhereValueRangeGeoCoordinatesLatitude = "The latitude (in decimal format) of the geoCoordinates to search around."
|
66 |
+
WhereValueRangeGeoCoordinatesLongitude = "The longitude (in decimal format) of the geoCoordinates to search around."
|
67 |
+
WhereValueRangeDistance = "The distance from the point specified via geoCoordinates."
|
68 |
+
WhereValueRangeDistanceMax = "The maximum distance from the point specified geoCoordinates."
|
69 |
+
WhereValueText = "Specify a Text value that the target property will be compared to"
|
70 |
+
WhereValueDate = "Specify a Date value that the target property will be compared to"
|
71 |
+
)
|
72 |
+
|
73 |
+
// Properties and Classes filter elements (used by Fetch and Introspect Where filters)
|
74 |
+
const (
|
75 |
+
WhereProperties = "Specify which properties to filter on"
|
76 |
+
WherePropertiesObj = "Specify which properties to filter on"
|
77 |
+
)
|
78 |
+
|
79 |
+
const (
|
80 |
+
WherePropertiesPropertyName = "Specify which property name to filter properties on"
|
81 |
+
WhereCertainty = "Specify the required degree of similarity between an object's characteristics and the provided filter values on a scale of 0-1"
|
82 |
+
WhereName = "Specify the name of the property to filter on"
|
83 |
+
)
|
84 |
+
|
85 |
+
const (
|
86 |
+
WhereKeywords = "Specify which keywords to filter on"
|
87 |
+
WhereKeywordsInpObj = "Specify the value and the weight of a keyword"
|
88 |
+
)
|
89 |
+
|
90 |
+
const (
|
91 |
+
WhereKeywordsValue = "Specify the value of the keyword"
|
92 |
+
WhereKeywordsWeight = "Specify the weight of the keyword"
|
93 |
+
)
|
94 |
+
|
95 |
+
const (
|
96 |
+
WhereClass = "Specify which classes to filter on"
|
97 |
+
WhereInpObj = "Specify which classes and properties to filter on"
|
98 |
+
)
|
99 |
+
|
100 |
+
// Unique Fetch filter elements
|
101 |
+
const (
|
102 |
+
FetchWhereFilterFields = "An object containing filter options for a network Fetch search, used to convert the result to the specified filters"
|
103 |
+
FetchWhereFilterFieldsInpObj = "Filter options for a network Fetch search, used to convert the result to the specified filters"
|
104 |
+
)
|
105 |
+
|
106 |
+
const (
|
107 |
+
FetchFuzzyValue = "Specify the concept that will be used to fetch Objects on the network (e.g. 'Airplane', or 'City')"
|
108 |
+
FetchFuzzyCertainty = "Specify how much a Beacon's characteristics must match the provided concept on a scale of 0 to 1"
|
109 |
+
)
|
110 |
+
|
111 |
+
// Unique Introspect filter elements
|
112 |
+
const (
|
113 |
+
IntrospectWhereFilterFields = "An object containing filter options for a network Fetch search, used to convert the result to the specified filters"
|
114 |
+
IntrospectWhereFilterFieldsInpObj = "Filter options for a network Fetch search, used to convert the result to the specified filters"
|
115 |
+
IntrospectBeaconId = "The id of the Beacon"
|
116 |
+
)
|
117 |
+
|
118 |
+
// GroupBy filter elements
|
119 |
+
const (
|
120 |
+
GroupByGroup = "Specify the property of the class to group by"
|
121 |
+
GroupByCount = "Get the number of instances of a property in a group"
|
122 |
+
GroupBySum = "Get the sum of the values of a property in a group"
|
123 |
+
GroupByMin = "Get the minimum occurring value of a property in a group"
|
124 |
+
GroupByMax = "Get the maximum occurring value of a property in a group"
|
125 |
+
GroupByMean = "Get the mean value of a property in a group"
|
126 |
+
GroupByMedian = "Get the median of a property in a group"
|
127 |
+
GroupByMode = "Get the mode of a property in a group"
|
128 |
+
)
|
129 |
+
|
130 |
+
// Request timeout filter elements
|
131 |
+
const NetworkTimeout = "Specify the time in seconds after which an unresolved request automatically fails"
|
132 |
+
|
133 |
+
// Pagination filter elements
|
134 |
+
const (
|
135 |
+
First = "Show the first x results (pagination option)"
|
136 |
+
After = "Show the results after the first x results (pagination option)"
|
137 |
+
)
|
138 |
+
|
139 |
+
// Cursor API
|
140 |
+
const (
|
141 |
+
AfterID = "Show the results after a given ID"
|
142 |
+
)
|
143 |
+
|
144 |
+
const (
|
145 |
+
SortPath = "Specify the path from the Objects fields to the property name (e.g. ['Get', 'City', 'population'] leads to the 'population' property of a 'City' object)"
|
146 |
+
SortOrder = "Specify the sort order, either ascending (asc) which is default or descending (desc)"
|
147 |
+
)
|
148 |
+
|
149 |
+
const (
|
150 |
+
GroupByFilter = "Specify the property of the class to group by"
|
151 |
+
GroupByPath = "Specify the path from the objects fields to the property name (e.g. ['Things', 'City', 'population'] leads to the 'population' property of a 'City' object)"
|
152 |
+
GroupByGroups = "Specify the number of groups to be created"
|
153 |
+
GroupByObjectsPerGroup = "Specify the number of max objects in group"
|
154 |
+
)
|
adapters/handlers/graphql/descriptions/get.go
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// Local
|
16 |
+
const (
|
17 |
+
GetObjects = "Get Objects on a local Weaviate"
|
18 |
+
)
|
19 |
+
|
20 |
+
const (
|
21 |
+
GetObj = "An object used to Get Objects on a local Weaviate"
|
22 |
+
Get = "Get Objects on a local Weaviate"
|
23 |
+
)
|
24 |
+
|
25 |
+
const GetObjectsActionsObj = "An object used to get %ss on a local Weaviate"
|
26 |
+
|
27 |
+
const GetClassUUID = "The UUID of a Object, assigned by its local Weaviate"
|
28 |
+
|
29 |
+
// Network
|
30 |
+
const (
|
31 |
+
NetworkGet = "Get Objects from a Weaviate in a network"
|
32 |
+
NetworkGetObj = "An object used to Get Objects from a Weaviate in a network"
|
33 |
+
)
|
34 |
+
|
35 |
+
const NetworkGetWeaviateObj = "An object containing Get Objects fields for network Weaviate instance: "
|
36 |
+
|
37 |
+
const (
|
38 |
+
NetworkGetObjects = "Get Objects from a Weaviate in a network"
|
39 |
+
)
|
40 |
+
|
41 |
+
const (
|
42 |
+
NetworkGetObjectsObj = "An object containing the Objects objects on this network Weaviate instance."
|
43 |
+
)
|
44 |
+
|
45 |
+
const NetworkGetClassUUID = "The UUID of a Object, assigned by the Weaviate network" // TODO check this with @lauraham
|
46 |
+
|
47 |
+
const ConsistencyLevel = "Determines how many replicas must acknowledge a request " +
|
48 |
+
"before it is considered successful. Can be 'ONE', 'QUORUM', or 'ALL'"
|
49 |
+
|
50 |
+
const Tenant = "The value by which a tenant is identified, specified in the class schema"
|
adapters/handlers/graphql/descriptions/getMeta.go
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// Local
|
16 |
+
const (
|
17 |
+
LocalMetaObj = "An object used to Get Meta information about Objects on a local Weaviate"
|
18 |
+
LocalMeta = "Get Meta information about Objects on a local Weaviate"
|
19 |
+
)
|
20 |
+
|
21 |
+
const (
|
22 |
+
MetaPropertyType = "The datatype of this property"
|
23 |
+
MetaPropertyCount = "The total amount of found instances for this property" // TODO check this with @lauraham
|
24 |
+
MetaPropertyTopOccurrences = "An object containing data about the most frequently occurring values for this property"
|
25 |
+
MetaPropertyTopOccurrencesValue = "The most frequently occurring value for this property"
|
26 |
+
MetaPropertyTopOccurrencesOccurs = "How often the most frequently occurring value for this property occurs" // TODO check this with @lauraham
|
27 |
+
MetaPropertyMinimum = "The minimum value for this property"
|
28 |
+
MetaPropertyMaximum = "The maximum value for this property"
|
29 |
+
MetaPropertyMean = "The mean of all values for this property"
|
30 |
+
MetaPropertySum = "The sum of all values for this property"
|
31 |
+
MetaPropertyObject = "An object containing meta information about this property"
|
32 |
+
)
|
33 |
+
|
34 |
+
const (
|
35 |
+
AggregatePropertyType = "The datatype of this property"
|
36 |
+
AggregatePropertyCount = "The total amount of found instances for this property" // TODO check this with @lauraham
|
37 |
+
AggregatePropertyTopOccurrences = "An object containing data about the most frequently occurring values for this property"
|
38 |
+
AggregatePropertyTopOccurrencesValue = "The most frequently occurring value for this property"
|
39 |
+
AggregatePropertyTopOccurrencesOccurs = "How often the most frequently occurring value for this property occurs" // TODO check this with @lauraham
|
40 |
+
AggregatePropertyMinimum = "The minimum value for this property"
|
41 |
+
AggregatePropertyMaximum = "The maximum value for this property"
|
42 |
+
AggregatePropertyMean = "The mean of all values for this property"
|
43 |
+
AggregatePropertySum = "The sum of all values for this property"
|
44 |
+
)
|
45 |
+
|
46 |
+
// Network
|
47 |
+
const (
|
48 |
+
NetworkMeta = "Get meta information about Objects from a Weaviate in a network"
|
49 |
+
NetworkMetaObj = "An object used to Get meta information about Objects from a Weaviate in a network"
|
50 |
+
NetworkMetaWeaviateObj = "An object containing the Meta Objects fields for network Weaviate instance: "
|
51 |
+
)
|
52 |
+
|
53 |
+
const (
|
54 |
+
MetaMetaProperty = "Meta information about the object"
|
55 |
+
MetaProperty = "Meta information about the property "
|
56 |
+
)
|
57 |
+
|
58 |
+
const (
|
59 |
+
MetaClassPropertyTotalTrue = "How often this boolean property's value is true in the dataset"
|
60 |
+
MetaClassPropertyPercentageTrue = "The percentage of true values for this boolean property in the dataset"
|
61 |
+
)
|
62 |
+
|
63 |
+
const (
|
64 |
+
MetaClassPropertyTotalFalse = "How often this boolean property's value is false in the dataset"
|
65 |
+
MetaClassPropertyPercentageFalse = "The percentage of false values for this boolean property in the dataset"
|
66 |
+
)
|
67 |
+
|
68 |
+
const (
|
69 |
+
MetaClassPropertyPointingTo = "The classes that this object contains a reference to"
|
70 |
+
MetaClassMetaCount = "The total amount of found instances for a class"
|
71 |
+
MetaClassMetaObj = "An object containing Meta information about a class"
|
72 |
+
)
|
73 |
+
|
74 |
+
const (
|
75 |
+
AggregateClassPropertyTotalTrue = "How often this boolean property's value is true in the dataset"
|
76 |
+
AggregateClassPropertyPercentageTrue = "The percentage of true values for this boolean property in the dataset"
|
77 |
+
)
|
78 |
+
|
79 |
+
const (
|
80 |
+
AggregateClassPropertyTotalFalse = "How often this boolean property's value is false in the dataset"
|
81 |
+
AggregateClassPropertyPercentageFalse = "The percentage of false values for this boolean property in the dataset"
|
82 |
+
)
|
83 |
+
|
84 |
+
const (
|
85 |
+
AggregateClassPropertyPointingTo = "The classes that this object contains a reference to"
|
86 |
+
AggregateClassAggregateCount = "The total amount of found instances for a class"
|
87 |
+
AggregateClassAggregateObj = "An object containing Aggregate information about a class"
|
88 |
+
)
|
adapters/handlers/graphql/descriptions/introspect.go
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// NETWORK
|
16 |
+
const (
|
17 |
+
NetworkIntrospect = "Get Introspection information about Objects and/or Beacons in a Weaviate network"
|
18 |
+
NetworkIntrospectObj = "An object used to perform an Introspection query on a Weaviate network"
|
19 |
+
)
|
20 |
+
|
21 |
+
const (
|
22 |
+
NetworkIntrospectWeaviate = "The Weaviate instance that the current Object or Beacon belongs to"
|
23 |
+
NetworkIntrospectClassName = "The name of the current Object or Beacon's class"
|
24 |
+
NetworkIntrospectCertainty = "The degree of similarity between a(n) Object or Beacon and the filter input"
|
25 |
+
)
|
26 |
+
|
27 |
+
const (
|
28 |
+
NetworkIntrospectBeaconProperties = "The properties of a Beacon"
|
29 |
+
NetworkIntrospectBeaconPropertiesPropertyName = "The names of the properties of a Beacon"
|
30 |
+
)
|
adapters/handlers/graphql/descriptions/merge.go
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// Local
|
16 |
+
const (
|
17 |
+
LocalMergeObj = "An object used to Merge Objects on a local Weaviate"
|
18 |
+
LocalMerge = "Merge Objects on a local Weaviate"
|
19 |
+
)
|
20 |
+
|
21 |
+
const LocalMergeClassUUID = "The UUID of a Object, assigned by its local Weaviate"
|
22 |
+
|
23 |
+
// Network
|
24 |
+
const (
|
25 |
+
NetworkMerge = "Merge Objects from a Weaviate in a network"
|
26 |
+
NetworkMergeObj = "An object used to Merge Objects from a Weaviate in a network"
|
27 |
+
)
|
28 |
+
|
29 |
+
const NetworkMergeWeaviateObj = "An object containing Merge Objects fields for network Weaviate instance: "
|
30 |
+
|
31 |
+
const NetworkMergeClassUUID = "The UUID of a Thing or Action, assigned by the Weaviate network" // TODO check this with @lauraham
|
adapters/handlers/graphql/descriptions/rootQuery.go
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Package descriptions provides the descriptions as used by the graphql endpoint for Weaviate
|
13 |
+
package descriptions
|
14 |
+
|
15 |
+
// ROOT
|
16 |
+
const (
|
17 |
+
WeaviateObj = "The location of the root query"
|
18 |
+
WeaviateNetwork = "Query a Weaviate network"
|
19 |
+
)
|
20 |
+
|
21 |
+
// LOCAL
|
22 |
+
const LocalObj = "A query on a local Weaviate"
|
23 |
+
|
24 |
+
// NETWORK
|
25 |
+
const (
|
26 |
+
NetworkWeaviate = "An object for the network Weaviate instance: "
|
27 |
+
NetworkObj = "An object used to perform queries on a Weaviate network"
|
28 |
+
)
|
adapters/handlers/graphql/graphiql/graphiql.go
ADDED
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
// Based on `graphiql.go` from https://github.com/graphql-go/handler
|
13 |
+
// only made RenderGraphiQL a public function.
|
14 |
+
package graphiql
|
15 |
+
|
16 |
+
import (
|
17 |
+
"encoding/json"
|
18 |
+
"html/template"
|
19 |
+
"net/http"
|
20 |
+
"strings"
|
21 |
+
)
|
22 |
+
|
23 |
+
// graphiqlVersion is the current version of GraphiQL
|
24 |
+
const graphiqlVersion = "0.11.11"
|
25 |
+
|
26 |
+
// graphiqlData is the page data structure of the rendered GraphiQL page
|
27 |
+
type graphiqlData struct {
|
28 |
+
GraphiqlVersion string
|
29 |
+
QueryString string
|
30 |
+
Variables string
|
31 |
+
OperationName string
|
32 |
+
AuthKey string
|
33 |
+
AuthToken string
|
34 |
+
}
|
35 |
+
|
36 |
+
func AddMiddleware(next http.Handler) http.Handler {
|
37 |
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
38 |
+
if strings.HasPrefix(r.URL.Path, "/v1/graphql") && r.Method == http.MethodGet {
|
39 |
+
renderGraphiQL(w, r)
|
40 |
+
} else {
|
41 |
+
next.ServeHTTP(w, r)
|
42 |
+
}
|
43 |
+
})
|
44 |
+
}
|
45 |
+
|
46 |
+
// renderGraphiQL renders the GraphiQL GUI
|
47 |
+
func renderGraphiQL(w http.ResponseWriter, r *http.Request) {
|
48 |
+
w.Header().Set("WWW-Authenticate", `Basic realm="Provide your key and token (as username as password respectively)"`)
|
49 |
+
|
50 |
+
user, password, authOk := r.BasicAuth()
|
51 |
+
if !authOk {
|
52 |
+
http.Error(w, "Not authorized", 401)
|
53 |
+
return
|
54 |
+
}
|
55 |
+
|
56 |
+
queryParams := r.URL.Query()
|
57 |
+
|
58 |
+
t := template.New("GraphiQL")
|
59 |
+
t, err := t.Parse(graphiqlTemplate)
|
60 |
+
if err != nil {
|
61 |
+
http.Error(w, err.Error(), http.StatusInternalServerError)
|
62 |
+
return
|
63 |
+
}
|
64 |
+
|
65 |
+
// Attempt to deserialize the 'variables' query key to something reasonable.
|
66 |
+
var queryVars interface{}
|
67 |
+
err = json.Unmarshal([]byte(queryParams.Get("variables")), &queryVars)
|
68 |
+
|
69 |
+
var varsString string
|
70 |
+
if err == nil {
|
71 |
+
vars, err := json.MarshalIndent(queryVars, "", " ")
|
72 |
+
if err != nil {
|
73 |
+
http.Error(w, err.Error(), http.StatusInternalServerError)
|
74 |
+
return
|
75 |
+
}
|
76 |
+
varsString = string(vars)
|
77 |
+
if varsString == "null" {
|
78 |
+
varsString = ""
|
79 |
+
}
|
80 |
+
}
|
81 |
+
|
82 |
+
// Create result string
|
83 |
+
d := graphiqlData{
|
84 |
+
GraphiqlVersion: graphiqlVersion,
|
85 |
+
QueryString: queryParams.Get("query"),
|
86 |
+
Variables: varsString,
|
87 |
+
OperationName: queryParams.Get("operationName"),
|
88 |
+
AuthKey: user,
|
89 |
+
AuthToken: password,
|
90 |
+
}
|
91 |
+
err = t.ExecuteTemplate(w, "index", d)
|
92 |
+
if err != nil {
|
93 |
+
http.Error(w, err.Error(), http.StatusInternalServerError)
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
// tmpl is the page template to render GraphiQL
|
98 |
+
const graphiqlTemplate = `
|
99 |
+
{{ define "index" }}
|
100 |
+
<!--
|
101 |
+
The request to this GraphQL server provided the header "Accept: text/html"
|
102 |
+
and as a result has been presented GraphiQL - an in-browser IDE for
|
103 |
+
exploring GraphQL.
|
104 |
+
|
105 |
+
If you wish to receive JSON, provide the header "Accept: application/json" or
|
106 |
+
add "&raw" to the end of the URL within a browser.
|
107 |
+
-->
|
108 |
+
<!DOCTYPE html>
|
109 |
+
<html>
|
110 |
+
<head>
|
111 |
+
<meta charset="utf-8" />
|
112 |
+
<title>GraphiQL</title>
|
113 |
+
<meta name="robots" content="noindex" />
|
114 |
+
<meta name="referrer" content="origin">
|
115 |
+
<style>
|
116 |
+
body {
|
117 |
+
height: 100%;
|
118 |
+
margin: 0;
|
119 |
+
overflow: hidden;
|
120 |
+
width: 100%;
|
121 |
+
}
|
122 |
+
#graphiql {
|
123 |
+
height: 100vh;
|
124 |
+
}
|
125 |
+
</style>
|
126 |
+
<link href="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.css" rel="stylesheet" />
|
127 |
+
<script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
|
128 |
+
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
|
129 |
+
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
|
130 |
+
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
|
131 |
+
<script src="//cdn.jsdelivr.net/npm/graphiql@{{ .GraphiqlVersion }}/graphiql.min.js"></script>
|
132 |
+
</head>
|
133 |
+
<body>
|
134 |
+
<div id="graphiql">Loading...</div>
|
135 |
+
<script>
|
136 |
+
// Collect the URL parameters
|
137 |
+
var parameters = {};
|
138 |
+
window.location.search.substr(1).split('&').forEach(function (entry) {
|
139 |
+
var eq = entry.indexOf('=');
|
140 |
+
if (eq >= 0) {
|
141 |
+
parameters[decodeURIComponent(entry.slice(0, eq))] =
|
142 |
+
decodeURIComponent(entry.slice(eq + 1));
|
143 |
+
}
|
144 |
+
});
|
145 |
+
|
146 |
+
// Produce a Location query string from a parameter object.
|
147 |
+
function locationQuery(params) {
|
148 |
+
return '?' + Object.keys(params).filter(function (key) {
|
149 |
+
return Boolean(params[key]);
|
150 |
+
}).map(function (key) {
|
151 |
+
return encodeURIComponent(key) + '=' +
|
152 |
+
encodeURIComponent(params[key]);
|
153 |
+
}).join('&');
|
154 |
+
}
|
155 |
+
|
156 |
+
// Derive a fetch URL from the current URL, sans the GraphQL parameters.
|
157 |
+
var graphqlParamNames = {
|
158 |
+
query: true,
|
159 |
+
variables: true,
|
160 |
+
operationName: true
|
161 |
+
};
|
162 |
+
|
163 |
+
var otherParams = {};
|
164 |
+
for (var k in parameters) {
|
165 |
+
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) {
|
166 |
+
otherParams[k] = parameters[k];
|
167 |
+
}
|
168 |
+
}
|
169 |
+
var fetchURL = locationQuery(otherParams);
|
170 |
+
|
171 |
+
// Defines a GraphQL fetcher using the fetch API.
|
172 |
+
function graphQLFetcher(graphQLParams) {
|
173 |
+
return fetch(fetchURL, {
|
174 |
+
method: 'post',
|
175 |
+
headers: {
|
176 |
+
'Accept': 'application/json',
|
177 |
+
'Content-Type': 'application/json',
|
178 |
+
'X-API-KEY': {{ .AuthKey }},
|
179 |
+
'X-API-TOKEN': {{ .AuthToken }}
|
180 |
+
},
|
181 |
+
body: JSON.stringify(graphQLParams),
|
182 |
+
credentials: 'include',
|
183 |
+
}).then(function (response) {
|
184 |
+
return response.text();
|
185 |
+
}).then(function (responseBody) {
|
186 |
+
try {
|
187 |
+
return JSON.parse(responseBody);
|
188 |
+
} catch (error) {
|
189 |
+
return responseBody;
|
190 |
+
}
|
191 |
+
});
|
192 |
+
}
|
193 |
+
|
194 |
+
// When the query and variables string is edited, update the URL bar so
|
195 |
+
// that it can be easily shared.
|
196 |
+
function onEditQuery(newQuery) {
|
197 |
+
parameters.query = newQuery;
|
198 |
+
updateURL();
|
199 |
+
}
|
200 |
+
|
201 |
+
function onEditVariables(newVariables) {
|
202 |
+
parameters.variables = newVariables;
|
203 |
+
updateURL();
|
204 |
+
}
|
205 |
+
|
206 |
+
function onEditOperationName(newOperationName) {
|
207 |
+
parameters.operationName = newOperationName;
|
208 |
+
updateURL();
|
209 |
+
}
|
210 |
+
|
211 |
+
function updateURL() {
|
212 |
+
history.replaceState(null, null, locationQuery(parameters));
|
213 |
+
}
|
214 |
+
|
215 |
+
// Render <GraphiQL /> into the body.
|
216 |
+
ReactDOM.render(
|
217 |
+
React.createElement(GraphiQL, {
|
218 |
+
fetcher: graphQLFetcher,
|
219 |
+
onEditQuery: onEditQuery,
|
220 |
+
onEditVariables: onEditVariables,
|
221 |
+
onEditOperationName: onEditOperationName,
|
222 |
+
query: {{ .QueryString }},
|
223 |
+
response: null,
|
224 |
+
variables: {{ .Variables }},
|
225 |
+
operationName: {{ .OperationName }},
|
226 |
+
}),
|
227 |
+
document.getElementById('graphiql')
|
228 |
+
);
|
229 |
+
</script>
|
230 |
+
</body>
|
231 |
+
</html>
|
232 |
+
{{ end }}
|
233 |
+
`
|
adapters/handlers/graphql/local/aggregate/aggregate.go
ADDED
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package aggregate
|
13 |
+
|
14 |
+
import (
|
15 |
+
"fmt"
|
16 |
+
|
17 |
+
"github.com/tailor-inc/graphql"
|
18 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/descriptions"
|
19 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/local/common_filters"
|
20 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/utils"
|
21 |
+
"github.com/weaviate/weaviate/entities/aggregation"
|
22 |
+
"github.com/weaviate/weaviate/entities/models"
|
23 |
+
"github.com/weaviate/weaviate/entities/schema"
|
24 |
+
"github.com/weaviate/weaviate/usecases/config"
|
25 |
+
)
|
26 |
+
|
27 |
+
type ModulesProvider interface {
|
28 |
+
AggregateArguments(class *models.Class) map[string]*graphql.ArgumentConfig
|
29 |
+
ExtractSearchParams(arguments map[string]interface{}, className string) map[string]interface{}
|
30 |
+
}
|
31 |
+
|
32 |
+
// Build the Aggregate Kinds schema
|
33 |
+
func Build(dbSchema *schema.Schema, config config.Config,
|
34 |
+
modulesProvider ModulesProvider,
|
35 |
+
) (*graphql.Field, error) {
|
36 |
+
if len(dbSchema.Objects.Classes) == 0 {
|
37 |
+
return nil, utils.ErrEmptySchema
|
38 |
+
}
|
39 |
+
|
40 |
+
var err error
|
41 |
+
var localAggregateObjects *graphql.Object
|
42 |
+
if len(dbSchema.Objects.Classes) > 0 {
|
43 |
+
localAggregateObjects, err = classFields(dbSchema.Objects.Classes, config, modulesProvider)
|
44 |
+
if err != nil {
|
45 |
+
return nil, err
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
field := graphql.Field{
|
50 |
+
Name: "Aggregate",
|
51 |
+
Description: descriptions.AggregateWhere,
|
52 |
+
Type: localAggregateObjects,
|
53 |
+
Resolve: passThroughResolver,
|
54 |
+
}
|
55 |
+
|
56 |
+
return &field, nil
|
57 |
+
}
|
58 |
+
|
59 |
+
func classFields(databaseSchema []*models.Class,
|
60 |
+
config config.Config, modulesProvider ModulesProvider,
|
61 |
+
) (*graphql.Object, error) {
|
62 |
+
fields := graphql.Fields{}
|
63 |
+
|
64 |
+
for _, class := range databaseSchema {
|
65 |
+
field, err := classField(class, class.Description, config, modulesProvider)
|
66 |
+
if err != nil {
|
67 |
+
return nil, err
|
68 |
+
}
|
69 |
+
|
70 |
+
fields[class.Class] = field
|
71 |
+
}
|
72 |
+
|
73 |
+
return graphql.NewObject(graphql.ObjectConfig{
|
74 |
+
Name: "AggregateObjectsObj",
|
75 |
+
Fields: fields,
|
76 |
+
Description: descriptions.AggregateObjectsObj,
|
77 |
+
}), nil
|
78 |
+
}
|
79 |
+
|
80 |
+
func classField(class *models.Class, description string,
|
81 |
+
config config.Config, modulesProvider ModulesProvider,
|
82 |
+
) (*graphql.Field, error) {
|
83 |
+
metaClassName := fmt.Sprintf("Aggregate%s", class.Class)
|
84 |
+
|
85 |
+
fields := graphql.ObjectConfig{
|
86 |
+
Name: metaClassName,
|
87 |
+
Fields: (graphql.FieldsThunk)(func() graphql.Fields {
|
88 |
+
fields, err := classPropertyFields(class)
|
89 |
+
if err != nil {
|
90 |
+
// we cannot return an error in this FieldsThunk and have to panic unfortunately
|
91 |
+
panic(fmt.Sprintf("Failed to assemble single Local Aggregate Class field: %s", err))
|
92 |
+
}
|
93 |
+
|
94 |
+
return fields
|
95 |
+
}),
|
96 |
+
Description: description,
|
97 |
+
}
|
98 |
+
|
99 |
+
fieldsObject := graphql.NewObject(fields)
|
100 |
+
fieldsField := &graphql.Field{
|
101 |
+
Type: graphql.NewList(fieldsObject),
|
102 |
+
Description: description,
|
103 |
+
Args: graphql.FieldConfigArgument{
|
104 |
+
"limit": &graphql.ArgumentConfig{
|
105 |
+
Description: descriptions.First,
|
106 |
+
Type: graphql.Int,
|
107 |
+
},
|
108 |
+
"where": &graphql.ArgumentConfig{
|
109 |
+
Description: descriptions.GetWhere,
|
110 |
+
Type: graphql.NewInputObject(
|
111 |
+
graphql.InputObjectConfig{
|
112 |
+
Name: fmt.Sprintf("AggregateObjects%sWhereInpObj", class.Class),
|
113 |
+
Fields: common_filters.BuildNew(fmt.Sprintf("AggregateObjects%s", class.Class)),
|
114 |
+
Description: descriptions.GetWhereInpObj,
|
115 |
+
},
|
116 |
+
),
|
117 |
+
},
|
118 |
+
"groupBy": &graphql.ArgumentConfig{
|
119 |
+
Description: descriptions.GroupBy,
|
120 |
+
Type: graphql.NewList(graphql.String),
|
121 |
+
},
|
122 |
+
"nearVector": nearVectorArgument(class.Class),
|
123 |
+
"nearObject": nearObjectArgument(class.Class),
|
124 |
+
"objectLimit": &graphql.ArgumentConfig{
|
125 |
+
Description: descriptions.First,
|
126 |
+
Type: graphql.Int,
|
127 |
+
},
|
128 |
+
"hybrid": hybridArgument(fieldsObject, class, modulesProvider),
|
129 |
+
},
|
130 |
+
Resolve: makeResolveClass(modulesProvider, class),
|
131 |
+
}
|
132 |
+
|
133 |
+
if modulesProvider != nil {
|
134 |
+
for name, argument := range modulesProvider.AggregateArguments(class) {
|
135 |
+
fieldsField.Args[name] = argument
|
136 |
+
}
|
137 |
+
}
|
138 |
+
|
139 |
+
if schema.MultiTenancyEnabled(class) {
|
140 |
+
fieldsField.Args["tenant"] = tenantArgument()
|
141 |
+
}
|
142 |
+
|
143 |
+
return fieldsField, nil
|
144 |
+
}
|
145 |
+
|
146 |
+
func classPropertyFields(class *models.Class) (graphql.Fields, error) {
|
147 |
+
fields := graphql.Fields{}
|
148 |
+
for _, property := range class.Properties {
|
149 |
+
propertyType, err := schema.GetPropertyDataType(class, property.Name)
|
150 |
+
if err != nil {
|
151 |
+
return nil, fmt.Errorf("%s.%s: %s", class.Class, property.Name, err)
|
152 |
+
}
|
153 |
+
|
154 |
+
convertedDataType, err := classPropertyField(*propertyType, class, property)
|
155 |
+
if err != nil {
|
156 |
+
return nil, err
|
157 |
+
}
|
158 |
+
|
159 |
+
fields[property.Name] = convertedDataType
|
160 |
+
}
|
161 |
+
|
162 |
+
// Special case: meta { count } appended to all regular props
|
163 |
+
fields["meta"] = &graphql.Field{
|
164 |
+
Description: descriptions.LocalMetaObj,
|
165 |
+
Type: metaObject(fmt.Sprintf("Aggregate%s", class.Class)),
|
166 |
+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
167 |
+
// pass-through
|
168 |
+
return p.Source, nil
|
169 |
+
},
|
170 |
+
}
|
171 |
+
|
172 |
+
// Always append Grouped By field
|
173 |
+
fields["groupedBy"] = &graphql.Field{
|
174 |
+
Description: descriptions.AggregateGroupedBy,
|
175 |
+
Type: groupedByProperty(class),
|
176 |
+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
177 |
+
switch typed := p.Source.(type) {
|
178 |
+
case aggregation.Group:
|
179 |
+
return typed.GroupedBy, nil
|
180 |
+
case map[string]interface{}:
|
181 |
+
return typed["groupedBy"], nil
|
182 |
+
default:
|
183 |
+
return nil, fmt.Errorf("groupedBy: unsupported type %T", p.Source)
|
184 |
+
}
|
185 |
+
},
|
186 |
+
}
|
187 |
+
|
188 |
+
return fields, nil
|
189 |
+
}
|
190 |
+
|
191 |
+
func metaObject(prefix string) *graphql.Object {
|
192 |
+
return graphql.NewObject(graphql.ObjectConfig{
|
193 |
+
Name: fmt.Sprintf("%sMetaObject", prefix),
|
194 |
+
Fields: graphql.Fields{
|
195 |
+
"count": &graphql.Field{
|
196 |
+
Type: graphql.Int,
|
197 |
+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
198 |
+
group, ok := p.Source.(aggregation.Group)
|
199 |
+
if !ok {
|
200 |
+
return nil, fmt.Errorf("meta count: expected aggregation.Group, got %T", p.Source)
|
201 |
+
}
|
202 |
+
|
203 |
+
return group.Count, nil
|
204 |
+
},
|
205 |
+
},
|
206 |
+
},
|
207 |
+
})
|
208 |
+
}
|
209 |
+
|
210 |
+
func classPropertyField(dataType schema.DataType, class *models.Class, property *models.Property) (*graphql.Field, error) {
|
211 |
+
switch dataType {
|
212 |
+
case schema.DataTypeText:
|
213 |
+
return makePropertyField(class, property, stringPropertyFields)
|
214 |
+
case schema.DataTypeInt:
|
215 |
+
return makePropertyField(class, property, numericPropertyFields)
|
216 |
+
case schema.DataTypeNumber:
|
217 |
+
return makePropertyField(class, property, numericPropertyFields)
|
218 |
+
case schema.DataTypeBoolean:
|
219 |
+
return makePropertyField(class, property, booleanPropertyFields)
|
220 |
+
case schema.DataTypeDate:
|
221 |
+
return makePropertyField(class, property, datePropertyFields)
|
222 |
+
case schema.DataTypeCRef:
|
223 |
+
return makePropertyField(class, property, referencePropertyFields)
|
224 |
+
case schema.DataTypeGeoCoordinates:
|
225 |
+
// simply skip for now, see gh-729
|
226 |
+
return nil, nil
|
227 |
+
case schema.DataTypePhoneNumber:
|
228 |
+
// skipping for now, see gh-1088 where it was outscoped
|
229 |
+
return nil, nil
|
230 |
+
case schema.DataTypeBlob:
|
231 |
+
return makePropertyField(class, property, stringPropertyFields)
|
232 |
+
case schema.DataTypeTextArray:
|
233 |
+
return makePropertyField(class, property, stringPropertyFields)
|
234 |
+
case schema.DataTypeIntArray, schema.DataTypeNumberArray:
|
235 |
+
return makePropertyField(class, property, numericPropertyFields)
|
236 |
+
case schema.DataTypeBooleanArray:
|
237 |
+
return makePropertyField(class, property, booleanPropertyFields)
|
238 |
+
case schema.DataTypeDateArray:
|
239 |
+
return makePropertyField(class, property, datePropertyFields)
|
240 |
+
case schema.DataTypeUUID, schema.DataTypeUUIDArray:
|
241 |
+
// not aggregatable
|
242 |
+
return nil, nil
|
243 |
+
case schema.DataTypeObject, schema.DataTypeObjectArray:
|
244 |
+
// TODO: check if it's aggregable, skip for now
|
245 |
+
return nil, nil
|
246 |
+
default:
|
247 |
+
return nil, fmt.Errorf(schema.ErrorNoSuchDatatype+": %s", dataType)
|
248 |
+
}
|
249 |
+
}
|
250 |
+
|
251 |
+
type propertyFieldMaker func(class *models.Class,
|
252 |
+
property *models.Property, prefix string) *graphql.Object
|
253 |
+
|
254 |
+
func makePropertyField(class *models.Class, property *models.Property,
|
255 |
+
fieldMaker propertyFieldMaker,
|
256 |
+
) (*graphql.Field, error) {
|
257 |
+
prefix := "Aggregate"
|
258 |
+
return &graphql.Field{
|
259 |
+
Description: fmt.Sprintf(`%s"%s"`, descriptions.AggregateProperty, property.Name),
|
260 |
+
Type: fieldMaker(class, property, prefix),
|
261 |
+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
262 |
+
switch typed := p.Source.(type) {
|
263 |
+
case aggregation.Group:
|
264 |
+
res, ok := typed.Properties[property.Name]
|
265 |
+
if !ok {
|
266 |
+
return nil, fmt.Errorf("missing property '%s'", property.Name)
|
267 |
+
}
|
268 |
+
|
269 |
+
return res, nil
|
270 |
+
|
271 |
+
default:
|
272 |
+
return nil, fmt.Errorf("property %s, unsupported type %T", property.Name, p.Source)
|
273 |
+
}
|
274 |
+
},
|
275 |
+
}, nil
|
276 |
+
}
|
277 |
+
|
278 |
+
func passThroughResolver(p graphql.ResolveParams) (interface{}, error) {
|
279 |
+
// bubble up root resolver
|
280 |
+
return p.Source, nil
|
281 |
+
}
|
adapters/handlers/graphql/local/aggregate/explore_argument.go
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package aggregate
|
13 |
+
|
14 |
+
import (
|
15 |
+
"github.com/tailor-inc/graphql"
|
16 |
+
"github.com/weaviate/weaviate/adapters/handlers/graphql/local/common_filters"
|
17 |
+
)
|
18 |
+
|
19 |
+
func nearVectorArgument(className string) *graphql.ArgumentConfig {
|
20 |
+
return common_filters.NearVectorArgument("AggregateObjects", className)
|
21 |
+
}
|
22 |
+
|
23 |
+
func nearObjectArgument(className string) *graphql.ArgumentConfig {
|
24 |
+
return common_filters.NearObjectArgument("AggregateObjects", className)
|
25 |
+
}
|
adapters/handlers/graphql/local/aggregate/helpers_for_test.go
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package aggregate
|
13 |
+
|
14 |
+
import (
|
15 |
+
"context"
|
16 |
+
"fmt"
|
17 |
+
|
18 |
+
testhelper "github.com/weaviate/weaviate/adapters/handlers/graphql/test/helper"
|
19 |
+
"github.com/weaviate/weaviate/entities/aggregation"
|
20 |
+
"github.com/weaviate/weaviate/entities/models"
|
21 |
+
"github.com/weaviate/weaviate/usecases/config"
|
22 |
+
)
|
23 |
+
|
24 |
+
type mockRequestsLog struct{}
|
25 |
+
|
26 |
+
func (m *mockRequestsLog) Register(first string, second string) {
|
27 |
+
}
|
28 |
+
|
29 |
+
type mockResolver struct {
|
30 |
+
testhelper.MockResolver
|
31 |
+
}
|
32 |
+
|
33 |
+
func newMockResolver(cfg config.Config) *mockResolver {
|
34 |
+
field, err := Build(&testhelper.CarSchema, cfg, nil)
|
35 |
+
if err != nil {
|
36 |
+
panic(fmt.Sprintf("could not build graphql test schema: %s", err))
|
37 |
+
}
|
38 |
+
mockLog := &mockRequestsLog{}
|
39 |
+
mocker := &mockResolver{}
|
40 |
+
mocker.RootFieldName = "Aggregate"
|
41 |
+
mocker.RootField = field
|
42 |
+
mocker.RootObject = map[string]interface{}{
|
43 |
+
"Resolver": Resolver(mocker),
|
44 |
+
"RequestsLog": mockLog,
|
45 |
+
"Config": cfg,
|
46 |
+
}
|
47 |
+
|
48 |
+
return mocker
|
49 |
+
}
|
50 |
+
|
51 |
+
func (m *mockResolver) Aggregate(ctx context.Context, principal *models.Principal,
|
52 |
+
params *aggregation.Params,
|
53 |
+
) (interface{}, error) {
|
54 |
+
args := m.Called(params)
|
55 |
+
return args.Get(0), args.Error(1)
|
56 |
+
}
|
adapters/handlers/graphql/local/aggregate/hybrid_search.go
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// _ _
|
2 |
+
// __ _____ __ ___ ___ __ _| |_ ___
|
3 |
+
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
|
4 |
+
// \ V V / __/ (_| |\ V /| | (_| | || __/
|
5 |
+
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
|
6 |
+
//
|
7 |
+
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
|
8 |
+
//
|
9 |
+
// CONTACT: [email protected]
|
10 |
+
//
|
11 |
+
|
12 |
+
package aggregate
|
13 |
+
|
14 |
+
import (
|
15 |
+
"fmt"
|
16 |
+
"os"
|
17 |
+
|
18 |
+
"github.com/tailor-inc/graphql"
|
19 |
+
"github.com/weaviate/weaviate/entities/models"
|
20 |
+
)
|
21 |
+
|
22 |
+
func hybridArgument(classObject *graphql.Object,
|
23 |
+
class *models.Class, modulesProvider ModulesProvider,
|
24 |
+
) *graphql.ArgumentConfig {
|
25 |
+
prefix := fmt.Sprintf("AggregateObjects%s", class.Class)
|
26 |
+
return &graphql.ArgumentConfig{
|
27 |
+
Type: graphql.NewInputObject(
|
28 |
+
graphql.InputObjectConfig{
|
29 |
+
Name: fmt.Sprintf("%sHybridInpObj", prefix),
|
30 |
+
Fields: hybridOperands(classObject, class, modulesProvider),
|
31 |
+
Description: "Hybrid search",
|
32 |
+
},
|
33 |
+
),
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
func hybridOperands(classObject *graphql.Object,
|
38 |
+
class *models.Class, modulesProvider ModulesProvider,
|
39 |
+
) graphql.InputObjectConfigFieldMap {
|
40 |
+
ss := graphql.NewInputObject(graphql.InputObjectConfig{
|
41 |
+
Name: class.Class + "HybridSubSearch",
|
42 |
+
Fields: hybridSubSearch(classObject, class, modulesProvider),
|
43 |
+
})
|
44 |
+
fieldMap := graphql.InputObjectConfigFieldMap{
|
45 |
+
"query": &graphql.InputObjectFieldConfig{
|
46 |
+
Description: "Query string",
|
47 |
+
Type: graphql.String,
|
48 |
+
},
|
49 |
+
"alpha": &graphql.InputObjectFieldConfig{
|
50 |
+
Description: "Search weight",
|
51 |
+
Type: graphql.Float,
|
52 |
+
},
|
53 |
+
"vector": &graphql.InputObjectFieldConfig{
|
54 |
+
Description: "Vector search",
|
55 |
+
Type: graphql.NewList(graphql.Float),
|
56 |
+
},
|
57 |
+
}
|
58 |
+
|
59 |
+
if os.Getenv("ENABLE_EXPERIMENTAL_HYBRID_OPERANDS") != "" {
|
60 |
+
fieldMap["operands"] = &graphql.InputObjectFieldConfig{
|
61 |
+
Description: "Subsearch list",
|
62 |
+
Type: graphql.NewList(ss),
|
63 |
+
}
|
64 |
+
}
|
65 |
+
|
66 |
+
return fieldMap
|
67 |
+
}
|
68 |
+
|
69 |
+
func hybridSubSearch(classObject *graphql.Object,
|
70 |
+
class *models.Class, modulesProvider ModulesProvider,
|
71 |
+
) graphql.InputObjectConfigFieldMap {
|
72 |
+
prefixName := class.Class + "SubSearch"
|
73 |
+
|
74 |
+
return graphql.InputObjectConfigFieldMap{
|
75 |
+
"weight": &graphql.InputObjectFieldConfig{
|
76 |
+
Description: "weight, 0 to 1",
|
77 |
+
Type: graphql.Float,
|
78 |
+
},
|
79 |
+
"sparseSearch": &graphql.InputObjectFieldConfig{
|
80 |
+
Description: "Sparse Search",
|
81 |
+
Type: graphql.NewInputObject(
|
82 |
+
graphql.InputObjectConfig{
|
83 |
+
Name: fmt.Sprintf("%sHybridAggregateBM25InpObj", prefixName),
|
84 |
+
Fields: bm25Fields(prefixName),
|
85 |
+
Description: "BM25f search",
|
86 |
+
},
|
87 |
+
),
|
88 |
+
},
|
89 |
+
}
|
90 |
+
}
|