-
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
Rewriter infrastructure revamp #5611
Conversation
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 is a major refactor/rewrite. Can you write a description for the PR that lays out how things are changing and why?
@@ -72,6 +72,11 @@ install: build | |||
parser: | |||
make -C go/vt/sqlparser | |||
|
|||
visitor: |
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.
shouldn't we be calling this whenever there is a change to sql.y (and sql.go)?
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.
not really. only when there is a change to ast.go
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.
will people need to know to call this manually? is there a test that will catch it if someone forgets to do so after changing ast.go?
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.
added pre-commit hook
@@ -0,0 +1,161 @@ | |||
package visitorgen |
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.
missing license header
go/visitorgen/sast.go
Outdated
@@ -0,0 +1,161 @@ | |||
package visitorgen | |||
|
|||
// simplified ast |
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 should be some explanatory docs here.
@@ -0,0 +1,79 @@ | |||
package visitorgen |
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.
missing license header.
e66b930
to
ceaccb7
Compare
a6f81f9
to
4c28604
Compare
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.
I believe this codegen is not as type-safe as I had expected. It can still panic if you pass the wrong type to cursor.Replace
. In this particular case, the codegen should have generated two visitor functions: one for *AliasedExpression, and one for Expr. The cursor.Replace
for *AliasedExpression
would only accept an AliasedExpression
instead of SQLNode
.
And the pattern I described for handling the substitutions in the rewriter would allow you to compose the two.
However, this is good enough for now. Maybe we can improve this later. Let's move forward with this. I can merge once the minor nits are taken care of.
PS: Note that I am nitpicking. This is otherwise an awesome piece of work!
@@ -87,6 +88,11 @@ install: build | |||
parser: | |||
make -C go/vt/sqlparser | |||
|
|||
visitor: | |||
go build -o visitorgen go/visitorgen/main/main.go | |||
./visitorgen -input=go/vt/sqlparser/ast.go -output=$(REWRITER) |
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.
It's better to just go run go/visitorgen/main/main.go -input go/vt/sqlparser/ast.go -output=go/vt/sqlparser/rewriter.go
. Then you don't have to worry about cleanup of the binary. I just tried, and it works as intended. For some reason, I had to remove the =
after input
.
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.
I can't get this to run on my Mac. All I get is:
> go run -o visitorgen go/visitorgen/main/main.go -input go/vt/sqlparser/ast.go -output=go/vt/sqlparser/rewriter.go
flag provided but not defined: -o
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.
> go run -o visitorgen go/visitorgen/main/main.go -input=go/vt/sqlparser/ast.go -output=go/vt/sqlparser/rewriter.go
flag provided but not defined: -o
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.
> go run -o visitorgen go/visitorgen/main/main.go -input=go/vt/sqlparser/ast.go -output go/vt/sqlparser/rewriter.go
flag provided but not defined: -o
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.
> go run -o visitorgen go/visitorgen/main/main.go -input go/vt/sqlparser/ast.go -output go/vt/sqlparser/rewriter.go
flag provided but not defined: -o
usage: go run [build flags] [-exec xprog] package [arguments...]
Run 'go help run' for details.
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.
I agree with Sugu that go run
would be much nicer here. In the error output you saw, it looks like you had go run -o vistorgen
and an error about the -o
. If you get rid of those arguments it seems like go run
should work
go/vt/sqlparser/ast_funcs.go
Outdated
return sqltypes.Int24 | ||
case keywordStrings[INT]: | ||
fallthrough | ||
case keywordStrings[INTEGER]: |
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.
better to use case keywordStrings[INT], keywordStrings[INTEGER]:
go/vt/sqlparser/ast_funcs.go
Outdated
func ReplaceExpr(root, from, to Expr) Expr { | ||
expr, success := Rewrite(root, replaceExpr(from, to), nil).(Expr) | ||
if !success { | ||
panic("expression rewriting ended up with a non-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.
Better not to panic. This impossible in the new framework, right? Then it's better to not checkat all.
if right, ok = node.Right.(*SQLVal); !ok { | ||
return false | ||
} | ||
if node.Operator == NotEqualStr && left.Type == right.Type { |
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.
use bytes.Equal
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.
Not sure what you mean, and this is not something I changed: https://github.com/vitessio/vitess/blob/master/go/vt/sqlparser/ast.go#L2431
type expressionRewriter struct { | ||
lastInsertID, database bool | ||
err error | ||
aliases []*AliasedExpr |
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.
I now see why you were trying to add aliases for every expression :).
If you were already willing to pay the price of generating an alias for every select expression, there is a simpler way: As soon as you encounter an unaliased *AliasedExpression, generate an alias for it locally. Then create a new RewriteASTResult
and invole a Rewrite
on it. If it returns saying that something has changed, then you substitute the new alias after it returns. And always return false
.
This avoids the need to maintain a separate aliases
list, and also there is no need for a comingUp
function.
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.
Nice idea. Much cleaner.
go/vt/sqlparser/rewriter.go
Outdated
cursor Cursor | ||
} | ||
|
||
func replaceAliasedExprAs(new SQLNode, parent SQLNode) { |
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.
Could you use something other than new
? It could confuse people that use it as keyword.
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
Signed-off-by: Andres Taylor <[email protected]>
I'm wondering if parameterizing the parser could avoid a whole bunch of code and complexity in supporting Suppose we could change this part of sql.y:
To something more in the spirit of this:
If a parameterized parser could be set up, is there any functionality in the rewriter today that a parameterized parser wouldn't be able to handle? |
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.
I have the larger question of whether the rewriter can be avoided entirely, but since the code is committed now I'll send some of the pending comments I had for the small fraction of the changes I've read so far
|
||
const usage = `Usage of visitorgen: | ||
|
||
go run go/visitorgen/main/main.go -input=/path/to/ast.go -output=/path/to/rewriter.go |
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.
"go run ./go/visitorgen/main" is a little shorter and avoids a "missing dot in first path element" error
@@ -0,0 +1,130 @@ | |||
/* |
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 package at go/ast/visitorgen
so it's more clear that it's related to ast
?
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.
Could do. I was thinking I wanted to use visitorgen
for our Primitive plans as well. We don't rewrite them today, but we do visit them using the Find
method, and I foresee rewriting of plans in our future as well.
Never mind - it will be easy to move it somewhere else when we do decide to start using it for more than the ast. For now, I'll move it into the sqlparser
package
if *inputFile == "" || *outputFile == "" { | ||
fmt.Println("> " + *inputFile) | ||
fmt.Println("> " + *outputFile) | ||
panic("need input and output file") |
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.
panic
ing with a printed stack trace feels a bit aggressive. Maybe just log error an exit? Same comment elsewhere
exit.Return
from vitess.io/vitess/go/exit
seems to be popular for most of the vitess commands, although it's not super clear to me what value it provides over directly calling os.Exit()
replacementMethods := visitorgen.EmitReplacementMethods(vd) | ||
typeSwitch := visitorgen.EmitTypeSwitches(vd) | ||
|
||
fw := newFileWriter(*outputFile) |
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.
Seems like the fileWriter
struct could be avoided here. One way:
var b bytes.Buffer
fmt.Fprintln(b, fileHeader)
fmt.Fprintln(b, ...)
err := ioutil.WriteFile(*outputFile, b.Bytes(), 0644)
visitor: | ||
go build -o visitorgen go/visitorgen/main/main.go | ||
./visitorgen -input=go/vt/sqlparser/ast.go -output=$(REWRITER) | ||
rm ./visitorgen |
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.
How do you feel about having the Makefile invoke go generate
and the //go:generate
annotation could have the go run
command?
Being able to use pure go build tools seems nice. Anyone who wants to use make
to invoke go
would have the option, although I don't think it would do much for them.
switch node := cursor.Node().(type) { | ||
case *AliasedExpr: | ||
if node.As.IsEmpty() { | ||
buf := NewTrackedBuffer(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.
Seems like this does a serialize / memory allocations / copying even when nothing needs to change
@@ -87,6 +88,11 @@ install: build | |||
parser: | |||
make -C go/vt/sqlparser | |||
|
|||
visitor: | |||
go build -o visitorgen go/visitorgen/main/main.go | |||
./visitorgen -input=go/vt/sqlparser/ast.go -output=$(REWRITER) |
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.
I agree with Sugu that go run
would be much nicer here. In the error output you saw, it looks like you had go run -o vistorgen
and an error about the -o
. If you get rid of those arguments it seems like go run
should work
Thanks for the feedback, @dweitzman. Addressed most of it with #5770 |
...
You are probably right, at least for this case. I'm thinking ahead, being guided by experience and gut feeling and not much hard data. Maybe I'm overestimating my pretty singular experience and not seeing aspects that are new in this situation. I'll try to be clear about the assumptions that lead to thinking this would be a good idea:
|
Before this change, we have two different AST visitors - one for rewriting expressions, and one for visiting SQLNodes. Both these needed to be manually updated whenever the AST changed.
This change introduces a new visitor pattern, that can do what the older visitors could, but that is generated from the AST instead of being hand coded.
To enable this, I had to restructure the AST a little. Now, the AST structs and interface implementations live in the
ast.go
file, which is what the visitor generator reads to produce the visitor. The rest of the AST functionality, all methods belonging to structs and not implementing any interfaces, now live inast_funcs.go
.I'm now using this new visitor to simplify the AST rewriting we do today: auto-parameterization (replacing values with bindvars),
last_insert_id()
anddatabase()
should all happen close to each other and not be done while planning.