mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-25 07:08:47 -05:00
Compare commits
33 Commits
benchmark-
...
readtimeou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2883a6245d | ||
|
|
936a84096e | ||
|
|
6e0bb09aff | ||
|
|
59eb411024 | ||
|
|
b53b4ef1de | ||
|
|
c05c740fa6 | ||
|
|
5b98860585 | ||
|
|
a3c3b6a07c | ||
|
|
790c6b165f | ||
|
|
a2935abe3d | ||
|
|
a4856b4a80 | ||
|
|
b5b15f29de | ||
|
|
e270cdbfd2 | ||
|
|
e2441696c2 | ||
|
|
28ec9c3282 | ||
|
|
920a6916c4 | ||
|
|
10e77768a5 | ||
|
|
e7a4cbaae5 | ||
|
|
e62e2e0f12 | ||
|
|
9cb973baac | ||
|
|
9e16bb9e29 | ||
|
|
641dac0a88 | ||
|
|
570ec0bf97 | ||
|
|
aaaf5cf5c4 | ||
|
|
fb8af22073 | ||
|
|
8c9f266ded | ||
|
|
f04f6ad470 | ||
|
|
c887947a85 | ||
|
|
ac8be264f0 | ||
|
|
2c18d5b010 | ||
|
|
44ee182aa3 | ||
|
|
d76cacd99f | ||
|
|
fb94f34a1f |
3
.github/settings.yml
vendored
3
.github/settings.yml
vendored
@@ -1 +1,4 @@
|
||||
_extends: gh-labels
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# The test runner source for UI tests
|
||||
WEB_COMMITID=425ec35c41a1f034a8f9dbc6ab7f7d27b994578a
|
||||
WEB_COMMITID=6abffcc9cff31c46a341105eb6030fec56338126
|
||||
WEB_BRANCH=main
|
||||
|
||||
|
||||
@@ -338,6 +338,7 @@ config = {
|
||||
"FRONTEND_READONLY_USER_ATTRIBUTES": "user.onPremisesSamAccountName,user.displayName,user.mail,user.passwordProfile,user.accountEnabled,user.appRoleAssignments",
|
||||
"OC_LDAP_SERVER_WRITE_ENABLED": False,
|
||||
"OC_EXCLUDE_RUN_SERVICES": "idm",
|
||||
"OC_LDAP_USER_ENABLED_ATTRIBUTE": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -350,7 +351,7 @@ config = {
|
||||
"part": {
|
||||
"skip": False,
|
||||
"totalParts": 4, # divide and run all suites in parts (divide pipelines)
|
||||
"xsuites": ["search", "app-provider", "app-provider-onlyOffice", "app-store", "keycloak", "oidc", "ocm", "a11y", "mobile-view"], # suites to skip
|
||||
"xsuites": ["search", "app-provider", "app-provider-onlyOffice", "app-store", "keycloak", "oidc", "ocm", "a11y", "mobile-view", "navigation"], # suites to skip
|
||||
},
|
||||
"search": {
|
||||
"skip": False,
|
||||
@@ -1076,6 +1077,7 @@ def localApiTests(name, suites, storage = "decomposed", extra_environment = {},
|
||||
"WITH_REMOTE_PHP": with_remote_php,
|
||||
"COLLABORATION_SERVICE_URL": "http://wopi-fakeoffice:9300",
|
||||
"OC_STORAGE_PATH": "$HOME/.opencloud/storage/users",
|
||||
"USE_BEARER_TOKEN": True,
|
||||
}
|
||||
|
||||
for item in extra_environment:
|
||||
@@ -1479,7 +1481,7 @@ def multiServiceE2ePipeline(ctx, watch_fs_enabled = False):
|
||||
}
|
||||
|
||||
if watch_fs_enabled:
|
||||
extra_server_environment["STORAGE_USES_POSIX_WATCH_FS"] = True
|
||||
extra_server_environment["STORAGE_USERS_POSIX_WATCH_FS"] = True
|
||||
|
||||
storage_users_environment = {
|
||||
"OC_CORS_ALLOW_ORIGINS": "%s,https://%s:9201" % (OC_URL, OC_SERVER_NAME),
|
||||
@@ -2071,6 +2073,7 @@ def opencloudServer(storage = "decomposed", accounts_hash_difficulty = 4, depend
|
||||
"WEB_DEBUG_ADDR": "0.0.0.0:9104",
|
||||
"WEBDAV_DEBUG_ADDR": "0.0.0.0:9119",
|
||||
"WEBFINGER_DEBUG_ADDR": "0.0.0.0:9279",
|
||||
"STORAGE_USERS_POSIX_SCAN_DEBOUNCE_DELAY": 0,
|
||||
}
|
||||
|
||||
if storage == "posix":
|
||||
@@ -2108,7 +2111,7 @@ def opencloudServer(storage = "decomposed", accounts_hash_difficulty = 4, depend
|
||||
environment["SEARCH_EXTRACTOR_CS3SOURCE_INSECURE"] = True
|
||||
|
||||
if watch_fs_enabled:
|
||||
environment["STORAGE_USES_POSIX_WATCH_FS"] = True
|
||||
environment["STORAGE_USERS_POSIX_WATCH_FS"] = True
|
||||
|
||||
# Pass in "default" accounts_hash_difficulty to not set this environment variable.
|
||||
# That will allow OpenCloud to use whatever its built-in default is.
|
||||
|
||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,5 +1,103 @@
|
||||
# Changelog
|
||||
|
||||
## [3.7.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.7.0) - 2025-11-03
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@ScharfViktor, @individual-it, @kulmann, @rhafer, @schweigisito, @sdwilsh
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- check status of postprocessing before accesing the file [[#1762](https://github.com/opencloud-eu/opencloud/pull/1762)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- multi-tenancy: Optional attributes on provision API [[#1663](https://github.com/opencloud-eu/opencloud/pull/1663)]
|
||||
- fix: fix #1698 - Notification email doesn't contain Message-Id header [[#1708](https://github.com/opencloud-eu/opencloud/pull/1708)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix: only search LDAP group by name [[#1724](https://github.com/opencloud-eu/opencloud/pull/1724)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- [full-ci] bump web 4.2.0 and opencloud 3.7.0 version [[#1765](https://github.com/opencloud-eu/opencloud/pull/1765)]
|
||||
|
||||
## [3.6.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.6.0) - 2025-10-27
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@AlexAndBear, @ScharfViktor, @butonic, @dragonchaser, @fschade, @micbar, @prashant-gurung899, @rhafer, @schweigisito, @tammi-23
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- allow specifying a shutdown order [[#1622](https://github.com/opencloud-eu/opencloud/pull/1622)]
|
||||
- change: use 404 as status when thumbnail can not be fetched [[#1582](https://github.com/opencloud-eu/opencloud/pull/1582)]
|
||||
- feat: add dedicated logo (web) for mobile view to theme [[#1579](https://github.com/opencloud-eu/opencloud/pull/1579)]
|
||||
- feat: make it possible to start the collaboration service in the single process [[#1569](https://github.com/opencloud-eu/opencloud/pull/1569)]
|
||||
- introduce AppURLs helper for atomic backgroud updates [[#1542](https://github.com/opencloud-eu/opencloud/pull/1542)]
|
||||
- chore: add config for capability CheckForUpdates [[#1556](https://github.com/opencloud-eu/opencloud/pull/1556)]
|
||||
|
||||
### ✅ Tests
|
||||
|
||||
- [full-ci] feat: implement OIDC authentication option [[#1676](https://github.com/opencloud-eu/opencloud/pull/1676)]
|
||||
- apiTest-coverage for #1523 [[#1660](https://github.com/opencloud-eu/opencloud/pull/1660)]
|
||||
- [full-ci] deleted unused step definitions [[#1639](https://github.com/opencloud-eu/opencloud/pull/1639)]
|
||||
- check thumbnails in the share with me response [[#1605](https://github.com/opencloud-eu/opencloud/pull/1605)]
|
||||
- [full-ci][tests-only] fix restore browsers cache workflow [[#1615](https://github.com/opencloud-eu/opencloud/pull/1615)]
|
||||
- [full-ci] Enhance getSpaceByName: check local cache before Graph API calls [[#1574](https://github.com/opencloud-eu/opencloud/pull/1574)]
|
||||
- [full-ci] getting personal space by userId instead of userName [[#1553](https://github.com/opencloud-eu/opencloud/pull/1553)]
|
||||
- apiTest-flaky: sync share before checking [[#1550](https://github.com/opencloud-eu/opencloud/pull/1550)]
|
||||
- [decomposed] use Alpine for opencloud starting [[#1547](https://github.com/opencloud-eu/opencloud/pull/1547)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- fix: apply changes from other fixes in compose repo [[#1707](https://github.com/opencloud-eu/opencloud/pull/1707)]
|
||||
- fix(settings): env var precedence [[#1625](https://github.com/opencloud-eu/opencloud/pull/1625)]
|
||||
- fix(antivirus): update icap-client library which fixes tcp socket reuse [[#1589](https://github.com/opencloud-eu/opencloud/pull/1589)]
|
||||
- fix: use valid autocomplete values (axe autocomplete-valid) [[#1588](https://github.com/opencloud-eu/opencloud/pull/1588)]
|
||||
- Fix collaboration service name [[#1577](https://github.com/opencloud-eu/opencloud/pull/1577)]
|
||||
- let the runtime always create a cancel context [[#1565](https://github.com/opencloud-eu/opencloud/pull/1565)]
|
||||
- Bump reva and cs3apis [[#1538](https://github.com/opencloud-eu/opencloud/pull/1538)]
|
||||
- use correct endpoint in nats check [[#1533](https://github.com/opencloud-eu/opencloud/pull/1533)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- adr: use eduation api for multi-tenancy provisioning [[#1548](https://github.com/opencloud-eu/opencloud/pull/1548)]
|
||||
- fix: remove deprecated web ui feature "OpenAppsInTab" [[#1575](https://github.com/opencloud-eu/opencloud/pull/1575)]
|
||||
|
||||
### 📦️ Dependencies
|
||||
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.26.0 to 2.27.1 [[#1705](https://github.com/opencloud-eu/opencloud/pull/1705)]
|
||||
- [decomposed] bump-version-v3.6.0 [[#1719](https://github.com/opencloud-eu/opencloud/pull/1719)]
|
||||
- revaBump-2.39.1 [[#1718](https://github.com/opencloud-eu/opencloud/pull/1718)]
|
||||
- chore: bump reva [[#1701](https://github.com/opencloud-eu/opencloud/pull/1701)]
|
||||
- build(deps): bump github.com/kovidgoyal/imaging from 1.6.4 to 1.7.2 [[#1696](https://github.com/opencloud-eu/opencloud/pull/1696)]
|
||||
- build(deps): bump github.com/blevesearch/bleve/v2 from 2.5.3 to 2.5.4 [[#1697](https://github.com/opencloud-eu/opencloud/pull/1697)]
|
||||
- build(deps): bump golang.org/x/oauth2 from 0.31.0 to 0.32.0 [[#1634](https://github.com/opencloud-eu/opencloud/pull/1634)]
|
||||
- build(deps): bump golang.org/x/net from 0.44.0 to 0.46.0 [[#1638](https://github.com/opencloud-eu/opencloud/pull/1638)]
|
||||
- revaBumb: add groupware capabilities [[#1689](https://github.com/opencloud-eu/opencloud/pull/1689)]
|
||||
- revaUpdate: adding groupware capabilities [[#1659](https://github.com/opencloud-eu/opencloud/pull/1659)]
|
||||
- chore/bump-web-4.1.0 [[#1652](https://github.com/opencloud-eu/opencloud/pull/1652)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.75.1 to 1.76.0 [[#1628](https://github.com/opencloud-eu/opencloud/pull/1628)]
|
||||
- build(deps): bump github.com/coreos/go-oidc/v3 from 3.15.0 to 3.16.0 [[#1627](https://github.com/opencloud-eu/opencloud/pull/1627)]
|
||||
- build(deps): bump github.com/grpc-ecosystem/grpc-gateway/v2 from 2.27.2 to 2.27.3 [[#1608](https://github.com/opencloud-eu/opencloud/pull/1608)]
|
||||
- build(deps): bump github.com/go-ldap/ldap/v3 from 3.4.11 to 3.4.12 [[#1609](https://github.com/opencloud-eu/opencloud/pull/1609)]
|
||||
- build(deps): bump google.golang.org/protobuf from 1.36.9 to 1.36.10 [[#1604](https://github.com/opencloud-eu/opencloud/pull/1604)]
|
||||
- build(deps): bump github.com/onsi/ginkgo/v2 from 2.25.3 to 2.26.0 [[#1603](https://github.com/opencloud-eu/opencloud/pull/1603)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.46.0 to 1.46.1 [[#1590](https://github.com/opencloud-eu/opencloud/pull/1590)]
|
||||
- build(deps): bump github.com/olekukonko/tablewriter from 1.0.9 to 1.1.0 [[#1584](https://github.com/opencloud-eu/opencloud/pull/1584)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.8.0 to 1.9.0 [[#1576](https://github.com/opencloud-eu/opencloud/pull/1576)]
|
||||
- build(deps): bump github.com/nats-io/nats-server/v2 from 2.11.9 to 2.12.0 [[#1568](https://github.com/opencloud-eu/opencloud/pull/1568)]
|
||||
- build(deps): bump golang.org/x/net from 0.43.0 to 0.44.0 [[#1567](https://github.com/opencloud-eu/opencloud/pull/1567)]
|
||||
- reva bump. getting #327 [[#1555](https://github.com/opencloud-eu/opencloud/pull/1555)]
|
||||
- build(deps): bump golang.org/x/image from 0.30.0 to 0.31.0 [[#1552](https://github.com/opencloud-eu/opencloud/pull/1552)]
|
||||
- build(deps): bump github.com/nats-io/nats.go from 1.45.0 to 1.46.0 [[#1551](https://github.com/opencloud-eu/opencloud/pull/1551)]
|
||||
- build(deps): bump golang.org/x/crypto from 0.41.0 to 0.42.0 [[#1545](https://github.com/opencloud-eu/opencloud/pull/1545)]
|
||||
- build(deps): bump github.com/testcontainers/testcontainers-go/modules/opensearch from 0.38.0 to 0.39.0 [[#1544](https://github.com/opencloud-eu/opencloud/pull/1544)]
|
||||
- build(deps): bump github.com/open-policy-agent/opa from 1.6.0 to 1.8.0 [[#1510](https://github.com/opencloud-eu/opencloud/pull/1510)]
|
||||
- build(deps): bump google.golang.org/grpc from 1.75.0 to 1.75.1 [[#1534](https://github.com/opencloud-eu/opencloud/pull/1534)]
|
||||
|
||||
## [3.5.0](https://github.com/opencloud-eu/opencloud/releases/tag/v3.5.0) - 2025-09-22
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@@ -663,6 +663,7 @@
|
||||
"profile",
|
||||
"roles",
|
||||
"groups",
|
||||
"OpenCloudUnique_ID",
|
||||
"basic",
|
||||
"email"
|
||||
],
|
||||
@@ -2308,7 +2309,7 @@
|
||||
"always"
|
||||
],
|
||||
"usePasswordModifyExtendedOp": [
|
||||
"false"
|
||||
"true"
|
||||
],
|
||||
"trustEmail": [
|
||||
"false"
|
||||
|
||||
26
go.mod
26
go.mod
@@ -11,7 +11,7 @@ require (
|
||||
github.com/Nerzal/gocloak/v13 v13.9.0
|
||||
github.com/bbalet/stopwords v1.0.0
|
||||
github.com/beevik/etree v1.6.0
|
||||
github.com/blevesearch/bleve/v2 v2.5.3
|
||||
github.com/blevesearch/bleve/v2 v2.5.4
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/coreos/go-oidc/v3 v3.16.0
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20250908152307-4ca807afe54e
|
||||
@@ -48,7 +48,7 @@ require (
|
||||
github.com/jellydator/ttlcache/v3 v3.4.0
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/justinas/alice v1.2.0
|
||||
github.com/kovidgoyal/imaging v1.6.4
|
||||
github.com/kovidgoyal/imaging v1.7.2
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/libregraph/idm v0.5.0
|
||||
github.com/libregraph/lico v0.66.0
|
||||
@@ -56,16 +56,16 @@ require (
|
||||
github.com/mna/pigeon v1.3.0
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/nats-io/nats-server/v2 v2.12.0
|
||||
github.com/nats-io/nats.go v1.46.1
|
||||
github.com/nats-io/nats.go v1.47.0
|
||||
github.com/oklog/run v1.2.0
|
||||
github.com/olekukonko/tablewriter v1.1.0
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.26.0
|
||||
github.com/onsi/ginkgo/v2 v2.27.2
|
||||
github.com/onsi/gomega v1.38.2
|
||||
github.com/open-policy-agent/opa v1.9.0
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76
|
||||
github.com/opencloud-eu/reva/v2 v2.39.1-0.20251020192555-e3aa6a7d6d43
|
||||
github.com/opencloud-eu/reva/v2 v2.39.2-0.20251030154544-cac8a0257da6
|
||||
github.com/opensearch-project/opensearch-go/v4 v4.5.0
|
||||
github.com/orcaman/concurrent-map v1.0.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
@@ -104,7 +104,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.38.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac
|
||||
golang.org/x/image v0.31.0
|
||||
golang.org/x/image v0.32.0
|
||||
golang.org/x/net v0.46.0
|
||||
golang.org/x/oauth2 v0.32.0
|
||||
golang.org/x/sync v0.17.0
|
||||
@@ -140,13 +140,13 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.8 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.2.10 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.25 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
@@ -156,7 +156,7 @@ require (
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.2.6 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/bombsimon/logrusr/v3 v3.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
@@ -229,7 +229,7 @@ require (
|
||||
github.com/gobwas/ws v1.2.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
@@ -257,6 +257,7 @@ require (
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
||||
github.com/kovidgoyal/go-parallel v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/dsig v1.0.0 // indirect
|
||||
@@ -327,6 +328,7 @@ require (
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russellhaering/goxmldsig v1.5.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/segmentio/kafka-go v0.4.49 // indirect
|
||||
github.com/segmentio/ksuid v1.0.4 // indirect
|
||||
@@ -334,7 +336,7 @@ require (
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/sethvargo/go-diceware v0.5.0 // indirect
|
||||
github.com/sethvargo/go-password v0.3.1 // indirect
|
||||
github.com/shamaton/msgpack/v2 v2.3.1 // indirect
|
||||
github.com/shamaton/msgpack/v2 v2.4.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
|
||||
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect
|
||||
@@ -369,7 +371,6 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
@@ -378,7 +379,6 @@ require (
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
golang.org/x/tools/godoc v0.1.0-deprecated // indirect
|
||||
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
|
||||
|
||||
56
go.sum
56
go.sum
@@ -151,10 +151,10 @@ github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/blevesearch/bleve/v2 v2.5.3 h1:9l1xtKaETv64SZc1jc4Sy0N804laSa/LeMbYddq1YEM=
|
||||
github.com/blevesearch/bleve/v2 v2.5.3/go.mod h1:Z/e8aWjiq8HeX+nW8qROSxiE0830yQA071dwR3yoMzw=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8 h1:Y98Pu5/MdlkRyLM0qDHostYo7i+Vv1cDNhqTeR4Sy6Y=
|
||||
github.com/blevesearch/bleve_index_api v1.2.8/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/bleve/v2 v2.5.4 h1:1iur8e+PHsxtncV2xIVuqlQme/V8guEDO2uV6Wll3lQ=
|
||||
github.com/blevesearch/bleve/v2 v2.5.4/go.mod h1:yB4PnV4N2q5rTEpB2ndG8N2ISexBQEFIYgwx4ztfvoo=
|
||||
github.com/blevesearch/bleve_index_api v1.2.10 h1:FMFmZCmTX6PdoLLvwUnKF2RsmILFFwO3h0WPevXY9fE=
|
||||
github.com/blevesearch/bleve_index_api v1.2.10/go.mod h1:rKQDl4u51uwafZxFrPD1R7xFOwKnzZW7s/LSeK4lgo0=
|
||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
||||
github.com/blevesearch/go-faiss v1.0.25 h1:lel1rkOUGbT1CJ0YgzKwC7k+XH0XVBHnCVWahdCXk4U=
|
||||
@@ -165,8 +165,8 @@ github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZG
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 h1:Yqk0XD1mE0fDZAJXTjawJ8If/85JxnLd8v5vG/jWE/s=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.10/go.mod h1:Z3e6ChN3qyN35yaQpl00MfI5s8AxUJbpTR/DL8QOQ+8=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12 h1:GGZc2qwbyRBwtckPPkHkLyXw64mmsLJxdturBI1cM+c=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.3.12/go.mod h1:JBRGAneqgLSI2+jCNjtwMqp2B7EBF3/VUzgDPIU33MM=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
@@ -185,8 +185,8 @@ github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT
|
||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4 h1:tGgfvleXTAkwsD5mEzgM3zCS/7pgocTCnO1oyAUjlww=
|
||||
github.com/blevesearch/zapx/v16 v16.2.4/go.mod h1:Rti/REtuuMmzwsI8/C/qIzRaEoSK/wiFYw5e5ctUKKs=
|
||||
github.com/blevesearch/zapx/v16 v16.2.6 h1:OHuUl2GhM+FpBq9RwNsJ4k/QodqbMMHoQEgn/IHYpu8=
|
||||
github.com/blevesearch/zapx/v16 v16.2.6/go.mod h1:cuAPB+YoIyRngNhno1S1GPr9SfMk+x/SgAHBLXSIq3k=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||
@@ -361,8 +361,8 @@ github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BN
|
||||
github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.5.14 h1:3fAqdB6BCPKHDMHAKRwtPUwYexKtGrNuw8HX/T/4neo=
|
||||
github.com/gkampitakis/go-snaps v0.5.14/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE=
|
||||
github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
|
||||
@@ -480,8 +480,8 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -729,8 +729,10 @@ github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXH
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kovidgoyal/imaging v1.6.4 h1:K0idhRPXnRrJBKnBYcTfI1HTWSNDeAn7hYDvf9I0dCk=
|
||||
github.com/kovidgoyal/imaging v1.6.4/go.mod h1:bEIgsaZmXlvFfkv/CUxr9rJook6AQkJnpB5EPosRfRY=
|
||||
github.com/kovidgoyal/go-parallel v1.0.1 h1:nYUjN+EdpbmQjTg3N5eTUInuXTB3/1oD2vHdaMfuHoI=
|
||||
github.com/kovidgoyal/go-parallel v1.0.1/go.mod h1:BJNIbe6+hxyFWv7n6oEDPj3PA5qSw5OCtf0hcVxWJiw=
|
||||
github.com/kovidgoyal/imaging v1.7.2 h1:mmT6k6Az3mC6dbqdZ6Q9KQCdZFWTAQ+q97NyGZgJ/2c=
|
||||
github.com/kovidgoyal/imaging v1.7.2/go.mod h1:GdkCORjfZMMGFY0Pb7TDmRhj7PDhxF/QShKukSCj0VU=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -899,8 +901,8 @@ github.com/nats-io/jwt/v2 v2.8.0 h1:K7uzyz50+yGZDO5o772eRE7atlcSEENpL7P+b74JV1g=
|
||||
github.com/nats-io/jwt/v2 v2.8.0/go.mod h1:me11pOkwObtcBNR8AiMrUbtVOUGkqYjMQZ6jnSdVUIA=
|
||||
github.com/nats-io/nats-server/v2 v2.12.0 h1:OIwe8jZUqJFrh+hhiyKu8snNib66qsx806OslqJuo74=
|
||||
github.com/nats-io/nats-server/v2 v2.12.0/go.mod h1:nr8dhzqkP5E/lDwmn+A2CvQPMd1yDKXQI7iGg3lAvww=
|
||||
github.com/nats-io/nats.go v1.46.1 h1:bqQ2ZcxVd2lpYI97xYASeRTY3I5boe/IVmuUDPitHfo=
|
||||
github.com/nats-io/nats.go v1.46.1/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
|
||||
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
@@ -931,8 +933,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE=
|
||||
github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
@@ -946,8 +948,8 @@ github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89 h1:W1ms+l
|
||||
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89/go.mod h1:vigJkNss1N2QEceCuNw/ullDehncuJNFB6mEnzfq9UI=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76 h1:vD/EdfDUrv4omSFjrinT8Mvf+8D7f9g4vgQ2oiDrVUI=
|
||||
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250724122329-41ba6b191e76/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
|
||||
github.com/opencloud-eu/reva/v2 v2.39.1-0.20251020192555-e3aa6a7d6d43 h1:GQWk2gk8BcbIVoEysI3QRVfARaZJG9jMOpNTKSIr/hY=
|
||||
github.com/opencloud-eu/reva/v2 v2.39.1-0.20251020192555-e3aa6a7d6d43/go.mod h1:rWCkqbdtVGVcZLZ2uw2kLGGjGnK8NTXfy9y0+rMyL8M=
|
||||
github.com/opencloud-eu/reva/v2 v2.39.2-0.20251030154544-cac8a0257da6 h1:BUrCUrRqBg04MJuhnIK4H1KNK4aebK6H/AYcHjQ0DM4=
|
||||
github.com/opencloud-eu/reva/v2 v2.39.2-0.20251030154544-cac8a0257da6/go.mod h1:Qm0CibFYrFc096OhWWL14nsGiFoE6g/4oMFHV5CqU+Q=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -999,8 +1001,6 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
||||
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
|
||||
github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/alertmanager v0.28.1 h1:BK5pCoAtaKg01BYRUJhEDV1tqJMEtYBGzPw8QdvnnvA=
|
||||
github.com/prometheus/alertmanager v0.28.1/go.mod h1:0StpPUDDHi1VXeM7p2yYfeZgLVi/PPlt39vo9LQUHxM=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -1079,6 +1079,8 @@ github.com/russellhaering/goxmldsig v1.5.0/go.mod h1:x98CjQNFJcWfMxeOrMnMKg70lvD
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
|
||||
@@ -1097,8 +1099,8 @@ github.com/sethvargo/go-diceware v0.5.0 h1:exrQ7GpaBo00GqRVM1N8ChXSsi3oS7tjQiIeh
|
||||
github.com/sethvargo/go-diceware v0.5.0/go.mod h1:Lg1SyPS7yQO6BBgTN5r4f2MUDkqGfLWsOjHPY0kA8iw=
|
||||
github.com/sethvargo/go-password v0.3.1 h1:WqrLTjo7X6AcVYfC6R7GtSyuUQR9hGyAj/f1PYQZCJU=
|
||||
github.com/sethvargo/go-password v0.3.1/go.mod h1:rXofC1zT54N7R8K/h1WDUdkf9BOx5OptoxrMBcrXzvs=
|
||||
github.com/shamaton/msgpack/v2 v2.3.1 h1:R3QNLIGA/tbdczNMZ5PCRxrXvy+fnzsIaHG4kKMgWYo=
|
||||
github.com/shamaton/msgpack/v2 v2.3.1/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||
github.com/shamaton/msgpack/v2 v2.4.0 h1:O5Z08MRmbo0lA9o2xnQ4TXx6teJbPqEurqcCOQ8Oi/4=
|
||||
github.com/shamaton/msgpack/v2 v2.4.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
@@ -1304,8 +1306,6 @@ go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzK
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
@@ -1360,8 +1360,8 @@ golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScy
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
|
||||
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
|
||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
||||
@@ -2,7 +2,6 @@ package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
@@ -11,11 +10,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
@@ -68,15 +64,8 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command {
|
||||
&cli.StringFlag{
|
||||
Name: "data",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. If you start the data with the letter @, the rest should be a file name to read the data from, or - if you want to read the data from stdin. When -d, --data is told to read from a file like that, carriage returns and newlines are stripped out. If you do not want the @ character to have a special interpretation use --data-raw instead.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "data-binary",
|
||||
Usage: "This posts data exactly as specified with no extra processing whatsoever. If you start the data with the letter @, the rest should be a file name to read the data from, or - if you want to read the data from stdin.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "data-raw",
|
||||
Usage: "Sends the specified data in a request to the HTTP server.",
|
||||
Usage: "Sends the specified data in a request to the HTTP server.",
|
||||
// TODE support multiple data flags, support data-binary, data-raw
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "header",
|
||||
@@ -118,97 +107,14 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command {
|
||||
},
|
||||
Category: "benchmark",
|
||||
Action: func(c *cli.Context) error {
|
||||
// Set up signal handling for Ctrl+C
|
||||
ctx, cancel := context.WithCancel(c.Context)
|
||||
defer cancel()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigChan
|
||||
fmt.Println("\nReceived interrupt signal, shutting down...")
|
||||
cancel()
|
||||
}()
|
||||
|
||||
opt := clientOptions{
|
||||
request: c.String("request"),
|
||||
url: c.Args().First(),
|
||||
insecure: c.Bool("insecure"),
|
||||
jobs: c.Int("jobs"),
|
||||
headers: make(map[string]string),
|
||||
data: []byte(c.String("data")),
|
||||
}
|
||||
|
||||
if d := c.String("data-raw"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
|
||||
if d := c.String("data"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
if strings.HasPrefix(d, "@") {
|
||||
filePath := strings.TrimPrefix(d, "@")
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
// read from file or stdin and trim trailing newlines
|
||||
if filePath == "-" {
|
||||
data, err = os.ReadFile("/dev/stdin")
|
||||
} else {
|
||||
data, err = os.ReadFile(filePath)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(errors.New("could not read data from file '" + filePath + "': " + err.Error()))
|
||||
}
|
||||
|
||||
// clean byte array similar to curl's --data parameter
|
||||
// It removes leading/trailing whitespace and converts line breaks to spaces
|
||||
|
||||
// Trim leading and trailing whitespace
|
||||
data = bytes.TrimSpace(data)
|
||||
|
||||
// Replace newlines and carriage returns with spaces
|
||||
data = bytes.ReplaceAll(data, []byte("\r\n"), []byte(" "))
|
||||
data = bytes.ReplaceAll(data, []byte("\n"), []byte(" "))
|
||||
data = bytes.ReplaceAll(data, []byte("\r"), []byte(" "))
|
||||
|
||||
// Replace multiple spaces with single space
|
||||
for bytes.Contains(data, []byte(" ")) {
|
||||
data = bytes.ReplaceAll(data, []byte(" "), []byte(" "))
|
||||
}
|
||||
|
||||
opt.data = data
|
||||
} else {
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
}
|
||||
|
||||
if d := c.String("data-binary"); d != "" {
|
||||
opt.request = "POST"
|
||||
opt.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
if strings.HasPrefix(d, "@") {
|
||||
filePath := strings.TrimPrefix(d, "@")
|
||||
var data []byte
|
||||
var err error
|
||||
if filePath == "-" {
|
||||
data, err = os.ReadFile("/dev/stdin")
|
||||
} else {
|
||||
data, err = os.ReadFile(filePath)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(errors.New("could not read data from file '" + filePath + "': " + err.Error()))
|
||||
}
|
||||
opt.data = data
|
||||
} else {
|
||||
opt.data = []byte(d)
|
||||
}
|
||||
}
|
||||
|
||||
// override method if specified
|
||||
if request := c.String("request"); request != "" {
|
||||
opt.request = request
|
||||
}
|
||||
|
||||
if opt.url == "" {
|
||||
log.Fatal(errors.New("no URL specified"))
|
||||
}
|
||||
@@ -273,7 +179,7 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command {
|
||||
defer opt.ticker.Stop()
|
||||
}
|
||||
|
||||
return client(ctx, opt)
|
||||
return client(opt)
|
||||
|
||||
},
|
||||
}
|
||||
@@ -291,19 +197,16 @@ type clientOptions struct {
|
||||
jobs int
|
||||
}
|
||||
|
||||
func client(ctx context.Context, o clientOptions) error {
|
||||
func client(o clientOptions) error {
|
||||
|
||||
type stat struct {
|
||||
job int
|
||||
duration time.Duration
|
||||
status int
|
||||
}
|
||||
stats := make(chan stat)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < o.jobs; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
@@ -314,13 +217,6 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
|
||||
cookies := map[string]*http.Cookie{}
|
||||
for {
|
||||
// Check if context is cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(o.request, o.url, bytes.NewReader(o.data))
|
||||
if err != nil {
|
||||
log.Printf("client %d: could not create request: %s\n", i, err)
|
||||
@@ -338,35 +234,20 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
res, err := client.Do(req)
|
||||
duration := -time.Until(start)
|
||||
if err != nil {
|
||||
// Check if error is due to context cancellation
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("client %d: could not create request: %s\n", i, err)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
res.Body.Close()
|
||||
select {
|
||||
case stats <- stat{
|
||||
stats <- stat{
|
||||
job: i,
|
||||
duration: duration,
|
||||
status: res.StatusCode,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
for _, c := range res.Cookies() {
|
||||
cookies[c.Name] = c
|
||||
}
|
||||
}
|
||||
// Sleep with context awareness
|
||||
if o.rateDelay > duration {
|
||||
select {
|
||||
case <-time.After(o.rateDelay - duration):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(o.rateDelay - duration)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
@@ -375,15 +256,9 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
if o.ticker == nil {
|
||||
// no ticker, just write every request
|
||||
for {
|
||||
select {
|
||||
case stat := <-stats:
|
||||
numRequests++
|
||||
fmt.Printf("req %d took %v and returned status %d\n", numRequests, stat.duration, stat.status)
|
||||
case <-ctx.Done():
|
||||
fmt.Println("\nShutting down...")
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
stat := <-stats
|
||||
numRequests++
|
||||
fmt.Printf("req %d took %v and returned status %d\n", numRequests, stat.duration, stat.status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,13 +274,6 @@ func client(ctx context.Context, o clientOptions) error {
|
||||
numRequests = 0
|
||||
duration = 0
|
||||
}
|
||||
case <-ctx.Done():
|
||||
if numRequests > 0 {
|
||||
fmt.Printf("\n%d req at %v/req\n", numRequests, duration/time.Duration(numRequests))
|
||||
}
|
||||
fmt.Println("Shutting down...")
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -521,6 +521,24 @@ func trapShutdownCtx(s *Service, srv *http.Server, ctx context.Context) error {
|
||||
s.Log.Debug().Msg("runtime listener shutdown done")
|
||||
}()
|
||||
|
||||
// shutdown services in the order defined in the config
|
||||
// any services not listed will be shutdown in parallel afterwards
|
||||
for _, sName := range s.cfg.Runtime.ShutdownOrder {
|
||||
if _, ok := s.serviceToken[sName]; !ok {
|
||||
s.Log.Warn().Str("service", sName).Msg("unknown service for ordered shutdown, skipping")
|
||||
continue
|
||||
}
|
||||
for i := range s.serviceToken[sName] {
|
||||
if err := s.Supervisor.RemoveAndWait(s.serviceToken[sName][i], _defaultShutdownTimeoutDuration); err != nil && !errors.Is(err, suture.ErrSupervisorNotRunning) {
|
||||
s.Log.Error().Err(err).Str("service", sName).Msg("could not shutdown service in order, skipping to next")
|
||||
// continue shutting down other services
|
||||
continue
|
||||
}
|
||||
s.Log.Debug().Str("service", sName).Msg("graceful ordered shutdown for service done")
|
||||
}
|
||||
delete(s.serviceToken, sName)
|
||||
}
|
||||
|
||||
for sName := range s.serviceToken {
|
||||
for i := range s.serviceToken[sName] {
|
||||
wg.Add(1)
|
||||
|
||||
@@ -50,24 +50,26 @@ type Mode int
|
||||
|
||||
// Runtime configures the OpenCloud runtime when running in supervised mode.
|
||||
type Runtime struct {
|
||||
Port string `yaml:"port" env:"OC_RUNTIME_PORT" desc:"The TCP port at which OpenCloud will be available" introductionVersion:"1.0.0"`
|
||||
Host string `yaml:"host" env:"OC_RUNTIME_HOST" desc:"The host at which OpenCloud will be available" introductionVersion:"1.0.0"`
|
||||
Services []string `yaml:"services" env:"OC_RUN_EXTENSIONS;OC_RUN_SERVICES" desc:"A comma-separated list of service names. Will start only the listed services." introductionVersion:"1.0.0"`
|
||||
Disabled []string `yaml:"disabled_services" env:"OC_EXCLUDE_RUN_SERVICES" desc:"A comma-separated list of service names. Will start all default services except of the ones listed. Has no effect when OC_RUN_SERVICES is set." introductionVersion:"1.0.0"`
|
||||
Additional []string `yaml:"add_services" env:"OC_ADD_RUN_SERVICES" desc:"A comma-separated list of service names. Will add the listed services to the default configuration. Has no effect when OC_RUN_SERVICES is set. Note that one can add services not started by the default list and exclude services from the default list by using both envvars at the same time." introductionVersion:"1.0.0"`
|
||||
Port string `yaml:"port" env:"OC_RUNTIME_PORT" desc:"The TCP port at which OpenCloud will be available" introductionVersion:"1.0.0"`
|
||||
Host string `yaml:"host" env:"OC_RUNTIME_HOST" desc:"The host at which OpenCloud will be available" introductionVersion:"1.0.0"`
|
||||
Services []string `yaml:"services" env:"OC_RUN_EXTENSIONS;OC_RUN_SERVICES" desc:"A comma-separated list of service names. Will start only the listed services." introductionVersion:"1.0.0"`
|
||||
Disabled []string `yaml:"disabled_services" env:"OC_EXCLUDE_RUN_SERVICES" desc:"A comma-separated list of service names. Will start all default services except of the ones listed. Has no effect when OC_RUN_SERVICES is set." introductionVersion:"1.0.0"`
|
||||
Additional []string `yaml:"add_services" env:"OC_ADD_RUN_SERVICES" desc:"A comma-separated list of service names. Will add the listed services to the default configuration. Has no effect when OC_RUN_SERVICES is set. Note that one can add services not started by the default list and exclude services from the default list by using both envvars at the same time." introductionVersion:"1.0.0"`
|
||||
ShutdownOrder []string `yaml:"shutdown_order" env:"OC_SHUTDOWN_ORDER" desc:"A comma-separated list of service names defining the order in which services are shut down. Services not listed will be stopped after the listed ones in random order." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
// Config combines all available configuration parts.
|
||||
type Config struct {
|
||||
*shared.Commons `yaml:"shared"`
|
||||
|
||||
Tracing *shared.Tracing `yaml:"tracing"`
|
||||
Log *shared.Log `yaml:"log"`
|
||||
Cache *shared.Cache `yaml:"cache"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *shared.GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
HTTPServiceTLS shared.HTTPServiceTLS `yaml:"http_service_tls"`
|
||||
Reva *shared.Reva `yaml:"reva"`
|
||||
Tracing *shared.Tracing `yaml:"tracing"`
|
||||
Log *shared.Log `yaml:"log"`
|
||||
Cache *shared.Cache `yaml:"cache"`
|
||||
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *shared.GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
HTTPServiceTLS shared.HTTPServiceTLS `yaml:"http_service_tls"`
|
||||
HTTPServiceTimeout shared.HTTPServiceTimeout `yaml:"http_service_timeout"`
|
||||
Reva *shared.Reva `yaml:"reva"`
|
||||
|
||||
Mode Mode // DEPRECATED
|
||||
File string
|
||||
|
||||
@@ -50,8 +50,9 @@ func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
OpenCloudURL: "https://localhost:9200",
|
||||
Runtime: Runtime{
|
||||
Port: "9250",
|
||||
Host: "localhost",
|
||||
Port: "9250",
|
||||
Host: "localhost",
|
||||
ShutdownOrder: []string{"proxy"},
|
||||
},
|
||||
Reva: &shared.Reva{
|
||||
Address: "eu.opencloud.api.gateway",
|
||||
|
||||
@@ -2,6 +2,7 @@ package parser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/envdecode"
|
||||
@@ -61,6 +62,8 @@ func EnsureDefaults(cfg *config.Config) {
|
||||
if cfg.Reva == nil {
|
||||
cfg.Reva = &shared.Reva{}
|
||||
}
|
||||
|
||||
cfg.HTTPServiceTimeout.Read = 60 * time.Second
|
||||
}
|
||||
|
||||
// EnsureCommons copies applicable parts of the OpenCloud config into the commons part
|
||||
@@ -83,6 +86,7 @@ func EnsureCommons(cfg *config.Config) {
|
||||
}
|
||||
|
||||
cfg.Commons.HTTPServiceTLS = cfg.HTTPServiceTLS
|
||||
cfg.Commons.HTTPServiceTimeout = cfg.HTTPServiceTimeout
|
||||
|
||||
cfg.Commons.TokenManager = structs.CopyOrZeroValue(cfg.TokenManager)
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
@@ -17,6 +18,7 @@ type Option func(o *Options)
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
TLSConfig shared.HTTPServiceTLS
|
||||
TimeoutConfig shared.HTTPServiceTimeout
|
||||
Namespace string
|
||||
Name string
|
||||
Version string
|
||||
@@ -96,6 +98,13 @@ func TLSConfig(config shared.HTTPServiceTLS) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TimeoutConfig provides a function to set the TimeOutConfig option.
|
||||
func TimeoutConfig(config shared.HTTPServiceTimeout) Option {
|
||||
return func(o *Options) {
|
||||
o.TimeoutConfig = config
|
||||
}
|
||||
}
|
||||
|
||||
// TraceProvider provides a function to set the TraceProvider option.
|
||||
func TraceProvider(tp trace.TracerProvider) Option {
|
||||
return func(o *Options) {
|
||||
|
||||
@@ -3,16 +3,18 @@ package http
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/broker"
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
netx "github.com/opencloud-eu/opencloud/pkg/x/net"
|
||||
|
||||
mhttps "github.com/go-micro/plugins/v4/server/http"
|
||||
mtracer "github.com/go-micro/plugins/v4/wrapper/trace/opentelemetry"
|
||||
occrypto "github.com/opencloud-eu/opencloud/pkg/crypto"
|
||||
"go-micro.dev/v4"
|
||||
"go-micro.dev/v4/server"
|
||||
|
||||
occrypto "github.com/opencloud-eu/opencloud/pkg/crypto"
|
||||
)
|
||||
|
||||
// Service simply wraps the go-micro web service.
|
||||
@@ -24,7 +26,9 @@ type Service struct {
|
||||
func NewService(opts ...Option) (Service, error) {
|
||||
noopBroker := broker.NoOp{}
|
||||
sopts := newOptions(opts...)
|
||||
var mServer server.Server
|
||||
|
||||
var listener net.Listener
|
||||
var err error
|
||||
if sopts.TLSConfig.Enabled {
|
||||
var cert tls.Certificate
|
||||
var err error
|
||||
@@ -50,11 +54,27 @@ func NewService(opts ...Option) (Service, error) {
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
mServer = mhttps.NewServer(server.TLSConfig(tlsConfig))
|
||||
// Create TLS listener
|
||||
listener, err = tls.Listen("tcp", sopts.Address, tlsConfig)
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("error starting TLS listener: %w", err)
|
||||
}
|
||||
} else {
|
||||
mServer = mhttps.NewServer()
|
||||
// Create Non-TLS listener
|
||||
listener, err = net.Listen("tcp", sopts.Address)
|
||||
if err != nil {
|
||||
return Service{}, fmt.Errorf("error starting TCP listener: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
mServer := mhttps.NewServer(
|
||||
// Wrap listener with timeoutListener to set a read timeout
|
||||
mhttps.Listener(netx.TimeoutListener{
|
||||
Listener: listener,
|
||||
ReadTimeout: sopts.TimeoutConfig.Read,
|
||||
}),
|
||||
)
|
||||
|
||||
wopts := []micro.Option{
|
||||
micro.Server(mServer),
|
||||
micro.Broker(noopBroker),
|
||||
|
||||
@@ -55,6 +55,10 @@ type HTTPServiceTLS struct {
|
||||
Key string `yaml:"key" env:"OC_HTTP_TLS_KEY" desc:"Path/File name for the TLS certificate key (in PEM format) for the server certificate to use for the http services." introductionVersion:"1.0.0"`
|
||||
}
|
||||
|
||||
type HTTPServiceTimeout struct {
|
||||
Read time.Duration `yaml:"duration" env:"OC_HTTP_TIMEOUT_READ" desc:"The duration after which a read operation will time out." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
Store string `yaml:"store" env:"OC_CACHE_STORE" desc:"The type of the cache store. Supported values are: 'memory', 'redis-sentinel', 'nats-js-kv', 'noop'. See the text description for details." introductionVersion:"1.0.0"`
|
||||
Nodes []string `yaml:"nodes" env:"OC_CACHE_STORE_NODES" desc:"A comma separated list of nodes to access the configured store. This has no effect when 'memory' store is configured. Note that the behaviour how nodes are used is dependent on the library of the configured store." introductionVersion:"1.0.0"`
|
||||
@@ -69,21 +73,22 @@ type Cache struct {
|
||||
// Commons holds configuration that are common to all extensions. Each extension can then decide whether
|
||||
// to overwrite its values.
|
||||
type Commons struct {
|
||||
Log *Log `yaml:"log"`
|
||||
Tracing *Tracing `yaml:"tracing"`
|
||||
Cache *Cache `yaml:"cache"`
|
||||
GRPCClientTLS *GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
HTTPServiceTLS HTTPServiceTLS `yaml:"http_service_tls"`
|
||||
OpenCloudURL string `yaml:"opencloud_url" env:"OC_URL" desc:"URL, where OpenCloud is reachable for users." introductionVersion:"1.0.0"`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
Reva *Reva `yaml:"reva"`
|
||||
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services." introductionVersion:"1.0.0"`
|
||||
TransferSecret string `mask:"password" yaml:"transfer_secret,omitempty" env:"REVA_TRANSFER_SECRET" desc:"The secret used for signing the requests towards the data gateway for up- and downloads." introductionVersion:"1.0.0"`
|
||||
SystemUserID string `yaml:"system_user_id" env:"OC_SYSTEM_USER_ID" desc:"ID of the OpenCloud storage-system system user. Admins need to set the ID for the storage-system system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"1.0.0"`
|
||||
SystemUserAPIKey string `mask:"password" yaml:"system_user_api_key" env:"SYSTEM_USER_API_KEY" desc:"API key for all system users." introductionVersion:"1.0.0"`
|
||||
AdminUserID string `yaml:"admin_user_id" env:"OC_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand." introductionVersion:"1.0.0"`
|
||||
MultiTenantEnabled bool `yaml:"multi_tenant_enabled" env:"OC_MULTI_TENANT_ENABLED" desc:"Set this to true to enable multi-tenant support." introductionVersion:"%%NEXT%%"`
|
||||
Log *Log `yaml:"log"`
|
||||
Tracing *Tracing `yaml:"tracing"`
|
||||
Cache *Cache `yaml:"cache"`
|
||||
GRPCClientTLS *GRPCClientTLS `yaml:"grpc_client_tls"`
|
||||
GRPCServiceTLS *GRPCServiceTLS `yaml:"grpc_service_tls"`
|
||||
HTTPServiceTLS HTTPServiceTLS `yaml:"http_service_tls"`
|
||||
HTTPServiceTimeout HTTPServiceTimeout `yaml:"http_service_timeout"`
|
||||
OpenCloudURL string `yaml:"opencloud_url" env:"OC_URL" desc:"URL, where OpenCloud is reachable for users." introductionVersion:"1.0.0"`
|
||||
TokenManager *TokenManager `mask:"struct" yaml:"token_manager"`
|
||||
Reva *Reva `yaml:"reva"`
|
||||
MachineAuthAPIKey string `mask:"password" yaml:"machine_auth_api_key" env:"OC_MACHINE_AUTH_API_KEY" desc:"Machine auth API key used to validate internal requests necessary for the access to resources from other services." introductionVersion:"1.0.0"`
|
||||
TransferSecret string `mask:"password" yaml:"transfer_secret,omitempty" env:"REVA_TRANSFER_SECRET" desc:"The secret used for signing the requests towards the data gateway for up- and downloads." introductionVersion:"1.0.0"`
|
||||
SystemUserID string `yaml:"system_user_id" env:"OC_SYSTEM_USER_ID" desc:"ID of the OpenCloud storage-system system user. Admins need to set the ID for the storage-system system user in this config option which is then used to reference the user. Any reasonable long string is possible, preferably this would be an UUIDv4 format." introductionVersion:"1.0.0"`
|
||||
SystemUserAPIKey string `mask:"password" yaml:"system_user_api_key" env:"SYSTEM_USER_API_KEY" desc:"API key for all system users." introductionVersion:"1.0.0"`
|
||||
AdminUserID string `yaml:"admin_user_id" env:"OC_ADMIN_USER_ID" desc:"ID of a user, that should receive admin privileges. Consider that the UUID can be encoded in some LDAP deployment configurations like in .ldif files. These need to be decoded beforehand." introductionVersion:"1.0.0"`
|
||||
MultiTenantEnabled bool `yaml:"multi_tenant_enabled" env:"OC_MULTI_TENANT_ENABLED" desc:"Set this to true to enable multi-tenant support." introductionVersion:"%%NEXT%%"`
|
||||
|
||||
// NOTE: you will not fing GRPCMaxReceivedMessageSize size being used in the code. The envvar is actually extracted in revas `pool` package: https://github.com/cs3org/reva/blob/edge/pkg/rgrpc/todo/pool/connection.go
|
||||
// It is mentioned here again so it is documented
|
||||
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
// LatestTag is the latest released version plus the dev meta version.
|
||||
// Will be overwritten by the release pipeline
|
||||
// Needs a manual change for every tagged release
|
||||
LatestTag = "3.5.0+dev"
|
||||
LatestTag = "3.7.0+dev"
|
||||
|
||||
// Date indicates the build date.
|
||||
// This has been removed, it looks like you can only replace static strings with recent go versions
|
||||
|
||||
47
pkg/x/net/listener.go
Normal file
47
pkg/x/net/listener.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"io"
|
||||
gonet "net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimeoutListener struct {
|
||||
gonet.Listener
|
||||
ReadTimeout time.Duration
|
||||
}
|
||||
|
||||
func (l TimeoutListener) Accept() (gonet.Conn, error) {
|
||||
c, err := l.Listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TimeoutConn{Conn: c, readTimeout: l.ReadTimeout}, nil
|
||||
}
|
||||
|
||||
type TimeoutConn struct {
|
||||
gonet.Conn
|
||||
readTimeout time.Duration
|
||||
bodyDone bool
|
||||
}
|
||||
|
||||
// Read implements a read with a sliding timeout window.
|
||||
func (c *TimeoutConn) Read(b []byte) (int, error) {
|
||||
if c.readTimeout > 0 && !c.bodyDone {
|
||||
if err := c.SetReadDeadline(time.Now().Add(c.readTimeout)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err := c.Conn.Read(b)
|
||||
if n > 0 && c.readTimeout > 0 && !c.bodyDone {
|
||||
// reset deadline after every successful read
|
||||
_ = c.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
c.bodyDone = true
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gofrs/uuid"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
libregraph "github.com/opencloud-eu/libre-graph-api-go"
|
||||
)
|
||||
|
||||
type educationConfig struct {
|
||||
@@ -119,16 +119,18 @@ func (i *LDAP) CreateEducationSchool(ctx context.Context, school libregraph.Educ
|
||||
}
|
||||
|
||||
// Check that the school number is not already used
|
||||
_, err := i.getSchoolByNumber(school.GetSchoolNumber())
|
||||
switch err {
|
||||
case nil:
|
||||
logger.Debug().Err(errSchoolNumberExists).Str("schoolNumber", school.GetSchoolNumber()).Msg("duplicate school number")
|
||||
return nil, errSchoolNumberExists
|
||||
case ErrNotFound:
|
||||
break
|
||||
default:
|
||||
logger.Error().Err(err).Str("schoolNumber", school.GetSchoolNumber()).Msg("error looking up school by number")
|
||||
return nil, errorcode.New(errorcode.GeneralException, "error looking up school by number")
|
||||
if school.HasSchoolNumber() {
|
||||
_, err := i.getSchoolByNumber(school.GetSchoolNumber())
|
||||
switch err {
|
||||
case nil:
|
||||
logger.Debug().Err(errSchoolNumberExists).Str("schoolNumber", school.GetSchoolNumber()).Msg("duplicate school number")
|
||||
return nil, errSchoolNumberExists
|
||||
case ErrNotFound:
|
||||
break
|
||||
default:
|
||||
logger.Error().Err(err).Str("schoolNumber", school.GetSchoolNumber()).Msg("error looking up school by number")
|
||||
return nil, errorcode.New(errorcode.GeneralException, "error looking up school by number")
|
||||
}
|
||||
}
|
||||
|
||||
attributeTypeAndValue := ldap.AttributeTypeAndValue{
|
||||
@@ -142,7 +144,9 @@ func (i *LDAP) CreateEducationSchool(ctx context.Context, school libregraph.Educ
|
||||
)
|
||||
ar := ldap.NewAddRequest(dn, nil)
|
||||
ar.Attribute(i.educationConfig.schoolAttributeMap.displayName, []string{school.GetDisplayName()})
|
||||
ar.Attribute(i.educationConfig.schoolAttributeMap.schoolNumber, []string{school.GetSchoolNumber()})
|
||||
if school.HasSchoolNumber() {
|
||||
ar.Attribute(i.educationConfig.schoolAttributeMap.schoolNumber, []string{school.GetSchoolNumber()})
|
||||
}
|
||||
if !i.useServerUUID {
|
||||
ar.Attribute(i.educationConfig.schoolAttributeMap.id, []string{uuid.Must(uuid.NewV4()).String()})
|
||||
}
|
||||
@@ -723,18 +727,22 @@ func (i *LDAP) createSchoolModelFromLDAP(e *ldap.Entry) *libregraph.EducationSch
|
||||
if err != nil && !errors.Is(err, errNotSet) {
|
||||
i.logger.Error().Err(err).Str("dn", e.DN).Msg("Error reading termination date for LDAP entry")
|
||||
}
|
||||
if id != "" && displayName != "" && schoolNumber != "" {
|
||||
school := libregraph.NewEducationSchool()
|
||||
school.SetDisplayName(displayName)
|
||||
school.SetSchoolNumber(schoolNumber)
|
||||
school.SetId(id)
|
||||
if t != nil {
|
||||
school.SetTerminationDate(*t)
|
||||
}
|
||||
return school
|
||||
|
||||
if id == "" || displayName == "" {
|
||||
i.logger.Warn().Str("dn", e.DN).Str("id", id).Str("displayName", displayName).Msg("Invalid School. Missing required attribute")
|
||||
return nil
|
||||
}
|
||||
i.logger.Warn().Str("dn", e.DN).Str("id", id).Str("displayName", displayName).Str("schoolNumber", schoolNumber).Msg("Invalid School. Missing required attribute")
|
||||
return nil
|
||||
|
||||
school := libregraph.NewEducationSchool()
|
||||
school.SetDisplayName(displayName)
|
||||
school.SetId(id)
|
||||
if schoolNumber != "" {
|
||||
school.SetSchoolNumber(schoolNumber)
|
||||
}
|
||||
if t != nil {
|
||||
school.SetTerminationDate(*t)
|
||||
}
|
||||
return school
|
||||
}
|
||||
|
||||
func (i *LDAP) getSchoolNumber(e *ldap.Entry) string {
|
||||
|
||||
@@ -83,9 +83,8 @@ func (i *LDAP) GetGroups(ctx context.Context, oreq *godata.GoDataRequest) ([]*li
|
||||
if search != "" {
|
||||
search = ldap.EscapeFilter(search)
|
||||
groupFilter = fmt.Sprintf(
|
||||
"(|(%s=*%s*)(%s=*%s*))",
|
||||
"(%s=*%s*)",
|
||||
i.groupAttributeMap.name, search,
|
||||
i.groupAttributeMap.id, search,
|
||||
)
|
||||
}
|
||||
groupFilter = fmt.Sprintf("(&%s(objectClass=%s)%s)", i.groupFilter, i.groupObjectClass, groupFilter)
|
||||
|
||||
@@ -305,7 +305,7 @@ func TestGetGroupsSearch(t *testing.T) {
|
||||
// only match if the filter contains the search term unquoted
|
||||
lm.On("Search", mock.MatchedBy(
|
||||
func(req *ldap.SearchRequest) bool {
|
||||
return req.Filter == "(&(objectClass=groupOfNames)(|(cn=*term*)(entryUUID=*term*)))"
|
||||
return req.Filter == "(&(objectClass=groupOfNames)(cn=*term*))"
|
||||
})).
|
||||
Return(&ldap.SearchResult{}, nil)
|
||||
b, _ := getMockedBackend(lm, lconfig, &logger)
|
||||
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-05 00:01+0000\n"
|
||||
"POT-Creation-Date: 2025-10-26 00:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
|
||||
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-12 00:00+0000\n"
|
||||
"POT-Creation-Date: 2025-11-01 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Lulufox, 2025\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
|
||||
|
||||
@@ -74,12 +74,6 @@ func (g Graph) PostEducationSchool(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := school.GetSchoolNumberOk(); !ok {
|
||||
logger.Debug().Interface("school", school).Msg("could not create school: missing required attribute")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "Missing Required Attribute")
|
||||
return
|
||||
}
|
||||
|
||||
// validate terminationDate attribute, needs to be "far enough" in the future, terminationDate can be nil (means
|
||||
// termination date is to be deleted
|
||||
if terminationDate, ok := school.GetTerminationDateOk(); ok && terminationDate != nil {
|
||||
|
||||
@@ -232,18 +232,6 @@ var _ = Describe("Schools", func() {
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
|
||||
It("handles missing school number", func() {
|
||||
newSchool = libregraph.NewEducationSchool()
|
||||
newSchool.SetDisplayName("New School")
|
||||
newSchoolJson, err := json.Marshal(newSchool)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/education/schools/", bytes.NewBuffer(newSchoolJson))
|
||||
|
||||
svc.PostEducationSchool(rr, r)
|
||||
Expect(rr.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
|
||||
It("disallows school create ids", func() {
|
||||
newSchool = libregraph.NewEducationSchool()
|
||||
newSchool.SetId("disallowed")
|
||||
|
||||
@@ -76,18 +76,7 @@ func (g Graph) PostEducationUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
identities, ok := u.GetIdentitiesOk()
|
||||
if !ok {
|
||||
logger.Debug().Err(err).Interface("user", u).Msg("could not create education user: missing required Collection: 'identities'")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'identities'")
|
||||
return
|
||||
}
|
||||
if len(identities) < 1 {
|
||||
logger.Debug().Err(err).Interface("user", u).Msg("could not create education user: missing entry in Collection: 'identities'")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Collection: 'identities'")
|
||||
return
|
||||
}
|
||||
for i, identity := range identities {
|
||||
for i, identity := range u.GetIdentities() {
|
||||
if _, ok := identity.GetIssuerOk(); !ok {
|
||||
logger.Debug().Err(err).Interface("user", u).Msgf("could not create education user: missing Attribute in 'identities' Collection Entry %d: 'issuer'", i)
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, fmt.Sprintf("missing Attribute in 'identities' Collection Entry %d: 'issuer'", i))
|
||||
@@ -130,12 +119,6 @@ func (g Graph) PostEducationUser(w http.ResponseWriter, r *http.Request) {
|
||||
u.SetUserType("Member")
|
||||
}
|
||||
|
||||
if _, ok := u.GetPrimaryRoleOk(); !ok {
|
||||
logger.Debug().Err(err).Interface("user", u).Msg("could not create education user: missing required Attribute: 'primaryRole'")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "missing required Attribute: 'primaryRole'")
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug().Interface("user", u).Msg("calling create education user on backend")
|
||||
if u, err = g.identityEducationBackend.CreateEducationUser(r.Context(), *u); err != nil {
|
||||
logger.Debug().Err(err).Msg("could not create education user: backend error")
|
||||
|
||||
@@ -3,9 +3,12 @@ package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
stdmail "net/mail"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
@@ -118,6 +121,7 @@ func (m Mail) SendMessage(_ context.Context, message *Message) error {
|
||||
email := mail.NewMSG()
|
||||
email.SetFrom(appendSender(message.Sender, m.smtpAddress)).AddTo(message.Recipient...)
|
||||
email.SetSubject(message.Subject)
|
||||
email.AddHeader("Message-ID", generateMessageID(m.smtpAddress.Address))
|
||||
email.SetBody(mail.TextPlain, message.TextBody)
|
||||
if message.HTMLBody != "" {
|
||||
email.AddAlternative(mail.TextHTML, message.HTMLBody)
|
||||
@@ -135,3 +139,22 @@ func appendSender(sender string, a stdmail.Address) string {
|
||||
}
|
||||
return a.String()
|
||||
}
|
||||
|
||||
// generateMessageID generates a unique Message-ID header value according to RFC 5322
|
||||
func generateMessageID(domain string) string {
|
||||
// Extract domain from email address if it contains @
|
||||
if idx := strings.LastIndex(domain, "@"); idx != -1 {
|
||||
domain = domain[idx+1:]
|
||||
}
|
||||
|
||||
// Generate random bytes for uniqueness
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
// Fallback to timestamp-based ID if random fails
|
||||
return fmt.Sprintf("<%d@%s>", time.Now().UnixNano(), domain)
|
||||
}
|
||||
|
||||
// Create Message-ID: <timestamp.random@domain>
|
||||
timestamp := time.Now().Unix()
|
||||
return fmt.Sprintf("<%d.%x@%s>", timestamp, b, domain)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-13 00:01+0000\n"
|
||||
"POT-Creation-Date: 2025-11-02 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: miguel tapias, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/opencloud-eu/teams/204053/es/)\n"
|
||||
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-05 00:01+0000\n"
|
||||
"POT-Creation-Date: 2025-10-26 00:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
|
||||
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-07 00:00+0000\n"
|
||||
"POT-Creation-Date: 2025-10-27 00:01+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Lulufox, 2025\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/opencloud-eu/teams/204053/ru/)\n"
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go-micro.dev/v4"
|
||||
|
||||
pkgcrypto "github.com/opencloud-eu/opencloud/pkg/crypto"
|
||||
"github.com/opencloud-eu/opencloud/pkg/service/http"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
"go-micro.dev/v4"
|
||||
)
|
||||
|
||||
// Server initializes the http service and server.
|
||||
@@ -40,6 +41,7 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
Cert: options.Config.HTTP.TLSCert,
|
||||
Key: options.Config.HTTP.TLSKey,
|
||||
}),
|
||||
http.TimeoutConfig(options.Config.Commons.HTTPServiceTimeout),
|
||||
http.Logger(options.Logger),
|
||||
http.Address(options.Config.HTTP.Addr),
|
||||
http.Namespace(options.Config.HTTP.Namespace),
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
# Translators:
|
||||
# idoet <idoet@protonmail.ch>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-29 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: idoet <idoet@protonmail.ch>, 2025\n"
|
||||
"Language-Team: Indonesian (https://app.transifex.com/opencloud-eu/teams/204053/id/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: id\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. name of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:20
|
||||
msgid "Added as space member"
|
||||
msgstr "Ditambahkan menjadi anggota ruang penyimpanan"
|
||||
|
||||
#. translation for the 'daily' email interval option
|
||||
#: pkg/store/defaults/templates.go:50
|
||||
msgid "Daily"
|
||||
msgstr "Harian"
|
||||
|
||||
#. name of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:44
|
||||
msgid "Email sending interval"
|
||||
msgstr "Interval pengiriman email"
|
||||
|
||||
#. name of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:40
|
||||
msgid "File rejected"
|
||||
msgstr "Berkas ditolak"
|
||||
|
||||
#. translation for the 'instant' email interval option
|
||||
#: pkg/store/defaults/templates.go:48
|
||||
msgid "Instant"
|
||||
msgstr "Instan"
|
||||
|
||||
#. translation for the 'never' email interval option
|
||||
#: pkg/store/defaults/templates.go:54
|
||||
msgid "Never"
|
||||
msgstr "Tidak pernah"
|
||||
|
||||
#. description of the notification option 'Space Shared'
|
||||
#: pkg/store/defaults/templates.go:22
|
||||
msgid "Notify when I have been added as a member to a space"
|
||||
msgstr ""
|
||||
"Beri tahu ketika saya telah ditambahkan menjadi anggota dari suatu ruang "
|
||||
"penyimpanan"
|
||||
|
||||
#. description of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:26
|
||||
msgid "Notify when I have been removed as member from a space"
|
||||
msgstr ""
|
||||
"Beri tahu ketika saya telah dihapus dari keanggotaan suatu ruang penyimpanan"
|
||||
|
||||
#. description of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:10
|
||||
msgid "Notify when I have received a share"
|
||||
msgstr "Beri tahu ketika saya telah menerima berbagi"
|
||||
|
||||
#. description of the notification option 'File Rejected'
|
||||
#: pkg/store/defaults/templates.go:42
|
||||
msgid ""
|
||||
"Notify when a file I uploaded was rejected because of a virus infection or "
|
||||
"policy violation"
|
||||
msgstr ""
|
||||
"Beri tahu jika file yang saya unggah ditolak karena infeksi virus atau "
|
||||
"pelanggaran kebijakan"
|
||||
|
||||
#. description of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:14
|
||||
msgid "Notify when a received share has been removed"
|
||||
msgstr "Beri tahu saat berbagi yang diterima telah dihapus"
|
||||
|
||||
#. description of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:18
|
||||
msgid "Notify when a received share has expired"
|
||||
msgstr "Beri tahu ketika berbagi yang diterima telah berakhir"
|
||||
|
||||
#. description of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:38
|
||||
msgid "Notify when a space I am member of has been deleted"
|
||||
msgstr "Beri tahu ketika ruang penyimpanan yang saya ikuti telah dihapus"
|
||||
|
||||
#. description of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:34
|
||||
msgid "Notify when a space I am member of has been disabled"
|
||||
msgstr ""
|
||||
"Beri tahu ketika ruang penyimpanan yang saya ikuti telah dinonaktifkan"
|
||||
|
||||
#. description of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:30
|
||||
msgid "Notify when a space membership has expired"
|
||||
msgstr "Beri tahu saat keanggotaan ruang penyimpanan telah berakhir"
|
||||
|
||||
#. name of the notification option 'Space Unshared'
|
||||
#: pkg/store/defaults/templates.go:24
|
||||
msgid "Removed as space member"
|
||||
msgstr "Dihapus dari anggota ruang penyimpanan"
|
||||
|
||||
#. description of the notification option 'Email Interval'
|
||||
#: pkg/store/defaults/templates.go:46
|
||||
msgid "Selected value:"
|
||||
msgstr "Yang dipilih:"
|
||||
|
||||
#. name of the notification option 'Share Expired'
|
||||
#: pkg/store/defaults/templates.go:16
|
||||
msgid "Share Expired"
|
||||
msgstr "Berbagi Berakhir"
|
||||
|
||||
#. name of the notification option 'Share Received'
|
||||
#: pkg/store/defaults/templates.go:8
|
||||
msgid "Share Received"
|
||||
msgstr "Berbagi Diterima"
|
||||
|
||||
#. name of the notification option 'Share Removed'
|
||||
#: pkg/store/defaults/templates.go:12
|
||||
msgid "Share Removed"
|
||||
msgstr "Berbagi Dihapus"
|
||||
|
||||
#. name of the notification option 'Space Deleted'
|
||||
#: pkg/store/defaults/templates.go:36
|
||||
msgid "Space deleted"
|
||||
msgstr "Ruang penyimpanan dihapus"
|
||||
|
||||
#. name of the notification option 'Space Disabled'
|
||||
#: pkg/store/defaults/templates.go:32
|
||||
msgid "Space disabled"
|
||||
msgstr "Ruang penyimpanan dinonaktifkan"
|
||||
|
||||
#. name of the notification option 'Space Membership Expired'
|
||||
#: pkg/store/defaults/templates.go:28
|
||||
msgid "Space membership expired"
|
||||
msgstr "Keanggotaan ruang penyimpanan telah berakhir"
|
||||
|
||||
#. translation for the 'weekly' email interval option
|
||||
#: pkg/store/defaults/templates.go:52
|
||||
msgid "Weekly"
|
||||
msgstr "Mingguan"
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2025-10-05 00:01+0000\n"
|
||||
"POT-Creation-Date: 2025-10-26 00:00+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2025\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/opencloud-eu/teams/204053/nl/)\n"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
SHELL := bash
|
||||
NAME := web
|
||||
WEB_ASSETS_VERSION = v4.1.0
|
||||
WEB_ASSETS_VERSION = v4.2.0
|
||||
WEB_ASSETS_BRANCH = main
|
||||
|
||||
ifneq (, $(shell command -v go 2> /dev/null)) # suppress `command not found warnings` for non go targets in CI
|
||||
|
||||
@@ -197,34 +197,6 @@ class GraphHelper {
|
||||
return $baseUrl . '/graph/v1beta1/' . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $method
|
||||
* @param string $path
|
||||
* @param string|null $body
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public static function createRequest(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $method,
|
||||
string $path,
|
||||
?string $body = null,
|
||||
?array $headers = []
|
||||
): RequestInterface {
|
||||
$fullUrl = self::getFullUrl($baseUrl, $path);
|
||||
return HttpRequestHelper::createRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
@@ -1908,7 +1880,7 @@ class GraphHelper {
|
||||
string $permissionsId
|
||||
): ResponseInterface {
|
||||
$url = self::getBetaFullUrl($baseUrl, "drives/$spaceId/items/$itemId/permissions/$permissionsId");
|
||||
return HttpRequestHelper::sendRequestOnce(
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'PATCH',
|
||||
@@ -2264,7 +2236,7 @@ class GraphHelper {
|
||||
): ResponseInterface {
|
||||
$url = self::getBetaFullUrl($baseUrl, "drives/$spaceId/root/permissions/$permissionsId");
|
||||
|
||||
return HttpRequestHelper::sendRequestOnce(
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'PATCH',
|
||||
|
||||
@@ -74,6 +74,7 @@ class HttpRequestHelper {
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
* @param string|null $bearerToken
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
@@ -90,7 +91,8 @@ class HttpRequestHelper {
|
||||
?CookieJar $cookies = null,
|
||||
bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null
|
||||
?Client $client = null,
|
||||
?string $bearerToken = null
|
||||
): ResponseInterface {
|
||||
if ($client === null) {
|
||||
$client = self::createClient(
|
||||
@@ -99,7 +101,8 @@ class HttpRequestHelper {
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout
|
||||
$timeout,
|
||||
$bearerToken
|
||||
);
|
||||
}
|
||||
|
||||
@@ -200,6 +203,13 @@ class HttpRequestHelper {
|
||||
} else {
|
||||
$debugResponses = false;
|
||||
}
|
||||
// use basic auth for 'public' user or no user
|
||||
if ($user === 'public' || $user === null || $user === '') {
|
||||
$bearerToken = null;
|
||||
} else {
|
||||
$useBearerToken = TokenHelper::useBearerToken();
|
||||
$bearerToken = $useBearerToken ? TokenHelper::getTokens($user, $password, $url)['access_token'] : null;
|
||||
}
|
||||
|
||||
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
|
||||
$sendCount = 0;
|
||||
@@ -217,7 +227,8 @@ class HttpRequestHelper {
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$client
|
||||
$client,
|
||||
$bearerToken,
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() >= 400
|
||||
@@ -348,6 +359,7 @@ class HttpRequestHelper {
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param string|null $bearerToken
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
@@ -357,10 +369,13 @@ class HttpRequestHelper {
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false,
|
||||
?int $timeout = 0
|
||||
?int $timeout = 0,
|
||||
?string $bearerToken = null
|
||||
): Client {
|
||||
$options = [];
|
||||
if ($user !== null) {
|
||||
if ($bearerToken !== null) {
|
||||
$options['headers']['Authorization'] = 'Bearer ' . $bearerToken;
|
||||
} elseif ($user !== null) {
|
||||
$options['auth'] = [$user, $password];
|
||||
}
|
||||
if ($config !== null) {
|
||||
|
||||
403
tests/acceptance/TestHelpers/TokenHelper.php
Normal file
403
tests/acceptance/TestHelpers/TokenHelper.php
Normal file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Viktor Scharf <v.scharf@opencloud.eu>
|
||||
* @copyright Copyright (c) 2025 Viktor Scharf <v.scharf@opencloud.eu>
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License,
|
||||
* as published by the Free Software Foundation;
|
||||
* either version 3 of the License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Helper for obtaining bearer tokens for users
|
||||
*/
|
||||
class TokenHelper {
|
||||
private const LOGON_URL = '/signin/v1/identifier/_/logon';
|
||||
private const REDIRECT_URL = '/oidc-callback.html';
|
||||
private const TOKEN_URL = '/konnect/v1/token';
|
||||
|
||||
// Static cache [username => token_data]
|
||||
private static array $tokenCache = [];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function useBearerToken(): bool {
|
||||
return \getenv('USE_BEARER_TOKEN') === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts base URL from a full URL
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string the base URL
|
||||
*/
|
||||
private static function extractBaseUrl(string $url): string {
|
||||
return preg_replace('#(https?://[^/]+).*#', '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access and refresh tokens for a user
|
||||
* Uses cache to avoid unnecessary requests
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $url
|
||||
*
|
||||
* @return array ['access_token' => string, 'refresh_token' => string, 'expires_at' => int]
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getTokens(string $username, string $password, string $url): array {
|
||||
// Extract base URL. I need to send $url to get correct server in case of multiple servers (ocm suite)
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
|
||||
// Check cache
|
||||
if (isset(self::$tokenCache[$cacheKey])) {
|
||||
$cachedToken = self::$tokenCache[$cacheKey];
|
||||
|
||||
// Check if access token has expired
|
||||
if (time() < $cachedToken['expires_at']) {
|
||||
return $cachedToken;
|
||||
}
|
||||
|
||||
$refreshedToken = self::refreshToken($cachedToken['refresh_token'], $baseUrl);
|
||||
$tokenData = [
|
||||
'access_token' => $refreshedToken['access_token'],
|
||||
'refresh_token' => $refreshedToken['refresh_token'],
|
||||
'expires_at' => time() + 300 // 5 minutes
|
||||
];
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
// Get new tokens
|
||||
$cookieJar = new CookieJar();
|
||||
|
||||
$continueUrl = self::getAuthorizedEndPoint($username, $password, $baseUrl, $cookieJar);
|
||||
$code = self::getCode($continueUrl, $baseUrl, $cookieJar);
|
||||
$tokens = self::getToken($code, $baseUrl, $cookieJar);
|
||||
|
||||
$tokenData = [
|
||||
'access_token' => $tokens['access_token'],
|
||||
'refresh_token' => $tokens['refresh_token'],
|
||||
'expires_at' => time() + 290 // set expiry to 290 seconds to allow for some buffer
|
||||
];
|
||||
|
||||
// Save to cache
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
*
|
||||
* @param string $refreshToken
|
||||
* @param string $baseUrl
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function refreshToken(string $refreshToken, string $baseUrl): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new Exception(
|
||||
\sprintf(
|
||||
'Token refresh failed: Expected status code 200 but received %d. Message: %s',
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($data['access_token']) || !isset($data['refresh_token'])) {
|
||||
throw new Exception('Missing tokens in refresh response');
|
||||
}
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached tokens for a specific user
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $url
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearUserTokens(string $username, string $url): void {
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
unset(self::$tokenCache[$cacheKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached tokens
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearAllTokens(): void {
|
||||
self::$tokenCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function makeLoginRequest(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): \Psr\Http\Message\ResponseInterface {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
return $client->post(
|
||||
$baseUrl . self::LOGON_URL,
|
||||
[
|
||||
'headers' => [
|
||||
'Kopano-Konnect-XSRF' => '1',
|
||||
'Referer' => $baseUrl,
|
||||
'Content-Type' => 'application/json'
|
||||
],
|
||||
'json' => [
|
||||
'params' => [$username, $password, '1'],
|
||||
'hello' => [
|
||||
'scope' => 'openid profile offline_access email',
|
||||
'client_id' => 'web',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'flow' => 'oidc'
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Login and get continue_uri
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAuthorizedEndPoint(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): string {
|
||||
$response = self::makeLoginRequest($username, $password, $baseUrl, $cookieJar);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new Exception(
|
||||
\sprintf(
|
||||
'Logon failed: Expected status code 200 but received %d. Message: %s',
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($data['hello']['continue_uri'])) {
|
||||
throw new Exception('Missing continue_uri in logon response');
|
||||
}
|
||||
|
||||
return $data['hello']['continue_uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Authorization and get code
|
||||
*
|
||||
* @param string $continueUrl
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getCode(string $continueUrl, string $baseUrl, CookieJar $cookieJar): string {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false, // Disable automatic redirects
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => 'web',
|
||||
'prompt' => 'none',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'response_mode' => 'query',
|
||||
'response_type' => 'code',
|
||||
'scope' => 'openid profile offline_access email'
|
||||
];
|
||||
|
||||
$response = $client->get(
|
||||
$continueUrl,
|
||||
[
|
||||
'query' => $params
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() !== 302) {
|
||||
// Add debugging to understand what is happening
|
||||
$body = $response->getBody()->getContents();
|
||||
throw new Exception(
|
||||
\sprintf(
|
||||
'Authorization failed: Expected status code 302 but received %d. Message: %s. Body: %s',
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase(),
|
||||
$body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$location = $response->getHeader('Location')[0] ?? '';
|
||||
|
||||
if (empty($location)) {
|
||||
throw new Exception('Missing Location header in authorization response');
|
||||
}
|
||||
|
||||
parse_str(parse_url($location, PHP_URL_QUERY), $queryParams);
|
||||
|
||||
// Check for errors
|
||||
if (isset($queryParams['error'])) {
|
||||
throw new Exception(
|
||||
\sprintf(
|
||||
'Authorization error: %s - %s',
|
||||
$queryParams['error'],
|
||||
urldecode($queryParams['error_description'] ?? 'No description')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($queryParams['code'])) {
|
||||
throw new Exception('Missing auth code in redirect URL. Location: ' . $location);
|
||||
}
|
||||
|
||||
return $queryParams['code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Get token
|
||||
*
|
||||
* @param string $code
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
private static function getToken(string $code, string $baseUrl, CookieJar $cookieJar): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'grant_type' => 'authorization_code'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new Exception(
|
||||
\sprintf(
|
||||
'Token request failed: Expected status code 200 but received %d. Message: %s',
|
||||
$response->getStatusCode(),
|
||||
$response->getReasonPhrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($data['access_token']) || !isset($data['refresh_token'])) {
|
||||
throw new Exception('Missing tokens in response');
|
||||
}
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use Behat\Behat\Context\Context;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\TokenHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
|
||||
/**
|
||||
@@ -714,4 +715,27 @@ class AuthContext implements Context {
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user should not be able to log in with wrong password :password
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userShouldNotBeAbleToLogInWithWrongPassword(
|
||||
string $user,
|
||||
string $password
|
||||
): void {
|
||||
TokenHelper::clearUserTokens($user, $this->featureContext->getBaseUrl());
|
||||
$response = TokenHelper::makeLoginRequest(
|
||||
$user,
|
||||
$password,
|
||||
$this->featureContext->getBaseUrl(),
|
||||
new \GuzzleHttp\Cookie\CookieJar()
|
||||
);
|
||||
// why is not 401 returned?
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, 'should not be able to log in', $response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,6 +696,10 @@ class CliContext implements Context {
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorReadsTheFileContent(string $user, string $file): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$body = [
|
||||
@@ -739,6 +743,10 @@ class CliContext implements Context {
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRenamesFile(string $user, string $file, string $newName): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
@@ -763,6 +771,10 @@ class CliContext implements Context {
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorMovesFileToFolder(string $user, string $file, string $folder): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
@@ -874,6 +886,10 @@ class CliContext implements Context {
|
||||
* @return void
|
||||
*/
|
||||
public function theAdminChecksTheAttributeOfFileForUser(string $attribute, string $file, string $user): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$body = [
|
||||
|
||||
@@ -17,6 +17,7 @@ use TestHelpers\GraphHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\TokenHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
@@ -2864,6 +2865,7 @@ class GraphContext implements Context {
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
$this->featureContext->updateUsernameInCreatedUserList($byUser, $userName);
|
||||
TokenHelper::clearUserTokens($byUser, $this->featureContext->getBaseUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,7 @@ use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\GraphHelper;
|
||||
use Laminas\Ldap\Exception\LdapException;
|
||||
use Laminas\Ldap\Ldap;
|
||||
use TestHelpers\TokenHelper;
|
||||
|
||||
/**
|
||||
* Functions for provisioning of users and groups
|
||||
@@ -558,110 +559,65 @@ trait Provisioning {
|
||||
*/
|
||||
public function usersHaveBeenCreated(
|
||||
TableNode $table,
|
||||
bool $useDefault=true,
|
||||
bool $initialize=true
|
||||
bool $useDefault = true,
|
||||
bool $initialize = true
|
||||
) {
|
||||
$this->verifyTableNodeColumns($table, ['username'], ['displayname', 'email', 'password']);
|
||||
$table = $table->getColumnsHash();
|
||||
$users = $this->buildUsersAttributesArray($useDefault, $table);
|
||||
|
||||
$requests = [];
|
||||
$client = HttpRequestHelper::createClient(
|
||||
$this->getAdminUsername(),
|
||||
$this->getAdminPassword()
|
||||
);
|
||||
|
||||
foreach ($users as $userAttributes) {
|
||||
$userName = $userAttributes['userid'];
|
||||
$password = $userAttributes['password'];
|
||||
$displayName = $userAttributes['displayName'];
|
||||
$email = $userAttributes['email'];
|
||||
|
||||
if ($this->isTestingWithLdap()) {
|
||||
$this->createLdapUser($userAttributes);
|
||||
} else {
|
||||
$attributesToCreateUser['userid'] = $userAttributes['userid'];
|
||||
$attributesToCreateUser['password'] = $userAttributes['password'];
|
||||
$attributesToCreateUser['displayname'] = $userAttributes['displayName'];
|
||||
if ($userAttributes['email'] === null) {
|
||||
Assert::assertArrayHasKey(
|
||||
'userid',
|
||||
$userAttributes,
|
||||
__METHOD__ . " userAttributes array does not have key 'userid'"
|
||||
try {
|
||||
$this->createLdapUser($userAttributes);
|
||||
} catch (LdapException $exception) {
|
||||
throw new Exception(
|
||||
__METHOD__ . " cannot create a LDAP user with provided data. Error: $exception"
|
||||
);
|
||||
$attributesToCreateUser['email'] = $userAttributes['userid'] . '@opencloud.eu';
|
||||
} else {
|
||||
$attributesToCreateUser['email'] = $userAttributes['email'];
|
||||
}
|
||||
$body = GraphHelper::prepareCreateUserPayload(
|
||||
$attributesToCreateUser['userid'],
|
||||
$attributesToCreateUser['password'],
|
||||
$attributesToCreateUser['email'],
|
||||
$attributesToCreateUser['displayname']
|
||||
);
|
||||
$request = GraphHelper::createRequest(
|
||||
} else {
|
||||
// Use the same logic as userHasBeenCreated for email generation
|
||||
if ($email === null) {
|
||||
$email = $this->getEmailAddressForUser($userName);
|
||||
if ($email === null) {
|
||||
// escape @ & space if present in userId
|
||||
$email = \str_replace(["@", " "], "", $userName) . '@opencloud.eu';
|
||||
}
|
||||
}
|
||||
|
||||
$userName = $this->getActualUsername($userName);
|
||||
$userName = \trim($userName);
|
||||
|
||||
$response = GraphHelper::createUser(
|
||||
$this->getBaseUrl(),
|
||||
$this->getStepLineRef(),
|
||||
"POST",
|
||||
'users',
|
||||
$body,
|
||||
$this->getAdminUsername(),
|
||||
$this->getAdminPassword(),
|
||||
$userName,
|
||||
$password,
|
||||
$email,
|
||||
$displayName,
|
||||
);
|
||||
// Add the request to the $requests array so that they can be sent in parallel.
|
||||
$requests[] = $request;
|
||||
|
||||
Assert::assertEquals(
|
||||
201,
|
||||
$response->getStatusCode(),
|
||||
__METHOD__ . " cannot create user '$userName' using Graph API.\nResponse:" .
|
||||
json_encode($this->getJsonDecodedResponse($response))
|
||||
);
|
||||
|
||||
$userId = $this->getJsonDecodedResponse($response)['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$exceptionToThrow = null;
|
||||
if (!$this->isTestingWithLdap()) {
|
||||
$results = HttpRequestHelper::sendBatchRequest($requests, $client);
|
||||
// Check all requests to inspect failures.
|
||||
foreach ($results as $key => $e) {
|
||||
if ($e instanceof ClientException) {
|
||||
$responseBody = $this->getJsonDecodedResponse($e->getResponse());
|
||||
$httpStatusCode = $e->getResponse()->getStatusCode();
|
||||
$graphStatusCode = $responseBody['error']['code'];
|
||||
$messageText = $responseBody['error']['message'];
|
||||
$exceptionToThrow = new Exception(
|
||||
__METHOD__ .
|
||||
" Unexpected failure when creating the user '" .
|
||||
$users[$key]['userid'] . "'" .
|
||||
"\nHTTP status $httpStatusCode " .
|
||||
"\nGraph status $graphStatusCode " .
|
||||
"\nError message $messageText"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->addUserToCreatedUsersList($userName, $password, $displayName, $email, $userId ?? null);
|
||||
|
||||
// Create requests for setting displayname and email for the newly created users.
|
||||
// These values cannot be set while creating the user, so we have to edit the newly created user to set these values.
|
||||
foreach ($users as $userAttributes) {
|
||||
if (!$this->isTestingWithLdap()) {
|
||||
// for graph api, we need to save the user id to be able to add it in some group
|
||||
// can be fetched with the "onPremisesSamAccountName" i.e. userid
|
||||
$response = $this->graphContext->adminHasRetrievedUserUsingTheGraphApi($userAttributes['userid']);
|
||||
$userAttributes['id'] = $this->getJsonDecodedResponse($response)['id'];
|
||||
} else {
|
||||
$userAttributes['id'] = null;
|
||||
}
|
||||
$this->addUserToCreatedUsersList(
|
||||
$userAttributes['userid'],
|
||||
$userAttributes['password'],
|
||||
$userAttributes['displayName'],
|
||||
$userAttributes['email'],
|
||||
$userAttributes['id']
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($exceptionToThrow)) {
|
||||
throw $exceptionToThrow;
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
Assert::assertTrue(
|
||||
$this->userExists($user["userid"]),
|
||||
"User '" . $user["userid"] . "' should exist but does not exist"
|
||||
);
|
||||
}
|
||||
|
||||
if ($initialize) {
|
||||
foreach ($users as $user) {
|
||||
$this->initializeUser($user['userid'], $user['password']);
|
||||
if ($initialize) {
|
||||
$this->initializeUser($userName, $password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -841,45 +797,16 @@ trait Provisioning {
|
||||
*/
|
||||
public function userHasBeenDeleted(string $user): void {
|
||||
$user = $this->getActualUsername($user);
|
||||
if ($this->userExists($user)) {
|
||||
if ($this->isTestingWithLdap() && \in_array($user, $this->ldapCreatedUsers)) {
|
||||
$this->deleteLdapUser($user);
|
||||
} else {
|
||||
$response = $this->deleteUser($user);
|
||||
$this->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
WebDavHelper::removeSpaceIdReferenceForUser($user);
|
||||
}
|
||||
if ($this->isTestingWithLdap() && \in_array($user, $this->ldapCreatedUsers)) {
|
||||
$this->deleteLdapUser($user);
|
||||
} else {
|
||||
$response = $this->deleteUser($user);
|
||||
$this->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
WebDavHelper::removeSpaceIdReferenceForUser($user);
|
||||
}
|
||||
Assert::assertFalse(
|
||||
$this->userExists($user),
|
||||
"User '$user' should not exist but does exist"
|
||||
);
|
||||
$this->rememberThatUserIsNotExpectedToExist($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given these users have been initialized:
|
||||
* expects a table of users with the heading
|
||||
* "|username|password|"
|
||||
*
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theseUsersHaveBeenInitialized(TableNode $table): void {
|
||||
foreach ($table as $row) {
|
||||
if (!isset($row ['password'])) {
|
||||
$password = $this->getPasswordForUser($row ['username']);
|
||||
} else {
|
||||
$password = $row ['password'];
|
||||
}
|
||||
$this->initializeUser(
|
||||
$row ['username'],
|
||||
$password
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all the existing groups
|
||||
*
|
||||
@@ -961,13 +888,14 @@ trait Provisioning {
|
||||
$url = $this->getBaseUrl()
|
||||
. "/ocs/v$this->ocsApiVersion.php/cloud/users/$user";
|
||||
}
|
||||
|
||||
HttpRequestHelper::get(
|
||||
$url,
|
||||
$this->getStepLineRef(),
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
if ($password !== '') {
|
||||
HttpRequestHelper::get(
|
||||
$url,
|
||||
$this->getStepLineRef(),
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1162,12 +1090,6 @@ trait Provisioning {
|
||||
}
|
||||
|
||||
$this->addUserToCreatedUsersList($user, $password, $displayName, $email, $userId);
|
||||
|
||||
Assert::assertTrue(
|
||||
$this->userExists($user),
|
||||
"User '$user' should exist but does not exist"
|
||||
);
|
||||
|
||||
$this->initializeUser($user, $password);
|
||||
}
|
||||
|
||||
@@ -1999,21 +1921,15 @@ trait Provisioning {
|
||||
$this->usingServer('LOCAL');
|
||||
foreach ($this->createdUsers as $userData) {
|
||||
$user = $userData['actualUsername'];
|
||||
TokenHelper::clearUserTokens($user, $this->getBaseUrl());
|
||||
$this->deleteUser($user);
|
||||
Assert::assertFalse(
|
||||
$this->userExists($user),
|
||||
"User '$user' should not exist but does exist"
|
||||
);
|
||||
$this->rememberThatUserIsNotExpectedToExist($user);
|
||||
}
|
||||
$this->usingServer('REMOTE');
|
||||
foreach ($this->createdRemoteUsers as $userData) {
|
||||
$user = $userData['actualUsername'];
|
||||
TokenHelper::clearUserTokens($user, $this->getBaseUrl());
|
||||
$this->deleteUser($user);
|
||||
Assert::assertFalse(
|
||||
$this->userExists($user),
|
||||
"User '$user' should not exist but does exist"
|
||||
);
|
||||
$this->rememberThatUserIsNotExpectedToExist($user);
|
||||
}
|
||||
$this->usingServer($previousServer);
|
||||
|
||||
@@ -741,6 +741,17 @@ trait WebDav {
|
||||
$this->setResponse($this->downloadFileWithRange($user, $fileSource, $range));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the user waits for :time seconds for postprocessing to finish
|
||||
*
|
||||
* @param int $time
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function waitForCertainSeconds(int $time): void {
|
||||
\sleep($time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^user "([^"]*)" using password "([^"]*)" should not be able to download file "([^"]*)"$/
|
||||
*
|
||||
|
||||
@@ -120,3 +120,26 @@ Feature: Propfind test
|
||||
| Manager | RDNVWZP |
|
||||
| Space Editor | DNVW |
|
||||
| Space Viewer | |
|
||||
|
||||
@issue-1523
|
||||
Scenario: propfind response contains a restored folder with correct name
|
||||
Given user "Alice" has created a folder "folderMain" in space "Personal"
|
||||
And user "Alice" has deleted folder "folderMain"
|
||||
And user "Alice" has created a folder "folderMain" in space "Personal"
|
||||
When user "Alice" restores the folder with original path "/folderMain" to "/folderMain (1)" using the trashbin API
|
||||
And user "Alice" sends PROPFIND request to space "Personal" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Alice" the PROPFIND response should contain a resource "folderMain" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| oc:permissions | RDNVCKZP |
|
||||
| oc:size | 0 |
|
||||
And as user "Alice" the PROPFIND response should contain a resource "folderMain (1)" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain (1) |
|
||||
| oc:permissions | RDNVCKZP |
|
||||
| oc:size | 0 |
|
||||
|
||||
@@ -219,7 +219,7 @@ Feature: edit user
|
||||
When the user "Brian" resets the password of user "Carol" to "newpassword" using the Graph API
|
||||
Then the HTTP status code should be "403"
|
||||
And the content of file "resetpassword.txt" for user "Carol" using password "1234" should be "test file for reset password"
|
||||
But user "Carol" using password "newpassword" should not be able to download file "resetpassword.txt"
|
||||
And user "Carol" should not be able to log in with wrong password "newpassword"
|
||||
Examples:
|
||||
| user-role | user-role-2 |
|
||||
| Space Admin | Space Admin |
|
||||
|
||||
@@ -542,6 +542,7 @@ Feature: enable or disable sync of incoming shares
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile0.txt" synced
|
||||
And the user "Admin" has deleted a user "Alice"
|
||||
When user "Brian" disables sync of share "textfile0.txt" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
@@ -820,6 +821,7 @@ Feature: enable or disable sync of incoming shares
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "<resource>" synced
|
||||
And user "Brian" has disabled sync of last shared resource
|
||||
When user "Brian" disables sync of share "<resource>" using the Graph API
|
||||
Then the HTTP status code should be "409"
|
||||
|
||||
@@ -14,7 +14,7 @@ Feature: reset user password via CLI command
|
||||
But the command output should not contain "Failed to update user password: entry does not exist"
|
||||
And the administrator has started the server
|
||||
And user "Alice" should be able to create folder "newFolder" using password "newpass"
|
||||
But user "Alice" should not be able to create folder "anotherFolder" using password "%alt1%"
|
||||
But user "Alice" should not be able to log in with wrong password "%alt1%"
|
||||
|
||||
|
||||
Scenario: try to reset password of non-existing user
|
||||
|
||||
@@ -567,3 +567,52 @@ Feature: restore deleted files/folders
|
||||
| dav-path-version |
|
||||
| spaces |
|
||||
| new |
|
||||
|
||||
@issue-1523
|
||||
Scenario Outline: restore deleted folder when folder with same name exists
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has created folder "new"
|
||||
And user "Alice" has uploaded file with content "content" to "new/test.txt"
|
||||
And user "Alice" has deleted folder "new"
|
||||
And user "Alice" has created folder "new"
|
||||
And user "Alice" has uploaded file with content "new content" to "new/new-file.txt"
|
||||
When user "Alice" restores the folder with original path "/new" to "/new (1)" using the trashbin API
|
||||
Then the HTTP status code should be "201"
|
||||
And as "Alice" the following folders should exist
|
||||
| path |
|
||||
| /new |
|
||||
| /new (1) |
|
||||
And as "Alice" the following files should exist
|
||||
| path |
|
||||
| /new/new-file.txt |
|
||||
| /new (1)/test.txt |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| spaces |
|
||||
| new |
|
||||
|
||||
@issue-1523
|
||||
Scenario Outline: restore deleted folder with files when folder with same name exists
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has created folder "folder-a"
|
||||
And user "Alice" has uploaded file with content "content b" to "folder-a/b.txt"
|
||||
And user "Alice" has uploaded file with content "content c" to "folder-a/c.txt"
|
||||
And user "Alice" has deleted file "folder-a/b.txt"
|
||||
And user "Alice" has deleted folder "folder-a"
|
||||
And user "Alice" has created folder "folder-a"
|
||||
When user "Alice" restores the file with original path "folder-a/b.txt" using the trashbin API
|
||||
Then the HTTP status code should be "201"
|
||||
When user "Alice" restores the folder with original path "/folder-a" to "/folder-a (1)" using the trashbin API
|
||||
Then the HTTP status code should be "201"
|
||||
And as "Alice" the following folders should exist
|
||||
| path |
|
||||
| /folder-a |
|
||||
| /folder-a (1) |
|
||||
And as "Alice" the following files should exist
|
||||
| path |
|
||||
| /folder-a/b.txt |
|
||||
| /folder-a (1)/c.txt |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| spaces |
|
||||
| new |
|
||||
|
||||
@@ -50,6 +50,7 @@ Feature: low level tests for upload of chunks
|
||||
| Upload-Metadata | filename ZmlsZS50eHQ= |
|
||||
When user "Alice" sends a chunk to the last created TUS Location with offset "0" and data "123" using the WebDAV API
|
||||
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "4567890" using the WebDAV API
|
||||
And the user waits for "2" seconds for postprocessing to finish
|
||||
And user "Alice" sends a chunk to the last created TUS Location with offset "3" and data "0000000" using the WebDAV API
|
||||
Then the HTTP status code should be "404"
|
||||
And the content of file "/file.txt" for user "Alice" should be "1234567890"
|
||||
|
||||
3
vendor/github.com/blevesearch/bleve/v2/README.md
generated
vendored
3
vendor/github.com/blevesearch/bleve/v2/README.md
generated
vendored
@@ -4,7 +4,6 @@
|
||||
[](https://coveralls.io/github/blevesearch/bleve?branch=master)
|
||||
[](https://pkg.go.dev/github.com/blevesearch/bleve/v2)
|
||||
[](https://app.gitter.im/#/room/#blevesearch_bleve:gitter.im)
|
||||
[](https://codebeat.co/projects/github-com-blevesearch-bleve)
|
||||
[](https://goreportcard.com/report/github.com/blevesearch/bleve/v2)
|
||||
[](https://sourcegraph.com/github.com/blevesearch/bleve?badge)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
@@ -27,6 +26,8 @@ A modern indexing + search library in GO
|
||||
* [synonym search](https://github.com/blevesearch/bleve/blob/master/docs/synonyms.md)
|
||||
* [tf-idf](https://github.com/blevesearch/bleve/blob/master/docs/scoring.md#tf-idf) / [bm25](https://github.com/blevesearch/bleve/blob/master/docs/scoring.md#bm25) scoring models
|
||||
* Hybrid search: exact + semantic
|
||||
* Supports [RRF (Reciprocal Rank Fusion) and RSF (Relative Score Fusion)](docs/score_fusion.md)
|
||||
* [Result pagination](https://github.com/blevesearch/bleve/blob/master/docs/pagination.md)
|
||||
* Query time boosting
|
||||
* Search result match highlighting with document fragments
|
||||
* Aggregations/faceting support:
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/builder.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/builder.go
generated
vendored
@@ -68,7 +68,7 @@ func newBuilder(path string, mapping mapping.IndexMapping, config map[string]int
|
||||
return nil, err
|
||||
}
|
||||
config["internal"] = map[string][]byte{
|
||||
string(mappingInternalKey): mappingBytes,
|
||||
string(util.MappingInternalKey): mappingBytes,
|
||||
}
|
||||
|
||||
// do not use real config, as these are options for the builder,
|
||||
|
||||
26
vendor/github.com/blevesearch/bleve/v2/fusion/fusion.go
generated
vendored
Normal file
26
vendor/github.com/blevesearch/bleve/v2/fusion/fusion.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fusion
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
)
|
||||
|
||||
type FusionResult struct {
|
||||
Hits search.DocumentMatchCollection
|
||||
Total uint64
|
||||
MaxScore float64
|
||||
}
|
||||
131
vendor/github.com/blevesearch/bleve/v2/fusion/rrf.go
generated
vendored
Normal file
131
vendor/github.com/blevesearch/bleve/v2/fusion/rrf.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fusion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
)
|
||||
|
||||
func formatRRFMessage(weight float64, rank int, rankConstant int) string {
|
||||
return fmt.Sprintf("rrf score (weight=%.3f, rank=%d, rank_constant=%d), normalized score of", weight, rank, rankConstant)
|
||||
}
|
||||
|
||||
// ReciprocalRankFusion performs a reciprocal rank fusion on the search results.
|
||||
func ReciprocalRankFusion(hits search.DocumentMatchCollection, weights []float64, rankConstant int, windowSize int, numKNNQueries int, explain bool) FusionResult {
|
||||
if len(hits) == 0 {
|
||||
return FusionResult{
|
||||
Hits: hits,
|
||||
Total: 0,
|
||||
MaxScore: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a map of document ID to a slice of ranks.
|
||||
// The first element of the slice is the rank from the FTS search,
|
||||
// and the subsequent elements are the ranks from the KNN searches.
|
||||
docRanks := make(map[string][]int)
|
||||
|
||||
// Pre-assign rank lists to each candidate document
|
||||
for _, hit := range hits {
|
||||
docRanks[hit.ID] = make([]int, numKNNQueries+1)
|
||||
}
|
||||
|
||||
// Only a max of `window_size` elements need to be counted for. Stop
|
||||
// calculating rank once this threshold is hit.
|
||||
sort.Slice(hits, func(a, b int) bool {
|
||||
return scoreSortFunc()(hits[a], hits[b]) < 0
|
||||
})
|
||||
// Only consider top windowSize docs for rescoring
|
||||
for i := range min(windowSize, len(hits)) {
|
||||
if hits[i].Score != 0.0 {
|
||||
// Skip if Score is 0, since that means the document was not
|
||||
// found as part of FTS, and only in KNN.
|
||||
docRanks[hits[i].ID][0] = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate knnDocs and reuse it within the loop
|
||||
knnDocs := make([]*search.DocumentMatch, 0, len(hits))
|
||||
|
||||
// For each KNN query, rank the documents based on their KNN score.
|
||||
for i := range numKNNQueries {
|
||||
knnDocs = knnDocs[:0]
|
||||
|
||||
for _, hit := range hits {
|
||||
if _, ok := hit.ScoreBreakdown[i]; ok {
|
||||
knnDocs = append(knnDocs, hit)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the documents based on their score for this KNN query.
|
||||
sort.Slice(knnDocs, func(a, b int) bool {
|
||||
return scoreBreakdownSortFunc(i)(knnDocs[a], knnDocs[b]) < 0
|
||||
})
|
||||
|
||||
// Update the ranks of the documents in the docRanks map.
|
||||
// Only consider top windowSize docs for rescoring.
|
||||
for j := range min(windowSize, len(knnDocs)) {
|
||||
docRanks[knnDocs[j].ID][i+1] = j + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the RRF score for each document.
|
||||
var maxScore float64
|
||||
for _, hit := range hits {
|
||||
var rrfScore float64
|
||||
var explChildren []*search.Explanation
|
||||
if explain {
|
||||
explChildren = make([]*search.Explanation, 0, numKNNQueries+1)
|
||||
}
|
||||
for i, rank := range docRanks[hit.ID] {
|
||||
if rank > 0 {
|
||||
partialRrfScore := weights[i] * 1.0 / float64(rankConstant+rank)
|
||||
if explain {
|
||||
expl := getFusionExplAt(
|
||||
hit,
|
||||
i,
|
||||
partialRrfScore,
|
||||
formatRRFMessage(weights[i], rank, rankConstant),
|
||||
)
|
||||
explChildren = append(explChildren, expl)
|
||||
}
|
||||
rrfScore += partialRrfScore
|
||||
}
|
||||
}
|
||||
hit.Score = rrfScore
|
||||
hit.ScoreBreakdown = nil
|
||||
if rrfScore > maxScore {
|
||||
maxScore = rrfScore
|
||||
}
|
||||
|
||||
if explain {
|
||||
finalizeFusionExpl(hit, explChildren)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(hits)
|
||||
if len(hits) > windowSize {
|
||||
hits = hits[:windowSize]
|
||||
}
|
||||
return FusionResult{
|
||||
Hits: hits,
|
||||
Total: uint64(len(hits)),
|
||||
MaxScore: maxScore,
|
||||
}
|
||||
}
|
||||
162
vendor/github.com/blevesearch/bleve/v2/fusion/rsf.go
generated
vendored
Normal file
162
vendor/github.com/blevesearch/bleve/v2/fusion/rsf.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fusion
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
)
|
||||
|
||||
func formatRSFMessage(weight float64, normalizedScore float64, minScore float64, maxScore float64) string {
|
||||
return fmt.Sprintf("rsf score (weight=%.3f, normalized=%.6f, min=%.6f, max=%.6f), normalized score of",
|
||||
weight, normalizedScore, minScore, maxScore)
|
||||
}
|
||||
|
||||
// RelativeScoreFusion normalizes scores based on min/max values for FTS and each KNN query, then applies weights.
|
||||
func RelativeScoreFusion(hits search.DocumentMatchCollection, weights []float64, windowSize int, numKNNQueries int, explain bool) FusionResult {
|
||||
if len(hits) == 0 {
|
||||
return FusionResult{
|
||||
Hits: hits,
|
||||
Total: 0,
|
||||
MaxScore: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
rsfScores := make(map[string]float64)
|
||||
|
||||
// contains the docs under consideration for scoring.
|
||||
// Reused for fts and knn hits
|
||||
scoringDocs := make([]*search.DocumentMatch, 0, len(hits))
|
||||
var explMap map[string][]*search.Explanation
|
||||
if explain {
|
||||
explMap = make(map[string][]*search.Explanation)
|
||||
}
|
||||
// remove non-fts hits
|
||||
for _, hit := range hits {
|
||||
if hit.Score != 0.0 {
|
||||
scoringDocs = append(scoringDocs, hit)
|
||||
}
|
||||
}
|
||||
// sort hits by fts score
|
||||
sort.Slice(scoringDocs, func(a, b int) bool {
|
||||
return scoreSortFunc()(scoringDocs[a], scoringDocs[b]) < 0
|
||||
})
|
||||
// Reslice to correct size
|
||||
if len(scoringDocs) > windowSize {
|
||||
scoringDocs = scoringDocs[:windowSize]
|
||||
}
|
||||
|
||||
var min, max float64
|
||||
if len(scoringDocs) > 0 {
|
||||
min, max = scoringDocs[len(scoringDocs)-1].Score, scoringDocs[0].Score
|
||||
}
|
||||
|
||||
for _, hit := range scoringDocs {
|
||||
var tempRsfScore float64
|
||||
if max > min {
|
||||
tempRsfScore = (hit.Score - min) / (max - min)
|
||||
} else {
|
||||
tempRsfScore = 1.0
|
||||
}
|
||||
|
||||
if explain {
|
||||
// create and replace new explanation
|
||||
expl := getFusionExplAt(
|
||||
hit,
|
||||
0,
|
||||
tempRsfScore,
|
||||
formatRSFMessage(weights[0], tempRsfScore, min, max),
|
||||
)
|
||||
explMap[hit.ID] = append(explMap[hit.ID], expl)
|
||||
}
|
||||
|
||||
rsfScores[hit.ID] = weights[0] * tempRsfScore
|
||||
}
|
||||
|
||||
for i := range numKNNQueries {
|
||||
scoringDocs = scoringDocs[:0]
|
||||
for _, hit := range hits {
|
||||
if _, exists := hit.ScoreBreakdown[i]; exists {
|
||||
scoringDocs = append(scoringDocs, hit)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(scoringDocs, func(a, b int) bool {
|
||||
return scoreBreakdownSortFunc(i)(scoringDocs[a], scoringDocs[b]) < 0
|
||||
})
|
||||
|
||||
if len(scoringDocs) > windowSize {
|
||||
scoringDocs = scoringDocs[:windowSize]
|
||||
}
|
||||
|
||||
if len(scoringDocs) > 0 {
|
||||
min, max = scoringDocs[len(scoringDocs)-1].ScoreBreakdown[i], scoringDocs[0].ScoreBreakdown[i]
|
||||
} else {
|
||||
min, max = 0.0, 0.0
|
||||
}
|
||||
|
||||
for _, hit := range scoringDocs {
|
||||
var tempRsfScore float64
|
||||
if max > min {
|
||||
tempRsfScore = (hit.ScoreBreakdown[i] - min) / (max - min)
|
||||
} else {
|
||||
tempRsfScore = 1.0
|
||||
}
|
||||
|
||||
if explain {
|
||||
expl := getFusionExplAt(
|
||||
hit,
|
||||
i+1,
|
||||
tempRsfScore,
|
||||
formatRSFMessage(weights[i+1], tempRsfScore, min, max),
|
||||
)
|
||||
explMap[hit.ID] = append(explMap[hit.ID], expl)
|
||||
}
|
||||
|
||||
rsfScores[hit.ID] += weights[i+1] * tempRsfScore
|
||||
}
|
||||
}
|
||||
|
||||
var maxScore float64
|
||||
for _, hit := range hits {
|
||||
if rsfScore, exists := rsfScores[hit.ID]; exists {
|
||||
hit.Score = rsfScore
|
||||
if rsfScore > maxScore {
|
||||
maxScore = rsfScore
|
||||
}
|
||||
if explain {
|
||||
finalizeFusionExpl(hit, explMap[hit.ID])
|
||||
}
|
||||
} else {
|
||||
hit.Score = 0.0
|
||||
}
|
||||
|
||||
hit.ScoreBreakdown = nil
|
||||
}
|
||||
|
||||
sort.Sort(hits)
|
||||
|
||||
if len(hits) > windowSize {
|
||||
hits = hits[:windowSize]
|
||||
}
|
||||
|
||||
return FusionResult{
|
||||
Hits: hits,
|
||||
Total: uint64(len(hits)),
|
||||
MaxScore: maxScore,
|
||||
}
|
||||
}
|
||||
96
vendor/github.com/blevesearch/bleve/v2/fusion/util.go
generated
vendored
Normal file
96
vendor/github.com/blevesearch/bleve/v2/fusion/util.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fusion
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
)
|
||||
|
||||
// scoreBreakdownSortFunc returns a comparison function for sorting DocumentMatch objects
|
||||
// by their ScoreBreakdown at the specified index in descending order.
|
||||
// In case of ties, documents with lower HitNumber (earlier hits) are preferred.
|
||||
// If either document is missing the ScoreBreakdown for the specified index,
|
||||
// it's treated as having a score of 0.0.
|
||||
func scoreBreakdownSortFunc(idx int) func(i, j *search.DocumentMatch) int {
|
||||
return func(i, j *search.DocumentMatch) int {
|
||||
// Safely extract scores, defaulting to 0.0 if missing
|
||||
iScore := 0.0
|
||||
jScore := 0.0
|
||||
|
||||
if i.ScoreBreakdown != nil {
|
||||
if score, ok := i.ScoreBreakdown[idx]; ok {
|
||||
iScore = score
|
||||
}
|
||||
}
|
||||
|
||||
if j.ScoreBreakdown != nil {
|
||||
if score, ok := j.ScoreBreakdown[idx]; ok {
|
||||
jScore = score
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by score in descending order (higher scores first)
|
||||
if iScore > jScore {
|
||||
return -1
|
||||
} else if iScore < jScore {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Break ties by HitNumber in ascending order (lower HitNumber wins)
|
||||
if i.HitNumber < j.HitNumber {
|
||||
return -1
|
||||
} else if i.HitNumber > j.HitNumber {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0 // Equal scores and HitNumbers
|
||||
}
|
||||
}
|
||||
|
||||
func scoreSortFunc() func(i, j *search.DocumentMatch) int {
|
||||
return func(i, j *search.DocumentMatch) int {
|
||||
// Sort by score in descending order
|
||||
if i.Score > j.Score {
|
||||
return -1
|
||||
} else if i.Score < j.Score {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Break ties by HitNumber
|
||||
if i.HitNumber < j.HitNumber {
|
||||
return -1
|
||||
} else if i.HitNumber > j.HitNumber {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getFusionExplAt(hit *search.DocumentMatch, i int, value float64, message string) *search.Explanation {
|
||||
return &search.Explanation{
|
||||
Value: value,
|
||||
Message: message,
|
||||
Children: []*search.Explanation{hit.Expl.Children[i]},
|
||||
}
|
||||
}
|
||||
|
||||
func finalizeFusionExpl(hit *search.DocumentMatch, explChildren []*search.Explanation) {
|
||||
hit.Expl.Children = explChildren
|
||||
|
||||
hit.Expl.Value = hit.Score
|
||||
hit.Expl.Message = "sum of"
|
||||
}
|
||||
2
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/geo/README.md
generated
vendored
@@ -308,5 +308,5 @@ First, all of this geo code is a Go adaptation of the [Lucene 5.3.2 sandbox geo
|
||||
- LineStrings and MultiLineStrings may only contain Points and MultiPoints.
|
||||
- Polygons or MultiPolygons intersecting Polygons and MultiPolygons may return arbitrary results when the overlap is only an edge or a vertex.
|
||||
- Circles containing polygon will return a false positive result if all of the vertices of the polygon are within the circle, but the orientation of those points are clock-wise.
|
||||
- The edges of an Envelope follows the latitude and logitude lines instead of the shortest path on a globe.
|
||||
- The edges of an Envelope follows the latitude and longitude lines instead of the shortest path on a globe.
|
||||
- Envelope intersecting queries with LineStrings, MultiLineStrings, Polygons and MultiPolygons implicitly converts the Envelope into a Polygon which changes the curvature of the edges causing inaccurate results for few edge cases.
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/geo/geo.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/geo/geo.go
generated
vendored
@@ -114,7 +114,7 @@ func DegreesToRadians(d float64) float64 {
|
||||
return d * degreesToRadian
|
||||
}
|
||||
|
||||
// RadiansToDegrees converts an angle in radians to degress
|
||||
// RadiansToDegrees converts an angle in radians to degrees
|
||||
func RadiansToDegrees(r float64) float64 {
|
||||
return r * radiansToDegrees
|
||||
}
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/geo/geo_dist.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/geo/geo_dist.go
generated
vendored
@@ -83,7 +83,7 @@ func ParseDistanceUnit(u string) (float64, error) {
|
||||
}
|
||||
|
||||
// Haversin computes the distance between two points.
|
||||
// This implemenation uses the sloppy math implemenations which trade off
|
||||
// This implementation uses the sloppy math implementations which trade off
|
||||
// accuracy for performance. The distance returned is in kilometers.
|
||||
func Haversin(lon1, lat1, lon2, lat2 float64) float64 {
|
||||
x1 := lat1 * degreesToRadian
|
||||
|
||||
4
vendor/github.com/blevesearch/bleve/v2/index.go
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/index.go
generated
vendored
@@ -149,7 +149,7 @@ func (b *Batch) String() string {
|
||||
}
|
||||
|
||||
// Reset returns a Batch to the empty state so that it can
|
||||
// be re-used in the future.
|
||||
// be reused in the future.
|
||||
func (b *Batch) Reset() {
|
||||
b.internal.Reset()
|
||||
b.lastDocSize = 0
|
||||
@@ -325,6 +325,8 @@ func Open(path string) (Index, error) {
|
||||
// The mapping used when it was created will be used for all Index/Search operations.
|
||||
// The provided runtimeConfig can override settings
|
||||
// persisted when the kvstore was created.
|
||||
// If runtimeConfig has updated mapping, then an index update is attempted
|
||||
// Throws an error without any changes to the index if an unupdatable mapping is provided
|
||||
func OpenUsing(path string, runtimeConfig map[string]interface{}) (Index, error) {
|
||||
return openIndexUsing(path, runtimeConfig)
|
||||
}
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/introducer.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/introducer.go
generated
vendored
@@ -293,7 +293,7 @@ func (s *Scorch) introducePersist(persist *persistIntroduction) {
|
||||
newIndexSnapshot.segment[i] = newSegmentSnapshot
|
||||
delete(persist.persisted, segmentSnapshot.id)
|
||||
|
||||
// update items persisted incase of a new segment snapshot
|
||||
// update items persisted in case of a new segment snapshot
|
||||
atomic.AddUint64(&s.stats.TotPersistedItems, newSegmentSnapshot.Count())
|
||||
atomic.AddUint64(&s.stats.TotPersistedSegments, 1)
|
||||
fileSegments++
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/mergeplan/merge_plan.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/mergeplan/merge_plan.go
generated
vendored
@@ -295,7 +295,7 @@ func plan(segmentsIn []Segment, o *MergePlanOptions) (*MergePlan, error) {
|
||||
if len(bestRoster) == 0 {
|
||||
return rv, nil
|
||||
}
|
||||
// create tasks with valid merges - i.e. there should be atleast 2 non-empty segments
|
||||
// create tasks with valid merges - i.e. there should be at least 2 non-empty segments
|
||||
if len(bestRoster) > 1 {
|
||||
rv.Tasks = append(rv.Tasks, &MergeTask{Segments: bestRoster})
|
||||
}
|
||||
|
||||
8
vendor/github.com/blevesearch/bleve/v2/index/scorch/optimize_knn.go
generated
vendored
8
vendor/github.com/blevesearch/bleve/v2/index/scorch/optimize_knn.go
generated
vendored
@@ -79,6 +79,12 @@ func (o *OptimizeVR) Finish() error {
|
||||
wg.Done()
|
||||
}()
|
||||
for field, vrs := range o.vrs {
|
||||
// Early exit if the field is supposed to be completely deleted or
|
||||
// if it's index data has been deleted
|
||||
if info, ok := o.snapshot.updatedFields[field]; ok && (info.Deleted || info.Index) {
|
||||
continue
|
||||
}
|
||||
|
||||
vecIndex, err := segment.InterpretVectorIndex(field,
|
||||
o.requiresFiltering, origSeg.deleted)
|
||||
if err != nil {
|
||||
@@ -185,7 +191,7 @@ func (s *IndexSnapshotVectorReader) VectorOptimize(ctx context.Context,
|
||||
err := cbF(sumVectorIndexSize)
|
||||
if err != nil {
|
||||
// it's important to invoke the end callback at this point since
|
||||
// if the earlier searchers of this optimze struct were successful
|
||||
// if the earlier searchers of this optimize struct were successful
|
||||
// the cost corresponding to it would be incremented and if the
|
||||
// current searcher fails the check then we end up erroring out
|
||||
// the overall optimized searcher creation, the cost needs to be
|
||||
|
||||
116
vendor/github.com/blevesearch/bleve/v2/index/scorch/persister.go
generated
vendored
116
vendor/github.com/blevesearch/bleve/v2/index/scorch/persister.go
generated
vendored
@@ -386,7 +386,7 @@ type flushable struct {
|
||||
totDocs uint64
|
||||
}
|
||||
|
||||
// number workers which parallely perform an in-memory merge of the segments
|
||||
// number workers which parallelly perform an in-memory merge of the segments
|
||||
// followed by a flush operation.
|
||||
var DefaultNumPersisterWorkers = 1
|
||||
|
||||
@@ -395,7 +395,7 @@ var DefaultNumPersisterWorkers = 1
|
||||
var DefaultMaxSizeInMemoryMergePerWorker = 0
|
||||
|
||||
func legacyFlushBehaviour(maxSizeInMemoryMergePerWorker, numPersisterWorkers int) bool {
|
||||
// DefaultMaxSizeInMemoryMergePerWorker = 0 is a special value to preserve the leagcy
|
||||
// DefaultMaxSizeInMemoryMergePerWorker = 0 is a special value to preserve the legacy
|
||||
// one-shot in-memory merge + flush behaviour.
|
||||
return maxSizeInMemoryMergePerWorker == 0 && numPersisterWorkers == 1
|
||||
}
|
||||
@@ -608,7 +608,7 @@ func persistToDirectory(seg segment.UnpersistedSegment, d index.Directory,
|
||||
func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
segPlugin SegmentPlugin, exclude map[uint64]struct{}, d index.Directory) (
|
||||
[]string, map[uint64]string, error) {
|
||||
snapshotsBucket, err := tx.CreateBucketIfNotExists(boltSnapshotsBucket)
|
||||
snapshotsBucket, err := tx.CreateBucketIfNotExists(util.BoltSnapshotsBucket)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -619,17 +619,17 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
}
|
||||
|
||||
// persist meta values
|
||||
metaBucket, err := snapshotBucket.CreateBucketIfNotExists(boltMetaDataKey)
|
||||
metaBucket, err := snapshotBucket.CreateBucketIfNotExists(util.BoltMetaDataKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = metaBucket.Put(boltMetaDataSegmentTypeKey, []byte(segPlugin.Type()))
|
||||
err = metaBucket.Put(util.BoltMetaDataSegmentTypeKey, []byte(segPlugin.Type()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
buf := make([]byte, binary.MaxVarintLen32)
|
||||
binary.BigEndian.PutUint32(buf, segPlugin.Version())
|
||||
err = metaBucket.Put(boltMetaDataSegmentVersionKey, buf)
|
||||
err = metaBucket.Put(util.BoltMetaDataSegmentVersionKey, buf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -643,13 +643,13 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = metaBucket.Put(boltMetaDataTimeStamp, timeStampBinary)
|
||||
err = metaBucket.Put(util.BoltMetaDataTimeStamp, timeStampBinary)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// persist internal values
|
||||
internalBucket, err := snapshotBucket.CreateBucketIfNotExists(boltInternalKey)
|
||||
internalBucket, err := snapshotBucket.CreateBucketIfNotExists(util.BoltInternalKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -665,7 +665,7 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
val := make([]byte, 8)
|
||||
bytesWritten := atomic.LoadUint64(&snapshot.parent.stats.TotBytesWrittenAtIndexTime)
|
||||
binary.LittleEndian.PutUint64(val, bytesWritten)
|
||||
err = internalBucket.Put(TotBytesWrittenKey, val)
|
||||
err = internalBucket.Put(util.TotBytesWrittenKey, val)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -689,7 +689,7 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
return nil, nil, fmt.Errorf("segment: %s copy err: %v", segPath, err)
|
||||
}
|
||||
filename := filepath.Base(segPath)
|
||||
err = snapshotSegmentBucket.Put(boltPathKey, []byte(filename))
|
||||
err = snapshotSegmentBucket.Put(util.BoltPathKey, []byte(filename))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -705,7 +705,7 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
return nil, nil, fmt.Errorf("segment: %s persist err: %v", path, err)
|
||||
}
|
||||
newSegmentPaths[segmentSnapshot.id] = path
|
||||
err = snapshotSegmentBucket.Put(boltPathKey, []byte(filename))
|
||||
err = snapshotSegmentBucket.Put(util.BoltPathKey, []byte(filename))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -721,7 +721,7 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error persisting roaring bytes: %v", err)
|
||||
}
|
||||
err = snapshotSegmentBucket.Put(boltDeletedKey, roaringBuf.Bytes())
|
||||
err = snapshotSegmentBucket.Put(util.BoltDeletedKey, roaringBuf.Bytes())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -733,7 +733,19 @@ func prepareBoltSnapshot(snapshot *IndexSnapshot, tx *bolt.Tx, path string,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = snapshotSegmentBucket.Put(boltStatsKey, b)
|
||||
err = snapshotSegmentBucket.Put(util.BoltStatsKey, b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// store updated field info
|
||||
if segmentSnapshot.updatedFields != nil {
|
||||
b, err := json.Marshal(segmentSnapshot.updatedFields)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = snapshotSegmentBucket.Put(util.BoltUpdatedFieldsKey, b)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -832,22 +844,9 @@ func zapFileName(epoch uint64) string {
|
||||
|
||||
// bolt snapshot code
|
||||
|
||||
var (
|
||||
boltSnapshotsBucket = []byte{'s'}
|
||||
boltPathKey = []byte{'p'}
|
||||
boltDeletedKey = []byte{'d'}
|
||||
boltInternalKey = []byte{'i'}
|
||||
boltMetaDataKey = []byte{'m'}
|
||||
boltMetaDataSegmentTypeKey = []byte("type")
|
||||
boltMetaDataSegmentVersionKey = []byte("version")
|
||||
boltMetaDataTimeStamp = []byte("timeStamp")
|
||||
boltStatsKey = []byte("stats")
|
||||
TotBytesWrittenKey = []byte("TotBytesWritten")
|
||||
)
|
||||
|
||||
func (s *Scorch) loadFromBolt() error {
|
||||
err := s.rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -912,7 +911,7 @@ func (s *Scorch) loadFromBolt() error {
|
||||
// NOTE: this is currently ONLY intended to be used by the command-line tool
|
||||
func (s *Scorch) LoadSnapshot(epoch uint64) (rv *IndexSnapshot, err error) {
|
||||
err = s.rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -940,14 +939,14 @@ func (s *Scorch) loadSnapshot(snapshot *bolt.Bucket) (*IndexSnapshot, error) {
|
||||
// first we look for the meta-data bucket, this will tell us
|
||||
// which segment type/version was used for this snapshot
|
||||
// all operations for this scorch will use this type/version
|
||||
metaBucket := snapshot.Bucket(boltMetaDataKey)
|
||||
metaBucket := snapshot.Bucket(util.BoltMetaDataKey)
|
||||
if metaBucket == nil {
|
||||
_ = rv.DecRef()
|
||||
return nil, fmt.Errorf("meta-data bucket missing")
|
||||
}
|
||||
segmentType := string(metaBucket.Get(boltMetaDataSegmentTypeKey))
|
||||
segmentType := string(metaBucket.Get(util.BoltMetaDataSegmentTypeKey))
|
||||
segmentVersion := binary.BigEndian.Uint32(
|
||||
metaBucket.Get(boltMetaDataSegmentVersionKey))
|
||||
metaBucket.Get(util.BoltMetaDataSegmentVersionKey))
|
||||
err := s.loadSegmentPlugin(segmentType, segmentVersion)
|
||||
if err != nil {
|
||||
_ = rv.DecRef()
|
||||
@@ -957,7 +956,7 @@ func (s *Scorch) loadSnapshot(snapshot *bolt.Bucket) (*IndexSnapshot, error) {
|
||||
var running uint64
|
||||
c := snapshot.Cursor()
|
||||
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
||||
if k[0] == boltInternalKey[0] {
|
||||
if k[0] == util.BoltInternalKey[0] {
|
||||
internalBucket := snapshot.Bucket(k)
|
||||
if internalBucket == nil {
|
||||
_ = rv.DecRef()
|
||||
@@ -972,11 +971,11 @@ func (s *Scorch) loadSnapshot(snapshot *bolt.Bucket) (*IndexSnapshot, error) {
|
||||
_ = rv.DecRef()
|
||||
return nil, err
|
||||
}
|
||||
} else if k[0] != boltMetaDataKey[0] {
|
||||
} else if k[0] != util.BoltMetaDataKey[0] {
|
||||
segmentBucket := snapshot.Bucket(k)
|
||||
if segmentBucket == nil {
|
||||
_ = rv.DecRef()
|
||||
return nil, fmt.Errorf("segment key, but bucket missing % x", k)
|
||||
return nil, fmt.Errorf("segment key, but bucket missing %x", k)
|
||||
}
|
||||
segmentSnapshot, err := s.loadSegment(segmentBucket)
|
||||
if err != nil {
|
||||
@@ -990,6 +989,10 @@ func (s *Scorch) loadSnapshot(snapshot *bolt.Bucket) (*IndexSnapshot, error) {
|
||||
}
|
||||
rv.segment = append(rv.segment, segmentSnapshot)
|
||||
rv.offsets = append(rv.offsets, running)
|
||||
// Merge all segment level updated field info for use during queries
|
||||
if segmentSnapshot.updatedFields != nil {
|
||||
rv.MergeUpdateFieldsInfo(segmentSnapshot.updatedFields)
|
||||
}
|
||||
running += segmentSnapshot.segment.Count()
|
||||
}
|
||||
}
|
||||
@@ -997,46 +1000,59 @@ func (s *Scorch) loadSnapshot(snapshot *bolt.Bucket) (*IndexSnapshot, error) {
|
||||
}
|
||||
|
||||
func (s *Scorch) loadSegment(segmentBucket *bolt.Bucket) (*SegmentSnapshot, error) {
|
||||
pathBytes := segmentBucket.Get(boltPathKey)
|
||||
pathBytes := segmentBucket.Get(util.BoltPathKey)
|
||||
if pathBytes == nil {
|
||||
return nil, fmt.Errorf("segment path missing")
|
||||
}
|
||||
segmentPath := s.path + string(os.PathSeparator) + string(pathBytes)
|
||||
segment, err := s.segPlugin.Open(segmentPath)
|
||||
seg, err := s.segPlugin.Open(segmentPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening bolt segment: %v", err)
|
||||
}
|
||||
|
||||
rv := &SegmentSnapshot{
|
||||
segment: segment,
|
||||
segment: seg,
|
||||
cachedDocs: &cachedDocs{cache: nil},
|
||||
cachedMeta: &cachedMeta{meta: nil},
|
||||
}
|
||||
deletedBytes := segmentBucket.Get(boltDeletedKey)
|
||||
deletedBytes := segmentBucket.Get(util.BoltDeletedKey)
|
||||
if deletedBytes != nil {
|
||||
deletedBitmap := roaring.NewBitmap()
|
||||
r := bytes.NewReader(deletedBytes)
|
||||
_, err := deletedBitmap.ReadFrom(r)
|
||||
if err != nil {
|
||||
_ = segment.Close()
|
||||
_ = seg.Close()
|
||||
return nil, fmt.Errorf("error reading deleted bytes: %v", err)
|
||||
}
|
||||
if !deletedBitmap.IsEmpty() {
|
||||
rv.deleted = deletedBitmap
|
||||
}
|
||||
}
|
||||
statBytes := segmentBucket.Get(boltStatsKey)
|
||||
statBytes := segmentBucket.Get(util.BoltStatsKey)
|
||||
if statBytes != nil {
|
||||
var statsMap map[string]map[string]uint64
|
||||
|
||||
err := json.Unmarshal(statBytes, &statsMap)
|
||||
stats := &fieldStats{statMap: statsMap}
|
||||
if err != nil {
|
||||
_ = segment.Close()
|
||||
_ = seg.Close()
|
||||
return nil, fmt.Errorf("error reading stat bytes: %v", err)
|
||||
}
|
||||
rv.stats = stats
|
||||
}
|
||||
updatedFieldBytes := segmentBucket.Get(util.BoltUpdatedFieldsKey)
|
||||
if updatedFieldBytes != nil {
|
||||
var updatedFields map[string]*index.UpdateFieldInfo
|
||||
|
||||
err := json.Unmarshal(updatedFieldBytes, &updatedFields)
|
||||
if err != nil {
|
||||
_ = seg.Close()
|
||||
return nil, fmt.Errorf("error reading updated field bytes: %v", err)
|
||||
}
|
||||
rv.updatedFields = updatedFields
|
||||
// Set the value within the segment base for use during merge
|
||||
rv.UpdateFieldsInfo(rv.updatedFields)
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
@@ -1215,7 +1231,7 @@ func (s *Scorch) removeOldBoltSnapshots() (numRemoved int, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return 0, nil
|
||||
}
|
||||
@@ -1293,7 +1309,7 @@ func (s *Scorch) removeOldZapFiles() error {
|
||||
// duration. This results in all of them being purged from the boltDB
|
||||
// and the next iteration of the removeOldData() would end up protecting
|
||||
// latest contiguous snapshot which is a poor pattern in the rollback checkpoints.
|
||||
// Hence we try to retain atmost retentionFactor portion worth of old snapshots
|
||||
// Hence we try to retain at most retentionFactor portion worth of old snapshots
|
||||
// in such a scenario using the following function
|
||||
func getBoundaryCheckPoint(retentionFactor float64,
|
||||
checkPoints []*snapshotMetaData, timeStamp time.Time,
|
||||
@@ -1325,7 +1341,7 @@ func (s *Scorch) rootBoltSnapshotMetaData() ([]*snapshotMetaData, error) {
|
||||
expirationDuration := time.Duration(s.numSnapshotsToKeep-1) * s.rollbackSamplingInterval
|
||||
|
||||
err := s.rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1349,11 +1365,11 @@ func (s *Scorch) rootBoltSnapshotMetaData() ([]*snapshotMetaData, error) {
|
||||
if snapshot == nil {
|
||||
continue
|
||||
}
|
||||
metaBucket := snapshot.Bucket(boltMetaDataKey)
|
||||
metaBucket := snapshot.Bucket(util.BoltMetaDataKey)
|
||||
if metaBucket == nil {
|
||||
continue
|
||||
}
|
||||
timeStampBytes := metaBucket.Get(boltMetaDataTimeStamp)
|
||||
timeStampBytes := metaBucket.Get(util.BoltMetaDataTimeStamp)
|
||||
var timeStamp time.Time
|
||||
err = timeStamp.UnmarshalText(timeStampBytes)
|
||||
if err != nil {
|
||||
@@ -1390,7 +1406,7 @@ func (s *Scorch) rootBoltSnapshotMetaData() ([]*snapshotMetaData, error) {
|
||||
func (s *Scorch) RootBoltSnapshotEpochs() ([]uint64, error) {
|
||||
var rv []uint64
|
||||
err := s.rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1411,7 +1427,7 @@ func (s *Scorch) RootBoltSnapshotEpochs() ([]uint64, error) {
|
||||
func (s *Scorch) loadZapFileNames() (map[string]struct{}, error) {
|
||||
rv := map[string]struct{}{}
|
||||
err := s.rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1423,14 +1439,14 @@ func (s *Scorch) loadZapFileNames() (map[string]struct{}, error) {
|
||||
}
|
||||
segc := snapshot.Cursor()
|
||||
for segk, _ := segc.First(); segk != nil; segk, _ = segc.Next() {
|
||||
if segk[0] == boltInternalKey[0] {
|
||||
if segk[0] == util.BoltInternalKey[0] {
|
||||
continue
|
||||
}
|
||||
segmentBucket := snapshot.Bucket(segk)
|
||||
if segmentBucket == nil {
|
||||
continue
|
||||
}
|
||||
pathBytes := segmentBucket.Get(boltPathKey)
|
||||
pathBytes := segmentBucket.Get(util.BoltPathKey)
|
||||
if pathBytes == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
9
vendor/github.com/blevesearch/bleve/v2/index/scorch/rollback.go
generated
vendored
9
vendor/github.com/blevesearch/bleve/v2/index/scorch/rollback.go
generated
vendored
@@ -19,6 +19,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/util"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
@@ -61,7 +62,7 @@ func RollbackPoints(path string) ([]*RollbackPoint, error) {
|
||||
_ = rootBolt.Close()
|
||||
}()
|
||||
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -87,7 +88,7 @@ func RollbackPoints(path string) ([]*RollbackPoint, error) {
|
||||
meta := map[string][]byte{}
|
||||
c2 := snapshot.Cursor()
|
||||
for j, _ := c2.First(); j != nil; j, _ = c2.Next() {
|
||||
if j[0] == boltInternalKey[0] {
|
||||
if j[0] == util.BoltInternalKey[0] {
|
||||
internalBucket := snapshot.Bucket(j)
|
||||
if internalBucket == nil {
|
||||
err = fmt.Errorf("internal bucket missing")
|
||||
@@ -151,7 +152,7 @@ func Rollback(path string, to *RollbackPoint) error {
|
||||
var found bool
|
||||
var eligibleEpochs []uint64
|
||||
err = rootBolt.View(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -193,7 +194,7 @@ func Rollback(path string, to *RollbackPoint) error {
|
||||
}
|
||||
}()
|
||||
|
||||
snapshots := tx.Bucket(boltSnapshotsBucket)
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
103
vendor/github.com/blevesearch/bleve/v2/index/scorch/scorch.go
generated
vendored
103
vendor/github.com/blevesearch/bleve/v2/index/scorch/scorch.go
generated
vendored
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/RoaringBitmap/roaring/v2"
|
||||
"github.com/blevesearch/bleve/v2/registry"
|
||||
"github.com/blevesearch/bleve/v2/util"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
segment "github.com/blevesearch/scorch_segment_api/v2"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -217,9 +218,11 @@ func (s *Scorch) fireAsyncError(err error) {
|
||||
}
|
||||
|
||||
func (s *Scorch) Open() error {
|
||||
err := s.openBolt()
|
||||
if err != nil {
|
||||
return err
|
||||
if s.rootBolt == nil {
|
||||
err := s.openBolt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.asyncTasks.Add(1)
|
||||
@@ -371,6 +374,7 @@ func (s *Scorch) Close() (err error) {
|
||||
}
|
||||
}
|
||||
s.root = nil
|
||||
s.rootBolt = nil
|
||||
s.rootLock.Unlock()
|
||||
}
|
||||
|
||||
@@ -940,3 +944,96 @@ func (s *Scorch) CopyReader() index.CopyReader {
|
||||
func (s *Scorch) FireIndexEvent() {
|
||||
s.fireEvent(EventKindIndexStart, 0)
|
||||
}
|
||||
|
||||
// Updates bolt db with the given field info. Existing field info already in bolt
|
||||
// will be merged before persisting. The index mapping is also overwritted both
|
||||
// in bolt as well as the index snapshot
|
||||
func (s *Scorch) UpdateFields(fieldInfo map[string]*index.UpdateFieldInfo, mappingBytes []byte) error {
|
||||
err := s.updateBolt(fieldInfo, mappingBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Pass the update field info to all snapshots and segment bases
|
||||
s.root.UpdateFieldsInfo(fieldInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scorch) OpenMeta() error {
|
||||
if s.rootBolt == nil {
|
||||
err := s.openBolt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge and update deleted field info and rewrite index mapping
|
||||
func (s *Scorch) updateBolt(fieldInfo map[string]*index.UpdateFieldInfo, mappingBytes []byte) error {
|
||||
return s.rootBolt.Update(func(tx *bolt.Tx) error {
|
||||
snapshots := tx.Bucket(util.BoltSnapshotsBucket)
|
||||
if snapshots == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := snapshots.Cursor()
|
||||
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
|
||||
_, _, err := decodeUvarintAscending(k)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to parse segment epoch %x, continuing", k)
|
||||
continue
|
||||
}
|
||||
snapshot := snapshots.Bucket(k)
|
||||
cc := snapshot.Cursor()
|
||||
for kk, _ := cc.First(); kk != nil; kk, _ = cc.Next() {
|
||||
if kk[0] == util.BoltInternalKey[0] {
|
||||
internalBucket := snapshot.Bucket(kk)
|
||||
if internalBucket == nil {
|
||||
return fmt.Errorf("segment key, but bucket missing %x", kk)
|
||||
}
|
||||
err = internalBucket.Put(util.MappingInternalKey, mappingBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if kk[0] != util.BoltMetaDataKey[0] {
|
||||
segmentBucket := snapshot.Bucket(kk)
|
||||
if segmentBucket == nil {
|
||||
return fmt.Errorf("segment key, but bucket missing %x", kk)
|
||||
}
|
||||
var updatedFields map[string]*index.UpdateFieldInfo
|
||||
updatedFieldBytes := segmentBucket.Get(util.BoltUpdatedFieldsKey)
|
||||
if updatedFieldBytes != nil {
|
||||
err := json.Unmarshal(updatedFieldBytes, &updatedFields)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading updated field bytes: %v", err)
|
||||
}
|
||||
for field, info := range fieldInfo {
|
||||
if val, ok := updatedFields[field]; ok {
|
||||
updatedFields[field] = &index.UpdateFieldInfo{
|
||||
Deleted: info.Deleted || val.Deleted,
|
||||
Store: info.Store || val.Store,
|
||||
DocValues: info.DocValues || val.DocValues,
|
||||
Index: info.Index || val.Index,
|
||||
}
|
||||
} else {
|
||||
updatedFields[field] = info
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updatedFields = fieldInfo
|
||||
}
|
||||
b, err := json.Marshal(updatedFields)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = segmentBucket.Put(util.BoltUpdatedFieldsKey, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
81
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index.go
generated
vendored
81
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index.go
generated
vendored
@@ -84,6 +84,13 @@ type IndexSnapshot struct {
|
||||
|
||||
m3 sync.RWMutex // bm25 metrics specific - not to interfere with TFR creation
|
||||
fieldCardinality map[string]int
|
||||
|
||||
// Stores information about zapx fields that have been
|
||||
// fully deleted (indicated by UpdateFieldInfo.Deleted) or
|
||||
// partially deleted index, store or docvalues (indicated by
|
||||
// UpdateFieldInfo.Index or .Store or .DocValues).
|
||||
// Used to short circuit queries trying to read stale data
|
||||
updatedFields map[string]*index.UpdateFieldInfo
|
||||
}
|
||||
|
||||
func (i *IndexSnapshot) Segments() []*SegmentSnapshot {
|
||||
@@ -509,6 +516,13 @@ func (is *IndexSnapshot) Document(id string) (rv index.Document, err error) {
|
||||
// Keeping that TODO for now until we have a cleaner way.
|
||||
rvd.StoredFieldsSize += uint64(len(val))
|
||||
|
||||
// Skip fields that have been completely deleted or had their
|
||||
// store data deleted
|
||||
if info, ok := is.updatedFields[name]; ok &&
|
||||
(info.Deleted || info.Store) {
|
||||
return true
|
||||
}
|
||||
|
||||
// copy value, array positions to preserve them beyond the scope of this callback
|
||||
value := append([]byte(nil), val...)
|
||||
arrayPos := append([]uint64(nil), pos...)
|
||||
@@ -634,10 +648,22 @@ func (is *IndexSnapshot) TermFieldReader(ctx context.Context, term []byte, field
|
||||
segBytesRead := s.segment.BytesRead()
|
||||
rv.incrementBytesRead(segBytesRead)
|
||||
}
|
||||
dict, err := s.segment.Dictionary(field)
|
||||
|
||||
var dict segment.TermDictionary
|
||||
var err error
|
||||
|
||||
// Skip fields that have been completely deleted or had their
|
||||
// index data deleted
|
||||
if info, ok := is.updatedFields[field]; ok &&
|
||||
(info.Index || info.Deleted) {
|
||||
dict, err = s.segment.Dictionary("")
|
||||
} else {
|
||||
dict, err = s.segment.Dictionary(field)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dictStats, ok := dict.(segment.DiskStatsReporter); ok {
|
||||
bytesRead := dictStats.BytesRead()
|
||||
rv.incrementBytesRead(bytesRead)
|
||||
@@ -783,6 +809,23 @@ func (is *IndexSnapshot) documentVisitFieldTermsOnSegment(
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out fields that have been completely deleted or had their
|
||||
// docvalues data deleted from both visitable fields and required fields
|
||||
filterUpdatedFields := func(fields []string) []string {
|
||||
filteredFields := make([]string, 0)
|
||||
for _, field := range fields {
|
||||
if info, ok := is.updatedFields[field]; ok &&
|
||||
(info.DocValues || info.Deleted) {
|
||||
continue
|
||||
}
|
||||
filteredFields = append(filteredFields, field)
|
||||
}
|
||||
return filteredFields
|
||||
}
|
||||
|
||||
fieldsFiltered := filterUpdatedFields(fields)
|
||||
vFieldsFiltered := filterUpdatedFields(vFields)
|
||||
|
||||
var errCh chan error
|
||||
|
||||
// cFields represents the fields that we'll need from the
|
||||
@@ -790,7 +833,7 @@ func (is *IndexSnapshot) documentVisitFieldTermsOnSegment(
|
||||
// if the caller happens to know we're on the same segmentIndex
|
||||
// from a previous invocation
|
||||
if cFields == nil {
|
||||
cFields = subtractStrings(fields, vFields)
|
||||
cFields = subtractStrings(fieldsFiltered, vFieldsFiltered)
|
||||
|
||||
if !ss.cachedDocs.hasFields(cFields) {
|
||||
errCh = make(chan error, 1)
|
||||
@@ -805,8 +848,8 @@ func (is *IndexSnapshot) documentVisitFieldTermsOnSegment(
|
||||
}
|
||||
}
|
||||
|
||||
if ssvOk && ssv != nil && len(vFields) > 0 {
|
||||
dvs, err = ssv.VisitDocValues(localDocNum, fields, visitor, dvs)
|
||||
if ssvOk && ssv != nil && len(vFieldsFiltered) > 0 {
|
||||
dvs, err = ssv.VisitDocValues(localDocNum, fieldsFiltered, visitor, dvs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -1161,3 +1204,33 @@ func (is *IndexSnapshot) ThesaurusKeysRegexp(name string,
|
||||
func (is *IndexSnapshot) UpdateSynonymSearchCount(delta uint64) {
|
||||
atomic.AddUint64(&is.parent.stats.TotSynonymSearches, delta)
|
||||
}
|
||||
|
||||
// Update current snapshot updated field data as well as pass it on to all segments and segment bases
|
||||
func (is *IndexSnapshot) UpdateFieldsInfo(updatedFields map[string]*index.UpdateFieldInfo) {
|
||||
is.m.Lock()
|
||||
defer is.m.Unlock()
|
||||
|
||||
is.MergeUpdateFieldsInfo(updatedFields)
|
||||
|
||||
for _, segmentSnapshot := range is.segment {
|
||||
segmentSnapshot.UpdateFieldsInfo(is.updatedFields)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge given updated field information with existing updated field information
|
||||
func (is *IndexSnapshot) MergeUpdateFieldsInfo(updatedFields map[string]*index.UpdateFieldInfo) {
|
||||
if is.updatedFields == nil {
|
||||
is.updatedFields = updatedFields
|
||||
} else {
|
||||
for fieldName, info := range updatedFields {
|
||||
if val, ok := is.updatedFields[fieldName]; ok {
|
||||
val.Deleted = val.Deleted || info.Deleted
|
||||
val.Index = val.Index || info.Index
|
||||
val.DocValues = val.DocValues || info.DocValues
|
||||
val.Store = val.Store || info.Store
|
||||
} else {
|
||||
is.updatedFields[fieldName] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_tfr.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_tfr.go
generated
vendored
@@ -163,7 +163,7 @@ func (i *IndexSnapshotTermFieldReader) Advance(ID index.IndexInternalID, preAllo
|
||||
// unadorned composite optimization
|
||||
// we need to reset all the iterators
|
||||
// back to the beginning, which effectively
|
||||
// achives the same thing as the above
|
||||
// achieves the same thing as the above
|
||||
for _, iter := range i.iterators {
|
||||
if optimizedIterator, ok := iter.(ResetablePostingsIterator); ok {
|
||||
optimizedIterator.ResetIterator()
|
||||
|
||||
4
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_vr.go
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_index_vr.go
generated
vendored
@@ -83,6 +83,10 @@ func (i *IndexSnapshotVectorReader) Next(preAlloced *index.VectorDoc) (
|
||||
}
|
||||
|
||||
for i.segmentOffset < len(i.iterators) {
|
||||
if i.iterators[i.segmentOffset] == nil {
|
||||
i.segmentOffset++
|
||||
continue
|
||||
}
|
||||
next, err := i.iterators[i.segmentOffset].Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
35
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_segment.go
generated
vendored
35
vendor/github.com/blevesearch/bleve/v2/index/scorch/snapshot_segment.go
generated
vendored
@@ -35,12 +35,13 @@ type SegmentSnapshot struct {
|
||||
// segment was mmaped recently, in which case
|
||||
// we consider the loading cost of the metadata
|
||||
// as part of IO stats.
|
||||
mmaped uint32
|
||||
id uint64
|
||||
segment segment.Segment
|
||||
deleted *roaring.Bitmap
|
||||
creator string
|
||||
stats *fieldStats
|
||||
mmaped uint32
|
||||
id uint64
|
||||
segment segment.Segment
|
||||
deleted *roaring.Bitmap
|
||||
creator string
|
||||
stats *fieldStats
|
||||
updatedFields map[string]*index.UpdateFieldInfo
|
||||
|
||||
cachedMeta *cachedMeta
|
||||
|
||||
@@ -146,6 +147,28 @@ func (s *SegmentSnapshot) Size() (rv int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Merge given updated field information with existing and pass it on to the segment base
|
||||
func (s *SegmentSnapshot) UpdateFieldsInfo(updatedFields map[string]*index.UpdateFieldInfo) {
|
||||
if s.updatedFields == nil {
|
||||
s.updatedFields = updatedFields
|
||||
} else {
|
||||
for fieldName, info := range updatedFields {
|
||||
if val, ok := s.updatedFields[fieldName]; ok {
|
||||
val.Deleted = val.Deleted || info.Deleted
|
||||
val.Index = val.Index || info.Index
|
||||
val.DocValues = val.DocValues || info.DocValues
|
||||
val.Store = val.Store || info.Store
|
||||
} else {
|
||||
s.updatedFields[fieldName] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if segment, ok := s.segment.(segment.UpdatableSegment); ok {
|
||||
segment.SetUpdatedFields(s.updatedFields)
|
||||
}
|
||||
}
|
||||
|
||||
type cachedFieldDocs struct {
|
||||
m sync.Mutex
|
||||
readyCh chan struct{} // closed when the cachedFieldDocs.docs is ready to be used.
|
||||
|
||||
13
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/protoc-README.md
generated
vendored
Normal file
13
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/protoc-README.md
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
## Instructions for generating new go stubs using upsidedown.proto
|
||||
|
||||
1. Download latest of protoc-gen-go
|
||||
```
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
```
|
||||
|
||||
2. To generate `upsidedown.pb.go` using upsdidedown.proto:
|
||||
```
|
||||
protoc --go_out=. --go_opt=Mindex/upsidedown/upsidedown.proto=index/upsidedown/ index/upsidedown/upsidedown.proto
|
||||
```
|
||||
|
||||
3. Manually add back Size and MarshalTo methods for BackIndexRowValue, BackIndexTermsEntry, BackIndexStoreEntry to support upside_down.
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/reader.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/reader.go
generated
vendored
@@ -371,6 +371,6 @@ func (r *UpsideDownCouchDocIDReader) nextOnly() bool {
|
||||
start = r.onlyPos
|
||||
r.onlyPos++
|
||||
}
|
||||
// inidicate if we got to the end of the list
|
||||
// indicate if we got to the end of the list
|
||||
return r.onlyPos < len(r.only)
|
||||
}
|
||||
|
||||
4
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/row.go
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/row.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/size"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -924,7 +924,7 @@ type backIndexFieldTermVisitor func(field uint32, term []byte)
|
||||
//
|
||||
// This code originates from:
|
||||
// func (m *BackIndexRowValue) Unmarshal(data []byte) error
|
||||
// the sections which create garbage or parse unintersting sections
|
||||
// the sections which create garbage or parse uninteresting sections
|
||||
// have been commented out. This was done by design to allow for easier
|
||||
// merging in the future if that original function is regenerated
|
||||
func visitBackIndexRow(data []byte, callback backIndexFieldTermVisitor) error {
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/upsidedown.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/upsidedown.go
generated
vendored
@@ -30,7 +30,7 @@ import (
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
store "github.com/blevesearch/upsidedown_store_api"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const Name = "upside_down"
|
||||
|
||||
865
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/upsidedown.pb.go
generated
vendored
865
vendor/github.com/blevesearch/bleve/v2/index/upsidedown/upsidedown.pb.go
generated
vendored
@@ -1,382 +1,319 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: upsidedown.proto
|
||||
// DO NOT EDIT!
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// source: index/upsidedown/upsidedown.proto
|
||||
|
||||
/*
|
||||
Package upsidedown is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
|
||||
upsidedown.proto
|
||||
|
||||
It has these top-level messages:
|
||||
|
||||
BackIndexTermsEntry
|
||||
BackIndexStoreEntry
|
||||
BackIndexRowValue
|
||||
*/
|
||||
package upsidedown
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
import (
|
||||
fmt "fmt"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
import io "io"
|
||||
import fmt "fmt"
|
||||
import github_com_golang_protobuf_proto "github.com/golang/protobuf/proto"
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = math.Inf
|
||||
var (
|
||||
ErrInvalidLengthUpsidedown = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
)
|
||||
|
||||
type BackIndexTermsEntry struct {
|
||||
Field *uint32 `protobuf:"varint,1,req,name=field" json:"field,omitempty"`
|
||||
Terms []string `protobuf:"bytes,2,rep,name=terms" json:"terms,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Field *uint32 `protobuf:"varint,1,req,name=field" json:"field,omitempty"`
|
||||
Terms []string `protobuf:"bytes,2,rep,name=terms" json:"terms,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (m *BackIndexTermsEntry) Reset() { *m = BackIndexTermsEntry{} }
|
||||
func (m *BackIndexTermsEntry) String() string { return proto.CompactTextString(m) }
|
||||
func (*BackIndexTermsEntry) ProtoMessage() {}
|
||||
func (x *BackIndexTermsEntry) Reset() {
|
||||
*x = BackIndexTermsEntry{}
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (m *BackIndexTermsEntry) GetField() uint32 {
|
||||
if m != nil && m.Field != nil {
|
||||
return *m.Field
|
||||
func (x *BackIndexTermsEntry) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BackIndexTermsEntry) ProtoMessage() {}
|
||||
|
||||
func (x *BackIndexTermsEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BackIndexTermsEntry.ProtoReflect.Descriptor instead.
|
||||
func (*BackIndexTermsEntry) Descriptor() ([]byte, []int) {
|
||||
return file_index_upsidedown_upsidedown_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *BackIndexTermsEntry) GetField() uint32 {
|
||||
if x != nil && x.Field != nil {
|
||||
return *x.Field
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *BackIndexTermsEntry) GetTerms() []string {
|
||||
if m != nil {
|
||||
return m.Terms
|
||||
func (x *BackIndexTermsEntry) GetTerms() []string {
|
||||
if x != nil {
|
||||
return x.Terms
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *BackIndexTermsEntry) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if x.Field == nil {
|
||||
return 0, fmt.Errorf("missing required `Field`")
|
||||
} else {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(*x.Field))
|
||||
}
|
||||
if len(x.Terms) > 0 {
|
||||
for _, s := range x.Terms {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
l = len(s)
|
||||
for l >= 1<<7 {
|
||||
data[i] = uint8(uint64(l)&0x7f | 0x80)
|
||||
l >>= 7
|
||||
i++
|
||||
}
|
||||
data[i] = uint8(l)
|
||||
i++
|
||||
i += copy(data[i:], s)
|
||||
}
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (x *BackIndexTermsEntry) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if x.Field != nil {
|
||||
n += 1 + sovUpsidedown(uint64(*x.Field))
|
||||
}
|
||||
if len(x.Terms) > 0 {
|
||||
for _, s := range x.Terms {
|
||||
l = len(s)
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type BackIndexStoreEntry struct {
|
||||
Field *uint32 `protobuf:"varint,1,req,name=field" json:"field,omitempty"`
|
||||
ArrayPositions []uint64 `protobuf:"varint,2,rep,name=arrayPositions" json:"arrayPositions,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Field *uint32 `protobuf:"varint,1,req,name=field" json:"field,omitempty"`
|
||||
ArrayPositions []uint64 `protobuf:"varint,2,rep,name=arrayPositions" json:"arrayPositions,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) Reset() { *m = BackIndexStoreEntry{} }
|
||||
func (m *BackIndexStoreEntry) String() string { return proto.CompactTextString(m) }
|
||||
func (*BackIndexStoreEntry) ProtoMessage() {}
|
||||
func (x *BackIndexStoreEntry) Reset() {
|
||||
*x = BackIndexStoreEntry{}
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) GetField() uint32 {
|
||||
if m != nil && m.Field != nil {
|
||||
return *m.Field
|
||||
func (x *BackIndexStoreEntry) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BackIndexStoreEntry) ProtoMessage() {}
|
||||
|
||||
func (x *BackIndexStoreEntry) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BackIndexStoreEntry.ProtoReflect.Descriptor instead.
|
||||
func (*BackIndexStoreEntry) Descriptor() ([]byte, []int) {
|
||||
return file_index_upsidedown_upsidedown_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *BackIndexStoreEntry) GetField() uint32 {
|
||||
if x != nil && x.Field != nil {
|
||||
return *x.Field
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) GetArrayPositions() []uint64 {
|
||||
if m != nil {
|
||||
return m.ArrayPositions
|
||||
func (x *BackIndexStoreEntry) GetArrayPositions() []uint64 {
|
||||
if x != nil {
|
||||
return x.ArrayPositions
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *BackIndexStoreEntry) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if x.Field == nil {
|
||||
return 0, fmt.Errorf("missing required `Field`")
|
||||
} else {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(*x.Field))
|
||||
}
|
||||
if len(x.ArrayPositions) > 0 {
|
||||
for _, num := range x.ArrayPositions {
|
||||
data[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(num))
|
||||
}
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (x *BackIndexStoreEntry) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if x.Field != nil {
|
||||
n += 1 + sovUpsidedown(uint64(*x.Field))
|
||||
}
|
||||
if len(x.ArrayPositions) > 0 {
|
||||
for _, e := range x.ArrayPositions {
|
||||
n += 1 + sovUpsidedown(uint64(e))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type BackIndexRowValue struct {
|
||||
TermsEntries []*BackIndexTermsEntry `protobuf:"bytes,1,rep,name=termsEntries" json:"termsEntries,omitempty"`
|
||||
StoredEntries []*BackIndexStoreEntry `protobuf:"bytes,2,rep,name=storedEntries" json:"storedEntries,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
TermsEntries []*BackIndexTermsEntry `protobuf:"bytes,1,rep,name=termsEntries" json:"termsEntries,omitempty"`
|
||||
StoredEntries []*BackIndexStoreEntry `protobuf:"bytes,2,rep,name=storedEntries" json:"storedEntries,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (m *BackIndexRowValue) Reset() { *m = BackIndexRowValue{} }
|
||||
func (m *BackIndexRowValue) String() string { return proto.CompactTextString(m) }
|
||||
func (*BackIndexRowValue) ProtoMessage() {}
|
||||
|
||||
func (m *BackIndexRowValue) GetTermsEntries() []*BackIndexTermsEntry {
|
||||
if m != nil {
|
||||
return m.TermsEntries
|
||||
}
|
||||
return nil
|
||||
func (x *BackIndexRowValue) Reset() {
|
||||
*x = BackIndexRowValue{}
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (m *BackIndexRowValue) GetStoredEntries() []*BackIndexStoreEntry {
|
||||
if m != nil {
|
||||
return m.StoredEntries
|
||||
}
|
||||
return nil
|
||||
func (x *BackIndexRowValue) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (m *BackIndexTermsEntry) Unmarshal(data []byte) error {
|
||||
var hasFields [1]uint64
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
func (*BackIndexRowValue) ProtoMessage() {}
|
||||
|
||||
func (x *BackIndexRowValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_index_upsidedown_upsidedown_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Field", wireType)
|
||||
}
|
||||
var v uint32
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (uint32(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Field = &v
|
||||
hasFields[0] |= uint64(0x00000001)
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Terms", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
postIndex := iNdEx + int(stringLen)
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Terms = append(m.Terms, string(data[iNdEx:postIndex]))
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
var sizeOfWire int
|
||||
for {
|
||||
sizeOfWire++
|
||||
wire >>= 7
|
||||
if wire == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx -= sizeOfWire
|
||||
skippy, err := skipUpsidedown(data[iNdEx:])
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BackIndexRowValue.ProtoReflect.Descriptor instead.
|
||||
func (*BackIndexRowValue) Descriptor() ([]byte, []int) {
|
||||
return file_index_upsidedown_upsidedown_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *BackIndexRowValue) GetTermsEntries() []*BackIndexTermsEntry {
|
||||
if x != nil {
|
||||
return x.TermsEntries
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *BackIndexRowValue) GetStoredEntries() []*BackIndexStoreEntry {
|
||||
if x != nil {
|
||||
return x.StoredEntries
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *BackIndexRowValue) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(x.TermsEntries) > 0 {
|
||||
for _, msg := range x.TermsEntries {
|
||||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthUpsidedown
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000001) == 0 {
|
||||
return new(github_com_golang_protobuf_proto.RequiredNotSetError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (m *BackIndexStoreEntry) Unmarshal(data []byte) error {
|
||||
var hasFields [1]uint64
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Field", wireType)
|
||||
}
|
||||
var v uint32
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (uint32(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Field = &v
|
||||
hasFields[0] |= uint64(0x00000001)
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ArrayPositions", wireType)
|
||||
}
|
||||
var v uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.ArrayPositions = append(m.ArrayPositions, v)
|
||||
default:
|
||||
var sizeOfWire int
|
||||
for {
|
||||
sizeOfWire++
|
||||
wire >>= 7
|
||||
if wire == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx -= sizeOfWire
|
||||
skippy, err := skipUpsidedown(data[iNdEx:])
|
||||
if len(x.StoredEntries) > 0 {
|
||||
for _, msg := range x.StoredEntries {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthUpsidedown
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if hasFields[0]&uint64(0x00000001) == 0 {
|
||||
return new(github_com_golang_protobuf_proto.RequiredNotSetError)
|
||||
}
|
||||
|
||||
return nil
|
||||
return i, nil
|
||||
}
|
||||
func (m *BackIndexRowValue) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field TermsEntries", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthUpsidedown
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.TermsEntries = append(m.TermsEntries, &BackIndexTermsEntry{})
|
||||
if err := m.TermsEntries[len(m.TermsEntries)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field StoredEntries", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthUpsidedown
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.StoredEntries = append(m.StoredEntries, &BackIndexStoreEntry{})
|
||||
if err := m.StoredEntries[len(m.StoredEntries)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
var sizeOfWire int
|
||||
for {
|
||||
sizeOfWire++
|
||||
wire >>= 7
|
||||
if wire == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx -= sizeOfWire
|
||||
skippy, err := skipUpsidedown(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthUpsidedown
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
|
||||
func (x *BackIndexRowValue) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if len(x.TermsEntries) > 0 {
|
||||
for _, e := range x.TermsEntries {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
if len(x.StoredEntries) > 0 {
|
||||
for _, e := range x.StoredEntries {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func skipUpsidedown(data []byte) (n int, err error) {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
@@ -465,66 +402,6 @@ func skipUpsidedown(data []byte) (n int, err error) {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthUpsidedown = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
)
|
||||
|
||||
func (m *BackIndexTermsEntry) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.Field != nil {
|
||||
n += 1 + sovUpsidedown(uint64(*m.Field))
|
||||
}
|
||||
if len(m.Terms) > 0 {
|
||||
for _, s := range m.Terms {
|
||||
l = len(s)
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.Field != nil {
|
||||
n += 1 + sovUpsidedown(uint64(*m.Field))
|
||||
}
|
||||
if len(m.ArrayPositions) > 0 {
|
||||
for _, e := range m.ArrayPositions {
|
||||
n += 1 + sovUpsidedown(uint64(e))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *BackIndexRowValue) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.TermsEntries) > 0 {
|
||||
for _, e := range m.TermsEntries {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
if len(m.StoredEntries) > 0 {
|
||||
for _, e := range m.StoredEntries {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovUpsidedown(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovUpsidedown(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
@@ -535,150 +412,7 @@ func sovUpsidedown(x uint64) (n int) {
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozUpsidedown(x uint64) (n int) {
|
||||
return sovUpsidedown(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *BackIndexTermsEntry) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *BackIndexTermsEntry) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Field == nil {
|
||||
return 0, new(github_com_golang_protobuf_proto.RequiredNotSetError)
|
||||
} else {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(*m.Field))
|
||||
}
|
||||
if len(m.Terms) > 0 {
|
||||
for _, s := range m.Terms {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
l = len(s)
|
||||
for l >= 1<<7 {
|
||||
data[i] = uint8(uint64(l)&0x7f | 0x80)
|
||||
l >>= 7
|
||||
i++
|
||||
}
|
||||
data[i] = uint8(l)
|
||||
i++
|
||||
i += copy(data[i:], s)
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *BackIndexStoreEntry) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Field == nil {
|
||||
return 0, new(github_com_golang_protobuf_proto.RequiredNotSetError)
|
||||
} else {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(*m.Field))
|
||||
}
|
||||
if len(m.ArrayPositions) > 0 {
|
||||
for _, num := range m.ArrayPositions {
|
||||
data[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(num))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *BackIndexRowValue) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *BackIndexRowValue) MarshalTo(data []byte) (n int, err error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.TermsEntries) > 0 {
|
||||
for _, msg := range m.TermsEntries {
|
||||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if len(m.StoredEntries) > 0 {
|
||||
for _, msg := range m.StoredEntries {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintUpsidedown(data, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Upsidedown(data []byte, offset int, v uint64) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
data[offset+4] = uint8(v >> 32)
|
||||
data[offset+5] = uint8(v >> 40)
|
||||
data[offset+6] = uint8(v >> 48)
|
||||
data[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Upsidedown(data []byte, offset int, v uint32) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintUpsidedown(data []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
data[offset] = uint8(v&0x7f | 0x80)
|
||||
@@ -688,3 +422,70 @@ func encodeVarintUpsidedown(data []byte, offset int, v uint64) int {
|
||||
data[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
|
||||
var File_index_upsidedown_upsidedown_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_index_upsidedown_upsidedown_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"!index/upsidedown/upsidedown.proto\"A\n" +
|
||||
"\x13BackIndexTermsEntry\x12\x14\n" +
|
||||
"\x05field\x18\x01 \x02(\rR\x05field\x12\x14\n" +
|
||||
"\x05terms\x18\x02 \x03(\tR\x05terms\"S\n" +
|
||||
"\x13BackIndexStoreEntry\x12\x14\n" +
|
||||
"\x05field\x18\x01 \x02(\rR\x05field\x12&\n" +
|
||||
"\x0earrayPositions\x18\x02 \x03(\x04R\x0earrayPositions\"\x89\x01\n" +
|
||||
"\x11BackIndexRowValue\x128\n" +
|
||||
"\ftermsEntries\x18\x01 \x03(\v2\x14.BackIndexTermsEntryR\ftermsEntries\x12:\n" +
|
||||
"\rstoredEntries\x18\x02 \x03(\v2\x14.BackIndexStoreEntryR\rstoredEntries"
|
||||
|
||||
var (
|
||||
file_index_upsidedown_upsidedown_proto_rawDescOnce sync.Once
|
||||
file_index_upsidedown_upsidedown_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_index_upsidedown_upsidedown_proto_rawDescGZIP() []byte {
|
||||
file_index_upsidedown_upsidedown_proto_rawDescOnce.Do(func() {
|
||||
file_index_upsidedown_upsidedown_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_index_upsidedown_upsidedown_proto_rawDesc), len(file_index_upsidedown_upsidedown_proto_rawDesc)))
|
||||
})
|
||||
return file_index_upsidedown_upsidedown_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_index_upsidedown_upsidedown_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_index_upsidedown_upsidedown_proto_goTypes = []any{
|
||||
(*BackIndexTermsEntry)(nil), // 0: BackIndexTermsEntry
|
||||
(*BackIndexStoreEntry)(nil), // 1: BackIndexStoreEntry
|
||||
(*BackIndexRowValue)(nil), // 2: BackIndexRowValue
|
||||
}
|
||||
var file_index_upsidedown_upsidedown_proto_depIdxs = []int32{
|
||||
0, // 0: BackIndexRowValue.termsEntries:type_name -> BackIndexTermsEntry
|
||||
1, // 1: BackIndexRowValue.storedEntries:type_name -> BackIndexStoreEntry
|
||||
2, // [2:2] is the sub-list for method output_type
|
||||
2, // [2:2] is the sub-list for method input_type
|
||||
2, // [2:2] is the sub-list for extension type_name
|
||||
2, // [2:2] is the sub-list for extension extendee
|
||||
0, // [0:2] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_index_upsidedown_upsidedown_proto_init() }
|
||||
func file_index_upsidedown_upsidedown_proto_init() {
|
||||
if File_index_upsidedown_upsidedown_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_index_upsidedown_upsidedown_proto_rawDesc), len(file_index_upsidedown_upsidedown_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_index_upsidedown_upsidedown_proto_goTypes,
|
||||
DependencyIndexes: file_index_upsidedown_upsidedown_proto_depIdxs,
|
||||
MessageInfos: file_index_upsidedown_upsidedown_proto_msgTypes,
|
||||
}.Build()
|
||||
File_index_upsidedown_upsidedown_proto = out.File
|
||||
file_index_upsidedown_upsidedown_proto_goTypes = nil
|
||||
file_index_upsidedown_upsidedown_proto_depIdxs = nil
|
||||
}
|
||||
|
||||
87
vendor/github.com/blevesearch/bleve/v2/index_alias_impl.go
generated
vendored
87
vendor/github.com/blevesearch/bleve/v2/index_alias_impl.go
generated
vendored
@@ -32,7 +32,7 @@ type indexAliasImpl struct {
|
||||
indexes []Index
|
||||
mutex sync.RWMutex
|
||||
open bool
|
||||
// if all the indexes in tha alias have the same mapping
|
||||
// if all the indexes in that alias have the same mapping
|
||||
// then the user can set the mapping here to avoid
|
||||
// checking the mapping of each index in the alias
|
||||
mapping mapping.IndexMapping
|
||||
@@ -186,6 +186,7 @@ func (i *indexAliasImpl) SearchInContext(ctx context.Context, req *SearchRequest
|
||||
if len(i.indexes) < 1 {
|
||||
return nil, ErrorAliasEmpty
|
||||
}
|
||||
|
||||
if _, ok := ctx.Value(search.PreSearchKey).(bool); ok {
|
||||
// since preSearchKey is set, it means that the request
|
||||
// is being executed as part of a preSearch, which
|
||||
@@ -227,6 +228,21 @@ func (i *indexAliasImpl) SearchInContext(ctx context.Context, req *SearchRequest
|
||||
return i.indexes[0].SearchInContext(ctx, req)
|
||||
}
|
||||
|
||||
// rescorer will be set if score fusion is supposed to happen
|
||||
// at this alias (root alias), else will be nil
|
||||
var rescorer *rescorer
|
||||
if _, ok := ctx.Value(search.ScoreFusionKey).(bool); !ok {
|
||||
// new context will be used in internal functions to collect data
|
||||
// as suitable for fusion. Rescorer is used for rescoring
|
||||
// using fusion algorithms.
|
||||
if IsScoreFusionRequested(req) {
|
||||
ctx = context.WithValue(ctx, search.ScoreFusionKey, true)
|
||||
rescorer = newRescorer(req)
|
||||
rescorer.prepareSearchRequest()
|
||||
defer rescorer.restoreSearchRequest()
|
||||
}
|
||||
}
|
||||
|
||||
// at this stage we know we have multiple indexes
|
||||
// check if preSearchData needs to be gathered from all indexes
|
||||
// before executing the query
|
||||
@@ -236,6 +252,14 @@ func (i *indexAliasImpl) SearchInContext(ctx context.Context, req *SearchRequest
|
||||
// - the request requires preSearch
|
||||
var preSearchDuration time.Duration
|
||||
var sr *SearchResult
|
||||
|
||||
// fusionKnnHits stores the KnnHits at the root alias.
|
||||
// This is used with score fusion in case there is no need to
|
||||
// send the knn hits to the leaf indexes in search phase.
|
||||
// Refer to constructPreSearchDataAndFusionKnnHits for more info.
|
||||
// This variable is left nil if we have to send the knn hits to leaf
|
||||
// indexes again, else contains the knn hits if not required.
|
||||
var fusionKnnHits search.DocumentMatchCollection
|
||||
flags, err := preSearchRequired(ctx, req, i.mapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -261,10 +285,10 @@ func (i *indexAliasImpl) SearchInContext(ctx context.Context, req *SearchRequest
|
||||
// if the request is satisfied by the preSearch result, then we can
|
||||
// directly return the preSearch result as the final result
|
||||
if requestSatisfiedByPreSearch(req, flags) {
|
||||
sr = finalizeSearchResult(req, preSearchResult)
|
||||
sr = finalizeSearchResult(ctx, req, preSearchResult, rescorer)
|
||||
// no need to run the 2nd phase MultiSearch(..)
|
||||
} else {
|
||||
preSearchData, err = constructPreSearchData(req, flags, preSearchResult, i.indexes)
|
||||
preSearchData, fusionKnnHits, err = constructPreSearchDataAndFusionKnnHits(req, flags, preSearchResult, rescorer, i.indexes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -274,7 +298,8 @@ func (i *indexAliasImpl) SearchInContext(ctx context.Context, req *SearchRequest
|
||||
|
||||
// check if search result was generated as part of preSearch itself
|
||||
if sr == nil {
|
||||
sr, err = MultiSearch(ctx, req, preSearchData, i.indexes...)
|
||||
multiSearchParams := &multiSearchParams{preSearchData, rescorer, fusionKnnHits}
|
||||
sr, err = MultiSearch(ctx, req, multiSearchParams, i.indexes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -653,7 +678,7 @@ func preSearch(ctx context.Context, req *SearchRequest, flags *preSearchFlags, i
|
||||
// if the request is satisfied by just the preSearch result,
|
||||
// finalize the result and return it directly without
|
||||
// performing multi search
|
||||
func finalizeSearchResult(req *SearchRequest, preSearchResult *SearchResult) *SearchResult {
|
||||
func finalizeSearchResult(ctx context.Context, req *SearchRequest, preSearchResult *SearchResult, rescorer *rescorer) *SearchResult {
|
||||
if preSearchResult == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -682,7 +707,16 @@ func finalizeSearchResult(req *SearchRequest, preSearchResult *SearchResult) *Se
|
||||
if req.SearchAfter != nil {
|
||||
preSearchResult.Hits = collector.FilterHitsBySearchAfter(preSearchResult.Hits, req.Sort, req.SearchAfter)
|
||||
}
|
||||
|
||||
if rescorer != nil {
|
||||
// rescore takes ftsHits and knnHits as first and second argument respectively
|
||||
// since this is pure knn, set ftsHits to nil. preSearchResult.Hits contains knn results
|
||||
preSearchResult.Hits, preSearchResult.Total, preSearchResult.MaxScore = rescorer.rescore(nil, preSearchResult.Hits)
|
||||
rescorer.restoreSearchRequest()
|
||||
}
|
||||
|
||||
preSearchResult.Hits = hitsInCurrentPage(req, preSearchResult.Hits)
|
||||
|
||||
if reverseQueryExecution {
|
||||
// reverse the sort back to the original
|
||||
req.Sort.Reverse()
|
||||
@@ -759,6 +793,31 @@ func constructPreSearchData(req *SearchRequest, flags *preSearchFlags,
|
||||
return mergedOut, nil
|
||||
}
|
||||
|
||||
// Constructs the presearch data if required during the search phase.
|
||||
// Also if we need to store knn hits at alias.
|
||||
// If we need to store knn hits at alias: returns all the knn hits
|
||||
// If we should send it to leaf indexes: includes in presearch data
|
||||
func constructPreSearchDataAndFusionKnnHits(req *SearchRequest, flags *preSearchFlags,
|
||||
preSearchResult *SearchResult, rescorer *rescorer, indexes []Index,
|
||||
) (map[string]map[string]interface{}, search.DocumentMatchCollection, error) {
|
||||
var fusionknnhits search.DocumentMatchCollection
|
||||
|
||||
// Checks if we need to send the KNN hits to the indexes in the
|
||||
// search phase. If there is score fusion enabled, we do not
|
||||
// send the KNN hits to the indexes.
|
||||
if rescorer != nil && flags.knn {
|
||||
fusionknnhits = preSearchResult.Hits
|
||||
preSearchResult.Hits = nil
|
||||
}
|
||||
|
||||
preSearchData, err := constructPreSearchData(req, flags, preSearchResult, indexes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return preSearchData, fusionknnhits, nil
|
||||
}
|
||||
|
||||
func preSearchDataSearch(ctx context.Context, req *SearchRequest, flags *preSearchFlags, indexes ...Index) (*SearchResult, error) {
|
||||
asyncResults := make(chan *asyncSearchResult, len(indexes))
|
||||
// run search on each index in separate go routine
|
||||
@@ -912,9 +971,16 @@ func hitsInCurrentPage(req *SearchRequest, hits []*search.DocumentMatch) []*sear
|
||||
return hits
|
||||
}
|
||||
|
||||
// Extra parameters for MultiSearch
|
||||
type multiSearchParams struct {
|
||||
preSearchData map[string]map[string]interface{}
|
||||
rescorer *rescorer
|
||||
fusionKnnHits search.DocumentMatchCollection
|
||||
}
|
||||
|
||||
// MultiSearch executes a SearchRequest across multiple Index objects,
|
||||
// then merges the results. The indexes must honor any ctx deadline.
|
||||
func MultiSearch(ctx context.Context, req *SearchRequest, preSearchData map[string]map[string]interface{}, indexes ...Index) (*SearchResult, error) {
|
||||
func MultiSearch(ctx context.Context, req *SearchRequest, params *multiSearchParams, indexes ...Index) (*SearchResult, error) {
|
||||
searchStart := time.Now()
|
||||
asyncResults := make(chan *asyncSearchResult, len(indexes))
|
||||
|
||||
@@ -939,8 +1005,8 @@ func MultiSearch(ctx context.Context, req *SearchRequest, preSearchData map[stri
|
||||
waitGroup.Add(len(indexes))
|
||||
for _, in := range indexes {
|
||||
var payload map[string]interface{}
|
||||
if preSearchData != nil {
|
||||
payload = preSearchData[in.Name()]
|
||||
if params.preSearchData != nil {
|
||||
payload = params.preSearchData[in.Name()]
|
||||
}
|
||||
go searchChildIndex(in, createChildSearchRequest(req, payload))
|
||||
}
|
||||
@@ -980,6 +1046,11 @@ func MultiSearch(ctx context.Context, req *SearchRequest, preSearchData map[stri
|
||||
}
|
||||
}
|
||||
|
||||
if params.rescorer != nil {
|
||||
sr.Hits, sr.Total, sr.MaxScore = params.rescorer.rescore(sr.Hits, params.fusionKnnHits)
|
||||
params.rescorer.restoreSearchRequest()
|
||||
}
|
||||
|
||||
sr.Hits = hitsInCurrentPage(req, sr.Hits)
|
||||
|
||||
// fix up facets
|
||||
|
||||
148
vendor/github.com/blevesearch/bleve/v2/index_impl.go
generated
vendored
148
vendor/github.com/blevesearch/bleve/v2/index_impl.go
generated
vendored
@@ -133,7 +133,7 @@ func newIndexUsing(path string, mapping mapping.IndexMapping, indexType string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rv.i.SetInternal(mappingInternalKey, mappingBytes)
|
||||
err = rv.i.SetInternal(util.MappingInternalKey, mappingBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -163,6 +163,9 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
||||
rv.meta.IndexType = upsidedown.Name
|
||||
}
|
||||
|
||||
var um *mapping.IndexMappingImpl
|
||||
var umBytes []byte
|
||||
|
||||
storeConfig := rv.meta.Config
|
||||
if storeConfig == nil {
|
||||
storeConfig = map[string]interface{}{}
|
||||
@@ -173,6 +176,21 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
||||
storeConfig["error_if_exists"] = false
|
||||
for rck, rcv := range runtimeConfig {
|
||||
storeConfig[rck] = rcv
|
||||
if rck == "updated_mapping" {
|
||||
if val, ok := rcv.(string); ok {
|
||||
if len(val) == 0 {
|
||||
return nil, fmt.Errorf("updated_mapping is empty")
|
||||
}
|
||||
umBytes = []byte(val)
|
||||
|
||||
err = util.UnmarshalJSON(umBytes, &um)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing updated_mapping into JSON: %v\nmapping contents:\n%v", err, rck)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("updated_mapping not of type string")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// open the index
|
||||
@@ -185,15 +203,32 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rv.i.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rv *indexImpl) {
|
||||
if !rv.open {
|
||||
rv.i.Close()
|
||||
|
||||
var ui index.UpdateIndex
|
||||
if um != nil {
|
||||
var ok bool
|
||||
ui, ok = rv.i.(index.UpdateIndex)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("updated mapping present for unupdatable index")
|
||||
}
|
||||
}(rv)
|
||||
|
||||
// Load the meta data from bolt so that we can read the current index
|
||||
// mapping to compare with
|
||||
err = ui.OpenMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err = rv.i.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rv *indexImpl) {
|
||||
if !rv.open {
|
||||
rv.i.Close()
|
||||
}
|
||||
}(rv)
|
||||
}
|
||||
|
||||
// now load the mapping
|
||||
indexReader, err := rv.i.Reader()
|
||||
@@ -206,7 +241,7 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
||||
}
|
||||
}()
|
||||
|
||||
mappingBytes, err := indexReader.GetInternal(mappingInternalKey)
|
||||
mappingBytes, err := indexReader.GetInternal(util.MappingInternalKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,19 +252,48 @@ func openIndexUsing(path string, runtimeConfig map[string]interface{}) (rv *inde
|
||||
return nil, fmt.Errorf("error parsing mapping JSON: %v\nmapping contents:\n%s", err, string(mappingBytes))
|
||||
}
|
||||
|
||||
// validate the mapping
|
||||
err = im.Validate()
|
||||
if err != nil {
|
||||
// no longer return usable index on error because there
|
||||
// is a chance the index is not open at this stage
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate and update the index with the new mapping
|
||||
if um != nil && ui != nil {
|
||||
err = um.Validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fieldInfo, err := DeletedFields(im, um)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ui.UpdateFields(fieldInfo, umBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
im = um
|
||||
|
||||
err = rv.i.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(rv *indexImpl) {
|
||||
if !rv.open {
|
||||
rv.i.Close()
|
||||
}
|
||||
}(rv)
|
||||
}
|
||||
|
||||
// mark the index as open
|
||||
rv.mutex.Lock()
|
||||
defer rv.mutex.Unlock()
|
||||
rv.open = true
|
||||
|
||||
// validate the mapping
|
||||
err = im.Validate()
|
||||
if err != nil {
|
||||
// note even if the mapping is invalid
|
||||
// we still return an open usable index
|
||||
return rv, err
|
||||
}
|
||||
|
||||
rv.m = im
|
||||
indexStats.Register(rv)
|
||||
return rv, err
|
||||
@@ -562,6 +626,21 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
|
||||
}
|
||||
}()
|
||||
|
||||
// rescorer will be set if score fusion is supposed to happen
|
||||
// at this alias (root alias), else will be nil
|
||||
var rescorer *rescorer
|
||||
if _, ok := ctx.Value(search.ScoreFusionKey).(bool); !ok {
|
||||
// new context will be used in internal functions to collect data
|
||||
// as suitable for hybrid search. Rescorer is used for rescoring
|
||||
// using fusion algorithms.
|
||||
if IsScoreFusionRequested(req) {
|
||||
ctx = context.WithValue(ctx, search.ScoreFusionKey, true)
|
||||
rescorer = newRescorer(req)
|
||||
rescorer.prepareSearchRequest()
|
||||
defer rescorer.restoreSearchRequest()
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := ctx.Value(search.PreSearchKey).(bool); ok {
|
||||
preSearchResult, err := i.preSearch(ctx, req, indexReader)
|
||||
if err != nil {
|
||||
@@ -632,10 +711,21 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
|
||||
}
|
||||
}
|
||||
}
|
||||
if !skipKNNCollector && requestHasKNN(req) {
|
||||
knnHits, err = i.runKnnCollector(ctx, req, indexReader, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
_, contextScoreFusionKeyExists := ctx.Value(search.ScoreFusionKey).(bool)
|
||||
|
||||
if !contextScoreFusionKeyExists {
|
||||
// if no score fusion, default behaviour
|
||||
if !skipKNNCollector && requestHasKNN(req) {
|
||||
knnHits, err = i.runKnnCollector(ctx, req, indexReader, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if score fusion, run collect if rescorer is defined
|
||||
if rescorer != nil && requestHasKNN(req) {
|
||||
knnHits, err = i.runKnnCollector(ctx, req, indexReader, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -650,7 +740,12 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
|
||||
}
|
||||
}
|
||||
|
||||
setKnnHitsInCollector(knnHits, req, coll)
|
||||
// if score fusion, no faceting for knn hits is done
|
||||
// hence we can skip setting the knn hits in the collector
|
||||
if !contextScoreFusionKeyExists {
|
||||
setKnnHitsInCollector(knnHits, req, coll)
|
||||
}
|
||||
|
||||
|
||||
if fts != nil {
|
||||
if is, ok := indexReader.(*scorch.IndexSnapshot); ok {
|
||||
@@ -859,6 +954,13 @@ func (i *indexImpl) SearchInContext(ctx context.Context, req *SearchRequest) (sr
|
||||
Facets: coll.FacetResults(),
|
||||
}
|
||||
|
||||
// rescore if fusion flag is set
|
||||
if rescorer != nil {
|
||||
rv.Hits, rv.Total, rv.MaxScore = rescorer.rescore(rv.Hits, knnHits)
|
||||
rescorer.restoreSearchRequest()
|
||||
rv.Hits = hitsInCurrentPage(req, rv.Hits)
|
||||
}
|
||||
|
||||
if req.Explain {
|
||||
rv.Request = req
|
||||
}
|
||||
|
||||
595
vendor/github.com/blevesearch/bleve/v2/index_update.go
generated
vendored
Normal file
595
vendor/github.com/blevesearch/bleve/v2/index_update.go
generated
vendored
Normal file
@@ -0,0 +1,595 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/mapping"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
)
|
||||
|
||||
// Store all the fields that interact with the data
|
||||
// from a document path
|
||||
type pathInfo struct {
|
||||
fieldMapInfo []*fieldMapInfo
|
||||
dynamic bool
|
||||
path string
|
||||
analyser string
|
||||
parentPath string
|
||||
}
|
||||
|
||||
// Store the field information with respect to the
|
||||
// document paths
|
||||
type fieldMapInfo struct {
|
||||
fieldMapping *mapping.FieldMapping
|
||||
analyzer string
|
||||
datetimeParser string
|
||||
rootName string
|
||||
parent *pathInfo
|
||||
}
|
||||
|
||||
// Compare two index mappings to identify all of the updatable changes
|
||||
func DeletedFields(ori, upd *mapping.IndexMappingImpl) (map[string]*index.UpdateFieldInfo, error) {
|
||||
// Compare all of the top level fields in an index mapping
|
||||
err := compareMappings(ori, upd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for new mappings present in the type mappings
|
||||
// of the updated compared to the original
|
||||
for name, updDMapping := range upd.TypeMapping {
|
||||
err = checkUpdatedMapping(ori.TypeMapping[name], updDMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check for new mappings present in the default mappings
|
||||
// of the updated compared to the original
|
||||
err = checkUpdatedMapping(ori.DefaultMapping, upd.DefaultMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oriPaths := make(map[string]*pathInfo)
|
||||
updPaths := make(map[string]*pathInfo)
|
||||
|
||||
// Go through each mapping present in the original
|
||||
// and consolidate according to the document paths
|
||||
for name, oriDMapping := range ori.TypeMapping {
|
||||
addPathInfo(oriPaths, "", oriDMapping, ori, nil, name)
|
||||
}
|
||||
addPathInfo(oriPaths, "", ori.DefaultMapping, ori, nil, "")
|
||||
|
||||
// Go through each mapping present in the updated
|
||||
// and consolidate according to the document paths
|
||||
for name, updDMapping := range upd.TypeMapping {
|
||||
addPathInfo(updPaths, "", updDMapping, upd, nil, name)
|
||||
}
|
||||
addPathInfo(updPaths, "", upd.DefaultMapping, upd, nil, "")
|
||||
|
||||
// Compare all components of custom analysis currently in use
|
||||
err = compareCustomComponents(oriPaths, updPaths, ori, upd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compare both the mappings based on the document paths
|
||||
// and create a list of index, docvalues, store differences
|
||||
// for every single field possible
|
||||
fieldInfo := make(map[string]*index.UpdateFieldInfo)
|
||||
for path, info := range oriPaths {
|
||||
err = addFieldInfo(fieldInfo, info, updPaths[path])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove entries from the list with no changes between the
|
||||
// original and the updated mapping
|
||||
for name, info := range fieldInfo {
|
||||
if !info.Deleted && !info.Index && !info.DocValues && !info.Store {
|
||||
delete(fieldInfo, name)
|
||||
}
|
||||
// A field cannot be completely deleted with any dynamic value turned on
|
||||
if info.Deleted {
|
||||
if upd.IndexDynamic {
|
||||
return nil, fmt.Errorf("Mapping cannot be removed when index dynamic is true")
|
||||
}
|
||||
if upd.StoreDynamic {
|
||||
return nil, fmt.Errorf("Mapping cannot be removed when store dynamic is true")
|
||||
}
|
||||
if upd.DocValuesDynamic {
|
||||
return nil, fmt.Errorf("Mapping cannot be removed when docvalues dynamic is true")
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldInfo, nil
|
||||
}
|
||||
|
||||
// Ensures none of the top level index mapping fields have changed
|
||||
func compareMappings(ori, upd *mapping.IndexMappingImpl) error {
|
||||
if ori.TypeField != upd.TypeField &&
|
||||
(len(ori.TypeMapping) != 0 || len(upd.TypeMapping) != 0) {
|
||||
return fmt.Errorf("type field cannot be changed when type mappings are present")
|
||||
}
|
||||
|
||||
if ori.DefaultType != upd.DefaultType {
|
||||
return fmt.Errorf("default type cannot be changed")
|
||||
}
|
||||
|
||||
if ori.IndexDynamic != upd.IndexDynamic {
|
||||
return fmt.Errorf("index dynamic cannot be changed")
|
||||
}
|
||||
|
||||
if ori.StoreDynamic != upd.StoreDynamic {
|
||||
return fmt.Errorf("store dynamic cannot be changed")
|
||||
}
|
||||
|
||||
if ori.DocValuesDynamic != upd.DocValuesDynamic {
|
||||
return fmt.Errorf("docvalues dynamic cannot be changed")
|
||||
}
|
||||
|
||||
if ori.DefaultAnalyzer != upd.DefaultAnalyzer && upd.IndexDynamic {
|
||||
return fmt.Errorf("default analyser cannot be changed if index dynamic is true")
|
||||
}
|
||||
|
||||
if ori.DefaultDateTimeParser != upd.DefaultDateTimeParser && upd.IndexDynamic {
|
||||
return fmt.Errorf("default datetime parser cannot be changed if index dynamic is true")
|
||||
}
|
||||
|
||||
// Scoring model changes between "", "tf-idf" and "bm25" require no index changes to be made
|
||||
if ori.ScoringModel != upd.ScoringModel {
|
||||
if ori.ScoringModel != "" && ori.ScoringModel != index.TFIDFScoring && ori.ScoringModel != index.BM25Scoring ||
|
||||
upd.ScoringModel != "" && upd.ScoringModel != index.TFIDFScoring && upd.ScoringModel != index.BM25Scoring {
|
||||
return fmt.Errorf("scoring model can only be changed between \"\", %q and %q", index.TFIDFScoring, index.BM25Scoring)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensures updated document mapping does not contain new
|
||||
// field mappings or document mappings
|
||||
func checkUpdatedMapping(ori, upd *mapping.DocumentMapping) error {
|
||||
// Check to verify both original and updated are not nil
|
||||
// and are enabled before proceeding
|
||||
if ori == nil {
|
||||
if upd == nil || !upd.Enabled {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("updated index mapping contains new properties")
|
||||
}
|
||||
|
||||
if upd == nil || !upd.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
// Recursively go through the child mappings
|
||||
for name, updDMapping := range upd.Properties {
|
||||
err = checkUpdatedMapping(ori.Properties[name], updDMapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Simple checks to ensure no new field mappings present
|
||||
// in updated
|
||||
for _, updFMapping := range upd.Fields {
|
||||
var oriFMapping *mapping.FieldMapping
|
||||
for _, fMapping := range ori.Fields {
|
||||
if updFMapping.Name == fMapping.Name {
|
||||
oriFMapping = fMapping
|
||||
}
|
||||
}
|
||||
if oriFMapping == nil {
|
||||
return fmt.Errorf("updated index mapping contains new fields")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Adds all of the field mappings while maintaining a tree of the document structure
|
||||
// to ensure traversal and verification is possible incase of multiple mappings defined
|
||||
// for a single field or multiple document fields' data getting written to a single zapx field
|
||||
func addPathInfo(paths map[string]*pathInfo, name string, mp *mapping.DocumentMapping,
|
||||
im *mapping.IndexMappingImpl, parent *pathInfo, rootName string) {
|
||||
// Early exit if mapping has been disabled
|
||||
// Comparisions later on will be done with a nil object
|
||||
if !mp.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Consolidate path information like index dynamic across multiple
|
||||
// mappings if path is the same
|
||||
var pInfo *pathInfo
|
||||
if val, ok := paths[name]; ok {
|
||||
pInfo = val
|
||||
} else {
|
||||
pInfo = &pathInfo{
|
||||
fieldMapInfo: make([]*fieldMapInfo, 0),
|
||||
}
|
||||
pInfo.dynamic = mp.Dynamic && im.IndexDynamic
|
||||
pInfo.analyser = im.AnalyzerNameForPath(name)
|
||||
}
|
||||
|
||||
pInfo.dynamic = (pInfo.dynamic || mp.Dynamic) && im.IndexDynamic
|
||||
pInfo.path = name
|
||||
if parent != nil {
|
||||
pInfo.parentPath = parent.path
|
||||
}
|
||||
|
||||
// Recursively add path information for all child mappings
|
||||
for cName, cMapping := range mp.Properties {
|
||||
var pathName string
|
||||
if name == "" {
|
||||
pathName = cName
|
||||
} else {
|
||||
pathName = name + "." + cName
|
||||
}
|
||||
addPathInfo(paths, pathName, cMapping, im, pInfo, rootName)
|
||||
}
|
||||
|
||||
// Add field mapping information keeping the document structure intact
|
||||
for _, fMap := range mp.Fields {
|
||||
fieldMapInfo := &fieldMapInfo{
|
||||
fieldMapping: fMap,
|
||||
rootName: rootName,
|
||||
parent: pInfo,
|
||||
}
|
||||
pInfo.fieldMapInfo = append(pInfo.fieldMapInfo, fieldMapInfo)
|
||||
}
|
||||
|
||||
paths[name] = pInfo
|
||||
}
|
||||
|
||||
// Compares all of the custom analysis components in use
|
||||
func compareCustomComponents(oriPaths, updPaths map[string]*pathInfo, ori, upd *mapping.IndexMappingImpl) error {
|
||||
// Compare all analysers currently in use
|
||||
err := compareAnalysers(oriPaths, updPaths, ori, upd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare all datetime parsers currently in use
|
||||
err = compareDateTimeParsers(oriPaths, updPaths, ori, upd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare all synonum sources
|
||||
err = compareSynonymSources(ori, upd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare all char filters, tokenizers, token filters and token maps
|
||||
err = compareAnalyserSubcomponents(ori, upd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compares all analysers currently in use
|
||||
// Standard analysers not in custom analysis are not compared
|
||||
// Analysers in custom analysis but not in use are not compared
|
||||
func compareAnalysers(oriPaths, updPaths map[string]*pathInfo, ori, upd *mapping.IndexMappingImpl) error {
|
||||
oriAnalyzers := make(map[string]interface{})
|
||||
updAnalyzers := make(map[string]interface{})
|
||||
|
||||
extractAnalyzers := func(paths map[string]*pathInfo, customAnalyzers map[string]map[string]interface{},
|
||||
analyzers map[string]interface{}, indexMapping *mapping.IndexMappingImpl) {
|
||||
for path, info := range paths {
|
||||
for _, fInfo := range info.fieldMapInfo {
|
||||
if fInfo.fieldMapping.Type == "text" {
|
||||
analyzerName := indexMapping.AnalyzerNameForPath(path)
|
||||
fInfo.analyzer = analyzerName
|
||||
if val, ok := customAnalyzers[analyzerName]; ok {
|
||||
analyzers[analyzerName] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractAnalyzers(oriPaths, ori.CustomAnalysis.Analyzers, oriAnalyzers, ori)
|
||||
extractAnalyzers(updPaths, upd.CustomAnalysis.Analyzers, updAnalyzers, upd)
|
||||
|
||||
for name, anUpd := range updAnalyzers {
|
||||
if anOri, ok := oriAnalyzers[name]; ok {
|
||||
if !reflect.DeepEqual(anUpd, anOri) {
|
||||
return fmt.Errorf("analyser %s changed while being used by fields", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("analyser %s newly added to an existing field", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compares all date time parsers currently in use
|
||||
// Date time parsers in custom analysis but not in use are not compared
|
||||
func compareDateTimeParsers(oriPaths, updPaths map[string]*pathInfo, ori, upd *mapping.IndexMappingImpl) error {
|
||||
oriDateTimeParsers := make(map[string]interface{})
|
||||
updDateTimeParsers := make(map[string]interface{})
|
||||
|
||||
extractDateTimeParsers := func(paths map[string]*pathInfo, customParsers map[string]map[string]interface{},
|
||||
parsers map[string]interface{}, indexMapping *mapping.IndexMappingImpl) {
|
||||
for _, info := range paths {
|
||||
for _, fInfo := range info.fieldMapInfo {
|
||||
if fInfo.fieldMapping.Type == "datetime" {
|
||||
parserName := fInfo.fieldMapping.DateFormat
|
||||
if parserName == "" {
|
||||
parserName = indexMapping.DefaultDateTimeParser
|
||||
}
|
||||
fInfo.datetimeParser = parserName
|
||||
if val, ok := customParsers[parserName]; ok {
|
||||
parsers[parserName] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extractDateTimeParsers(oriPaths, ori.CustomAnalysis.DateTimeParsers, oriDateTimeParsers, ori)
|
||||
extractDateTimeParsers(updPaths, upd.CustomAnalysis.DateTimeParsers, updDateTimeParsers, upd)
|
||||
|
||||
for name, dtUpd := range updDateTimeParsers {
|
||||
if dtOri, ok := oriDateTimeParsers[name]; ok {
|
||||
if !reflect.DeepEqual(dtUpd, dtOri) {
|
||||
return fmt.Errorf("datetime parser %s changed while being used by fields", name)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("datetime parser %s added to an existing field", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compares all synonym sources
|
||||
// Synonym sources currently not in use are also compared
|
||||
func compareSynonymSources(ori, upd *mapping.IndexMappingImpl) error {
|
||||
if !reflect.DeepEqual(ori.CustomAnalysis.SynonymSources, upd.CustomAnalysis.SynonymSources) {
|
||||
return fmt.Errorf("synonym sources cannot be changed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compares all char filters, tokenizers, token filters and token maps
|
||||
// Components not currently in use are also compared
|
||||
func compareAnalyserSubcomponents(ori, upd *mapping.IndexMappingImpl) error {
|
||||
if !reflect.DeepEqual(ori.CustomAnalysis.CharFilters, upd.CustomAnalysis.CharFilters) {
|
||||
return fmt.Errorf("char filters cannot be changed")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ori.CustomAnalysis.TokenFilters, upd.CustomAnalysis.TokenFilters) {
|
||||
return fmt.Errorf("token filters cannot be changed")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ori.CustomAnalysis.TokenMaps, upd.CustomAnalysis.TokenMaps) {
|
||||
return fmt.Errorf("token maps cannot be changed")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ori.CustomAnalysis.Tokenizers, upd.CustomAnalysis.Tokenizers) {
|
||||
return fmt.Errorf("tokenizers cannot be changed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compare all of the fields at a particular document path and add its field information
|
||||
func addFieldInfo(fInfo map[string]*index.UpdateFieldInfo, ori, upd *pathInfo) error {
|
||||
var info *index.UpdateFieldInfo
|
||||
var err error
|
||||
|
||||
// Assume deleted or disabled mapping if upd is nil. Checks for ori being nil
|
||||
// or upd having mappings not in orihave already been done before this stage
|
||||
if upd == nil {
|
||||
for _, oriFMapInfo := range ori.fieldMapInfo {
|
||||
info, err = compareFieldMapping(oriFMapInfo.fieldMapping, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = validateFieldInfo(info, fInfo, ori, oriFMapInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if upd.dynamic && ori.analyser != upd.analyser {
|
||||
return fmt.Errorf("analyser has been changed for a dynamic mapping")
|
||||
}
|
||||
for _, oriFMapInfo := range ori.fieldMapInfo {
|
||||
var updFMap *mapping.FieldMapping
|
||||
var updAnalyser string
|
||||
var updDatetimeParser string
|
||||
|
||||
// For multiple fields at a single document path, compare
|
||||
// only with the matching ones
|
||||
for _, updFMapInfo := range upd.fieldMapInfo {
|
||||
if oriFMapInfo.rootName == updFMapInfo.rootName &&
|
||||
oriFMapInfo.fieldMapping.Name == updFMapInfo.fieldMapping.Name {
|
||||
updFMap = updFMapInfo.fieldMapping
|
||||
if updFMap.Type == "text" {
|
||||
updAnalyser = updFMapInfo.analyzer
|
||||
} else if updFMap.Type == "datetime" {
|
||||
updDatetimeParser = updFMapInfo.datetimeParser
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compare analyser, datetime parser and synonym source before comparing
|
||||
// the field mapping as it might not have this information
|
||||
if updAnalyser != "" && oriFMapInfo.analyzer != updAnalyser {
|
||||
return fmt.Errorf("analyser has been changed for a text field")
|
||||
}
|
||||
if updDatetimeParser != "" && oriFMapInfo.datetimeParser != updDatetimeParser {
|
||||
return fmt.Errorf("datetime parser has been changed for a date time field")
|
||||
}
|
||||
info, err = compareFieldMapping(oriFMapInfo.fieldMapping, updFMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate to ensure change is possible
|
||||
// Needed if multiple mappings are aliased to the same field
|
||||
err = validateFieldInfo(info, fInfo, ori, oriFMapInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compares two field mappings against each other, checking for changes in index, store, doc values
|
||||
// and complete deletiion of the mapping while noting that the changes made are doable based on
|
||||
// other values like includeInAll and dynamic
|
||||
// first return argument gives an empty fieldInfo if no changes detected
|
||||
// second return argument gives a flag indicating whether any changes, if detected, are doable or if
|
||||
// update is impossible
|
||||
// third argument is an error explaining exactly why the change is not possible
|
||||
func compareFieldMapping(original, updated *mapping.FieldMapping) (*index.UpdateFieldInfo, error) {
|
||||
rv := &index.UpdateFieldInfo{}
|
||||
|
||||
if updated == nil {
|
||||
if original != nil && !original.IncludeInAll {
|
||||
rv.Deleted = true
|
||||
return rv, nil
|
||||
} else if original == nil {
|
||||
return nil, fmt.Errorf("both field mappings cannot be nil")
|
||||
}
|
||||
return nil, fmt.Errorf("deleted field present in '_all' field")
|
||||
} else if original == nil {
|
||||
return nil, fmt.Errorf("matching field not found in original index mapping")
|
||||
}
|
||||
|
||||
if original.Type != updated.Type {
|
||||
return nil, fmt.Errorf("field type cannot be updated")
|
||||
}
|
||||
if original.Type == "text" {
|
||||
if original.Analyzer != updated.Analyzer {
|
||||
return nil, fmt.Errorf("analyzer cannot be updated for text fields")
|
||||
}
|
||||
}
|
||||
if original.Type == "datetime" {
|
||||
if original.DateFormat != updated.DateFormat {
|
||||
return nil, fmt.Errorf("dateFormat cannot be updated for datetime fields")
|
||||
}
|
||||
}
|
||||
if original.Type == "vector" || original.Type == "vector_base64" {
|
||||
if original.Dims != updated.Dims {
|
||||
return nil, fmt.Errorf("dimensions cannot be updated for vector and vector_base64 fields")
|
||||
}
|
||||
if original.Similarity != updated.Similarity {
|
||||
return nil, fmt.Errorf("similarity cannot be updated for vector and vector_base64 fields")
|
||||
}
|
||||
if original.VectorIndexOptimizedFor != updated.VectorIndexOptimizedFor {
|
||||
return nil, fmt.Errorf("vectorIndexOptimizedFor cannot be updated for vector and vector_base64 fields")
|
||||
}
|
||||
}
|
||||
if original.IncludeInAll != updated.IncludeInAll {
|
||||
return nil, fmt.Errorf("includeInAll cannot be changed")
|
||||
}
|
||||
if original.IncludeTermVectors != updated.IncludeTermVectors {
|
||||
return nil, fmt.Errorf("includeTermVectors cannot be changed")
|
||||
}
|
||||
if original.SkipFreqNorm != updated.SkipFreqNorm {
|
||||
return nil, fmt.Errorf("skipFreqNorm cannot be changed")
|
||||
}
|
||||
|
||||
// Updating is not possible if store changes from true
|
||||
// to false when the field is included in _all
|
||||
if original.Store != updated.Store {
|
||||
if updated.Store {
|
||||
return nil, fmt.Errorf("store cannot be changed from false to true")
|
||||
} else if updated.IncludeInAll {
|
||||
return nil, fmt.Errorf("store cannot be changed if field present in `_all' field")
|
||||
} else {
|
||||
rv.Store = true
|
||||
}
|
||||
}
|
||||
|
||||
// Updating is not possible if index changes from true
|
||||
// to false when the field is included in _all
|
||||
if original.Index != updated.Index {
|
||||
if updated.Index {
|
||||
return nil, fmt.Errorf("index cannot be changed from false to true")
|
||||
} else if updated.IncludeInAll {
|
||||
return nil, fmt.Errorf("index cannot be changed if field present in `_all' field")
|
||||
} else {
|
||||
rv.Index = true
|
||||
rv.DocValues = true
|
||||
}
|
||||
}
|
||||
|
||||
// Updating is not possible if docvalues changes from true
|
||||
// to false when the field is included in _all
|
||||
if original.DocValues != updated.DocValues {
|
||||
if updated.DocValues {
|
||||
return nil, fmt.Errorf("docvalues cannot be changed from false to true")
|
||||
} else if updated.IncludeInAll {
|
||||
return nil, fmt.Errorf("docvalues cannot be changed if field present in `_all' field")
|
||||
} else {
|
||||
rv.DocValues = true
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// After identifying changes, validate against the existing changes incase of duplicate fields.
|
||||
// In such a situation, any conflicting changes found will abort the update process
|
||||
func validateFieldInfo(newInfo *index.UpdateFieldInfo, fInfo map[string]*index.UpdateFieldInfo,
|
||||
ori *pathInfo, oriFMapInfo *fieldMapInfo) error {
|
||||
var name string
|
||||
if oriFMapInfo.parent.parentPath == "" {
|
||||
if oriFMapInfo.fieldMapping.Name == "" {
|
||||
name = oriFMapInfo.parent.path
|
||||
} else {
|
||||
name = oriFMapInfo.fieldMapping.Name
|
||||
}
|
||||
} else {
|
||||
if oriFMapInfo.fieldMapping.Name == "" {
|
||||
name = oriFMapInfo.parent.parentPath + "." + oriFMapInfo.parent.path
|
||||
} else {
|
||||
name = oriFMapInfo.parent.parentPath + "." + oriFMapInfo.fieldMapping.Name
|
||||
}
|
||||
}
|
||||
if (newInfo.Deleted || newInfo.Index || newInfo.DocValues || newInfo.Store) && ori.dynamic {
|
||||
return fmt.Errorf("updated field is under a dynamic property")
|
||||
}
|
||||
if oldInfo, ok := fInfo[name]; ok {
|
||||
if !reflect.DeepEqual(oldInfo, newInfo) {
|
||||
return fmt.Errorf("updated field impossible to verify because multiple mappings point to the same field name")
|
||||
}
|
||||
} else {
|
||||
fInfo[name] = newInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
2
vendor/github.com/blevesearch/bleve/v2/numeric/bin.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/numeric/bin.go
generated
vendored
@@ -13,7 +13,7 @@ var interleaveMagic = []uint64{
|
||||
var interleaveShift = []uint{1, 2, 4, 8, 16}
|
||||
|
||||
// Interleave the first 32 bits of each uint64
|
||||
// apdated from org.apache.lucene.util.BitUtil
|
||||
// adapted from org.apache.lucene.util.BitUtil
|
||||
// which was adapted from:
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
|
||||
func Interleave(v1, v2 uint64) uint64 {
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/registry/store.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/registry/store.go
generated
vendored
@@ -30,7 +30,7 @@ func RegisterKVStore(name string, constructor KVStoreConstructor) error {
|
||||
}
|
||||
|
||||
// KVStoreConstructor is used to build a KVStore of a specific type when
|
||||
// specificied by the index configuration. In addition to meeting the
|
||||
// specified by the index configuration. In addition to meeting the
|
||||
// store.KVStore interface, KVStores must also support this constructor.
|
||||
// Note that currently the values of config must
|
||||
// be able to be marshaled and unmarshaled using the encoding/json library (used
|
||||
|
||||
162
vendor/github.com/blevesearch/bleve/v2/rescorer.go
generated
vendored
Normal file
162
vendor/github.com/blevesearch/bleve/v2/rescorer.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"github.com/blevesearch/bleve/v2/fusion"
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultScoreRankConstant = 60
|
||||
)
|
||||
|
||||
// Rescorer is applied after all the query and knn results are obtained.
|
||||
// The main use of Rescorer is in hybrid search; all the individual scores
|
||||
// for query and knn are combined using Rescorer. Makes use of algorithms
|
||||
// defined in `fusion`
|
||||
type rescorer struct {
|
||||
req *SearchRequest
|
||||
|
||||
// Stores the original From, Size and Boost parameters from the request
|
||||
origFrom int
|
||||
origSize int
|
||||
origBoosts []float64
|
||||
|
||||
// Flag variable to make sure that restoreSearchRequest is only run once
|
||||
// when it is deferred
|
||||
restored bool
|
||||
}
|
||||
|
||||
// Stores information about the hybrid search into FusionRescorer.
|
||||
// Also mutates the SearchRequest by:
|
||||
// - Setting boosts to 1: top level boosts only used for rescoring
|
||||
// - Setting From and Size to 0 and ScoreWindowSize
|
||||
func (r *rescorer) prepareSearchRequest() error {
|
||||
if r.req.Params == nil {
|
||||
r.req.Params = NewDefaultParams(r.req.From, r.req.Size)
|
||||
}
|
||||
|
||||
r.origFrom = r.req.From
|
||||
r.origSize = r.req.Size
|
||||
|
||||
r.req.From = 0
|
||||
r.req.Size = r.req.Params.ScoreWindowSize
|
||||
|
||||
// req.Query's top level boost comes first, followed by the KNN queries
|
||||
numQueries := numKNNQueries(r.req) + 1
|
||||
r.origBoosts = make([]float64, numQueries)
|
||||
|
||||
// only modify queries if it is boostable. If not, ignore
|
||||
if bQuery, ok := r.req.Query.(query.BoostableQuery); ok {
|
||||
r.origBoosts[0] = bQuery.Boost()
|
||||
bQuery.SetBoost(1.0)
|
||||
} else {
|
||||
r.origBoosts[0] = 1.0
|
||||
}
|
||||
|
||||
// for all the knn queries, replace boost values
|
||||
r.prepareKnnRequest()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rescorer) restoreSearchRequest() {
|
||||
// Skip if already restored
|
||||
if r.restored {
|
||||
return
|
||||
}
|
||||
r.restored = true
|
||||
|
||||
r.req.From = r.origFrom
|
||||
r.req.Size = r.origSize
|
||||
|
||||
if bQuery, ok := r.req.Query.(query.BoostableQuery); ok {
|
||||
bQuery.SetBoost(r.origBoosts[0])
|
||||
}
|
||||
|
||||
// for all the knn queries, restore boost values
|
||||
r.restoreKnnRequest()
|
||||
}
|
||||
|
||||
func (r *rescorer) rescore(ftsHits, knnHits search.DocumentMatchCollection) (search.DocumentMatchCollection, uint64, float64) {
|
||||
mergedHits := r.mergeDocs(ftsHits, knnHits)
|
||||
|
||||
var fusionResult *fusion.FusionResult
|
||||
|
||||
switch r.req.Score {
|
||||
case ScoreRRF:
|
||||
res := fusion.ReciprocalRankFusion(
|
||||
mergedHits,
|
||||
r.origBoosts,
|
||||
r.req.Params.ScoreRankConstant,
|
||||
r.req.Params.ScoreWindowSize,
|
||||
numKNNQueries(r.req),
|
||||
r.req.Explain,
|
||||
)
|
||||
fusionResult = &res
|
||||
case ScoreRSF:
|
||||
res := fusion.RelativeScoreFusion(
|
||||
mergedHits,
|
||||
r.origBoosts,
|
||||
r.req.Params.ScoreWindowSize,
|
||||
numKNNQueries(r.req),
|
||||
r.req.Explain,
|
||||
)
|
||||
fusionResult = &res
|
||||
}
|
||||
|
||||
return fusionResult.Hits, fusionResult.Total, fusionResult.MaxScore
|
||||
}
|
||||
|
||||
// Merge all the FTS and KNN docs along with explanations
|
||||
func (r *rescorer) mergeDocs(ftsHits, knnHits search.DocumentMatchCollection) search.DocumentMatchCollection {
|
||||
if len(knnHits) == 0 {
|
||||
return ftsHits
|
||||
}
|
||||
|
||||
knnHitMap := make(map[string]*search.DocumentMatch, len(knnHits))
|
||||
|
||||
for _, hit := range knnHits {
|
||||
knnHitMap[hit.ID] = hit
|
||||
}
|
||||
|
||||
for _, hit := range ftsHits {
|
||||
if knnHit, ok := knnHitMap[hit.ID]; ok {
|
||||
hit.ScoreBreakdown = knnHit.ScoreBreakdown
|
||||
if r.req.Explain {
|
||||
hit.Expl = &search.Explanation{Value: 0.0, Message: "", Children: append([]*search.Explanation{hit.Expl}, knnHit.Expl.Children...)}
|
||||
}
|
||||
delete(knnHitMap, hit.ID)
|
||||
}
|
||||
}
|
||||
|
||||
for _, hit := range knnHitMap {
|
||||
hit.Score = 0
|
||||
ftsHits = append(ftsHits, hit)
|
||||
if r.req.Explain {
|
||||
hit.Expl = &search.Explanation{Value: 0.0, Message: "", Children: append([]*search.Explanation{nil}, hit.Expl.Children...)}
|
||||
}
|
||||
}
|
||||
|
||||
return ftsHits
|
||||
}
|
||||
|
||||
func newRescorer(req *SearchRequest) *rescorer {
|
||||
return &rescorer{
|
||||
req: req,
|
||||
}
|
||||
}
|
||||
157
vendor/github.com/blevesearch/bleve/v2/search.go
generated
vendored
157
vendor/github.com/blevesearch/bleve/v2/search.go
generated
vendored
@@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/analysis"
|
||||
@@ -47,6 +48,15 @@ var cache = registry.NewCache()
|
||||
|
||||
const defaultDateTimeParser = optional.Name
|
||||
|
||||
const (
|
||||
ScoreDefault = ""
|
||||
ScoreNone = "none"
|
||||
ScoreRRF = "rrf"
|
||||
ScoreRSF = "rsf"
|
||||
)
|
||||
|
||||
var AllowedFusionSort = search.SortOrder{&search.SortScore{Desc: true}}
|
||||
|
||||
type dateTimeRange struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Start time.Time `json:"start,omitempty"`
|
||||
@@ -311,13 +321,71 @@ func (r *SearchRequest) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
err := validateKNN(r)
|
||||
err := r.validatePagination()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if IsScoreFusionRequested(r) {
|
||||
if r.SearchAfter != nil || r.SearchBefore != nil {
|
||||
return fmt.Errorf("cannot use search after or search before with score fusion")
|
||||
}
|
||||
|
||||
if r.Sort != nil {
|
||||
if !reflect.DeepEqual(r.Sort, AllowedFusionSort) {
|
||||
return fmt.Errorf("sort must be empty or descending order of score for score fusion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = validateKNN(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Facets.Validate()
|
||||
}
|
||||
|
||||
// Validates SearchAfter/SearchBefore
|
||||
func (r *SearchRequest) validatePagination() error {
|
||||
var pagination []string
|
||||
var afterOrBefore string
|
||||
|
||||
if r.SearchAfter != nil {
|
||||
pagination = r.SearchAfter
|
||||
afterOrBefore = "search after"
|
||||
} else if r.SearchBefore != nil {
|
||||
pagination = r.SearchBefore
|
||||
afterOrBefore = "search before"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range pagination {
|
||||
switch ss := r.Sort[i].(type) {
|
||||
case *search.SortGeoDistance:
|
||||
_, err := strconv.ParseFloat(pagination[i], 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s value for sort field '%s': '%s'. %s", afterOrBefore, ss.Field, pagination[i], err)
|
||||
}
|
||||
case *search.SortField:
|
||||
switch ss.Type {
|
||||
case search.SortFieldAsNumber:
|
||||
_, err := strconv.ParseFloat(pagination[i], 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s value for sort field '%s': '%s'. %s", afterOrBefore, ss.Field, pagination[i], err)
|
||||
}
|
||||
case search.SortFieldAsDate:
|
||||
_, err := time.Parse(time.RFC3339Nano, pagination[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid %s value for sort field '%s': '%s'. %s", afterOrBefore, ss.Field, pagination[i], err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddFacet adds a FacetRequest to this SearchRequest
|
||||
func (r *SearchRequest) AddFacet(facetName string, f *FacetRequest) {
|
||||
if r.Facets == nil {
|
||||
@@ -353,6 +421,11 @@ func (r *SearchRequest) SetSearchBefore(before []string) {
|
||||
r.SearchBefore = before
|
||||
}
|
||||
|
||||
// AddParams adds a RequestParams field to the search request
|
||||
func (r *SearchRequest) AddParams(params RequestParams) {
|
||||
r.Params = ¶ms
|
||||
}
|
||||
|
||||
// NewSearchRequest creates a new SearchRequest
|
||||
// for the Query, using default values for all
|
||||
// other search parameters.
|
||||
@@ -377,7 +450,7 @@ func NewSearchRequestOptions(q query.Query, size, from int, explain bool) *Searc
|
||||
// IndexErrMap tracks errors with the name of the index where it occurred
|
||||
type IndexErrMap map[string]error
|
||||
|
||||
// MarshalJSON seralizes the error into a string for JSON consumption
|
||||
// MarshalJSON serializes the error into a string for JSON consumption
|
||||
func (iem IndexErrMap) MarshalJSON() ([]byte, error) {
|
||||
tmp := make(map[string]string, len(iem))
|
||||
for k, v := range iem {
|
||||
@@ -398,7 +471,7 @@ func (iem IndexErrMap) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchStatus is a secion in the SearchResult reporting how many
|
||||
// SearchStatus is a section in the SearchResult reporting how many
|
||||
// underlying indexes were queried, how many were successful/failed
|
||||
// and a map of any errors that were encountered
|
||||
type SearchStatus struct {
|
||||
@@ -433,7 +506,7 @@ func (ss *SearchStatus) Merge(other *SearchStatus) {
|
||||
// scores, score explanation, location info and so on.
|
||||
// Total - The total number of documents that matched the query.
|
||||
// Cost - indicates how expensive was the query with respect to bytes read
|
||||
// from the mmaped index files.
|
||||
// from the mapped index files.
|
||||
// MaxScore - The maximum score seen across all document hits seen for this query.
|
||||
// Took - The time taken to execute the search.
|
||||
// Facets - The facet results for the search.
|
||||
@@ -607,3 +680,79 @@ func isMatchAllQuery(q query.Query) bool {
|
||||
_, ok := q.(*query.MatchAllQuery)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Checks if the request is hybrid search. Currently supports: RRF, RSF.
|
||||
func IsScoreFusionRequested(req *SearchRequest) bool {
|
||||
switch req.Score {
|
||||
case ScoreRRF, ScoreRSF:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Additional parameters in the search request. Currently only being
|
||||
// used for score fusion parameters.
|
||||
type RequestParams struct {
|
||||
ScoreRankConstant int `json:"score_rank_constant,omitempty"`
|
||||
ScoreWindowSize int `json:"score_window_size,omitempty"`
|
||||
}
|
||||
|
||||
func NewDefaultParams(from, size int) *RequestParams {
|
||||
return &RequestParams{
|
||||
ScoreRankConstant: DefaultScoreRankConstant,
|
||||
ScoreWindowSize: from + size,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *RequestParams) UnmarshalJSON(input []byte) error {
|
||||
var temp struct {
|
||||
ScoreRankConstant *int `json:"score_rank_constant,omitempty"`
|
||||
ScoreWindowSize *int `json:"score_window_size,omitempty"`
|
||||
}
|
||||
|
||||
if err := util.UnmarshalJSON(input, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if temp.ScoreRankConstant != nil {
|
||||
p.ScoreRankConstant = *temp.ScoreRankConstant
|
||||
}
|
||||
|
||||
if temp.ScoreWindowSize != nil {
|
||||
p.ScoreWindowSize = *temp.ScoreWindowSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *RequestParams) Validate(size int) error {
|
||||
if p.ScoreWindowSize < 1 {
|
||||
return fmt.Errorf("score window size must be greater than 0")
|
||||
} else if p.ScoreWindowSize < size {
|
||||
return fmt.Errorf("score window size must be greater than or equal to Size (%d)", size)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseParams(r *SearchRequest, input []byte) (*RequestParams, error) {
|
||||
params := NewDefaultParams(r.From, r.Size)
|
||||
if len(input) == 0 {
|
||||
return params, nil
|
||||
}
|
||||
|
||||
err := util.UnmarshalJSON(input, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// validate params
|
||||
err = params.Validate(r.Size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
|
||||
49
vendor/github.com/blevesearch/bleve/v2/search/collector/topn.go
generated
vendored
49
vendor/github.com/blevesearch/bleve/v2/search/collector/topn.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/numeric"
|
||||
"github.com/blevesearch/bleve/v2/search"
|
||||
"github.com/blevesearch/bleve/v2/size"
|
||||
index "github.com/blevesearch/bleve_index_api"
|
||||
@@ -117,9 +118,15 @@ func newTopNCollector(size int, skip int, sort search.SortOrder) *TopNCollector
|
||||
return hc
|
||||
}
|
||||
|
||||
// Creates a dummy document to compare with for pagination.
|
||||
func createSearchAfterDocument(sort search.SortOrder, after []string) *search.DocumentMatch {
|
||||
encodedAfter := make([]string, len(after))
|
||||
for i, ss := range sort {
|
||||
encodedAfter[i] = encodeSearchAfter(ss, after[i])
|
||||
}
|
||||
|
||||
rv := &search.DocumentMatch{
|
||||
Sort: after,
|
||||
Sort: encodedAfter,
|
||||
}
|
||||
for pos, ss := range sort {
|
||||
if ss.RequiresDocID() {
|
||||
@@ -134,6 +141,46 @@ func createSearchAfterDocument(sort search.SortOrder, after []string) *search.Do
|
||||
return rv
|
||||
}
|
||||
|
||||
// encodeSearchAfter applies prefix-coding to SearchAfter
|
||||
// if required to enable pagination on numeric, datetime,
|
||||
// and geo fields
|
||||
func encodeSearchAfter(ss search.SearchSort, after string) string {
|
||||
encodeFloat := func() string {
|
||||
f64, _ := strconv.ParseFloat(after, 64) // error checking in SearchRequest.Validate
|
||||
i64 := numeric.Float64ToInt64(f64)
|
||||
return string(numeric.MustNewPrefixCodedInt64(i64, 0))
|
||||
}
|
||||
|
||||
encodeDate := func() string {
|
||||
t, _ := time.Parse(time.RFC3339Nano, after) // error checking in SearchRequest.Validate
|
||||
i64 := t.UnixNano()
|
||||
return string(numeric.MustNewPrefixCodedInt64(i64, 0))
|
||||
}
|
||||
|
||||
switch ss := ss.(type) {
|
||||
case *search.SortGeoDistance:
|
||||
return encodeFloat()
|
||||
case *search.SortField:
|
||||
switch ss.Type {
|
||||
case search.SortFieldAsNumber:
|
||||
return encodeFloat()
|
||||
case search.SortFieldAsDate:
|
||||
return encodeDate()
|
||||
default:
|
||||
// For SortFieldAsString and SortFieldAuto
|
||||
// NOTE: SortFieldAuto is used if you set Sort with a string
|
||||
// or if the type of the field is not set in the object
|
||||
// in the Sort slice. We cannot perform type inference in
|
||||
// this case, so we return the original string, even if
|
||||
// its actually numeric or date.
|
||||
return after
|
||||
}
|
||||
default:
|
||||
// For SortDocID and SortScore
|
||||
return after
|
||||
}
|
||||
}
|
||||
|
||||
// Filter document matches based on the SearchAfter field in the SearchRequest.
|
||||
func FilterHitsBySearchAfter(hits []*search.DocumentMatch, sort search.SortOrder, after []string) []*search.DocumentMatch {
|
||||
if len(hits) == 0 {
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/pool.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/pool.go
generated
vendored
@@ -31,7 +31,7 @@ func init() {
|
||||
// a message, or panic, etc.
|
||||
type DocumentMatchPoolTooSmall func(p *DocumentMatchPool) *DocumentMatch
|
||||
|
||||
// DocumentMatchPool manages use/re-use of DocumentMatch instances
|
||||
// DocumentMatchPool manages use/reuse of DocumentMatch instances
|
||||
// it pre-allocates space from a single large block with the expected
|
||||
// number of instances. It is not thread-safe as currently all
|
||||
// aspects of search take place in a single goroutine.
|
||||
|
||||
92
vendor/github.com/blevesearch/bleve/v2/search/query/boolean.go
generated
vendored
92
vendor/github.com/blevesearch/bleve/v2/search/query/boolean.go
generated
vendored
@@ -15,6 +15,7 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -30,6 +31,7 @@ type BooleanQuery struct {
|
||||
Must Query `json:"must,omitempty"`
|
||||
Should Query `json:"should,omitempty"`
|
||||
MustNot Query `json:"must_not,omitempty"`
|
||||
Filter Query `json:"filter,omitempty"`
|
||||
BoostVal *Boost `json:"boost,omitempty"`
|
||||
queryStringMode bool
|
||||
}
|
||||
@@ -115,6 +117,13 @@ func (q *BooleanQuery) AddMustNot(m ...Query) {
|
||||
}
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) AddFilter(m Query) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
q.Filter = m
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) SetBoost(b float64) {
|
||||
boost := Boost(b)
|
||||
q.BoostVal = &boost
|
||||
@@ -162,11 +171,61 @@ func (q *BooleanQuery) Searcher(ctx context.Context, i index.IndexReader, m mapp
|
||||
}
|
||||
}
|
||||
|
||||
// if all 3 are nil, return MatchNone
|
||||
if mustSearcher == nil && shouldSearcher == nil && mustNotSearcher == nil {
|
||||
var filterFunc searcher.FilterFunc
|
||||
if q.Filter != nil {
|
||||
// create a new searcher options with disabled scoring, since filter should not affect scoring
|
||||
// and we don't want to pay the cost of scoring if we don't need it, also disable term vectors
|
||||
// and explain, since we don't need them for filters
|
||||
filterOptions := search.SearcherOptions{
|
||||
Explain: false,
|
||||
IncludeTermVectors: false,
|
||||
Score: "none",
|
||||
}
|
||||
filterSearcher, err := q.Filter.Searcher(ctx, i, m, filterOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterFunc = func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
|
||||
// Attempt to advance the filter searcher to the document identified by
|
||||
// the base searcher's (unfiltered boolean) current result (d.IndexInternalID).
|
||||
//
|
||||
// If the filter searcher successfully finds a document with the same
|
||||
// internal ID, it means the document satisfies the filter and should be kept.
|
||||
//
|
||||
// If the filter searcher returns an error, does not find a matching document,
|
||||
// or finds a document with a different internal ID, the document should be discarded.
|
||||
dm, err := filterSearcher.Advance(sctx, d.IndexInternalID)
|
||||
return err == nil && dm != nil && bytes.Equal(dm.IndexInternalID, d.IndexInternalID)
|
||||
}
|
||||
}
|
||||
|
||||
// if all 4 are nil, return MatchNone
|
||||
if mustSearcher == nil && shouldSearcher == nil && mustNotSearcher == nil && filterFunc == nil {
|
||||
return searcher.NewMatchNoneSearcher(i)
|
||||
}
|
||||
|
||||
// optimization, if only must searcher, just return it instead
|
||||
if mustSearcher != nil && shouldSearcher == nil && mustNotSearcher == nil && filterFunc == nil {
|
||||
return mustSearcher, nil
|
||||
}
|
||||
|
||||
// optimization, if only should searcher, just return it instead
|
||||
if mustSearcher == nil && shouldSearcher != nil && mustNotSearcher == nil && filterFunc == nil {
|
||||
return shouldSearcher, nil
|
||||
}
|
||||
|
||||
// optimization, if only filter searcher, wrap around a MatchAllSearcher
|
||||
if mustSearcher == nil && shouldSearcher == nil && mustNotSearcher == nil && filterFunc != nil {
|
||||
mustSearcher, err = searcher.NewMatchAllSearcher(ctx, i, 1.0, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return searcher.NewFilteringSearcher(ctx,
|
||||
mustSearcher,
|
||||
filterFunc,
|
||||
), nil
|
||||
}
|
||||
|
||||
// if only mustNotSearcher, start with MatchAll
|
||||
if mustSearcher == nil && shouldSearcher == nil && mustNotSearcher != nil {
|
||||
mustSearcher, err = searcher.NewMatchAllSearcher(ctx, i, 1.0, options)
|
||||
@@ -175,12 +234,15 @@ func (q *BooleanQuery) Searcher(ctx context.Context, i index.IndexReader, m mapp
|
||||
}
|
||||
}
|
||||
|
||||
// optimization, if only should searcher, just return it instead
|
||||
if mustSearcher == nil && shouldSearcher != nil && mustNotSearcher == nil {
|
||||
return shouldSearcher, nil
|
||||
bs, err := searcher.NewBooleanSearcher(ctx, i, mustSearcher, shouldSearcher, mustNotSearcher, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return searcher.NewBooleanSearcher(ctx, i, mustSearcher, shouldSearcher, mustNotSearcher, options)
|
||||
if filterFunc != nil {
|
||||
return searcher.NewFilteringSearcher(ctx, bs, filterFunc), nil
|
||||
}
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
func (q *BooleanQuery) Validate() error {
|
||||
@@ -202,8 +264,14 @@ func (q *BooleanQuery) Validate() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.Must == nil && q.Should == nil && q.MustNot == nil {
|
||||
return fmt.Errorf("boolean query must contain at least one must or should or not must clause")
|
||||
if qf, ok := q.Filter.(ValidatableQuery); ok {
|
||||
err := qf.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if q.Must == nil && q.Should == nil && q.MustNot == nil && q.Filter == nil {
|
||||
return fmt.Errorf("boolean query must contain at least one must or should or not must or filter clause")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -213,6 +281,7 @@ func (q *BooleanQuery) UnmarshalJSON(data []byte) error {
|
||||
Must json.RawMessage `json:"must,omitempty"`
|
||||
Should json.RawMessage `json:"should,omitempty"`
|
||||
MustNot json.RawMessage `json:"must_not,omitempty"`
|
||||
Filter json.RawMessage `json:"filter,omitempty"`
|
||||
Boost *Boost `json:"boost,omitempty"`
|
||||
}{}
|
||||
err := util.UnmarshalJSON(data, &tmp)
|
||||
@@ -253,6 +322,13 @@ func (q *BooleanQuery) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
}
|
||||
|
||||
if tmp.Filter != nil {
|
||||
q.Filter, err = ParseQuery(tmp.Filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
q.BoostVal = tmp.Boost
|
||||
|
||||
return nil
|
||||
|
||||
3
vendor/github.com/blevesearch/bleve/v2/search/query/query.go
generated
vendored
3
vendor/github.com/blevesearch/bleve/v2/search/query/query.go
generated
vendored
@@ -196,7 +196,8 @@ func ParseQuery(input []byte) (Query, error) {
|
||||
_, hasMust := tmp["must"]
|
||||
_, hasShould := tmp["should"]
|
||||
_, hasMustNot := tmp["must_not"]
|
||||
if hasMust || hasShould || hasMustNot {
|
||||
_, hasFilter := tmp["filter"]
|
||||
if hasMust || hasShould || hasMustNot || hasFilter {
|
||||
var rv BooleanQuery
|
||||
err := util.UnmarshalJSON(input, &rv)
|
||||
if err != nil {
|
||||
|
||||
11
vendor/github.com/blevesearch/bleve/v2/search/search.go
generated
vendored
11
vendor/github.com/blevesearch/bleve/v2/search/search.go
generated
vendored
@@ -17,6 +17,7 @@ package search
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"github.com/blevesearch/bleve/v2/size"
|
||||
@@ -41,15 +42,7 @@ func init() {
|
||||
type ArrayPositions []uint64
|
||||
|
||||
func (ap ArrayPositions) Equals(other ArrayPositions) bool {
|
||||
if len(ap) != len(other) {
|
||||
return false
|
||||
}
|
||||
for i := range ap {
|
||||
if ap[i] != other[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return slices.Equal(ap, other)
|
||||
}
|
||||
|
||||
func (ap ArrayPositions) Compare(other ArrayPositions) int {
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_disjunction.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_disjunction.go
generated
vendored
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
// DisjunctionMaxClauseCount is a compile time setting that applications can
|
||||
// adjust to non-zero value to cause the DisjunctionSearcher to return an
|
||||
// error instead of exeucting searches when the size exceeds this value.
|
||||
// error instead of executing searches when the size exceeds this value.
|
||||
var DisjunctionMaxClauseCount = 0
|
||||
|
||||
// DisjunctionHeapTakeover is a compile time setting that applications can
|
||||
|
||||
6
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_filter.go
generated
vendored
6
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_filter.go
generated
vendored
@@ -33,7 +33,7 @@ func init() {
|
||||
// FilterFunc defines a function which can filter documents
|
||||
// returning true means keep the document
|
||||
// returning false means do not keep the document
|
||||
type FilterFunc func(d *search.DocumentMatch) bool
|
||||
type FilterFunc func(sctx *search.SearchContext, d *search.DocumentMatch) bool
|
||||
|
||||
// FilteringSearcher wraps any other searcher, but checks any Next/Advance
|
||||
// call against the supplied FilterFunc
|
||||
@@ -57,7 +57,7 @@ func (f *FilteringSearcher) Size() int {
|
||||
func (f *FilteringSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch, error) {
|
||||
next, err := f.child.Next(ctx)
|
||||
for next != nil && err == nil {
|
||||
if f.accept(next) {
|
||||
if f.accept(ctx, next) {
|
||||
return next, nil
|
||||
}
|
||||
next, err = f.child.Next(ctx)
|
||||
@@ -73,7 +73,7 @@ func (f *FilteringSearcher) Advance(ctx *search.SearchContext, ID index.IndexInt
|
||||
if adv == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if f.accept(adv) {
|
||||
if f.accept(ctx, adv) {
|
||||
return adv, nil
|
||||
}
|
||||
return f.Next(ctx)
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geoboundingbox.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geoboundingbox.go
generated
vendored
@@ -208,7 +208,7 @@ func buildIsIndexedFunc(ctx context.Context, indexReader index.IndexReader, fiel
|
||||
func buildRectFilter(ctx context.Context, dvReader index.DocValueReader, field string,
|
||||
minLon, minLat, maxLon, maxLat float64,
|
||||
) FilterFunc {
|
||||
return func(d *search.DocumentMatch) bool {
|
||||
return func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
|
||||
// check geo matches against all numeric type terms indexed
|
||||
var lons, lats []float64
|
||||
var found bool
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geopointdistance.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geopointdistance.go
generated
vendored
@@ -115,7 +115,7 @@ func boxSearcher(ctx context.Context, indexReader index.IndexReader,
|
||||
|
||||
func buildDistFilter(ctx context.Context, dvReader index.DocValueReader, field string,
|
||||
centerLon, centerLat, maxDist float64) FilterFunc {
|
||||
return func(d *search.DocumentMatch) bool {
|
||||
return func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
|
||||
// check geo matches against all numeric type terms indexed
|
||||
var lons, lats []float64
|
||||
var found bool
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geopolygon.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geopolygon.go
generated
vendored
@@ -85,7 +85,7 @@ func almostEqual(a, b float64) bool {
|
||||
// here: https://wrf.ecse.rpi.edu/nikola/pubdetails/pnpoly.html
|
||||
func buildPolygonFilter(ctx context.Context, dvReader index.DocValueReader, field string,
|
||||
coordinates []geo.Point) FilterFunc {
|
||||
return func(d *search.DocumentMatch) bool {
|
||||
return func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
|
||||
// check geo matches against all numeric type terms indexed
|
||||
var lons, lats []float64
|
||||
var found bool
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geoshape.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_geoshape.go
generated
vendored
@@ -77,7 +77,7 @@ func buildRelationFilterOnShapes(ctx context.Context, dvReader index.DocValueRea
|
||||
bufPool = bufPoolCallback()
|
||||
}
|
||||
|
||||
return func(d *search.DocumentMatch) bool {
|
||||
return func(sctx *search.SearchContext, d *search.DocumentMatch) bool {
|
||||
var found bool
|
||||
|
||||
err := dvReader.VisitDocValues(d.IndexInternalID,
|
||||
|
||||
4
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_phrase.go
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_phrase.go
generated
vendored
@@ -296,7 +296,7 @@ func (s *PhraseSearcher) Next(ctx *search.SearchContext) (*search.DocumentMatch,
|
||||
}
|
||||
|
||||
// checkCurrMustMatch is solely concerned with determining if the DocumentMatch
|
||||
// pointed to by s.currMust (which satisifies the pre-condition searcher)
|
||||
// pointed to by s.currMust (which satisfies the pre-condition searcher)
|
||||
// also satisfies the phrase constraints. if so, it returns a DocumentMatch
|
||||
// for this document, otherwise nil
|
||||
func (s *PhraseSearcher) checkCurrMustMatch(ctx *search.SearchContext) *search.DocumentMatch {
|
||||
@@ -458,7 +458,7 @@ func findPhrasePaths(prevPos uint64, ap search.ArrayPositions, phraseTerms [][]s
|
||||
if len(car) == 0 || (len(car) == 1 && car[0] == "") {
|
||||
nextPos := prevPos + 1
|
||||
if prevPos == 0 {
|
||||
// if prevPos was 0, don't set it to 1 (as thats not a real abs pos)
|
||||
// if prevPos was 0, don't set it to 1 (as that's not a real abs pos)
|
||||
nextPos = 0 // don't advance nextPos if prevPos was 0
|
||||
}
|
||||
return findPhrasePaths(nextPos, ap, cdr, tlm, p, remainingSlop, rv)
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_regexp.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/searcher/search_regexp.go
generated
vendored
@@ -96,7 +96,7 @@ func NewRegexpStringSearcher(ctx context.Context, indexReader index.IndexReader,
|
||||
// NewRegexpSearcher creates a searcher which will match documents that
|
||||
// contain terms which match the pattern regexp. The match must be EXACT
|
||||
// matching the entire term. The provided regexp SHOULD NOT start with ^
|
||||
// or end with $ as this can intefere with the implementation. Separately,
|
||||
// or end with $ as this can interfere with the implementation. Separately,
|
||||
// matches will be checked to ensure they match the entire term.
|
||||
func NewRegexpSearcher(ctx context.Context, indexReader index.IndexReader, pattern Regexp,
|
||||
field string, boost float64, options search.SearcherOptions) (
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve/v2/search/sort.go
generated
vendored
2
vendor/github.com/blevesearch/bleve/v2/search/sort.go
generated
vendored
@@ -408,7 +408,7 @@ func (s *SortField) DecodeValue(value string) string {
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
return time.Unix(0, i64).UTC().String()
|
||||
return time.Unix(0, i64).UTC().Format(time.RFC3339Nano)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
|
||||
4
vendor/github.com/blevesearch/bleve/v2/search/util.go
generated
vendored
4
vendor/github.com/blevesearch/bleve/v2/search/util.go
generated
vendored
@@ -152,6 +152,10 @@ const (
|
||||
// BM25StatsKey is used to store and transport the BM25 Data
|
||||
// to the actual search phase which would use it to perform the search.
|
||||
BM25StatsKey ContextKey = "_bm25_stats_key"
|
||||
|
||||
// ScoreFusionKey is used to communicate whether KNN hits need to be preserved for
|
||||
// hybrid search algorithms (like RRF)
|
||||
ScoreFusionKey ContextKey = "_fusion_rescoring_key"
|
||||
)
|
||||
|
||||
func RecordSearchCost(ctx context.Context,
|
||||
|
||||
53
vendor/github.com/blevesearch/bleve/v2/search_knn.go
generated
vendored
53
vendor/github.com/blevesearch/bleve/v2/search_knn.go
generated
vendored
@@ -67,6 +67,8 @@ type SearchRequest struct {
|
||||
|
||||
PreSearchData map[string]interface{} `json:"pre_search_data,omitempty"`
|
||||
|
||||
Params *RequestParams `json:"params,omitempty"`
|
||||
|
||||
sortFunc func(sort.Interface)
|
||||
}
|
||||
|
||||
@@ -148,6 +150,7 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
||||
KNN []*tempKNNReq `json:"knn"`
|
||||
KNNOperator knnOperator `json:"knn_operator"`
|
||||
PreSearchData json.RawMessage `json:"pre_search_data"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(input, &temp)
|
||||
@@ -189,6 +192,22 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
||||
r.From = 0
|
||||
}
|
||||
|
||||
if IsScoreFusionRequested(r) {
|
||||
if temp.Params == nil {
|
||||
// If params is not present and it is requires rescoring, assign
|
||||
// default values
|
||||
r.Params = NewDefaultParams(r.From, r.Size)
|
||||
} else {
|
||||
// if it is a request that requires rescoring, parse the rescoring
|
||||
// parameters.
|
||||
params, err := ParseParams(r, temp.Params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Params = params
|
||||
}
|
||||
}
|
||||
|
||||
r.KNN = make([]*KNNRequest, len(temp.KNN))
|
||||
for i, knnReq := range temp.KNN {
|
||||
r.KNN[i] = &KNNRequest{}
|
||||
@@ -243,6 +262,7 @@ func copySearchRequest(req *SearchRequest, preSearchData map[string]interface{})
|
||||
KNN: req.KNN,
|
||||
KNNOperator: req.KNNOperator,
|
||||
PreSearchData: preSearchData,
|
||||
Params: req.Params,
|
||||
}
|
||||
return &rv
|
||||
|
||||
@@ -327,6 +347,7 @@ func validateKNN(req *SearchRequest) error {
|
||||
default:
|
||||
return fmt.Errorf("knn_operator must be either 'and' / 'or'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -458,6 +479,12 @@ func finalizeKNNResults(req *SearchRequest, knnHits []*search.DocumentMatch) []*
|
||||
}
|
||||
knnHits = knnHits[:idx]
|
||||
}
|
||||
|
||||
// if score fusion required, return early because
|
||||
// score breakdown is retained
|
||||
if IsScoreFusionRequested(req) {
|
||||
return knnHits
|
||||
}
|
||||
// fix the score using score breakdown now
|
||||
// if the score is none, then we need to set the score to 0.0
|
||||
// if req.Explain is true, then we need to use the expl breakdown to
|
||||
@@ -537,6 +564,10 @@ func requestHasKNN(req *SearchRequest) bool {
|
||||
return len(req.KNN) > 0
|
||||
}
|
||||
|
||||
func numKNNQueries(req *SearchRequest) int {
|
||||
return len(req.KNN)
|
||||
}
|
||||
|
||||
// returns true if the search request contains a KNN request that can be
|
||||
// satisfied by just performing a preSearch, completely bypassing the
|
||||
// actual search.
|
||||
@@ -608,3 +639,25 @@ func newKnnPreSearchResultProcessor(req *SearchRequest) *knnPreSearchResultProce
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Replace knn boost values for fusion rescoring queries
|
||||
func (r *rescorer) prepareKnnRequest() {
|
||||
for i := range r.req.KNN {
|
||||
b := r.req.KNN[i].Boost
|
||||
if b != nil {
|
||||
r.origBoosts[i+1] = b.Value()
|
||||
newB := query.Boost(1.0)
|
||||
r.req.KNN[i].Boost = &newB
|
||||
} else {
|
||||
r.origBoosts[i+1] = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore knn boost values for fusion rescoring queries
|
||||
func (r *rescorer) restoreKnnRequest() {
|
||||
for i := range r.req.KNN {
|
||||
b := query.Boost(r.origBoosts[i+1])
|
||||
r.req.KNN[i].Boost = &b
|
||||
}
|
||||
}
|
||||
|
||||
30
vendor/github.com/blevesearch/bleve/v2/search_no_knn.go
generated
vendored
30
vendor/github.com/blevesearch/bleve/v2/search_no_knn.go
generated
vendored
@@ -77,6 +77,8 @@ type SearchRequest struct {
|
||||
|
||||
PreSearchData map[string]interface{} `json:"pre_search_data,omitempty"`
|
||||
|
||||
Params *RequestParams `json:"params,omitempty"`
|
||||
|
||||
sortFunc func(sort.Interface)
|
||||
}
|
||||
|
||||
@@ -97,6 +99,7 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
||||
SearchAfter []string `json:"search_after"`
|
||||
SearchBefore []string `json:"search_before"`
|
||||
PreSearchData json.RawMessage `json:"pre_search_data"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(input, &temp)
|
||||
@@ -137,6 +140,23 @@ func (r *SearchRequest) UnmarshalJSON(input []byte) error {
|
||||
if r.From < 0 {
|
||||
r.From = 0
|
||||
}
|
||||
|
||||
if IsScoreFusionRequested(r) {
|
||||
if temp.Params == nil {
|
||||
// If params is not present and it is requires rescoring, assign
|
||||
// default values
|
||||
r.Params = NewDefaultParams(r.From, r.Size)
|
||||
} else {
|
||||
// if it is a request that requires rescoring, parse the rescoring
|
||||
// parameters.
|
||||
params, err := ParseParams(r, temp.Params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Params = params
|
||||
}
|
||||
}
|
||||
|
||||
if temp.PreSearchData != nil {
|
||||
r.PreSearchData, err = query.ParsePreSearchData(temp.PreSearchData)
|
||||
if err != nil {
|
||||
@@ -184,6 +204,10 @@ func requestHasKNN(req *SearchRequest) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func numKNNQueries(req *SearchRequest) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func addKnnToDummyRequest(dummyReq *SearchRequest, realReq *SearchRequest) {
|
||||
}
|
||||
|
||||
@@ -207,3 +231,9 @@ func finalizeKNNResults(req *SearchRequest, knnHits []*search.DocumentMatch) []*
|
||||
func newKnnPreSearchResultProcessor(req *SearchRequest) *knnPreSearchResultProcessor {
|
||||
return &knnPreSearchResultProcessor{} // equivalent to nil
|
||||
}
|
||||
|
||||
func (r *rescorer) prepareKnnRequest() {
|
||||
}
|
||||
|
||||
func (r *rescorer) restoreKnnRequest() {
|
||||
}
|
||||
|
||||
32
vendor/github.com/blevesearch/bleve/v2/util/keys.go
generated
vendored
Normal file
32
vendor/github.com/blevesearch/bleve/v2/util/keys.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2025 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
var (
|
||||
// Bolt keys
|
||||
BoltSnapshotsBucket = []byte{'s'}
|
||||
BoltPathKey = []byte{'p'}
|
||||
BoltDeletedKey = []byte{'d'}
|
||||
BoltInternalKey = []byte{'i'}
|
||||
BoltMetaDataKey = []byte{'m'}
|
||||
BoltMetaDataSegmentTypeKey = []byte("type")
|
||||
BoltMetaDataSegmentVersionKey = []byte("version")
|
||||
BoltMetaDataTimeStamp = []byte("timeStamp")
|
||||
BoltStatsKey = []byte("stats")
|
||||
BoltUpdatedFieldsKey = []byte("fields")
|
||||
TotBytesWrittenKey = []byte("TotBytesWritten")
|
||||
|
||||
MappingInternalKey = []byte("_mapping")
|
||||
)
|
||||
13
vendor/github.com/blevesearch/bleve_index_api/index.go
generated
vendored
13
vendor/github.com/blevesearch/bleve_index_api/index.go
generated
vendored
@@ -65,6 +65,19 @@ type EventIndex interface {
|
||||
FireIndexEvent()
|
||||
}
|
||||
|
||||
type UpdateFieldInfo struct {
|
||||
Deleted bool
|
||||
Store bool
|
||||
Index bool
|
||||
DocValues bool
|
||||
}
|
||||
|
||||
type UpdateIndex interface {
|
||||
Index
|
||||
UpdateFields(fieldInfo map[string]*UpdateFieldInfo, updatedMapping []byte) error
|
||||
OpenMeta() error
|
||||
}
|
||||
|
||||
type IndexReader interface {
|
||||
TermFieldReader(ctx context.Context, term []byte, field string, includeFreq, includeNorm, includeTermVectors bool) (TermFieldReader, error)
|
||||
|
||||
|
||||
2
vendor/github.com/blevesearch/bleve_index_api/indexing_options.go
generated
vendored
2
vendor/github.com/blevesearch/bleve_index_api/indexing_options.go
generated
vendored
@@ -26,7 +26,7 @@ const (
|
||||
|
||||
const (
|
||||
BM25Scoring = "bm25"
|
||||
TFIDFScoring = "tfidf"
|
||||
TFIDFScoring = "tf-idf"
|
||||
)
|
||||
|
||||
// Scoring model indicates the algorithm used to rank documents fetched
|
||||
|
||||
6
vendor/github.com/blevesearch/scorch_segment_api/v2/segment.go
generated
vendored
6
vendor/github.com/blevesearch/scorch_segment_api/v2/segment.go
generated
vendored
@@ -61,6 +61,12 @@ type PersistedSegment interface {
|
||||
Path() string
|
||||
}
|
||||
|
||||
type UpdatableSegment interface {
|
||||
Segment
|
||||
GetUpdatedFields() map[string]*index.UpdateFieldInfo
|
||||
SetUpdatedFields(fieldInfo map[string]*index.UpdateFieldInfo)
|
||||
}
|
||||
|
||||
type TermDictionary interface {
|
||||
PostingsList(term []byte, except *roaring.Bitmap, prealloc PostingsList) (PostingsList, error)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user