Files
grafana/pkg/services/ngalert/store/json.go

122 lines
3.6 KiB
Go

package store
import (
"encoding/json"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
// JSON functions for MySQL/PostgreSQL
func jsonEquals(dialect migrator.Dialect, column, key, value string) (string, []any) {
switch dialect.DriverName() {
case migrator.MySQL:
return fmt.Sprintf(`JSON_UNQUOTE(JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$."', ?, '"'))) = ?`, column), []any{key, value}
case migrator.Postgres:
return fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?) = ?", column), []any{key, value}
default:
return "", nil
}
}
func jsonNotEquals(dialect migrator.Dialect, column, key, value string) (string, []any) {
var jx string
switch dialect.DriverName() {
case migrator.MySQL:
jx = fmt.Sprintf(`JSON_UNQUOTE(JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$."', ?, '"')))`, column)
case migrator.Postgres:
jx = fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?)", column)
default:
return "", nil
}
return fmt.Sprintf("(%s IS NULL OR %s != ?)", jx, jx), []any{key, key, value}
}
func jsonKeyMissing(dialect migrator.Dialect, column, key string) (string, []any, error) {
return jsonKeyCondition(dialect, column, key, false)
}
func jsonKeyExists(dialect migrator.Dialect, column, key string) (string, []any, error) {
return jsonKeyCondition(dialect, column, key, true)
}
func jsonKeyCondition(dialect migrator.Dialect, column, key string, exists bool) (string, []any, error) {
nullCheck := "IS NULL"
if exists {
nullCheck = "IS NOT NULL"
}
switch dialect.DriverName() {
case migrator.MySQL:
return fmt.Sprintf(`JSON_EXTRACT(NULLIF(%s, ''), CONCAT('$."', ?, '"')) %s`, column, nullCheck), []any{key}, nil
case migrator.Postgres:
return fmt.Sprintf("jsonb_extract_path_text(NULLIF(%s, '')::jsonb, ?) %s", column, nullCheck), []any{key}, nil
default:
return "", nil, fmt.Errorf("unsupported dialect for JSON key condition: %s", dialect.DriverName())
}
}
// GLOB functions for SQLite
func globEquals(column, key, value string) (string, []any, error) {
pattern, err := buildGlobPattern(key, value)
if err != nil {
return "", nil, err
}
return column + " GLOB ?", []any{"*" + pattern + "*"}, nil
}
func globNotEquals(column, key, value string) (string, []any, error) {
pattern, err := buildGlobPattern(key, value)
if err != nil {
return "", nil, err
}
return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil
}
func globKeyMissing(column, key string) (string, []any, error) {
pattern, err := buildGlobKeyPattern(key)
if err != nil {
return "", nil, err
}
return column + " NOT GLOB ?", []any{"*" + pattern + "*"}, nil
}
func globKeyExists(column, key string) (string, []any, error) {
pattern, err := buildGlobKeyPattern(key)
if err != nil {
return "", nil, err
}
return column + " GLOB ?", []any{"*" + pattern + "*"}, nil
}
// Search for `"key":"value"`
func buildGlobPattern(key, value string) (string, error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return "", fmt.Errorf("failed to marshal key: %w", err)
}
valueJSON, err := json.Marshal(value)
if err != nil {
return "", fmt.Errorf("failed to marshal value: %w", err)
}
return escapeGlobPattern(fmt.Sprintf(`%s:%s`, string(keyJSON), string(valueJSON))), nil
}
// Search for `"key":`
func buildGlobKeyPattern(key string) (string, error) {
keyJSON, err := json.Marshal(key)
if err != nil {
return "", fmt.Errorf("failed to marshal key: %w", err)
}
return escapeGlobPattern(string(keyJSON) + ":"), nil
}
func escapeGlobPattern(pattern string) string {
pattern = strings.ReplaceAll(pattern, "[", "[[]")
pattern = strings.ReplaceAll(pattern, "*", "[*]")
pattern = strings.ReplaceAll(pattern, "?", "[?]")
return pattern
}