mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-19 04:40:28 +01:00
Some checks failed
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat: add functional unique index * chore: add tag migration * chore: add failing scratch test (to discuss) * chore: fetch expressions as well in getindices call * fix: remove tag unique index (will be added in a separate PR) * fix: remove tag unique index (will be added in a separate PR) * chore: remove temporary tests * fix: go lint fix * chore: better comment for unique index * test: add test for case insensitive expression equality * test: add equality test for columns with quotes and capital letters * chore: add separate type for unique indices with expressions * test: add test for postgres provider
168 lines
3.2 KiB
Go
168 lines
3.2 KiB
Go
package sqlschema
|
|
|
|
import (
|
|
"fmt"
|
|
"hash/fnv"
|
|
"strings"
|
|
)
|
|
|
|
type expressionNormalizer struct {
|
|
input string
|
|
foldCase bool
|
|
}
|
|
|
|
func (n *expressionNormalizer) hash() string {
|
|
hasher := fnv.New32a()
|
|
_, _ = hasher.Write([]byte(n.normalize()))
|
|
return fmt.Sprintf("%08x", hasher.Sum32())
|
|
}
|
|
|
|
func (n *expressionNormalizer) normalize() string {
|
|
expr := strings.TrimSpace(n.input)
|
|
expr = n.stripOuterParentheses(expr)
|
|
|
|
var output strings.Builder
|
|
output.Grow(len(expr))
|
|
|
|
for i := 0; i < len(expr); i++ {
|
|
switch expr[i] {
|
|
case ' ', '\t', '\n', '\r':
|
|
if output.Len() > 0 {
|
|
last := output.String()[output.Len()-1]
|
|
if last != ' ' {
|
|
output.WriteByte(' ')
|
|
}
|
|
}
|
|
case '\'':
|
|
end := n.consumeSingleQuotedLiteral(expr, i, &output)
|
|
i = end
|
|
case '"':
|
|
token, end := n.consumeDoubleQuotedToken(expr, i)
|
|
output.WriteString(token)
|
|
i = end
|
|
default:
|
|
c := expr[i]
|
|
if n.foldCase && c >= 'A' && c <= 'Z' {
|
|
c += 'a' - 'A'
|
|
}
|
|
output.WriteByte(c)
|
|
}
|
|
}
|
|
|
|
return strings.TrimSpace(output.String())
|
|
}
|
|
|
|
func (n *expressionNormalizer) stripOuterParentheses(s string) string {
|
|
for {
|
|
s = strings.TrimSpace(s)
|
|
if len(s) < 2 || s[0] != '(' || s[len(s)-1] != ')' || !n.hasWrappingParentheses(s) {
|
|
return s
|
|
}
|
|
s = s[1 : len(s)-1]
|
|
}
|
|
}
|
|
|
|
func (n *expressionNormalizer) hasWrappingParentheses(s string) bool {
|
|
depth := 0
|
|
inSingleQuotedLiteral := false
|
|
inDoubleQuotedToken := false
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
switch s[i] {
|
|
case '\'':
|
|
if inDoubleQuotedToken {
|
|
continue
|
|
}
|
|
if inSingleQuotedLiteral && i+1 < len(s) && s[i+1] == '\'' {
|
|
i++
|
|
continue
|
|
}
|
|
inSingleQuotedLiteral = !inSingleQuotedLiteral
|
|
case '"':
|
|
if inSingleQuotedLiteral {
|
|
continue
|
|
}
|
|
if inDoubleQuotedToken && i+1 < len(s) && s[i+1] == '"' {
|
|
i++
|
|
continue
|
|
}
|
|
inDoubleQuotedToken = !inDoubleQuotedToken
|
|
case '(':
|
|
if inSingleQuotedLiteral || inDoubleQuotedToken {
|
|
continue
|
|
}
|
|
depth++
|
|
case ')':
|
|
if inSingleQuotedLiteral || inDoubleQuotedToken {
|
|
continue
|
|
}
|
|
depth--
|
|
if depth == 0 && i != len(s)-1 {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return depth == 0
|
|
}
|
|
|
|
func (n *expressionNormalizer) consumeSingleQuotedLiteral(s string, start int, output *strings.Builder) int {
|
|
output.WriteByte(s[start])
|
|
for i := start + 1; i < len(s); i++ {
|
|
output.WriteByte(s[i])
|
|
if s[i] == '\'' {
|
|
if i+1 < len(s) && s[i+1] == '\'' {
|
|
i++
|
|
output.WriteByte(s[i])
|
|
continue
|
|
}
|
|
return i
|
|
}
|
|
}
|
|
|
|
return len(s) - 1
|
|
}
|
|
|
|
func (n *expressionNormalizer) consumeDoubleQuotedToken(s string, start int) (string, int) {
|
|
var ident strings.Builder
|
|
|
|
for i := start + 1; i < len(s); i++ {
|
|
if s[i] == '"' {
|
|
if i+1 < len(s) && s[i+1] == '"' {
|
|
ident.WriteByte('"')
|
|
i++
|
|
continue
|
|
}
|
|
|
|
if n.isSimpleUnquotedIdentifier(ident.String()) {
|
|
return ident.String(), i
|
|
}
|
|
|
|
return s[start : i+1], i
|
|
}
|
|
|
|
ident.WriteByte(s[i])
|
|
}
|
|
|
|
return s[start:], len(s) - 1
|
|
}
|
|
|
|
func (n *expressionNormalizer) isSimpleUnquotedIdentifier(s string) bool {
|
|
if s == "" || strings.ToLower(s) != s {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
ch := s[i]
|
|
if (ch >= 'a' && ch <= 'z') || ch == '_' {
|
|
continue
|
|
}
|
|
if i > 0 && ch >= '0' && ch <= '9' {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|