Compare commits

...

1 Commits

Author SHA1 Message Date
srikanthccv
7497a7b971 refactor(qb): quote field names with the ClickHouse quoting helpers
Every plain field-name literal and identifier in the statement-builder
layer now goes through querybuilder.ClickHouseStringLiteral and
querybuilder.ClickHouseIdentifier: map reads, mapContains, JSON
subcolumn paths, JSONExtractString and JSONExtractKeys on labels,
simpleJSONExtractString and simpleJSONHas on the fingerprint labels,
and the column aliases.

The helpers escape backslashes, quotes, and backticks, so a field name
that contains a quote or a backtick can no longer break out of its
literal or identifier. For every other name the output is byte
identical, and the full test suite passes unchanged.

Out of scope, on purpose: LIKE index-hint fragments and body JSON path
builders (they have pattern and path semantics, not plain literals),
and the materialized-column name builders in telemetrytypes (importing
querybuilder there is an import cycle).

Assisted-by: Claude Fable 5
2026-08-18 04:34:59 +05:30
12 changed files with 33 additions and 30 deletions

View File

@@ -88,5 +88,5 @@ func (m *fieldMapper) ColumnExpressionFor(ctx context.Context, orgID valuer.UUID
if err != nil {
return "", err
}
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(colName), field.Name), nil
return fmt.Sprintf("%s AS %s", sqlbuilder.Escape(colName), querybuilder.ClickHouseIdentifier(field.Name)), nil
}

View File

@@ -43,7 +43,7 @@ func ExistsExpression(columns []*schema.Column, key *telemetrytypes.TelemetryFie
if len(evolutionsEntries) > 0 && evolutionsEntries[0] != nil {
columnName = evolutionsEntries[0].ColumnName
}
rawPath := fmt.Sprintf("%s.`%s`", columnName, key.Name)
rawPath := fmt.Sprintf("%s.%s", columnName, ClickHouseIdentifier(key.Name))
if exists {
return rawPath + " IS NOT NULL", nil
}
@@ -88,7 +88,7 @@ func ExistsExpression(columns []*schema.Column, key *telemetrytypes.TelemetryFie
switch valueType := column.Type.(schema.MapColumnType).ValueType; valueType.GetType() {
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumBool, schema.ColumnTypeEnumFloat64:
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", column.Name, key.Name)
leftOperand := fmt.Sprintf("mapContains(%s, %s)", column.Name, ClickHouseStringLiteral(key.Name))
if key.Materialized {
leftOperand = telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
}

View File

@@ -96,7 +96,7 @@ func valueIndexCondition(
func memberPresenceCondition(sb *sqlbuilder.SelectBuilder, column string, members []*telemetrytypes.TelemetryFieldKey, exists bool) string {
conditions := make([]string, 0, len(members))
for _, member := range members {
field := fmt.Sprintf("simpleJSONHas(%s, '%s')", column, member.Name)
field := fmt.Sprintf("simpleJSONHas(%s, %s)", column, querybuilder.ClickHouseStringLiteral(member.Name))
if exists {
conditions = append(conditions, sb.E(field, true))
} else {

View File

@@ -5,6 +5,7 @@ import (
"fmt"
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
"github.com/SigNoz/signoz/pkg/querybuilder"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -66,7 +67,7 @@ func (m *defaultFieldMapper) FieldFor(
return "", err
}
if key.FieldContext == telemetrytypes.FieldContextResource {
return fmt.Sprintf("simpleJSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
return fmt.Sprintf("simpleJSONExtractString(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return columns[0].Name, nil
}
@@ -91,7 +92,7 @@ func (m *defaultFieldMapper) ExistsFor(
}
return "false", nil
}
pred := fmt.Sprintf("simpleJSONHas(%s, '%s')", columns[0].Name, key.Name)
pred := fmt.Sprintf("simpleJSONHas(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name))
if exists {
return pred, nil
}
@@ -110,5 +111,5 @@ func (m *defaultFieldMapper) ColumnExpressionFor(
if err != nil {
return "", err
}
return fmt.Sprintf("%s AS `%s`", fieldExpression, key.Name), nil
return fmt.Sprintf("%s AS %s", fieldExpression, querybuilder.ClickHouseIdentifier(key.Name)), nil
}

View File

@@ -168,7 +168,7 @@ func (c *conditionBuilder) conditionForKey(
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
ValueType: schema.ColumnTypeString,
}:
leftOperand := fmt.Sprintf("mapContains(%s, '%s')", columns[0].Name, key.Name)
leftOperand := fmt.Sprintf("mapContains(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name))
if operator == qbtypes.FilterOperatorExists {
cond = sb.E(leftOperand, true)
} else {

View File

@@ -7,6 +7,7 @@ import (
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/querybuilder"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -64,7 +65,7 @@ func (m *fieldMapper) ExistsFor(ctx context.Context, _ valuer.UUID, tsStart, tsE
if err != nil {
return "", err
}
pred := fmt.Sprintf("mapContains(%s, '%s')", columns[0].Name, key.Name)
pred := fmt.Sprintf("mapContains(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name))
if exists {
return pred, nil
}
@@ -82,7 +83,7 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, startNs, endN
KeyType: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString},
ValueType: schema.ColumnTypeString,
}:
return fmt.Sprintf("%s['%s']", columns[0].Name, key.Name), nil
return fmt.Sprintf("%s[%s]", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return columns[0].Name, nil
}
@@ -130,5 +131,5 @@ func (m *fieldMapper) ColumnExpressionFor(
}
}
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
return fmt.Sprintf("%s AS %s", sqlbuilder.Escape(fieldExpression), querybuilder.ClickHouseIdentifier(field.Name)), nil
}

View File

@@ -68,7 +68,7 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, _, _ uint64,
if key.FieldContext != telemetrytypes.FieldContextResource {
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "only resource context fields are supported for json columns in audit, got %s", key.FieldContext.String)
}
return fmt.Sprintf("%s.`%s`::String", column.Name, key.Name), nil
return fmt.Sprintf("%s.%s::String", column.Name, querybuilder.ClickHouseIdentifier(key.Name)), nil
case schema.ColumnTypeEnumLowCardinality:
return column.Name, nil
case schema.ColumnTypeEnumString, schema.ColumnTypeEnumUInt64, schema.ColumnTypeEnumUInt32, schema.ColumnTypeEnumUInt8:
@@ -84,7 +84,7 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, _, _ uint64,
if key.Materialized {
return telemetrytypes.FieldKeyToMaterializedColumnName(key), nil
}
return fmt.Sprintf("%s['%s']", column.Name, key.Name), nil
return fmt.Sprintf("%s[%s]", column.Name, querybuilder.ClickHouseStringLiteral(key.Name)), nil
default:
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported map value type %s", valueType)
}
@@ -156,7 +156,7 @@ func (m *fieldMapper) ColumnExpressionFor(
return fmt.Sprintf("multiIf(%s, %s, NULL)", guard, coerced), nil
}
return fmt.Sprintf("%s AS `%s`", sqlbuilder.Escape(fieldExpression), field.Name), nil
return fmt.Sprintf("%s AS %s", sqlbuilder.Escape(fieldExpression), querybuilder.ClickHouseIdentifier(field.Name)), nil
}
// CandidateKeys returns nil: audit has no synthesize-on-unknown-key fallback, so an

View File

@@ -141,8 +141,8 @@ func (m *fieldMapper) FieldFor(ctx context.Context, orgID valuer.UUID, tsStart,
case schema.ColumnTypeEnumJSON:
switch key.FieldContext {
case telemetrytypes.FieldContextResource:
exprs = append(exprs, fmt.Sprintf("%s.`%s`::String", columnName, key.Name))
existExpr = append(existExpr, fmt.Sprintf("%s.`%s` IS NOT NULL", columnName, key.Name))
exprs = append(exprs, fmt.Sprintf("%s.%s::String", columnName, querybuilder.ClickHouseIdentifier(key.Name)))
existExpr = append(existExpr, fmt.Sprintf("%s.%s IS NOT NULL", columnName, querybuilder.ClickHouseIdentifier(key.Name)))
case telemetrytypes.FieldContextBody:
if key.Name == messageSubField {
exprs = append(exprs, messageSubColumn)
@@ -186,8 +186,8 @@ func (m *fieldMapper) FieldFor(ctx context.Context, orgID valuer.UUID, tsStart,
exprs = append(exprs, telemetrytypes.FieldKeyToMaterializedColumnName(key))
existExpr = append(existExpr, telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key))
} else {
exprs = append(exprs, fmt.Sprintf("%s['%s']", columnName, key.Name))
existExpr = append(existExpr, fmt.Sprintf("mapContains(%s, '%s')", columnName, key.Name))
exprs = append(exprs, fmt.Sprintf("%s[%s]", columnName, querybuilder.ClickHouseStringLiteral(key.Name)))
existExpr = append(existExpr, fmt.Sprintf("mapContains(%s, %s)", columnName, querybuilder.ClickHouseStringLiteral(key.Name)))
}
default:
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "exists operator is not supported for map column type %s", valueType)
@@ -415,7 +415,7 @@ func (m *fieldMapper) buildFieldForJSON(key *telemetrytypes.TelemetryFieldKey) (
elemType = telemetrytypes.String
}
fieldPath := fmt.Sprintf("%s.`%s`", LogsV2BodyV2Column, key.Name)
fieldPath := fmt.Sprintf("%s.%s", LogsV2BodyV2Column, querybuilder.ClickHouseIdentifier(key.Name))
return fmt.Sprintf("dynamicElement(%s, '%s')", fieldPath, elemType.StringValue()), nil
}

View File

@@ -41,7 +41,7 @@ func (c *jsonConditionBuilder) buildJSONCondition(operator qbtypes.FilterOperato
// path index
if operator.AddDefaultExistsFilter() {
pathIndex := fmt.Sprintf(`has(%s, '%s')`, schemamigrator.JSONPathsIndexExpr(LogsV2BodyV2Column), c.key.ArrayParentPaths()[0])
pathIndex := fmt.Sprintf(`has(%s, %s)`, schemamigrator.JSONPathsIndexExpr(LogsV2BodyV2Column), querybuilder.ClickHouseStringLiteral(c.key.ArrayParentPaths()[0]))
return sb.And(baseCond, pathIndex), nil
}

View File

@@ -136,9 +136,9 @@ func (c *conditionBuilder) conditionFor(
}
if operator == qbtypes.FilterOperatorExists {
return fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", key.Name), nil
return fmt.Sprintf("has(JSONExtractKeys(labels), %s)", querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", key.Name), nil
return fmt.Sprintf("not has(JSONExtractKeys(labels), %s)", querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return "", errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported operator: %v", operator)
}

View File

@@ -6,6 +6,7 @@ import (
"slices"
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
"github.com/SigNoz/signoz/pkg/querybuilder"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -79,14 +80,14 @@ func (m *fieldMapper) FieldFor(ctx context.Context, _ valuer.UUID, startNs, endN
switch key.FieldContext {
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextScope, telemetrytypes.FieldContextAttribute:
return fmt.Sprintf("JSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
return fmt.Sprintf("JSONExtractString(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name)), nil
case telemetrytypes.FieldContextMetric:
return columns[0].Name, nil
case telemetrytypes.FieldContextUnspecified:
if slices.Contains(IntrinsicFields, key.Name) {
return columns[0].Name, nil
}
return fmt.Sprintf("JSONExtractString(%s, '%s')", columns[0].Name, key.Name), nil
return fmt.Sprintf("JSONExtractString(%s, %s)", columns[0].Name, querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return columns[0].Name, nil
@@ -103,9 +104,9 @@ func (m *fieldMapper) ExistsFor(_ context.Context, _ valuer.UUID, _, _ uint64, k
return "true", nil
}
if exists {
return fmt.Sprintf("has(JSONExtractKeys(labels), '%s')", key.Name), nil
return fmt.Sprintf("has(JSONExtractKeys(labels), %s)", querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
return fmt.Sprintf("not has(JSONExtractKeys(labels), '%s')", key.Name), nil
return fmt.Sprintf("not has(JSONExtractKeys(labels), %s)", querybuilder.ClickHouseStringLiteral(key.Name)), nil
}
func (m *fieldMapper) ColumnExpressionFor(

View File

@@ -298,8 +298,8 @@ func (m *fieldMapper) resolveColumnExprs(
}
// have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY
// once clickHouse dependency is updated, we need to check if we can remove it.
exprs = append(exprs, fmt.Sprintf("%s.`%s`::String", columnName, key.Name))
existExprs = append(existExprs, fmt.Sprintf("%s.`%s` IS NOT NULL", columnName, key.Name))
exprs = append(exprs, fmt.Sprintf("%s.%s::String", columnName, querybuilder.ClickHouseIdentifier(key.Name)))
existExprs = append(existExprs, fmt.Sprintf("%s.%s IS NOT NULL", columnName, querybuilder.ClickHouseIdentifier(key.Name)))
case schema.ColumnTypeEnumString,
schema.ColumnTypeEnumUInt64,
schema.ColumnTypeEnumUInt32,
@@ -329,8 +329,8 @@ func (m *fieldMapper) resolveColumnExprs(
exprs = append(exprs, telemetrytypes.FieldKeyToMaterializedColumnName(key))
existExprs = append(existExprs, telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key))
} else {
exprs = append(exprs, fmt.Sprintf("%s['%s']", columnName, key.Name))
existExprs = append(existExprs, fmt.Sprintf("mapContains(%s, '%s')", columnName, key.Name))
exprs = append(exprs, fmt.Sprintf("%s[%s]", columnName, querybuilder.ClickHouseStringLiteral(key.Name)))
existExprs = append(existExprs, fmt.Sprintf("mapContains(%s, %s)", columnName, querybuilder.ClickHouseStringLiteral(key.Name)))
}
default:
return nil, nil, nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "value type %s is not supported for map column type %s", valueType, column.Type)