-
Notifications
You must be signed in to change notification settings - Fork 28
Query endpoint #8
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
Conversation
server/handler/query.go
Outdated
return func(r *http.Request) (*serializer.Response, error) { | ||
var queryRequest queryRequest | ||
body, err := ioutil.ReadAll(r.Body) | ||
defer r.Body.Close() |
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.
https://golang.org/pkg/net/http/#Request
The Server will close the request body. The ServeHTTP Handler does not need to.
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.
Fixed, thanks! I actually took it from https://github.com/src-d/code-annotation/blob/master/server/handler/assignments.go#L82
server/handler/query.go
Outdated
query := fmt.Sprintf("%s LIMIT %d", queryRequest.Query, queryRequest.Limit) | ||
rows, err := db.Query(query) | ||
if err != nil { | ||
return nil, serializer.NewHTTPError(http.StatusBadRequest, err.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.
as TODO. Mysql returns MySQLError
with 2 fields number&message. Would be nice to map them into our json.
https://github.com/go-sql-driver/mysql/blob/master/errors.go#L58
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.
+1
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.
Good idea, done in e6deab2
server/handler/query.go
Outdated
return nil, err | ||
} | ||
|
||
colData := make(map[string]string) |
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.
colData := make(map[string]string, nColumns)
server/handler/query.go
Outdated
|
||
columnNames, _ := rows.Columns() | ||
nColumns := len(columnNames) | ||
columnValsPtr := genericVals(nColumns) |
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 does it work for integers?
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.
ah. I see you changed it in next commit.
server/handler/query.go
Outdated
func genericVals(colTypes []*sql.ColumnType) []interface{} { | ||
columnValsPtr := make([]interface{}, len(colTypes)) | ||
|
||
for i, colType := range colTypes { |
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 we cover all types correctly? For example I don't see double or float.
https://github.com/go-sql-driver/mysql/blob/master/fields.go#L16
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.
You are right, I just added the types I encountered them in gitbase replies, but it's better to make it complete since the beginning. Done in e6deab2
server/handler/query.go
Outdated
colData[columnNames[i]] = sqlVal.String | ||
} | ||
case *[]byte: | ||
// TODO (carlosms) this may not be an array always |
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 else can it be? In engine there is always array.
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 was thinking that gitbase may at some point add something new that could return a JSON object. For instance, a new function 'first_uast_as_json()'
server/handler/query.go
Outdated
return nil, err | ||
} | ||
|
||
nodes := make([]*uast.Node, len(protobufs)) |
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.
dashboard replaces Role ID by Role name. Most probably playground should be compatible with it:
https://github.com/bblfsh/dashboard/blob/master/server/server.go#L217
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.
+1
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've opened an issue so we don't forget about it when we start working on the integration with the uast frontend component:
#9
server/handler/query.go
Outdated
|
||
// TODO (carlosms) this only works if the query does not end in ; | ||
// and does not have a limit. It will also fail for queries like | ||
// DESCRIBE TABLE |
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.
gitbase doesn't support anything besides SELECT, SHOW, DESCRIBE.
Add limit makes sense only to SELECT queries. Ending ;
also very easy to fix:
query = strings.TrimRight(strings.TrimSpace(query), ";")
not perfect but better than nothing
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.
👍 Implemented the todo with your suggestion in e6deab2
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.
Great job!
LGTM
I'd modify some things, but nothing that could not be done later after merging.
(the issue that concerns me more, is the huge handler.Query
method, too large imho; I'd split and extract DB logic to a new package as we did in CAT)
server/handler/query.go
Outdated
// TODO (carlosms) this only works if the query does not end in ; | ||
// and does not have a limit. It will also fail for queries like | ||
// DESCRIBE TABLE | ||
query := fmt.Sprintf("%s LIMIT %d", queryRequest.Query, queryRequest.Limit) |
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'd move this to a service that would take care of the query sent by the user; in the future it could do validations, return readable errors...
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 the functionality is so basic, for now I've moved it to a new function in e6deab2
server/handler/query.go
Outdated
// TODO (carlosms) this may not be an array always | ||
var protobufs [][]byte | ||
if err := json.Unmarshal(*val.(*[]byte), &protobufs); err != nil { | ||
return nil, err |
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 we return a 500 if one UAST cannot be read? I'd return the rows, and warning
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 actually found a problem with this. gitbase can also return a JSON column with an array of strings, for example in commits.parents
With the changes in e6deab2 if the unmarshal fails the data will be returned as a string.
server/handler/query.go
Outdated
return nil, err | ||
} | ||
|
||
return serializer.NewQueryResponse(tableData, columnNames), 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.
won't be useful to send the type too? That way it won't be needed to check it in the frontend too.
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.
👍 good suggestion. Done in e6deab2
server/handler/query.go
Outdated
if err == nil { | ||
colData[columnNames[i]] = nodes | ||
} else { | ||
var strings []string |
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 strings? Maybe []interface{}
is 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.
I did it thinking in the commits.parents column, which is an array of strings. But it actually doesn't even have to be an array, using interface{}
will work for any JSON array or object we receive. Changed in 28fc331.
server/handler/query.go
Outdated
func addLimit(query string, limit int) string { | ||
upper := strings.ToUpper(query) | ||
if strings.Contains(upper, "DESCRIBE") || strings.Contains(upper, "SHOW") { | ||
return query |
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.
please consider to rewrite it like this:
func addLimit(query string, limit int) string {
query = strings.TrimRight(strings.TrimSpace(query), ";")
if strings.HasPrefix(strings.ToUpper(query), "SELECT") {
return fmt.Sprintf("%s LIMIT %d", query, limit)
}
return query
it covers more cases and imo easier to read
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.
Makes sense, changed in 28fc331
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.
Now it looks good!
The only thing I would add tests.
@bzz, a comment for your consideration when adding your review. |
@carlosms great job!
In my experience, it's valuable to have PRs as self-contained as possible - including code, at leqast some simple docs and at least some simple tests (completeness is not the goal at this point).
|
// Otherwise gitbase will be asked for the max_allowed_packet column and the | ||
// query will fail. | ||
// The next release should make this parameter optional for us: | ||
// https://github.com/go-sql-driver/mysql/pull/680 | ||
type appConfig struct { | ||
Env string `envconfig:"ENV" default:"production"` | ||
Host string `envconfig:"HOST" default:"0.0.0.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.
Sorry if I missed that somehow, not very related to the change, but while we are here - did not we have issues with this before, being not a valid IP on some OSes and decided that it safer to have default as localhost or 127.0.0.1 instead?
Remember discussing it with @smacker
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 not 0.0.0.0
the right one?
That's how it was configured in CAT, because that way the container is listening "everywhere"
https://github.com/src-d/code-annotation/pull/153/files
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.
0.0.0.0
is a correct one. We use this variable for the server. And server should listen on all IPs.
server/handler/query.go
Outdated
} | ||
|
||
if err != nil { | ||
return nil, err |
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.
Question on user input validation on API level - what is the expected behaviour of playground on /query
endpoint, in case "limit":"20"
is specified, in payload, instead of "limit":20
?
As an API user, I would expect it to result in
{
"status": 4XX,
"errors": [
{
"status": 4XX,
"title": "Error description"
}
]
}
but I belive current implementation might result in 500 without any payload in response.
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.
Improved in 99af6d0. Now limit is optional, and if the json unmarshall fails the reply will contain:
{
"status": 400,
"errors": [
{
"status": 400,
"title": "Bad Request. Expected body: { \"query\": \"SQL statement\", \"limit\": 1234 }"
}
]
}
@bzz:
I was planning to squash before the merge, but left the small commits so you could follow the PR discussions easier. I've squashed the previous commits and added 2 small new ones. Please take another look and I'll squash again before merging.
Added in 7dc93b3. |
Awesome, thank you very much for taking care @carlosms ! Sorry, I can not test it locally right now due to src-d/gitbase#255 but I wander, what what would be the result on the user input with multiple queries like
? As a client of API, I would expect the limit argument to be added to each of the queries. |
server/handler/query.go
Outdated
} | ||
|
||
// addLimit adds LIMIT to the query, performing basic tests to skip it | ||
// for DESCRIBE TABLE, SHOW TABLES, and avoid '; limit' |
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.
May be it would be enough to say that it's only applied to SELECT
?
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.
Changed in fe5811f
This: {
"status": 400,
"errors": [
{
"status": 400,
"title": "unknown error: syntax error at position 66 near 'select'",
"mysqlCode": 1105
}
]
} And any other thing would be strange in my opinion. |
I'm a bit confused then, how do you propose handling the case of user input with multiple queries? Having input from the user in form of like SELECT ... ;
SELECT ... ; is a requirement gitbase playground should handle. Right now, we discussed and decided, that frontend side of the gitbase does not make any modifications/parsing/validation of the user input and just passes it though to the gitbase. We also discussed that backend side does query re-writing, applying the limit clause, before passing it to gitbase server. Please, help me understand how those requirements are going to be satisfied with current implementation. |
@bzz sorry, I don't remember such requirement in the design document. I also don't understand how it would work. Should it open 2 tabs? We didn't discuss it at all. |
I also don't remember any mention to multiple queries 🤔 |
@carlosms @dpordomingo not sure, what do you guys suggest on how to proceed here, but glad that we stumbled up on this. From my side, I'm totally open for discussing/updating design doc next week on details of "multiple query user input" feature, potentially handing it in subsequent PRs and merging this as is now. Please let me know! |
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.
SGTM, given some answer to the discussion of multiple queries feature above
Is gitbase prepared to accept two queries, or does it return an error? |
sorry, was not able to test before src-d/gitbase#255 but may be @carlosms could clarify?
I think it does that way |
It will result in error. If we need to support multiple queries separated by And I still think this would not be the expected behaviour for most people. One query -> one tab; one request -> one response; anything else would be confusing. |
👍 this is a strong argument for merging this work asap and iterate lated on, based on product feedback (as noted by @dpordomingo it follows the conventions we've agreed up on). |
Signed-off-by: Carlos Martín <[email protected]>
Latest release for mysql driver, 1.3, does not support ColumnType.DatabaseTypeName. See go-sql-driver/mysql#667 Signed-off-by: Carlos Martín <[email protected]>
Signed-off-by: Carlos Martín <[email protected]>
Part of #2. Adds a new endpoint,
POST /query
. It forwards SQL queries to gitbase.A few notes:
I've used the master branch as the constraint of
github.com/go-sql-driver/mysql
because the latest release, 1.3, does not support ColumnType.DatabaseTypeName. See go-sql-driver/mysql#667In the design we talked about adding extra information, such as the elapsed time, to the
meta
field. In this PRmeta
only contains the table headers. I think it's ok to add extra pieces of information later as we find the need for them.I've left a couple of TODO comments with minor things, I think we can take care of that later on.-> Solved with the review suggestions.
The requests and results look like this:
A failure:
For a query with uast, the protobuf response is unmarshalled and the json is returned: