Skip to content

Commit

Permalink
Handle IS precendence correctly
Browse files Browse the repository at this point in the history
Signed-off-by: Andres Taylor <andres@planetscale.com>
  • Loading branch information
systay committed Mar 27, 2020
1 parent 77cbf27 commit 5b7b806
Show file tree
Hide file tree
Showing 5 changed files with 720 additions and 635 deletions.
2 changes: 2 additions & 0 deletions go/vt/sqlparser/precedence.go
Expand Up @@ -62,6 +62,8 @@ func precedenceFor(in Expr) Precendence {
case EqualStr, NotEqualStr, GreaterThanStr, GreaterEqualStr, LessThanStr, LessEqualStr, LikeStr, InStr, RegexpStr:
return P11
}
case *IsExpr:
return P11
case *BinaryExpr:
switch node.Operator {
case BitOrStr:
Expand Down
11 changes: 4 additions & 7 deletions go/vt/sqlparser/precedence_test.go
Expand Up @@ -18,7 +18,6 @@ package sqlparser

import (
"fmt"
"math/rand"
"testing"
"time"

Expand Down Expand Up @@ -140,6 +139,7 @@ func TestParens(t *testing.T) {
{in: "(a) between (5) and (7)", expected: "a between 5 and 7"},
{in: "(a | b) between (5) and (7)", expected: "a | b between 5 and 7"},
{in: "(a and b) between (5) and (7)", expected: "(a and b) between 5 and 7"},
{in: "(true is true) is null", expected: "(true is true) is null"},
}

for _, tc := range tests {
Expand All @@ -157,11 +157,8 @@ func TestRandom(t *testing.T) {
// The idea is to generate random queries, and pass them through the parser and then the unparser, and one more time. The result of the first unparse should be the same as the second result.
seed := time.Now().UnixNano()
fmt.Println(fmt.Sprintf("seed is %d", seed))
g := generator{
seed: seed,
r: rand.New(rand.NewSource(seed)),
}
endBy := time.Now().Add(5 * time.Second)
g := newGenerator(seed, 5)
endBy := time.Now().Add(1 * time.Second)

for {
if time.Now().After(endBy) {
Expand All @@ -173,7 +170,7 @@ func TestRandom(t *testing.T) {

// When it's parsed and unparsed
parsedInput, err := Parse(inputQ)
require.NoError(t, err)
require.NoError(t, err, inputQ)

// Then the unparsing should be the same as the input query
outputOfParseResult := String(parsedInput)
Expand Down
107 changes: 88 additions & 19 deletions go/vt/sqlparser/random_expr.go
Expand Up @@ -23,10 +23,20 @@ import (

// This file is used to generate random expressions to be used for testing

func newGenerator(seed int64, maxDepth int) *generator {
g := generator{
seed: seed,
r: rand.New(rand.NewSource(seed)),
maxDepth: maxDepth,
}
return &g
}

type generator struct {
seed int64
r *rand.Rand
depth int
seed int64
r *rand.Rand
depth int
maxDepth int
}

// enter should be called whenever we are producing an intermediate node. it should be followed by a `defer g.exit()`
Expand All @@ -41,15 +51,32 @@ func (g *generator) exit() {

// atMaxDepth returns true if we have reached the maximum allowed depth or the expression tree
func (g *generator) atMaxDepth() bool {
return g.depth >= 3
return g.depth >= g.maxDepth
}

/* Creates a random expression. It builds an expression tree using the following constructs:
- true/false
- AND/OR/NOT
- string literalrs, numeric literals (-/+ 1000)
- =, >, <, >=, <=, <=>, !=
- &, |, ^, +, -, *, /, div, %, <<, >>
- IN, BETWEEN and CASE
- IS NULL, IS NOT NULL, IS TRUE, IS NOT TRUE, IS FALSE, IS NOT FALSE
Note: It's important to update this method so that it produces all expressions that need precedence checking.
It's currently missing function calls and string operators
*/
func (g *generator) expression() Expr {
if g.randomBool() {
return g.booleanExpr()
}
options := []exprF{
func() Expr { return g.intExpr() },
func() Expr { return g.stringExpr() },
func() Expr { return g.booleanExpr() },
}

return g.intExpr()
return g.randomOf(options)
}

func (g *generator) booleanExpr() Expr {
Expand All @@ -60,11 +87,14 @@ func (g *generator) booleanExpr() Expr {
options := []exprF{
func() Expr { return g.andExpr() },
func() Expr { return g.orExpr() },
func() Expr { return g.booleanLiteral() },
func() Expr { return g.comparison() },
func() Expr { return g.comparison(g.intExpr) },
func() Expr { return g.comparison(g.stringExpr) },
//func() Expr { return g.comparison(g.booleanExpr) }, // this is not accepted by the parser
func() Expr { return g.inExpr() },
func() Expr { return g.between() },
func() Expr { return g.isExpr() },
func() Expr { return g.notExpr() },
func() Expr { return g.likeExpr() },
}

return g.randomOf(options)
Expand All @@ -78,7 +108,7 @@ func (g *generator) intExpr() Expr {
options := []exprF{
func() Expr { return g.arithmetic() },
func() Expr { return g.intLiteral() },
func() Expr { return g.caseExpr() },
func() Expr { return g.caseExpr(g.intExpr) },
}

return g.randomOf(options)
Expand All @@ -98,34 +128,60 @@ func (g *generator) intLiteral() Expr {
return NewIntVal([]byte(t))
}

var comparisonOps = []string{"=", ">", "<", ">=", "<=", "<=>", "!="}
var words = []string{"ox", "ant", "ape", "asp", "bat", "bee", "boa", "bug", "cat", "cod", "cow", "cub", "doe", "dog", "eel", "eft", "elf", "elk", "emu", "ewe", "fly", "fox", "gar", "gnu", "hen", "hog", "imp", "jay", "kid", "kit", "koi", "lab", "man", "owl", "pig", "pug", "pup", "ram", "rat", "ray", "yak", "bass", "bear", "bird", "boar", "buck", "bull", "calf", "chow", "clam", "colt", "crab", "crow", "dane", "deer", "dodo", "dory", "dove", "drum", "duck", "fawn", "fish", "flea", "foal", "fowl", "frog", "gnat", "goat", "grub", "gull", "hare", "hawk", "ibex", "joey", "kite", "kiwi", "lamb", "lark", "lion", "loon", "lynx", "mako", "mink", "mite", "mole", "moth", "mule", "mutt", "newt", "orca", "oryx", "pika", "pony", "puma", "seal", "shad", "slug", "sole", "stag", "stud", "swan", "tahr", "teal", "tick", "toad", "tuna", "wasp", "wolf", "worm", "wren", "yeti", "adder", "akita", "alien", "aphid", "bison", "boxer", "bream", "bunny", "burro", "camel", "chimp", "civet", "cobra", "coral", "corgi", "crane", "dingo", "drake", "eagle", "egret", "filly", "finch", "gator", "gecko", "ghost", "ghoul", "goose", "guppy", "heron", "hippo", "horse", "hound", "husky", "hyena", "koala", "krill", "leech", "lemur", "liger", "llama", "louse", "macaw", "midge", "molly", "moose", "moray", "mouse", "panda", "perch", "prawn", "quail", "racer", "raven", "rhino", "robin", "satyr", "shark", "sheep", "shrew", "skink", "skunk", "sloth", "snail", "snake", "snipe", "squid", "stork", "swift", "swine", "tapir", "tetra", "tiger", "troll", "trout", "viper", "wahoo", "whale", "zebra", "alpaca", "amoeba", "baboon", "badger", "beagle", "bedbug", "beetle", "bengal", "bobcat", "caiman", "cattle", "cicada", "collie", "condor", "cougar", "coyote", "dassie", "donkey", "dragon", "earwig", "falcon", "feline", "ferret", "gannet", "gibbon", "glider", "goblin", "gopher", "grouse", "guinea", "hermit", "hornet", "iguana", "impala", "insect", "jackal", "jaguar", "jennet", "kitten", "kodiak", "lizard", "locust", "maggot", "magpie", "mammal", "mantis", "marlin", "marmot", "marten", "martin", "mayfly", "minnow", "monkey", "mullet", "muskox", "ocelot", "oriole", "osprey", "oyster", "parrot", "pigeon", "piglet", "poodle", "possum", "python", "quagga", "rabbit", "raptor", "rodent", "roughy", "salmon", "sawfly", "serval", "shiner", "shrimp", "spider", "sponge", "tarpon", "thrush", "tomcat", "toucan", "turkey", "turtle", "urchin", "vervet", "walrus", "weasel", "weevil", "wombat", "anchovy", "anemone", "bluejay", "buffalo", "bulldog", "buzzard", "caribou", "catfish", "chamois", "cheetah", "chicken", "chigger", "cowbird", "crappie", "crawdad", "cricket", "dogfish", "dolphin", "firefly", "garfish", "gazelle", "gelding", "giraffe", "gobbler", "gorilla", "goshawk", "grackle", "griffon", "grizzly", "grouper", "haddock", "hagfish", "halibut", "hamster", "herring", "jackass", "javelin", "jawfish", "jaybird", "katydid", "ladybug", "lamprey", "lemming", "leopard", "lioness", "lobster", "macaque", "mallard", "mammoth", "manatee", "mastiff", "meerkat", "mollusk", "monarch", "mongrel", "monitor", "monster", "mudfish", "muskrat", "mustang", "narwhal", "oarfish", "octopus", "opossum", "ostrich", "panther", "peacock", "pegasus", "pelican", "penguin", "phoenix", "piranha", "polecat", "primate", "quetzal", "raccoon", "rattler", "redbird", "redfish", "reptile", "rooster", "sawfish", "sculpin", "seagull", "skylark", "snapper", "spaniel", "sparrow", "sunbeam", "sunbird", "sunfish", "tadpole", "termite", "terrier", "unicorn", "vulture", "wallaby", "walleye", "warthog", "whippet", "wildcat", "aardvark", "airedale", "albacore", "anteater", "antelope", "arachnid", "barnacle", "basilisk", "blowfish", "bluebird", "bluegill", "bonefish", "bullfrog", "cardinal", "chipmunk", "cockatoo", "crayfish", "dinosaur", "doberman", "duckling", "elephant", "escargot", "flamingo", "flounder", "foxhound", "glowworm", "goldfish", "grubworm", "hedgehog", "honeybee", "hookworm", "humpback", "kangaroo", "killdeer", "kingfish", "labrador", "lacewing", "ladybird", "lionfish", "longhorn", "mackerel", "malamute", "marmoset", "mastodon", "moccasin", "mongoose", "monkfish", "mosquito", "pangolin", "parakeet", "pheasant", "pipefish", "platypus", "polliwog", "porpoise", "reindeer", "ringtail", "sailfish", "scorpion", "seahorse", "seasnail", "sheepdog", "shepherd", "silkworm", "squirrel", "stallion", "starfish", "starling", "stingray", "stinkbug", "sturgeon", "terrapin", "titmouse", "tortoise", "treefrog", "werewolf", "woodcock"}

func (g *generator) stringLiteral() Expr {
return NewStrVal([]byte(g.randomOfS(words)))
}

func (g *generator) stringExpr() Expr {
if g.atMaxDepth() {
return g.stringLiteral()
}

options := []exprF{
func() Expr { return g.stringLiteral() },
func() Expr { return g.caseExpr(g.stringExpr) },
}

return g.randomOf(options)
}

func (g *generator) comparison() Expr {
func (g *generator) likeExpr() Expr {
g.enter()
defer g.exit()
return &ComparisonExpr{
Operator: LikeStr,
Left: g.stringExpr(),
Right: g.stringExpr(),
}
}

v := g.r.Intn(len(comparisonOps))
var comparisonOps = []string{EqualStr, LessThanStr, GreaterThanStr, LessEqualStr, GreaterEqualStr, NotEqualStr, NullSafeEqualStr}

func (g *generator) comparison(f func() Expr) Expr {
g.enter()
defer g.exit()

cmp := &ComparisonExpr{
Operator: comparisonOps[v],
Left: g.intExpr(),
Right: g.intExpr(),
Operator: g.randomOfS(comparisonOps),
Left: f(),
Right: f(),
}

return cmp
}

func (g *generator) caseExpr() Expr {
func (g *generator) caseExpr(valueF func() Expr) Expr {
g.enter()
defer g.exit()

var exp Expr
var elseExpr Expr
if g.randomBool() {
exp = g.intExpr()
exp = valueF()
}
if g.randomBool() {
elseExpr = g.intExpr()
elseExpr = valueF()
}

size := g.r.Intn(5) + 2
Expand Down Expand Up @@ -194,6 +250,12 @@ func (g *generator) orExpr() Expr {
}
}

func (g *generator) notExpr() Expr {
g.enter()
defer g.exit()
return &NotExpr{g.booleanExpr()}
}

func (g *generator) inExpr() Expr {
g.enter()
defer g.exit()
Expand All @@ -220,8 +282,15 @@ func (g *generator) between() Expr {
g.enter()
defer g.exit()

var op string
if g.randomBool() {
op = BetweenStr
} else {
op = NotBetweenStr
}

return &RangeCond{
Operator: BetweenStr,
Operator: op,
Left: g.intExpr(),
From: g.intExpr(),
To: g.intExpr(),
Expand Down

0 comments on commit 5b7b806

Please sign in to comment.