snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
298
internal/engine/googlesql/utils.go
Normal file
298
internal/engine/googlesql/utils.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package googlesql
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
zjast "github.com/sqlc-dev/zetajones/ast"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/debug"
|
||||
"github.com/sqlc-dev/sqlc/internal/sql/ast"
|
||||
)
|
||||
|
||||
func todo(n zjast.Node) *ast.TODO {
|
||||
if debug.Active {
|
||||
log.Printf("googlesql.convert: Unknown node type %T\n", n)
|
||||
}
|
||||
return &ast.TODO{}
|
||||
}
|
||||
|
||||
// identifier passes table and column identifiers through unchanged. GoogleSQL
|
||||
// keywords are case-insensitive, but table names (on BigQuery) are
|
||||
// case-sensitive, so unlike the MySQL and ClickHouse engines we do not
|
||||
// lowercase identifiers here. Function and type names, which are
|
||||
// case-insensitive, are lowercased at their call sites instead.
|
||||
func identifier(id string) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func NewIdentifier(t string) *ast.String {
|
||||
return &ast.String{Str: identifier(t)}
|
||||
}
|
||||
|
||||
// pathParts returns the identifier names of a dotted path expression.
|
||||
func pathParts(n *zjast.PathExpression) []string {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
parts := make([]string, 0, len(n.Names))
|
||||
for _, id := range n.Names {
|
||||
if id == nil {
|
||||
continue
|
||||
}
|
||||
parts = append(parts, id.Name)
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
func parseTableName(n *zjast.PathExpression) *ast.TableName {
|
||||
name := &ast.TableName{}
|
||||
parts := pathParts(n)
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
case 1:
|
||||
name.Name = identifier(parts[0])
|
||||
case 2:
|
||||
name.Schema = identifier(parts[0])
|
||||
name.Name = identifier(parts[1])
|
||||
default:
|
||||
name.Catalog = identifier(parts[0])
|
||||
name.Schema = identifier(parts[len(parts)-2])
|
||||
name.Name = identifier(parts[len(parts)-1])
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func parseRangeVar(n *zjast.PathExpression) *ast.RangeVar {
|
||||
tn := parseTableName(n)
|
||||
rv := &ast.RangeVar{}
|
||||
if n != nil {
|
||||
rv.Location = n.Pos()
|
||||
}
|
||||
if tn.Catalog != "" {
|
||||
catalog := tn.Catalog
|
||||
rv.Catalogname = &catalog
|
||||
}
|
||||
if tn.Schema != "" {
|
||||
schema := tn.Schema
|
||||
rv.Schemaname = &schema
|
||||
}
|
||||
if tn.Name != "" {
|
||||
relname := tn.Name
|
||||
rv.Relname = &relname
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func convertAlias(a *zjast.Alias) *ast.Alias {
|
||||
if a == nil || a.Identifier == nil {
|
||||
return nil
|
||||
}
|
||||
name := identifier(a.Identifier.Name)
|
||||
return &ast.Alias{Aliasname: &name}
|
||||
}
|
||||
|
||||
// whereExpr unwraps a WHERE clause node to its expression. The DELETE and
|
||||
// UPDATE statement nodes hold either a *WhereClause or a bare expression.
|
||||
func whereExpr(node zjast.Node) zjast.Node {
|
||||
if wc, ok := node.(*zjast.WhereClause); ok {
|
||||
return wc.Expr
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// stringLiteralValue concatenates and unquotes the components of a string
|
||||
// literal.
|
||||
func stringLiteralValue(n *zjast.StringLiteral) string {
|
||||
var sb strings.Builder
|
||||
for _, comp := range n.Components {
|
||||
if comp == nil {
|
||||
continue
|
||||
}
|
||||
sb.WriteString(unquote(comp.Image))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// unquote strips the optional r/b prefix and the surrounding quotes from a
|
||||
// string literal component and decodes escape sequences for non-raw strings.
|
||||
// It is best-effort: malformed input is returned unchanged.
|
||||
func unquote(image string) string {
|
||||
raw := false
|
||||
i := 0
|
||||
prefix:
|
||||
for i < len(image) {
|
||||
switch image[i] {
|
||||
case 'r', 'R':
|
||||
raw = true
|
||||
i++
|
||||
case 'b', 'B':
|
||||
i++
|
||||
default:
|
||||
break prefix
|
||||
}
|
||||
}
|
||||
s := image[i:]
|
||||
for _, q := range []string{`'''`, `"""`, `'`, `"`} {
|
||||
if len(s) >= 2*len(q) && strings.HasPrefix(s, q) && strings.HasSuffix(s, q) {
|
||||
s = s[len(q) : len(s)-len(q)]
|
||||
if raw || !strings.ContainsRune(s, '\\') {
|
||||
return s
|
||||
}
|
||||
return decodeEscapes(s)
|
||||
}
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
func decodeEscapes(s string) string {
|
||||
var sb strings.Builder
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch != '\\' || i+1 >= len(s) {
|
||||
sb.WriteByte(ch)
|
||||
continue
|
||||
}
|
||||
i++
|
||||
switch s[i] {
|
||||
case 'n':
|
||||
sb.WriteByte('\n')
|
||||
case 't':
|
||||
sb.WriteByte('\t')
|
||||
case 'r':
|
||||
sb.WriteByte('\r')
|
||||
case 'a':
|
||||
sb.WriteByte('\a')
|
||||
case 'b':
|
||||
sb.WriteByte('\b')
|
||||
case 'f':
|
||||
sb.WriteByte('\f')
|
||||
case 'v':
|
||||
sb.WriteByte('\v')
|
||||
case '\\', '\'', '"', '?', '`':
|
||||
sb.WriteByte(s[i])
|
||||
default:
|
||||
// Unknown escape (\x, \u, ...): keep it verbatim.
|
||||
sb.WriteByte('\\')
|
||||
sb.WriteByte(s[i])
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// typeName renders a zetajones type node (used by CAST) as a lowercased type
|
||||
// name. Nested types degrade to a readable representation.
|
||||
// typeName spells a type the way the analysis core reads one: a family
|
||||
// applied to its arguments, with an array's element, a struct's labelled
|
||||
// fields, a range's subtype and a map's key and value nested, and a
|
||||
// parameter list's integers and MAX written as they were.
|
||||
func typeName(node zjast.Node) string {
|
||||
switch t := node.(type) {
|
||||
case *zjast.SimpleType:
|
||||
return strings.ToLower(strings.Join(pathParts(t.Name), ".")) + typeParameters(t.TypeParameters)
|
||||
case *zjast.ArrayType:
|
||||
return "array(" + typeName(t.ElementType) + ")"
|
||||
case *zjast.StructType:
|
||||
fields := make([]string, 0, len(t.Fields))
|
||||
for _, f := range t.Fields {
|
||||
field := typeName(f.Type)
|
||||
if f.Name != nil {
|
||||
field = identifier(f.Name.Name) + ": " + field
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
return "struct(" + strings.Join(fields, ", ") + ")"
|
||||
case *zjast.RangeType:
|
||||
return "range(" + typeName(t.ElementType) + ")"
|
||||
case *zjast.MapType:
|
||||
return "map(" + typeName(t.KeyType) + ", " + typeName(t.ValueType) + ")"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// typeParameters spells a parameter list, STRING(10) or BYTES(MAX), as the
|
||||
// arguments of a call.
|
||||
func typeParameters(params *zjast.TypeParameterList) string {
|
||||
if params == nil || len(params.Parameters) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(params.Parameters))
|
||||
for _, p := range params.Parameters {
|
||||
switch v := p.(type) {
|
||||
case *zjast.IntLiteral:
|
||||
parts = append(parts, v.Image)
|
||||
case *zjast.MaxLiteral:
|
||||
parts = append(parts, "max")
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return ""
|
||||
}
|
||||
return "(" + strings.Join(parts, ", ") + ")"
|
||||
}
|
||||
|
||||
// columnSchemaTypeName renders a CREATE TABLE column schema as a lowercased
|
||||
// type name. Nested types degrade to a readable representation.
|
||||
// columnSchemaTypeName spells a column's type the way typeName spells a
|
||||
// type.
|
||||
func columnSchemaTypeName(node zjast.Node) string {
|
||||
switch t := node.(type) {
|
||||
case *zjast.SimpleColumnSchema:
|
||||
return strings.ToLower(strings.Join(pathParts(t.Type), ".")) + typeParameters(t.TypeParameters)
|
||||
case *zjast.ArrayColumnSchema:
|
||||
return "array(" + columnSchemaTypeName(t.ElementSchema) + ")"
|
||||
case *zjast.StructColumnSchema:
|
||||
fields := make([]string, 0, len(t.Fields))
|
||||
for _, f := range t.Fields {
|
||||
field := columnSchemaTypeName(f.Schema)
|
||||
if f.Name != nil {
|
||||
field = identifier(f.Name.Name) + ": " + field
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
return "struct(" + strings.Join(fields, ", ") + ")"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// columnAttributes returns the attribute list (NOT NULL, PRIMARY KEY, ...) of
|
||||
// a column schema, if any.
|
||||
func columnAttributes(node zjast.Node) *zjast.ColumnAttributeList {
|
||||
switch t := node.(type) {
|
||||
case *zjast.SimpleColumnSchema:
|
||||
return t.Attributes
|
||||
case *zjast.ArrayColumnSchema:
|
||||
return t.Attributes
|
||||
case *zjast.StructColumnSchema:
|
||||
return t.Attributes
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// columnRefFields flattens an identifier, path expression, or dotted
|
||||
// identifier chain into ColumnRef fields.
|
||||
func columnRefFields(node zjast.Node) ([]ast.Node, bool) {
|
||||
switch n := node.(type) {
|
||||
case *zjast.Identifier:
|
||||
return []ast.Node{NewIdentifier(n.Name)}, true
|
||||
case *zjast.PathExpression:
|
||||
var fields []ast.Node
|
||||
for _, part := range pathParts(n) {
|
||||
fields = append(fields, NewIdentifier(part))
|
||||
}
|
||||
return fields, len(fields) > 0
|
||||
case *zjast.DotIdentifier:
|
||||
base, ok := columnRefFields(n.Expr)
|
||||
if !ok || n.Name == nil {
|
||||
return nil, false
|
||||
}
|
||||
return append(base, NewIdentifier(n.Name.Name)), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user