-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
evalengine: Implement integer division and modulo #12656
Conversation
Signed-off-by: Dirkjan Bussink <[email protected]>
This adds the NOT and generic logical operations to the compiler and fixes a number of existing bugs in the evalengine. Specifically NOT is currently broken as we don't translate it properly. Some main issues are that we need to ensure lazy evaluation for the logical operations but also needing it for arithmetic as well. All cases where we've been pushing the boolean singleton value need to be fixed as well in the compiler, because we inline update things in arithmetic operations and we'd update the singleton value before. It also needs to split parsing into JSON from using an argument as a partial JSON value. This specifically is needed for CAST() with an input string where it should parse it. Additionally, convering a JSON boolean to numeric needs to create a floating point value, not an integer one. Signed-off-by: Dirkjan Bussink <[email protected]>
57adadf
to
7b83dbb
Compare
@@ -134,6 +134,8 @@ func (ast *astCompiler) cardExpr(expr Expr) error { | |||
return ast.cardUnary(expr.Inner) | |||
case *BitwiseNotExpr: | |||
return ast.cardUnary(expr.Inner) | |||
case *NotExpr: | |||
return ast.cardUnary(expr.Inner) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@systay I've included the fix for NOT
here and also fixed up the types and updated it all with additional tests. It also adds the other logical operations like AND
to the compiler with tests which we were missing as well.
Signed-off-by: Dirkjan Bussink <[email protected]>
@@ -92,14 +100,15 @@ func (c *compiler) compileArithmeticAdd(left, right Expr) (ctype, error) { | |||
if err != nil { | |||
return ctype{}, err | |||
} | |||
skip1 := c.compileNullCheck1(lt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are needed because arithmetic operations short circuit if the left hand side has a nil
and it doesn't evaluable the right hand side then.
|
||
rt, err := c.compileExpr(right) | ||
if err != nil { | ||
return ctype{}, err | ||
} | ||
|
||
swap := false | ||
skip := c.compileNullCheck2(lt, rt) | ||
skip2 := c.compileNullCheck2(lt, rt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need compileNullCheck2
because at this point the left hand side value is already pushed onto the stack and if we'd use compileNullCheck1
just for the right hand side, we'd leave that lingering on the stack risking future stack overflows.
@@ -66,14 +66,34 @@ func (c *compiler) compileToJSON(doct ctype, offset int) (ctype, error) { | |||
return ctype{Type: sqltypes.TypeJSON, Col: collationJSON}, nil | |||
} | |||
|
|||
func (c *compiler) compileArgToJSON(doct ctype, offset int) (ctype, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need separate handling for an argument to say JSON_OBJECT
to a partial JSON object. For example, a JSON string on its own is not a valid JSON object but for JSON_OBJECT
we need to allow partial JSON types so we can instantiate the right one.
On the other hand, usages like CAST('' as json)
evaluate if the string is a full JSON object and don't allow partial things like strings so it's different. So hence a new function here.
@@ -111,9 +111,9 @@ func evalToNumeric(e eval) evalNumeric { | |||
case *evalJSON: | |||
switch e.Type() { | |||
case json.TypeTrue: | |||
return newEvalBool(true) | |||
return &evalFloat{f: 1.0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out that converting a JSON boolean turns it into a 1.0 or 0.0 float
value and not an int64
1 or 0. This was not covered by tests before.
@@ -100,55 +100,100 @@ func (left boolean) not() boolean { | |||
} | |||
} | |||
|
|||
func (left boolean) and(right boolean) boolean { | |||
func opAnd(le, re Expr, env *ExpressionEnv) (boolean, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are all changed so we can also do lazy evaluation if the left hand side already gives a definitive answer. If the left hand side of an AND
is already explicitly false
, we never evaluable the right hand side (which matches MySQL behavior but was never tested before).
@@ -162,21 +207,18 @@ func (n *NotExpr) eval(env *ExpressionEnv) (eval, error) { | |||
|
|||
func (n *NotExpr) typeof(env *ExpressionEnv) (sqltypes.Type, typeFlag) { | |||
_, flags := n.Inner.typeof(env) | |||
return sqltypes.Uint64, flags | |||
return sqltypes.Int64, flags | flagIsBoolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These were just plain wrong before, it's actually an Int64
empirically when testing against MySQL.
@@ -872,5 +897,10 @@ func InStatement(yield Query) { | |||
yield(fmt.Sprintf("%s IN (%s, %s)", inputs[2], inputs[1], inputs[0]), nil) | |||
yield(fmt.Sprintf("%s IN (%s, %s)", inputs[1], inputs[0], inputs[2]), nil) | |||
yield(fmt.Sprintf("%s IN (%s, %s, %s)", inputs[0], inputs[1], inputs[2], inputs[0]), nil) | |||
|
|||
yield(fmt.Sprintf("%s NOT IN (%s, %s)", inputs[0], inputs[1], inputs[2]), nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was no coverage yet of NOT IN
so added that here as well.
"cast('{}' as json)", "cast('[]' as json)", | ||
"cast('null' as json)", "cast('true' as json)", "cast('false' as json)", | ||
"cast('1' as json)", "cast('1.1' as json)", "cast('-1.1' as json)", | ||
"cast('\"foo\"' as json)", "cast('invalid' as json)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These all test various JSON edge cases in casting and operations that drive a bunch of the changes here.
Signed-off-by: Dirkjan Bussink <[email protected]>
As follow up on #12369, this implements additional missing arithmetic support for
DIV
,%
(and theMOD
alias).It also adds support to the compiler for
NOT
,AND
,OR
andXOR
which was missing as well.Related Issue(s)
Implements part of the items listed in #9647 as well.
Checklist