Some checks failed
Coverage / coverage (push) Failing after 6s
Tests / test (1.24.x, ubuntu-latest) (push) Failing after 42s
Tests / test (1.26.x, ubuntu-latest) (push) Failing after 38s
Tests / test (1.25.x, ubuntu-latest) (push) Failing after 35s
Tests / test (1.25.x, windows-latest) (push) Has been cancelled
Tests / test (1.26.x, macos-latest) (push) Has been cancelled
Tests / test (1.26.x, windows-latest) (push) Has been cancelled
Tests / test (1.24.x, macos-latest) (push) Has been cancelled
Tests / test (1.24.x, windows-latest) (push) Has been cancelled
Tests / test (1.25.x, macos-latest) (push) Has been cancelled
137 lines
3.9 KiB
Go
137 lines
3.9 KiB
Go
// Copyright (c) 2026 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 query
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/blevesearch/bleve/v2/mapping"
|
|
"github.com/blevesearch/bleve/v2/util"
|
|
index "github.com/blevesearch/bleve_index_api"
|
|
)
|
|
|
|
func unmarshalCustomQueryPayload(data []byte, key string) (Query, []string, map[string]interface{}, error) {
|
|
tmp := map[string]json.RawMessage{}
|
|
err := util.UnmarshalJSON(data, &tmp)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
innerRaw, ok := tmp[key]
|
|
if !ok || innerRaw == nil {
|
|
return nil, nil, nil, nil
|
|
}
|
|
|
|
var inner map[string]json.RawMessage
|
|
err = util.UnmarshalJSON(innerRaw, &inner)
|
|
if err != nil || inner == nil {
|
|
return nil, nil, nil, fmt.Errorf("%s query must be a JSON object", key)
|
|
}
|
|
|
|
var child Query
|
|
if childQuery, ok := inner["query"]; ok && childQuery != nil {
|
|
child, err = ParseQuery(childQuery)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
}
|
|
|
|
var fields []string
|
|
if rawFields, ok := inner["fields"]; ok && rawFields != nil {
|
|
if err := util.UnmarshalJSON(rawFields, &fields); err != nil {
|
|
return nil, nil, nil, fmt.Errorf("%s query has invalid %q: %w",
|
|
key, "fields", err)
|
|
}
|
|
}
|
|
|
|
payload := make(map[string]interface{}, len(inner))
|
|
for k, raw := range inner {
|
|
if k == "query" || k == "fields" {
|
|
continue
|
|
}
|
|
var v interface{}
|
|
if raw != nil {
|
|
err = util.UnmarshalJSON(raw, &v)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("%s query has invalid %q payload: %w",
|
|
key, k, err)
|
|
}
|
|
}
|
|
payload[k] = v
|
|
}
|
|
|
|
return child, fields, payload, nil
|
|
}
|
|
|
|
// resolveFieldTypes looks up each field name in the index mapping and returns
|
|
// a map of field name → mapping type (e.g. "datetime", "number", "text").
|
|
// This is used by the searcher layer to correctly decode doc value bytes.
|
|
func resolveFieldTypes(fields []string, m mapping.IndexMapping) map[string]string {
|
|
if m == nil || len(fields) == 0 {
|
|
return nil
|
|
}
|
|
types := make(map[string]string, len(fields))
|
|
for _, f := range fields {
|
|
fm := m.FieldMappingForPath(f)
|
|
if fm.Type != "" {
|
|
types[f] = fm.Type
|
|
}
|
|
}
|
|
if len(types) == 0 {
|
|
return nil
|
|
}
|
|
return types
|
|
}
|
|
|
|
// expandFieldWildcard replaces a "*" entry in the requested fields with the
|
|
// concrete set of fields in the index, mirroring how the standard stored-field
|
|
// loader treats "*" (see LoadAndHighlightFields). Custom-query callbacks read
|
|
// field values via the doc-value reader, which matches field names literally
|
|
// and has no wildcard handling of its own; without this expansion a request for
|
|
// fields:["*"] resolves to a single field literally named "*", matches nothing,
|
|
// and leaves d.Fields empty in the callback.
|
|
//
|
|
// The internal composite/identifier fields ("_all", "_id") are excluded so the
|
|
// callback sees the same fields the stored-field "*" path would surface rather
|
|
// than the tokenized _all blob or the document id (already available as the
|
|
// hit's ID). When fields does not contain "*", it is returned unchanged.
|
|
func expandFieldWildcard(fields []string, i index.IndexReader) ([]string, error) {
|
|
hasWildcard := false
|
|
for _, f := range fields {
|
|
if f == "*" {
|
|
hasWildcard = true
|
|
break
|
|
}
|
|
}
|
|
if !hasWildcard {
|
|
return fields, nil
|
|
}
|
|
|
|
allFields, err := i.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
expanded := make([]string, 0, len(allFields))
|
|
for _, f := range allFields {
|
|
if f == "_all" || f == "_id" {
|
|
continue
|
|
}
|
|
expanded = append(expanded, f)
|
|
}
|
|
return expanded, nil
|
|
}
|