-
Notifications
You must be signed in to change notification settings - Fork 110
Implement CONVERT expression and cast expressions in comparisons. #103
Conversation
sql/analyzer/rules.go
Outdated
@@ -411,3 +412,71 @@ func pushdown(a *Analyzer, n sql.Node) (sql.Node, error) { | |||
return node, nil | |||
}) | |||
} | |||
|
|||
func castCompares(a *Analyzer, n sql.Node) (sql.Node, 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.
What do you think about putting this logic inside the Comparison
Compare
method instead?
Compare
has access to the types and it could handle them itself using the Convert
method of the types. The less rules we need, the better.
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.
The rule that I'm missing here is one to cast literals in comparisons when the types on left and right are not equals.
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.
Literal
s are like any other Expression
and they have certain type. The rule can't be cast always the literal to the other type, at least mysql doesn't work in this way.
For example, when the types at both side of the comparison are different, mysql cast to numbers if some of the type of them is a number, independently if there is a literal or not.
As you can see in this example, mysql cast the text values from the num
column to numbers, (the casting of the text values evals to 0 because they don't represent numbers):
MySQL [testing]> select * from tabletest;
+----+------+------+
| id | name | num |
+----+------+------+
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
| 4 | d | 4 |
+----+------+------+
4 rows in set (0.00 sec)
MySQL [testing]> select * from tabletest where name > 0;
Empty set (0.00 sec)
MySQL [testing]> select * from tabletest where name < 0;
Empty set (0.01 sec)
MySQL [testing]> select * from tabletest where name = 0;
+----+------+------+
| id | name | num |
+----+------+------+
| 1 | a | 1 |
| 2 | b | 2 |
| 3 | c | 3 |
| 4 | d | 4 |
+----+------+------+
4 rows in set (0.00 sec)
sql/expression/comparison.go
Outdated
} | ||
|
||
// Eval implements the Expression interface. | ||
func (gte GreaterThanOrEqual) Eval( | ||
session sql.Session, | ||
row sql.Row, | ||
) (interface{}, error) { | ||
a, err := gte.Left.Eval(session, row) | ||
a, err := gte.BinaryExpression.Left.Eval(session, row) |
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.
why add the BinaryExpression
here?
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.
Because of the Left()
method in the interface, it shadows the Left
field of the embedded BinaryExpression
.
sql/expression/comparison.go
Outdated
IsComparison() bool | ||
Left() sql.Expression | ||
Right() sql.Expression | ||
SetLeft(sql.Expression) |
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.
nodes should be immutable. If it needs to change, a new node should be created
sql/expression/convert.go
Outdated
|
||
// TransformUp implements the Expression interface. | ||
func (c *Convert) TransformUp(f func(sql.Expression) (sql.Expression, error)) (sql.Expression, error) { | ||
child, err := c.UnaryExpression.Child.TransformUp(f) |
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.
UnaryExpression
here is redundant
sql/expression/convert.go
Outdated
|
||
// Eval implements the Expression interface. | ||
func (c *Convert) Eval(session sql.Session, row sql.Row) (interface{}, error) { | ||
val, err := c.UnaryExpression.Child.Eval(session, row) |
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.
UnaryExpression
here is redundant
}, | ||
) | ||
|
||
testQuery(t, e, |
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.
This test and the above one are the same
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.
yep
sql/analyzer/rules.go
Outdated
@@ -411,3 +412,71 @@ func pushdown(a *Analyzer, n sql.Node) (sql.Node, error) { | |||
return node, nil | |||
}) | |||
} | |||
|
|||
func castCompares(a *Analyzer, n sql.Node) (sql.Node, 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.
The rule that I'm missing here is one to cast literals in comparisons when the types on left and right are not equals.
sql/analyzer/rules.go
Outdated
}) | ||
} | ||
|
||
func isNumber(t sql.Type) bool { |
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.
Maybe this logic to check types should be in types.go file
sql/analyzer/rules_test.go
Outdated
expected: plan.NewProject( | ||
[]sql.Expression{ | ||
expression.NewGreaterThanOrEqual( | ||
expression.NewConvert( |
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.
The auto casting should be always only on the literal side I think
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.
As explained up there, the cast can't be just for literals and should be for both expressions because of the "types" the convert expression accepts.
Anyway, I'm moving all the logic to Comparison expression and removing the rule as @erizocosmico suggested.
sql/expression/comparison.go
Outdated
SetRight(sql.Expression) | ||
} | ||
|
||
type comparison struct { |
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.
this should be public, to be able to check if the expression is a Comparison on rules.
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.
That check is done by the interface Comparer
, because comparison
is just used as an embedded type in the real comparison expressions.
If you try a type assertion on a interface by the embedded type of the type, it doesn't work.
See: https://play.golang.org/p/NJHOB7G27mc
094658c
to
2508134
Compare
@erizocosmico @ajnavarro I removed the rule and moved the casting types logic to the comparison expressions |
sql/expression/comparison.go
Outdated
@@ -3,73 +3,165 @@ package expression | |||
import ( | |||
"regexp" | |||
|
|||
errors "gopkg.in/src-d/go-errors.v0" |
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 should be using errors v1
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.
Should I change it across all the repo?
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.
Change only the added ones, I opened an issue for the rest: #115
sql/expression/comparison.go
Outdated
Right() sql.Expression | ||
} | ||
|
||
var ErrNilOperand = errors.NewKind("nil operand found in comparison") |
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.
comment missing for this
sql/expression/convert.go
Outdated
} | ||
|
||
// ConvetValue cast the value of val to castTo throw the Convert expression rules. | ||
func ConvertValue(val interface{}, castTo string) (interface{}, 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.
is this used outside this package? If it's not, I'd keep it lowercase
|
||
const ( | ||
// ConvertTo represents those conversion types Convert accepts. | ||
ConvertToBinary = "binary" |
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.
are these constants used outside this package? If not, I'd keep them lowercase. And if so, I would make them integers, the take less space and the string is not really needed. They're only needed as an enum
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.
Those are the string go-vitess' parser returns, I can change it to enums, but I'd have to create two maps kept in memory to translate strings->int and int->strings and make the logic a little bit complex.
They are only used inside the expression package, but I think it's ok to have them public if at some point we need to create Convert
expressions in a rule or something like that.
Do you think it will make a really improve the enum thing?
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.
Then I think the enum is not worth it, too much hassle for so little benefit. LGTM as is, then.
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.
LGTM, good job!
50219df
to
e5e4cac
Compare
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
…rand Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
Signed-off-by: Manuel Carmona <manu.carmona90@gmail.com>
e5e4cac
to
2f1992d
Compare
Closes #92