surrealpatch/sql/string.go
2021-12-14 08:13:19 +00:00

960 lines
20 KiB
Go

// Copyright © 2016 SurrealDB Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sql
import (
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"time"
)
const (
_select method = iota
_create
_update
_delete
)
type method int
type methods []method
func (this method) String() string {
switch this {
case _select:
return "select"
case _create:
return "create"
case _update:
return "update"
case _delete:
return "delete"
}
return ""
}
func (this methods) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
// ---------------------------------------------
// Helpers
// ---------------------------------------------
func print(s string, a ...interface{}) string {
for k, v := range a {
switch v.(type) {
default:
case nil:
a[k] = "NULL"
case []interface{}:
out, _ := json.Marshal(v)
a[k] = string(out)
case map[string]interface{}:
out, _ := json.Marshal(v)
a[k] = string(out)
}
}
return fmt.Sprintf(s, a...)
}
func maybe(b bool, v ...interface{}) string {
switch b {
case false:
if len(v) >= 2 {
return fmt.Sprint(v[1])
}
case true:
if len(v) >= 1 {
return fmt.Sprint(v[0])
}
}
return ""
}
func binar(b []byte) string {
return fmt.Sprintf("%q", b)
}
func quote(s string) string {
t := newToken(s)
switch t {
case ILLEGAL:
if toQuote(s) {
return "`" + s + "`"
}
return s
default:
switch {
case t.isKeyword():
return "`" + s + "`"
case t.isOperator():
return "`" + s + "`"
}
return s
}
}
func toQuote(s string) bool {
for _, c := range s {
switch {
case c >= 'a' && c <= 'z':
continue
case c >= 'A' && c <= 'Z':
continue
case c >= '0' && c <= '9':
continue
case c == '[', c == ']':
continue
case c == '.', c == '*':
continue
case c == '_':
continue
default:
return true
}
}
return false
}
// ---------------------------------------------
// Statements
// ---------------------------------------------
func (this OptStatement) String() string {
return print("OPTION %v%v",
quote(this.Name),
maybe(this.What == false, print(" = %v", this.What)),
)
}
func (this UseStatement) String() string {
switch {
case len(this.NS) == 0:
return print("USE DATABASE %v", quote(this.DB))
case len(this.DB) == 0:
return print("USE NAMESPACE %v", quote(this.NS))
default:
return print("USE NAMESPACE %v DATABASE %v", quote(this.NS), quote(this.DB))
}
}
func (this BeginStatement) String() string {
return "BEGIN TRANSACTION"
}
func (this CancelStatement) String() string {
return "CANCEL TRANSACTION"
}
func (this CommitStatement) String() string {
return "COMMIT TRANSACTION"
}
func (this InfoStatement) String() string {
switch this.Kind {
case ALL:
return "INFO FOR ALL"
case NAMESPACE, NS:
return "INFO FOR NAMESPACE"
case DATABASE, DB:
return "INFO FOR DATABASE"
case SCOPE:
return print("INFO FOR SCOPE %s", this.What)
default:
return print("INFO FOR TABLE %s", this.What)
}
}
func (this RunStatement) String() string {
return print("RUN %v",
this.Expr,
)
}
func (this LetStatement) String() string {
return print("LET %v = %v",
this.Name,
this.What,
)
}
func (this LiveStatement) String() string {
return print("LIVE SELECT %v%v FROM %v%v%v",
maybe(this.Diff, "DIFF"),
this.Expr,
this.What,
maybe(this.Cond != nil, print(" WHERE %v", this.Cond)),
this.Fetch,
)
}
func (this KillStatement) String() string {
return print("KILL %v",
this.What,
)
}
func (this ReturnStatement) String() string {
return print("RETURN %v",
this.What,
)
}
func (this IfelseStatement) String() string {
m := make([]string, len(this.Cond))
for k := range this.Cond {
m[k] = print("%v THEN %v", this.Cond[k], this.Then[k])
}
return print("IF %v%v END",
strings.Join(m, " ELSE IF "),
maybe(this.Else != nil, print(" ELSE %v", this.Else)),
)
}
func (this SelectStatement) String() string {
return print("SELECT %v FROM %v%v%v%v%v%v%v%v%v%v",
this.Expr,
this.What,
maybe(this.Cond != nil, print(" WHERE %v", this.Cond)),
maybe(this.Split != nil, print(" SPLIT %v", this.Split)),
this.Group,
this.Order,
maybe(this.Limit != nil, print(" LIMIT %v", this.Limit)),
maybe(this.Start != nil, print(" START %v", this.Start)),
this.Fetch,
maybe(this.Version != nil, print(" VERSION %v", this.Version)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this CreateStatement) String() string {
return print("CREATE %v%v%v%v",
this.What,
maybe(this.Data != nil, print("%v", this.Data)),
maybe(this.Echo != AFTER, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this UpdateStatement) String() string {
return print("UPDATE %v%v%v%v%v",
this.What,
maybe(this.Data != nil, print("%v", this.Data)),
maybe(this.Cond != nil, print(" WHERE %v", this.Cond)),
maybe(this.Echo != AFTER, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this DeleteStatement) String() string {
return print("DELETE %v%v%v%v",
this.What,
maybe(this.Cond != nil, print(" WHERE %v", this.Cond)),
maybe(this.Echo != NONE, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this RelateStatement) String() string {
return print("RELATE %v -> %v -> %v%v%v%v%v",
this.From,
this.Type,
this.With,
maybe(this.Data != nil, print("%v", this.Data)),
maybe(this.Uniq, " UNIQUE"),
maybe(this.Echo != AFTER, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this InsertStatement) String() string {
return print("INSERT %v INTO %v%v%v",
this.Data,
this.Into,
maybe(this.Echo != AFTER, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this UpsertStatement) String() string {
return print("UPSERT %v INTO %v%v%v",
this.Data,
this.Into,
maybe(this.Echo != AFTER, print(" RETURN %v", this.Echo)),
maybe(this.Timeout > 0, print(" TIMEOUT %v", this.Timeout.String())),
)
}
func (this DefineNamespaceStatement) String() string {
return print("DEFINE NAMESPACE %v",
this.Name,
)
}
func (this RemoveNamespaceStatement) String() string {
return print("REMOVE NAMESPACE %v",
this.Name,
)
}
func (this DefineDatabaseStatement) String() string {
return print("DEFINE DATABASE %v",
this.Name,
)
}
func (this RemoveDatabaseStatement) String() string {
return print("REMOVE DATABASE %v",
this.Name,
)
}
func (this DefineLoginStatement) String() string {
return print("DEFINE LOGIN %v ON %v PASSHASH %s",
this.User,
this.Kind,
binar(this.Pass),
)
}
func (this RemoveLoginStatement) String() string {
return print("REMOVE LOGIN %v ON %v",
this.User,
this.Kind,
)
}
func (this DefineTokenStatement) String() string {
return print("DEFINE TOKEN %v ON %v TYPE %v VALUE %s",
this.Name,
maybe(this.Kind == SCOPE, print("%v %v", this.Kind, this.What), print("%v", this.Kind)),
this.Type,
binar(this.Code),
)
}
func (this RemoveTokenStatement) String() string {
return print("REMOVE TOKEN %v ON %v",
this.Name,
maybe(this.Kind == SCOPE, print("%v %v", this.Kind, this.What), print("%v", this.Kind)),
)
}
func (this DefineScopeStatement) String() string {
return print("DEFINE SCOPE %v%v%v%v%v%v%v",
this.Name,
maybe(this.Time > 0, print(" SESSION %v", this.Time)),
maybe(this.Signup != nil, print(" SIGNUP AS %v", this.Signup)),
maybe(this.Signin != nil, print(" SIGNIN AS %v", this.Signin)),
maybe(this.Connect != nil, print(" CONNECT AS %v", this.Connect)),
maybe(this.OnSignup != nil, print(" ON SIGNUP %v", this.OnSignup)),
maybe(this.OnSignin != nil, print(" ON SIGNIN %v", this.OnSignin)),
)
}
func (this RemoveScopeStatement) String() string {
return print("REMOVE SCOPE %v",
this.Name,
)
}
func (this DefineTableStatement) String() (s string) {
w := maybe(this.Cond != nil, print(" WHERE %v", this.Cond))
return print("DEFINE TABLE %v%v%v%v%v%v",
maybe(this.Name != nil, print("%s", this.Name), print("%s", this.What)),
maybe(this.Full, " SCHEMAFULL"),
maybe(this.Vers, " VERSIONED"),
maybe(this.Drop, " DROP"),
maybe(this.Lock, print(" AS SELECT %v FROM %v%v%v", this.Expr, this.From, w, this.Group)),
maybe(this.Perms != nil, this.Perms),
)
}
func (this RemoveTableStatement) String() string {
return print("REMOVE TABLE %v",
this.What,
)
}
func (this DefineEventStatement) String() string {
return print("DEFINE EVENT %v ON %v WHEN %v THEN %v",
this.Name,
this.What,
this.When,
this.Then,
)
}
func (this RemoveEventStatement) String() string {
return print("REMOVE EVENT %v ON %v",
this.Name,
this.What,
)
}
func (this DefineFieldStatement) String() string {
return print("DEFINE FIELD %v ON %v%v%v%v%v%v%v",
this.Name,
this.What,
maybe(this.Type != "", print(" TYPE %v", this.Type)),
maybe(this.Kind != "", print(" (%v)", quote(this.Kind))),
maybe(this.Value != nil, print(" VALUE %v", this.Value)),
maybe(this.Assert != nil, print(" ASSERT %v", this.Assert)),
maybe(this.Priority != 0, print(" PRIORITY %v", this.Priority)),
maybe(this.Perms != nil, this.Perms),
)
}
func (this RemoveFieldStatement) String() string {
return print("REMOVE FIELD %v ON %v",
this.Name,
this.What,
)
}
func (this DefineIndexStatement) String() string {
return print("DEFINE INDEX %v ON %v COLUMNS %v%v",
this.Name,
this.What,
this.Cols,
maybe(this.Uniq, " UNIQUE"),
)
}
func (this RemoveIndexStatement) String() string {
return print("REMOVE INDEX %v ON %v",
this.Name,
this.What,
)
}
// ---------------------------------------------
// Literals
// ---------------------------------------------
func (this Exprs) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = print("%v", v)
}
return strings.Join(m, ", ")
}
func (this All) String() string {
return "*"
}
func (this Any) String() string {
return "?"
}
func (this Null) String() string {
return "NULL"
}
func (this Void) String() string {
return "VOID"
}
func (this Empty) String() string {
return "EMPTY"
}
// ---------------------------------------------
// Field
// ---------------------------------------------
func (this Fields) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return print("%v",
strings.Join(m, ", "),
)
}
func (this Field) String() string {
return print("%v%v",
this.Expr,
maybe(this.Alias != "", print(" AS %s", quote(this.Alias))),
)
}
// ---------------------------------------------
// Group
// ---------------------------------------------
func (this Groups) String() string {
if len(this) == 0 {
return ""
}
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return print(" GROUP BY %v",
strings.Join(m, ", "),
)
}
func (this Group) String() string {
return print("%v",
this.Expr,
)
}
// ---------------------------------------------
// Order
// ---------------------------------------------
func (this Orders) String() string {
if len(this) == 0 {
return ""
}
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return print(" ORDER BY %v",
strings.Join(m, ", "),
)
}
func (this Order) String() string {
return print("%v %v%v",
this.Expr,
maybe(this.Dir, "ASC", "DESC"),
maybe(!this.Tag.IsRoot(), print(" COLLATE %s", this.Tag.String())),
)
}
// ---------------------------------------------
// Fetch
// ---------------------------------------------
func (this Fetchs) String() string {
if len(this) == 0 {
return ""
}
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return print(" FETCH %v",
strings.Join(m, ", "),
)
}
func (this Fetch) String() string {
return print("%v",
this.Expr,
)
}
// ---------------------------------------------
// Param
// ---------------------------------------------
func (this Params) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Param) String() string {
return print("$%v", this.VA)
}
// ---------------------------------------------
// Ident
// ---------------------------------------------
func (this Idents) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Ident) String() string {
return quote(this.VA)
}
// ---------------------------------------------
// Value
// ---------------------------------------------
func (this Values) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Value) String() string {
return print(`"%v"`, this.VA)
}
// ---------------------------------------------
// Regex
// ---------------------------------------------
func (this Regexs) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Regex) String() string {
return print("/%v/", this.VA)
}
// ---------------------------------------------
// Table
// ---------------------------------------------
func (this Tables) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Table) String() string {
return quote(this.TB)
}
// ---------------------------------------------
// Batch
// ---------------------------------------------
func (this Batchs) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Batch) String() string {
return print("batch(%v, [%v]", this.TB, this.BA)
}
// ---------------------------------------------
// Model
// ---------------------------------------------
func (this Model) String() string {
switch {
case this.INC == 0:
max := strconv.FormatFloat(this.MAX, 'f', -1, 64)
return print("|%s:%s|", quote(this.TB), max)
case this.INC == 1:
min := strconv.FormatFloat(this.MIN, 'f', -1, 64)
max := strconv.FormatFloat(this.MAX, 'f', -1, 64)
return print("|%s:%s..%s|", quote(this.TB), min, max)
default:
inc := strconv.FormatFloat(this.INC, 'f', -1, 64)
min := strconv.FormatFloat(this.MIN, 'f', -1, 64)
max := strconv.FormatFloat(this.MAX, 'f', -1, 64)
return print("|%s:%s,%s..%s|", quote(this.TB), min, inc, max)
}
}
// ---------------------------------------------
// Thing
// ---------------------------------------------
func (this Things) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Thing) String() string {
tb := this.TB
if toQuote(this.TB) {
tb = print("⟨%v⟩", this.TB)
}
id := this.ID
switch v := this.ID.(type) {
case int64:
id = strconv.FormatInt(v, 10)
case float64:
id = strconv.FormatFloat(v, 'f', -1, 64)
case time.Time:
id = print("⟨%v⟩", v.Format(RFCNano))
case string:
if toQuote(v) {
id = print("⟨%v⟩", v)
}
default:
if toQuote(fmt.Sprint(v)) {
id = print("⟨%v⟩", v)
}
}
return print("%v:%v", tb, id)
}
// ---------------------------------------------
// Point
// ---------------------------------------------
func (this Points) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Point) String() string {
return print("geo.point(%v,%v)", this.LA, this.LO)
}
func (this Point) JSON() string {
return fmt.Sprintf(`[%v,%v]`, this.LA, this.LO)
}
// ---------------------------------------------
// Circle
// ---------------------------------------------
func (this Circles) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Circle) String() string {
return print("geo.circle(%v,%v)", this.CE, this.RA)
}
func (this Circle) JSON() string {
return fmt.Sprintf(`{"type":"circle","center":%v,"radius":%v}`, this.CE.JSON(), this.RA)
}
// ---------------------------------------------
// Polygon
// ---------------------------------------------
func (this Polygons) String() string {
m := make([]string, len(this))
for k, v := range this {
m[k] = v.String()
}
return strings.Join(m, ", ")
}
func (this Polygon) String() string {
var m []string
for _, v := range this.PS {
m = append(m, v.String())
}
return print("geo.polygon(%v)",
strings.Join(m, ", "),
)
}
func (this Polygon) JSON() string {
var m []string
for _, p := range this.PS {
m = append(m, p.JSON())
}
return fmt.Sprintf(`{"type":"polygon","points":[%v]}`, strings.Join(m, ","))
}
// ---------------------------------------------
// Expressions
// ---------------------------------------------
func (this SubExpression) String() string {
switch this.Expr.(type) {
case *IfelseStatement:
return print("%v", this.Expr)
default:
return print("(%v)", this.Expr)
}
}
func (this MultExpression) String() string {
m := make([]string, len(this.Expr))
for k := range this.Expr {
m[k] = print("%v", this.Expr[k])
}
return print("(%v)",
strings.Join(m, "; "),
)
}
func (this FuncExpression) String() string {
return print("%v(%v)",
this.Name,
this.Args,
)
}
func (this ItemExpression) String() string {
return print("%v %v %v",
this.LHS,
this.Op,
this.RHS,
)
}
func (this BinaryExpression) String() string {
return print("%v %v %v",
this.LHS,
this.Op,
this.RHS,
)
}
func (this PathExpression) String() string {
var m []string
for _, v := range this.Expr {
m = append(m, print("%v", v))
}
return strings.Join(m, "")
}
func (this PartExpression) String() string {
return print("%v",
this.Part,
)
}
func (this JoinExpression) String() string {
return print("%v",
this.Join,
)
}
func (this SubpExpression) String() string {
return print("(%v%v%v)",
this.What,
maybe(this.Name != nil, print(" AS %v", this.Name)),
maybe(this.Cond != nil, print(" WHERE %v", this.Cond)),
)
}
func (this DataExpression) String() string {
var m []string
for _, v := range this.Data {
m = append(m, v.String())
}
return print(" SET %v",
strings.Join(m, ", "),
)
}
func (this DiffExpression) String() string {
return print(" DIFF %v",
this.Data,
)
}
func (this MergeExpression) String() string {
return print(" MERGE %v",
this.Data,
)
}
func (this ContentExpression) String() string {
return print(" CONTENT %v",
this.Data,
)
}
func (this PermExpression) String() string {
var k, o string
a := []string{}
m := map[string]methods{}
if v, ok := this.Select.(bool); ok {
k = maybe(v, "FULL", "NONE")
m[k] = append(m[k], _select)
} else {
k = print("WHERE %v", this.Select)
m[k] = append(m[k], _select)
}
if v, ok := this.Create.(bool); ok {
k = maybe(v, "FULL", "NONE")
m[k] = append(m[k], _create)
} else {
k = print("WHERE %v", this.Create)
m[k] = append(m[k], _create)
}
if v, ok := this.Update.(bool); ok {
k = maybe(v, "FULL", "NONE")
m[k] = append(m[k], _update)
} else {
k = print("WHERE %v", this.Update)
m[k] = append(m[k], _update)
}
if v, ok := this.Delete.(bool); ok {
k = maybe(v, "FULL", "NONE")
m[k] = append(m[k], _delete)
} else {
k = print("WHERE %v", this.Delete)
m[k] = append(m[k], _delete)
}
if len(m) == 1 {
for k := range m {
return print(" PERMISSIONS %v", k)
}
}
for k := range m {
a = append(a, k)
}
sort.Slice(a, func(i, j int) bool {
return m[a[i]][0] < m[a[j]][0]
})
for _, v := range a {
o += print(" FOR %v %v", m[v], v)
}
return print(" PERMISSIONS%v", o)
}