-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Add biquery query parameter option to support parametrize query #39389
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -114,6 +114,9 @@ func constructSelectStatement(t reflect.Type, tagKey string, table string) strin | |||||||||||||||||||||||||
| type QueryOptions struct { | ||||||||||||||||||||||||||
| // UseStandardSQL enables BigQuery's Standard SQL dialect when executing a query. | ||||||||||||||||||||||||||
| UseStandardSQL bool | ||||||||||||||||||||||||||
| // Parameters are the query parameters for parameterized queries. | ||||||||||||||||||||||||||
| // bigquery.QueryParameter cannot be encoded/decoded by beam coder. | ||||||||||||||||||||||||||
| parameters []bigquery.QueryParameter | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // UseStandardSQL enables BigQuery's Standard SQL dialect when executing a query. | ||||||||||||||||||||||||||
|
|
@@ -124,6 +127,14 @@ func UseStandardSQL() func(qo *QueryOptions) error { | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // WithQueryParameters sets the query parameters for parameterized queries. | ||||||||||||||||||||||||||
| func WithQueryParameters(params ...bigquery.QueryParameter) func(qo *QueryOptions) error { | ||||||||||||||||||||||||||
| return func(qo *QueryOptions) error { | ||||||||||||||||||||||||||
| qo.parameters = params | ||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // Query executes a query. The output must have a schema compatible with the given | ||||||||||||||||||||||||||
| // type, t. It returns a PCollection<t>. | ||||||||||||||||||||||||||
| func Query(s beam.Scope, project, q string, t reflect.Type, options ...func(*QueryOptions) error) beam.PCollection { | ||||||||||||||||||||||||||
|
|
@@ -142,7 +153,16 @@ func query(s beam.Scope, project, query string, t reflect.Type, options ...func( | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| imp := beam.Impulse(s) | ||||||||||||||||||||||||||
| return beam.ParDo(s, &queryFn{Project: project, Query: query, Type: beam.EncodedType{T: t}, Options: queryOptions}, imp, beam.TypeDefinition{Var: beam.XType, T: t}) | ||||||||||||||||||||||||||
| queryParameters, err := encodeQueryParameters(queryOptions.parameters) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| panic(errors.Wrapf(err, "bigqueryio.Query: failed to encode query parameters")) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| return beam.ParDo( | ||||||||||||||||||||||||||
| s, | ||||||||||||||||||||||||||
| &queryFn{Project: project, Query: query, Type: beam.EncodedType{T: t}, QueryParameters: queryParameters, Options: queryOptions}, | ||||||||||||||||||||||||||
| imp, | ||||||||||||||||||||||||||
| beam.TypeDefinition{Var: beam.XType, T: t}, | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| type queryFn struct { | ||||||||||||||||||||||||||
|
|
@@ -152,6 +172,8 @@ type queryFn struct { | |||||||||||||||||||||||||
| Query string `json:"query"` | ||||||||||||||||||||||||||
| // Type is the encoded schema type. | ||||||||||||||||||||||||||
| Type beam.EncodedType `json:"type"` | ||||||||||||||||||||||||||
| // QueryParameters are serialized query parameters for parameterized queries. | ||||||||||||||||||||||||||
| QueryParameters []byte `json:"query_parameters"` | ||||||||||||||||||||||||||
| // Options specifies additional query execution options. | ||||||||||||||||||||||||||
| Options QueryOptions `json:"options"` | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
@@ -167,6 +189,11 @@ func (f *queryFn) ProcessElement(ctx context.Context, _ []byte, emit func(beam.X | |||||||||||||||||||||||||
| if !f.Options.UseStandardSQL { | ||||||||||||||||||||||||||
| q.UseLegacySQL = true | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| parameters, err := decodeQueryParameters(f.QueryParameters) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
| return errors.Wrapf(err, "bigqueryio.queryFn: failed to decode query parameters") | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| q.Parameters = parameters | ||||||||||||||||||||||||||
|
Comment on lines
+192
to
+196
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| it, err := q.Read(ctx) | ||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| // Licensed to the Apache Software Foundation (ASF) under one or more | ||
| // contributor license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright ownership. | ||
| // The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| // (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package bigqueryio | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
|
|
||
| "cloud.google.com/go/bigquery" | ||
| ) | ||
|
|
||
| func encodeQueryParameters(params []bigquery.QueryParameter) ([]byte, error) { | ||
| out, err := json.Marshal(params) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error encoding JSON: %w", err) | ||
| } | ||
|
|
||
| return out, nil | ||
| } | ||
|
|
||
| func decodeQueryParameters(bytes []byte) ([]bigquery.QueryParameter, error) { | ||
| var out []bigquery.QueryParameter | ||
| if err := json.Unmarshal(bytes, &out); err != nil { | ||
| return nil, fmt.Errorf("error decoding JSON: %w", err) | ||
| } | ||
|
|
||
| return out, nil | ||
| } | ||
|
Comment on lines
+18
to
+41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using Consider using import (
"bytes"
"encoding/gob"
"fmt"
"time"
"cloud.google.com/go/bigquery"
"cloud.google.com/go/civil"
)
func init() {
gob.Register(time.Time{})
gob.Register(civil.Date{})
gob.Register(civil.DateTime{})
gob.Register(civil.Time{})
}
func encodeQueryParameters(params []bigquery.QueryParameter) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := enc.Encode(params); err != nil {
return nil, fmt.Errorf("error encoding gob: %w", err)
}
return buf.Bytes(), nil
}
func decodeQueryParameters(b []byte) ([]bigquery.QueryParameter, error) {
var out []bigquery.QueryParameter
dec := gob.NewDecoder(bytes.NewReader(b))
if err := dec.Decode(&out); err != nil {
return nil, fmt.Errorf("error decoding gob: %w", err)
}
return out, nil
} |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| // Licensed to the Apache Software Foundation (ASF) under one or more | ||
| // contributor license agreements. See the NOTICE file distributed with | ||
| // this work for additional information regarding copyright ownership. | ||
| // The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| // (the "License"); you may not use this file except in compliance with | ||
| // the License. You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| package bigqueryio | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "cloud.google.com/go/bigquery" | ||
| "github.com/google/go-cmp/cmp" | ||
| ) | ||
|
|
||
| func Test_encodeDecodeQueryOptions(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| val bigquery.QueryParameter | ||
| }{ | ||
| { | ||
| name: "Encode/decode QueryOptions with parameters", | ||
| val: bigquery.QueryParameter{ | ||
| Name: "key", | ||
| Value: "value", | ||
| }, | ||
| }, | ||
| { | ||
| name: "Encode/decode QueryOptions with nil parameter", | ||
| val: bigquery.QueryParameter{ | ||
| Name: "key", | ||
| Value: nil, | ||
| }, | ||
| }, | ||
| } | ||
|
Comment on lines
+26
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current test cases only cover tests := []struct {
name string
val bigquery.QueryParameter
}{
{
name: "Encode/decode QueryOptions with string parameter",
val: bigquery.QueryParameter{
Name: "key",
Value: "value",
},
},
{
name: "Encode/decode QueryOptions with int parameter",
val: bigquery.QueryParameter{
Name: "key",
Value: 42,
},
},
{
name: "Encode/decode QueryOptions with float parameter",
val: bigquery.QueryParameter{
Name: "key",
Value: 3.14,
},
},
{
name: "Encode/decode QueryOptions with nil parameter",
val: bigquery.QueryParameter{
Name: "key",
Value: nil,
},
},
} |
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| encoded, err := encodeQueryParameters([]bigquery.QueryParameter{tt.val}) | ||
| if err != nil { | ||
| t.Fatalf("encodeQueryParameters() error = %v", err) | ||
| } | ||
|
|
||
| decoded, err := decodeQueryParameters(encoded) | ||
| if err != nil { | ||
| t.Fatalf("decodeQueryParameters() error = %v", err) | ||
| } | ||
|
|
||
| if diff := cmp.Diff(tt.val, decoded[0]); diff != "" { | ||
| t.Errorf("encode/decode mismatch (-want +got):\n%s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
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.
To avoid unnecessary serialization overhead when no query parameters are provided, we should only encode the parameters if the slice is not empty.