mirror of
https://github.com/SigNoz/signoz.git
synced 2026-06-02 15:10:34 +01:00
Compare commits
5 Commits
fts-logs-2
...
fts-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bac933968 | ||
|
|
8df70ed4be | ||
|
|
806f1d0062 | ||
|
|
19d7d5b2ad | ||
|
|
2f762a86b3 |
@@ -112,7 +112,7 @@ functionCall
|
||||
;
|
||||
|
||||
/*
|
||||
* Search call: search(field) or search("query text")
|
||||
* Search call: search() function
|
||||
* First-class rule so search-specific semantics can be extended independently.
|
||||
*/
|
||||
searchCall
|
||||
|
||||
@@ -105,6 +105,3 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported operator: %v", operator)
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,3 @@ func (m *fieldMapper) ColumnExpressionFor(ctx context.Context, tsStart, tsEnd ui
|
||||
}
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ func newProvider(
|
||||
logAggExprRewriter,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
logConditionBuilder.ConditionForSearch,
|
||||
flagger,
|
||||
)
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ func prepareQuerierForLogs(t *testing.T, telemetryStore telemetrystore.Telemetry
|
||||
logAggExprRewriter,
|
||||
telemetrylogs.DefaultFullTextColumn,
|
||||
telemetrylogs.GetBodyJSONKey,
|
||||
logConditionBuilder.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
|
||||
@@ -12,14 +12,6 @@ const (
|
||||
// FullTextSearchDefaultWarning is emitted when a search() function call is used.
|
||||
FullTextSearchDefaultWarning = "Full text searches across all fields and will be slow and expensive. Consider using specific field to search e.g. <context>.<field_key>:<type>"
|
||||
|
||||
// FTSInternalKey is the sentinel Name on TelemetryFieldKey instances that represent
|
||||
// wildcard map searches (all attribute/resource keys+values). The unconventional value
|
||||
// prevents collision with any real field name a user could type.
|
||||
FTSInternalKey = "_X_INTERNAL_FTS_KEY"
|
||||
|
||||
// SearchFunctionName is the grammar function name for full-text search.
|
||||
SearchFunctionName = "search"
|
||||
|
||||
// FTSMaxWindowNs is the maximum allowed time range for a search() query (6 hours).
|
||||
FTSMaxWindowNs = uint64(6 * 60 * 60 * 1_000_000_000)
|
||||
)
|
||||
|
||||
@@ -38,7 +38,8 @@ type filterExpressionVisitor struct {
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
bodyJSONEnabled bool
|
||||
skipResourceFilter bool
|
||||
skipFullTextFilter bool
|
||||
skipFreeTextFilter bool
|
||||
skipFullTextSearch bool
|
||||
skipFunctionCalls bool
|
||||
ignoreNotFoundKeys bool
|
||||
variables map[string]qbtypes.VariableItem
|
||||
@@ -46,7 +47,7 @@ type filterExpressionVisitor struct {
|
||||
keysWithWarnings map[string]bool
|
||||
startNs uint64
|
||||
endNs uint64
|
||||
ftsContexts []telemetrytypes.FieldContext
|
||||
ftsCondition qbtypes.FTSConditionFunc
|
||||
}
|
||||
|
||||
type FilterExprVisitorOpts struct {
|
||||
@@ -58,17 +59,16 @@ type FilterExprVisitorOpts struct {
|
||||
Builder *sqlbuilder.SelectBuilder
|
||||
FreeTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
JsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
FTSCondition qbtypes.FTSConditionFunc
|
||||
BodyJSONEnabled bool
|
||||
SkipResourceFilter bool
|
||||
SkipFullTextFilter bool
|
||||
SkipFreeTextFilter bool
|
||||
SkipFullTextSearch bool
|
||||
SkipFunctionCalls bool
|
||||
IgnoreNotFoundKeys bool
|
||||
Variables map[string]qbtypes.VariableItem
|
||||
StartNs uint64
|
||||
EndNs uint64
|
||||
// FTSContexts enables search() for this query context. nil disables search()
|
||||
// (traces, metrics, and non-log callers leave this nil).
|
||||
FTSContexts []telemetrytypes.FieldContext
|
||||
}
|
||||
|
||||
// newFilterExpressionVisitor creates a new filterExpressionVisitor.
|
||||
@@ -82,16 +82,17 @@ func newFilterExpressionVisitor(opts FilterExprVisitorOpts) *filterExpressionVis
|
||||
builder: opts.Builder,
|
||||
freeTextColumn: opts.FreeTextColumn,
|
||||
jsonKeyToKey: opts.JsonKeyToKey,
|
||||
ftsCondition: opts.FTSCondition,
|
||||
bodyJSONEnabled: opts.BodyJSONEnabled,
|
||||
skipResourceFilter: opts.SkipResourceFilter,
|
||||
skipFullTextFilter: opts.SkipFullTextFilter,
|
||||
skipFreeTextFilter: opts.SkipFreeTextFilter,
|
||||
skipFullTextSearch: opts.SkipFullTextSearch,
|
||||
skipFunctionCalls: opts.SkipFunctionCalls,
|
||||
ignoreNotFoundKeys: opts.IgnoreNotFoundKeys,
|
||||
variables: opts.Variables,
|
||||
keysWithWarnings: make(map[string]bool),
|
||||
startNs: opts.StartNs,
|
||||
endNs: opts.EndNs,
|
||||
ftsContexts: opts.FTSContexts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,12 +343,12 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
||||
|
||||
// Handle standalone key/value as a full text search term
|
||||
if ctx.GetChildCount() == 1 {
|
||||
if v.skipFullTextFilter {
|
||||
if v.skipFreeTextFilter {
|
||||
return SkipConditionLiteral
|
||||
}
|
||||
|
||||
if v.freeTextColumn == nil {
|
||||
v.errors = append(v.errors, "full text search is not supported")
|
||||
v.errors = append(v.errors, "free text search is not supported")
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
|
||||
@@ -710,7 +711,7 @@ func (v *filterExpressionVisitor) VisitValueList(ctx *grammar.ValueListContext)
|
||||
|
||||
// VisitFullText handles standalone quoted strings for full-text search.
|
||||
func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) any {
|
||||
if v.skipFullTextFilter {
|
||||
if v.skipFreeTextFilter {
|
||||
// A skipped FT term must be treated as TrueConditionLiteral, not "".
|
||||
// Returning "" would silently drop this branch from an OR, incorrectly
|
||||
// excluding rows that could match the FT condition on the real table.
|
||||
@@ -726,7 +727,7 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
|
||||
}
|
||||
|
||||
if v.freeTextColumn == nil {
|
||||
v.errors = append(v.errors, "full text search is not supported")
|
||||
v.errors = append(v.errors, "free text search is not supported")
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
cond, err := v.conditionBuilder.ConditionFor(v.context, v.startNs, v.endNs, v.freeTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder)
|
||||
@@ -744,12 +745,12 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
|
||||
|
||||
// VisitSearchCall handles the search() function call.
|
||||
func (v *filterExpressionVisitor) VisitSearchCall(ctx *grammar.SearchCallContext) any {
|
||||
if v.skipFunctionCalls {
|
||||
if v.skipFunctionCalls || v.skipFullTextSearch {
|
||||
return SkipConditionLiteral
|
||||
}
|
||||
// ftsContexts == nil means search() is not enabled for this signal/query type.
|
||||
// Only log statement builders set FTSContexts; traces/metrics leave it nil.
|
||||
if len(v.ftsContexts) == 0 {
|
||||
// ftsCondition nil means search() is not enabled for this signal.
|
||||
// Only log statement builders set these; traces/metrics leave them nil.
|
||||
if v.ftsCondition == nil {
|
||||
v.errors = append(v.errors, "search() is only supported for log queries")
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
@@ -774,27 +775,19 @@ func (v *filterExpressionVisitor) VisitSearchCall(ctx *grammar.SearchCallContext
|
||||
}
|
||||
|
||||
if v.endNs > 0 && v.startNs > 0 && (v.endNs-v.startNs) > FTSMaxWindowNs {
|
||||
v.errors = append(v.errors, "full text search is restricted to a maximum of 6-hour time window")
|
||||
v.errors = append(v.errors, "search() is restricted to a maximum of 6-hour time window")
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
|
||||
formattedText := FormatFullTextSearch(searchText)
|
||||
var ftsConds []string
|
||||
for _, fieldCtx := range v.ftsContexts {
|
||||
cond, err := v.conditionBuilder.ConditionForContext(v.context, fieldCtx, formattedText, v.builder)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("search() could not build condition for context %q: %s", fieldCtx.StringValue(), err.Error()))
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
ftsConds = append(ftsConds, cond)
|
||||
}
|
||||
if len(ftsConds) == 0 {
|
||||
v.errors = append(v.errors, "search() failed: no searchable fields could be queried")
|
||||
cond, err := v.ftsCondition(v.context, formattedText, v.builder)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("search() could not build condition: %s", err.Error()))
|
||||
return ErrorConditionLiteral
|
||||
}
|
||||
|
||||
v.warnings = append(v.warnings, FullTextSearchDefaultWarning)
|
||||
return v.builder.Or(ftsConds...)
|
||||
return cond
|
||||
}
|
||||
|
||||
// VisitFunctionCall handles function calls like has(), hasAny(), hasAll(), hasToken().
|
||||
|
||||
@@ -744,10 +744,6 @@ func (b *resourceConditionBuilder) ConditionFor(
|
||||
return fmt.Sprintf("%s_cond", key.Name), nil
|
||||
}
|
||||
|
||||
func (b *resourceConditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type conditionBuilder struct{}
|
||||
|
||||
func (b *conditionBuilder) ConditionFor(
|
||||
@@ -762,10 +758,6 @@ func (b *conditionBuilder) ConditionFor(
|
||||
return fmt.Sprintf("%s_cond", key.Name), nil
|
||||
}
|
||||
|
||||
func (b *conditionBuilder) ConditionForContext(_ context.Context, fc telemetrytypes.FieldContext, _ any, sb *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return fmt.Sprintf("fts_%s_cond", fc.StringValue()), nil
|
||||
}
|
||||
|
||||
// visitComparisonCase is a single test case for the TestVisitComparison_* family.
|
||||
// Each case is run under two independent configurations:
|
||||
//
|
||||
@@ -805,7 +797,7 @@ func visitComparisonOpts(t *testing.T) (rsbOpts, sbOpts FilterExprVisitorOpts) {
|
||||
ConditionBuilder: &resourceConditionBuilder{},
|
||||
Variables: allVariable,
|
||||
SkipResourceFilter: false,
|
||||
SkipFullTextFilter: true,
|
||||
SkipFreeTextFilter: true,
|
||||
SkipFunctionCalls: true,
|
||||
IgnoreNotFoundKeys: true,
|
||||
}
|
||||
@@ -815,7 +807,7 @@ func visitComparisonOpts(t *testing.T) (rsbOpts, sbOpts FilterExprVisitorOpts) {
|
||||
ConditionBuilder: &conditionBuilder{},
|
||||
Variables: allVariable,
|
||||
SkipResourceFilter: true,
|
||||
SkipFullTextFilter: false,
|
||||
SkipFreeTextFilter: false,
|
||||
SkipFunctionCalls: false,
|
||||
IgnoreNotFoundKeys: false,
|
||||
FreeTextColumn: bodyCol,
|
||||
@@ -1701,10 +1693,12 @@ func TestVisitComparison_FullTextSearch(t *testing.T) {
|
||||
FieldKeys: visitTestKeys,
|
||||
ConditionBuilder: &conditionBuilder{},
|
||||
SkipResourceFilter: false,
|
||||
SkipFullTextFilter: false,
|
||||
SkipFreeTextFilter: false,
|
||||
SkipFunctionCalls: false,
|
||||
IgnoreNotFoundKeys: false,
|
||||
FTSContexts: []telemetrytypes.FieldContext{telemetrytypes.FieldContextResource},
|
||||
FTSCondition: func(_ context.Context, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return fmt.Sprintf("fts_cond"), nil
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -199,6 +199,3 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return condition, nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -122,7 +122,3 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,47 +25,48 @@ func NewConditionBuilder(fm qbtypes.FieldMapper, fl flagger.Flagger) *conditionB
|
||||
return &conditionBuilder{fm: fm, fl: fl}
|
||||
}
|
||||
|
||||
// ConditionForContext builds the search condition for all FTS columns belonging
|
||||
// ConditionForSearch builds the search condition for all FTS columns belonging
|
||||
// to fieldContext. JSON columns are skipped when useJSONBody is disabled.
|
||||
// Returns ("", nil) when no searchable columns exist for the context.
|
||||
func (c *conditionBuilder) ConditionForContext(
|
||||
func (c *conditionBuilder) ConditionForSearch(
|
||||
ctx context.Context,
|
||||
fieldContext telemetrytypes.FieldContext,
|
||||
value any,
|
||||
sb *sqlbuilder.SelectBuilder,
|
||||
) (string, error) {
|
||||
columns := c.fm.GetColumns(ctx, fieldContext)
|
||||
contexts := []telemetrytypes.FieldContext{
|
||||
telemetrytypes.FieldContextLog,
|
||||
telemetrytypes.FieldContextBody,
|
||||
telemetrytypes.FieldContextAttribute,
|
||||
telemetrytypes.FieldContextResource,
|
||||
}
|
||||
|
||||
useJSONBody := c.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{}))
|
||||
|
||||
var conditions []string
|
||||
for _, col := range columns {
|
||||
switch col.Type.GetType() {
|
||||
case schema.ColumnTypeEnumMap:
|
||||
keysExpr := fmt.Sprintf("mapKeys(%s)", col.Name)
|
||||
valsExpr := fmt.Sprintf("mapValues(%s)", col.Name)
|
||||
if mc, ok := col.Type.(schema.MapColumnType); ok && mc.ValueType.GetType() != schema.ColumnTypeEnumString {
|
||||
valsExpr = fmt.Sprintf("arrayMap(x -> toString(x), mapValues(%s))", col.Name)
|
||||
for _, fieldContext := range contexts {
|
||||
for _, col := range ftsColumns(ctx, fieldContext, useJSONBody) {
|
||||
switch col.Type.GetType() {
|
||||
case schema.ColumnTypeEnumMap:
|
||||
keysExpr := fmt.Sprintf("mapKeys(%s)", col.Name)
|
||||
valsExpr := fmt.Sprintf("mapValues(%s)", col.Name)
|
||||
if mc, ok := col.Type.(schema.MapColumnType); ok && mc.ValueType.GetType() != schema.ColumnTypeEnumString {
|
||||
valsExpr = fmt.Sprintf("arrayMap(x -> toString(x), mapValues(%s))", col.Name)
|
||||
}
|
||||
conditions = append(conditions, sb.Or(
|
||||
fmt.Sprintf(`arrayExists(x -> match(x, %s), %s)`, sb.Var(value), keysExpr),
|
||||
fmt.Sprintf(`arrayExists(x -> match(x, %s), %s)`, sb.Var(value), valsExpr),
|
||||
))
|
||||
case schema.ColumnTypeEnumJSON:
|
||||
conditions = append(conditions, fmt.Sprintf(`match(LOWER(toString(%s)), LOWER(%s))`, col.Name, sb.Var(value)))
|
||||
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumLowCardinality:
|
||||
conditions = append(conditions, fmt.Sprintf(`match(LOWER(%s), LOWER(%s))`, col.Name, sb.Var(value)))
|
||||
default:
|
||||
return "", errors.Newf(errors.TypeInternal, errors.CodeInternal, "FTS unsupported column type %s", col.Type)
|
||||
}
|
||||
conditions = append(conditions, sb.Or(
|
||||
fmt.Sprintf(`arrayExists(x -> match(x, %s), %s)`, sb.Var(value), keysExpr),
|
||||
fmt.Sprintf(`arrayExists(x -> match(x, %s), %s)`, sb.Var(value), valsExpr),
|
||||
))
|
||||
case schema.ColumnTypeEnumJSON:
|
||||
conditions = append(conditions, fmt.Sprintf(`match(LOWER(toString(%s)), LOWER(%s))`, col.Name, sb.Var(value)))
|
||||
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumLowCardinality:
|
||||
conditions = append(conditions, fmt.Sprintf(`match(LOWER(%s), LOWER(%s))`, col.Name, sb.Var(value)))
|
||||
default:
|
||||
return "", errors.Newf(errors.TypeInternal, errors.CodeInternal, "FTS unsupported column type %s", col.Type)
|
||||
}
|
||||
}
|
||||
|
||||
switch len(conditions) {
|
||||
case 0:
|
||||
return "", nil
|
||||
case 1:
|
||||
return conditions[0], nil
|
||||
default:
|
||||
return sb.Or(conditions...), nil
|
||||
}
|
||||
return sb.Or(conditions...), nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) conditionFor(
|
||||
|
||||
@@ -126,16 +126,6 @@ var (
|
||||
Direction: qbtypes.OrderDirectionDesc,
|
||||
},
|
||||
}
|
||||
|
||||
// ftsSupportedContexts is the ordered list of field contexts that search()
|
||||
// fans out across. The field mapper's GetColumns is called for each to
|
||||
// retrieve the physical columns.
|
||||
ftsSupportedContexts = []telemetrytypes.FieldContext{
|
||||
telemetrytypes.FieldContextLog,
|
||||
telemetrytypes.FieldContextBody,
|
||||
telemetrytypes.FieldContextAttribute,
|
||||
telemetrytypes.FieldContextResource,
|
||||
}
|
||||
)
|
||||
|
||||
func bodyAliasExpression(bodyJSONEnabled bool) string {
|
||||
|
||||
@@ -547,7 +547,7 @@ func (m *fieldMapper) buildArrayMap(currentNode *telemetrytypes.JSONAccessNode,
|
||||
// GetColumns returns the physical columns for the given field context that
|
||||
// search() fans out across. For FieldContextBody the active column set depends
|
||||
// on the useJSONBody feature flag.
|
||||
func (m *fieldMapper) GetColumns(ctx context.Context, fieldContext telemetrytypes.FieldContext) []*schema.Column {
|
||||
func ftsColumns(ctx context.Context, fieldContext telemetrytypes.FieldContext, useJSONBody bool) []*schema.Column {
|
||||
switch fieldContext {
|
||||
case telemetrytypes.FieldContextLog:
|
||||
return []*schema.Column{
|
||||
@@ -556,8 +556,7 @@ func (m *fieldMapper) GetColumns(ctx context.Context, fieldContext telemetrytype
|
||||
logsV2Columns[LogsV2SpanIDColumn],
|
||||
}
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// TODO(Tushar): thread orgID here to evaluate correctly
|
||||
if m.fl.BooleanOrEmpty(ctx, flagger.FeatureUseJSONBody, featuretypes.NewFlaggerEvaluationContext(valuer.UUID{})) {
|
||||
if useJSONBody {
|
||||
return []*schema.Column{logsV2Columns[LogsV2BodyV2Column]}
|
||||
}
|
||||
return []*schema.Column{logsV2Columns[LogsV2BodyColumn]}
|
||||
|
||||
@@ -1204,6 +1204,7 @@ func buildJSONTestStatementBuilder(t *testing.T, addIndexes bool) (*logQueryStat
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@ type logQueryStatementBuilder struct {
|
||||
aggExprRewriter qbtypes.AggExprRewriter
|
||||
fl flagger.Flagger
|
||||
|
||||
freeTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
freeTextColumn *telemetrytypes.TelemetryFieldKey
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc
|
||||
ftsConditionFunc qbtypes.FTSConditionFunc
|
||||
}
|
||||
|
||||
var _ qbtypes.StatementBuilder[qbtypes.LogAggregation] = (*logQueryStatementBuilder)(nil)
|
||||
@@ -41,6 +42,7 @@ func NewLogQueryStatementBuilder(
|
||||
aggExprRewriter qbtypes.AggExprRewriter,
|
||||
freeTextColumn *telemetrytypes.TelemetryFieldKey,
|
||||
jsonKeyToKey qbtypes.JsonKeyToFieldFunc,
|
||||
ftsConditionFunc qbtypes.FTSConditionFunc,
|
||||
fl flagger.Flagger,
|
||||
) *logQueryStatementBuilder {
|
||||
logsSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/telemetrylogs")
|
||||
@@ -65,8 +67,9 @@ func NewLogQueryStatementBuilder(
|
||||
resourceFilterStmtBuilder: resourceFilterStmtBuilder,
|
||||
aggExprRewriter: aggExprRewriter,
|
||||
fl: fl,
|
||||
freeTextColumn: freeTextColumn,
|
||||
jsonKeyToKey: jsonKeyToKey,
|
||||
freeTextColumn: freeTextColumn,
|
||||
jsonKeyToKey: jsonKeyToKey,
|
||||
ftsConditionFunc: ftsConditionFunc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +666,7 @@ func (b *logQueryStatementBuilder) addFilterCondition(
|
||||
EndNs: end,
|
||||
}
|
||||
if enableFTS {
|
||||
opts.FTSContexts = ftsSupportedContexts
|
||||
opts.FTSCondition = b.ftsConditionFunc
|
||||
}
|
||||
|
||||
// add filter expression
|
||||
|
||||
@@ -212,6 +212,7 @@ func TestStatementBuilderTimeSeries(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -353,6 +354,7 @@ func TestStatementBuilderListQuery(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -483,6 +485,7 @@ func TestStatementBuilderListQueryResourceTests(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -559,6 +562,7 @@ func TestStatementBuilderTimeSeriesBodyGroupBy(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -654,6 +658,7 @@ func TestStatementBuilderListQueryServiceCollision(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -878,6 +883,7 @@ func TestAdjustKey(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -1023,6 +1029,7 @@ func TestStmtBuilderBodyField(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
|
||||
@@ -1199,6 +1206,7 @@ func TestStmtBuilderTextSearch(t *testing.T) {
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
cb.ConditionForSearch,
|
||||
fl,
|
||||
)
|
||||
startMs := uint64(1747947419000)
|
||||
|
||||
@@ -136,6 +136,3 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return fmt.Sprintf(expr, columns[0].Name, sb.Var(key.Name), cond), nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -116,7 +116,3 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -158,6 +158,3 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return condition, nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -104,7 +104,3 @@ func (m *fieldMapper) ColumnExpressionFor(
|
||||
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
func (m *fieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -207,6 +207,3 @@ func (b *defaultConditionBuilder) ConditionFor(
|
||||
return "", qbtypes.ErrUnsupportedOperator
|
||||
}
|
||||
|
||||
func (b *defaultConditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -74,7 +74,3 @@ func (m *defaultFieldMapper) ColumnExpressionFor(
|
||||
}
|
||||
return fmt.Sprintf("%s AS `%s`", fieldExpression, key.Name), nil
|
||||
}
|
||||
|
||||
func (m *defaultFieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -155,7 +155,8 @@ func (b *resourceFilterStatementBuilder[T]) addConditions(
|
||||
BodyJSONEnabled: bodyJSONEnabled,
|
||||
FreeTextColumn: b.fullTextColumn,
|
||||
JsonKeyToKey: b.jsonKeyToKey,
|
||||
SkipFullTextFilter: true,
|
||||
SkipFreeTextFilter: true,
|
||||
SkipFullTextSearch: true,
|
||||
SkipFunctionCalls: true,
|
||||
// there is no need for "key" not found error for resource filtering
|
||||
IgnoreNotFoundKeys: true,
|
||||
|
||||
@@ -265,9 +265,6 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return condition, nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) ConditionForContext(_ context.Context, _ telemetrytypes.FieldContext, _ any, _ *sqlbuilder.SelectBuilder) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) isSpanScopeField(name string) bool {
|
||||
keyName := strings.ToLower(name)
|
||||
|
||||
@@ -364,7 +364,3 @@ func (m *defaultFieldMapper) ColumnExpressionFor(
|
||||
|
||||
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
|
||||
}
|
||||
|
||||
func (m *defaultFieldMapper) GetColumns(_ context.Context, _ telemetrytypes.FieldContext) []*schema.Column {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ var (
|
||||
|
||||
type JsonKeyToFieldFunc func(context.Context, *telemetrytypes.TelemetryFieldKey, FilterOperator, any) (string, any)
|
||||
|
||||
// FTSConditionFunc builds the search() SQL condition for all FTS columns belonging
|
||||
// to fieldContext. Returns ("", nil) when no columns are searchable for the
|
||||
// context (e.g. body JSON when useJSONBody is off). Pass nil to disable search().
|
||||
type FTSConditionFunc func(ctx context.Context, value any, sb *sqlbuilder.SelectBuilder) (string, error)
|
||||
|
||||
// FieldMapper maps the telemetry field key to the table field name.
|
||||
type FieldMapper interface {
|
||||
// FieldFor returns the field name for the given key.
|
||||
@@ -27,20 +32,12 @@ type FieldMapper interface {
|
||||
ColumnFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey) ([]*schema.Column, error)
|
||||
// ColumnExpressionFor returns the column expression for the given key.
|
||||
ColumnExpressionFor(ctx context.Context, tsStart, tsEnd uint64, key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemetrytypes.TelemetryFieldKey) (string, error)
|
||||
// GetColumns returns the physical columns to fan out across for FTS search()
|
||||
// for the given field context. Non-log signal implementations return nil.
|
||||
GetColumns(ctx context.Context, fieldContext telemetrytypes.FieldContext) []*schema.Column
|
||||
}
|
||||
|
||||
// ConditionBuilder builds the condition for the filter.
|
||||
type ConditionBuilder interface {
|
||||
// ConditionFor returns the condition for the given key, operator and value.
|
||||
ConditionFor(ctx context.Context, startNs uint64, endNs uint64, key *telemetrytypes.TelemetryFieldKey, operator FilterOperator, value any, sb *sqlbuilder.SelectBuilder) (string, error)
|
||||
// ConditionForContext builds the search expression for all FTS columns belonging
|
||||
// to fieldContext. Returns ("", nil) when no searchable columns exist for the
|
||||
// context (e.g. JSON body when useJSONBody is disabled). Non-log
|
||||
// implementations may return ("", nil) unconditionally.
|
||||
ConditionForContext(ctx context.Context, fieldContext telemetrytypes.FieldContext, value any, sb *sqlbuilder.SelectBuilder) (string, error)
|
||||
}
|
||||
|
||||
type AggExprRewriter interface {
|
||||
|
||||
Reference in New Issue
Block a user