Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Implement CONVERT expression and cast expressions in comparisons. #103

Merged
merged 9 commits into from
Mar 16, 2018

Conversation

mcarmonaa
Copy link
Contributor

@mcarmonaa mcarmonaa commented Mar 14, 2018

Closes #92

@@ -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) {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Literals 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)

}

// 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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

IsComparison() bool
Left() sql.Expression
Right() sql.Expression
SetLeft(sql.Expression)
Copy link
Contributor

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


// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnaryExpression here is redundant


// 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)
Copy link
Contributor

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,
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep

@@ -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) {
Copy link
Contributor

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.

})
}

func isNumber(t sql.Type) bool {
Copy link
Contributor

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

expected: plan.NewProject(
[]sql.Expression{
expression.NewGreaterThanOrEqual(
expression.NewConvert(
Copy link
Contributor

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

Copy link
Contributor Author

@mcarmonaa mcarmonaa Mar 15, 2018

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.

SetRight(sql.Expression)
}

type comparison struct {
Copy link
Contributor

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.

Copy link
Contributor Author

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

@mcarmonaa
Copy link
Contributor Author

@erizocosmico @ajnavarro I removed the rule and moved the casting types logic to the comparison expressions

@@ -3,73 +3,165 @@ package expression
import (
"regexp"

errors "gopkg.in/src-d/go-errors.v0"
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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

Right() sql.Expression
}

var ErrNilOperand = errors.NewKind("nil operand found in comparison")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment missing for this

}

// ConvetValue cast the value of val to castTo throw the Convert expression rules.
func ConvertValue(val interface{}, castTo string) (interface{}, error) {
Copy link
Contributor

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"
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor

@erizocosmico erizocosmico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, good job!

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>
@mcarmonaa mcarmonaa merged commit 87960db into src-d:master Mar 16, 2018
@mcarmonaa mcarmonaa deleted the fix/cast-literals branch March 16, 2018 11:22
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants