Skip to content
63 changes: 56 additions & 7 deletions go/vt/sqlparser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

package sqlparser

//go:generate goyacc -o sql.go sql.y
//go:generate goyacc -o sql.go sql3.y

import (
"context"
Expand Down Expand Up @@ -149,7 +149,7 @@ func ParseOneWithOptions(ctx context.Context, sql string, options ParserOptions)
}
}

return tree, tokenizer.Position-1, nil
return tree, tokenizer.Position - 1, nil
}

func parseTokenizer(sql string, tokenizer *Tokenizer) (Statement, error) {
Expand Down Expand Up @@ -599,6 +599,9 @@ func (node *Select) SetInto(into *Into) error {
if into == nil {
return nil
}
if into.Variables == nil && into.Dumpfile == "" && into.Outfile == "" {
return nil
}
if node.Into != nil {
return fmt.Errorf("Multiple INTO clauses in one query block")
}
Expand Down Expand Up @@ -865,7 +868,7 @@ type Load struct {
*Lines
IgnoreNum *SQLVal
Columns
SetExprs AssignmentExprs
SetExprs AssignmentExprs
IgnoreOrReplace string
}

Expand Down Expand Up @@ -3554,6 +3557,52 @@ func (node *AutoIncSpec) walkSubtree(visit Visit) error {
return err
}

<<<<<<< Updated upstream
=======
// ColumnTypeSpec defines a change to a column's type, without fully specifying the column definition.
type ColumnTypeSpec struct {
Column ColIdent
Type ColumnType
}

var _ SQLNode = (*ColumnTypeSpec)(nil)

func (node ColumnTypeSpec) Format(buf *TrackedBuffer) {
buf.Myprintf("alter column %v type ")
node.Type.Format(buf)
}

// walkSubtree implements SQLNode.
func (node *ColumnTypeSpec) walkSubtree(visit Visit) error {
return Walk(visit, node.Column)
}

// NotNullSpec defines a SET / DROP on a column for its NOT NULL constraint.
type NotNullSpec struct {
// Action is SET to set a NOT NULL constraint on a column, or DROP to drop
// a NOT NULL constraint on a column.
Action string
Column ColIdent
}

var _ SQLNode = (*NotNullSpec)(nil)

// Format implements SQLNode.
func (node *NotNullSpec) Format(buf *TrackedBuffer) {
switch node.Action {
case SetStr:
buf.Myprintf("alter column %v set not null", node.Column)
case DropStr:
buf.Myprintf("alter column %v drop not null", node.Column)
}
}

// walkSubtree implements SQLNode.
func (node *NotNullSpec) walkSubtree(visit Visit) error {
return Walk(visit, node.Column)
}

>>>>>>> Stashed changes
// DefaultSpec defines a SET / DROP on a column for its default value.
type DefaultSpec struct {
Action string
Expand Down Expand Up @@ -3978,7 +4027,7 @@ type FlushOption struct {

// PurgeBinaryLogs represents a PURGE BINARY LOGS statement.
type PurgeBinaryLogs struct {
To string
To string
Before Expr
}

Expand Down Expand Up @@ -4755,7 +4804,7 @@ func (node EventName) IsEmpty() bool {
// This means two TableName vars can be compared for equality
// and a TableName can also be used as key in a map.
// SchemaQualifier, if specified, represents a schema name, which is an additional level of namespace supported in
// other dialects. Supported here so that this AST can act as a translation layer for those dialects, but is unused in
// other dialects. Supported here so that this AST can act as a translation layer for those dialects, but is unused in
// MySQL.
type TableName struct {
Name, DbQualifier, SchemaQualifier TableIdent
Expand Down Expand Up @@ -7564,8 +7613,8 @@ func (d InjectedExpr) Format(buf *TrackedBuffer) {
// InjectedStatement allows bypassing AST analysis. This is used by projects that rely on Vitess, but may not implement
// MySQL's dialect.
type InjectedStatement struct {
Statement Injectable
Children Exprs
Statement Injectable
Children Exprs
}

var _ Statement = InjectedStatement{}
Expand Down
172 changes: 172 additions & 0 deletions go/vt/sqlparser/gen/cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package main

import (
"fmt"
"log"
"os"
"strings"
)

//go:generate go run ./main.go ../testdata/expr_functions

func main() {
// split file into definitions
// each definition gets a symbol
// parse each definition into a function
args := os.Args[1:]
//outfile, err := os.CreateTemp("", "")
// //if err != nil {
// // log.Fatal(err)
// //}
outfile, err := os.OpenFile("../../sql.gen.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
log.Println("writing to: ", outfile.Name())

for _, inp := range args {
bytes, err := os.ReadFile(inp)
if err != nil {
log.Fatalf("invalid filename: %s; err=%s", inp, err)
}
p := &pgen{lines: strings.Split(string(bytes), "\n")}
out, err := p.gen()
if err != nil {
log.Fatalf("invalid contents: %s; err=%s", inp, err)
}
n, err := outfile.WriteString(out)
if err != nil || n != len(out) {
log.Fatalf("failed to write results: %s; err=%s", inp, err)
}
}
outfile.Sync()
}

type pgen struct {
lines []string
i int
}

func (p *pgen) nextLine() (string, bool) {
if p.i >= len(p.lines) {
return "", false
}
l := p.lines[p.i]
p.i++
l = strings.TrimSpace(l)
if len(l) > 2 && l[:2] == "| " {
l = l[2:]
}
if len(l) > 2 && l[:2] == "//" {
return p.nextLine()
}

if len(l) == 0 {
return p.nextLine()
}
return l, true
}

func (p *pgen) gen() (string, error) {
b := strings.Builder{}
fmt.Fprintf(&b, "package sqlparser\n\n")

topIfElse := " if"
currentFname := ""
for {
def, ok := p.nextLine()
if !ok {
break
}

if def[len(def)-1] == ':' {
fid := strings.Split(def, ":")
if len(fid) < 1 {
return "", fmt.Errorf("function id invalid")
}
if p.i > 1 {
fmt.Fprintf(&b, "\n return nil, false\n}\n")
}
currentFname = fid[0]
fmt.Fprintf(&b, "func (p *parser) %s() (Expr, bool) {\n", camelize(fid[0]))
fmt.Fprintf(&b, " id, tok := p.peek()\n")
topIfElse = " if"
continue
}

open, ok := p.nextLine()
if !ok || open != "{" {
return "", fmt.Errorf("missing openb, line %d, %s->%s", p.i, def, open)
}
ret, ok := p.nextLine()
if !ok {
return "", fmt.Errorf("missing return type")
}
close, ok := p.nextLine()
if !ok || close != "}" {
return "", fmt.Errorf("missing closeb")
}

// progress string for fail message
// differentiate end token vs function
// extend reading tokens into variables
// return
parts := strings.Split(def, " ")
for i, p := range parts {
var cmp string
if p == "openb" {
cmp = "'('"
} else if p == "closeb" {
cmp = "')'"
} else if strings.ToLower(p) == p {
} else {
cmp = p
}

if i == 0 {
if cmp != "" {
fmt.Fprintf(&b, "%s id == %s {\n", topIfElse, cmp)
fmt.Fprintf(&b, " // %s\n", def)
fmt.Fprintf(&b, " var1, _ := p.next()\n")
} else {
fname := camelize(p)
fmt.Fprintf(&b, "%s var1, ok := p.%s(); ok {\n", topIfElse, fname)
fmt.Fprintf(&b, " // %s\n", def)
}
topIfElse = " else if"
continue
}
if cmp != "" {
fmt.Fprintf(&b, " var%d, tok := p.next()\n", i+1)
fmt.Fprintf(&b, " if var%d != %s {\n", i+1, cmp)
fmt.Fprintf(&b, " p.fail(\"expected: '%s: %s <%s>', found: '\" + string(tok) + \"'\")\n", currentFname, strings.Join(parts[:i], " "), cmp)
fmt.Fprintf(&b, " }\n")
} else if strings.ToLower(p) == p {
fname := camelize(p)
fmt.Fprintf(&b, " var%d, ok := p.%s()\n", i+1, fname)
fmt.Fprintf(&b, " if !ok {\n")
fmt.Fprintf(&b, " p.fail(\"expected: '%s: %s <%s>', found: 'string(tok)'\")\n", currentFname, strings.Join(parts[:i], " "), p)
fmt.Fprintf(&b, " }\n")
}
}
ret = strings.Replace(ret, "$$ = ", "", -1)
if ret[0] == '&' {
ret = ret[1:]
}
ret = strings.Replace(ret, "$", "var", -1)
fmt.Fprintf(&b, " return &%s, true\n", ret)
fmt.Fprintf(&b, " }")
}
fmt.Fprintf(&b, "\n return nil, false\n}\n")

return b.String(), nil
}

func camelize(name string) string {
words := strings.Split(name, "_")
key := strings.ToLower(words[0])
for _, word := range words[1:] {
key += strings.Title(word)
}
return key
}
Loading