Skip to content

Commit

Permalink
bigquery: introduce UseLegacySQL options
Browse files Browse the repository at this point in the history
This is the first step in switching to standard SQL as the default.
We'll announce this change and ask people who want to use legacy
SQL to explictly set these options. Then we can switch the default
without breakage.

Change-Id: I59c8ae4ace63fa0df59c86f23746ad5aa197857e
Reviewed-on: https://code-review.googlesource.com/16630
Reviewed-by: kokoro <[email protected]>
Reviewed-by: Sarah Adams <[email protected]>
Reviewed-by: Sai Cheemalapati <[email protected]>
  • Loading branch information
jba committed Sep 8, 2017
1 parent 74d277f commit 1816f0b
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 1 deletion.
12 changes: 12 additions & 0 deletions bigquery/create_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,15 @@ func TestCreateTableOptions(t *testing.T) {
}
}
}

func TestCreateTableOptionsLegacySQL(t *testing.T) {
c := &Client{
projectID: "p",
service: &bigqueryService{},
}
ds := c.Dataset("d")
table := ds.Table("t")
if err := table.Create(context.Background(), UseStandardSQL(), UseLegacySQL()); err == nil {
t.Fatal("no error using both standard and legacy SQL options")
}
}
71 changes: 71 additions & 0 deletions bigquery/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,77 @@ func TestIntegration_ReadNullIntoStruct(t *testing.T) {
}
}

const (
stdName = "`bigquery-public-data.samples.shakespeare`"
legacyName = "[bigquery-public-data:samples.shakespeare]"
)

// These tests exploit the fact that the two SQL versions have different syntaxes for
// fully-qualified table names.
var useLegacySqlTests = []struct {
t string // name of table
std, legacy bool // use standard/legacy SQL
err bool // do we expect an error?
}{
{t: legacyName, std: false, legacy: true, err: false},
{t: legacyName, std: true, legacy: false, err: true},
{t: legacyName, std: false, legacy: false, err: false}, // legacy SQL is default
{t: legacyName, std: true, legacy: true, err: true},
{t: stdName, std: false, legacy: true, err: true},
{t: stdName, std: true, legacy: false, err: false},
{t: stdName, std: false, legacy: false, err: true}, // legacy SQL is default
{t: stdName, std: true, legacy: true, err: true},
}

func TestIntegration_QueryUseLegacySQL(t *testing.T) {
// Test the UseLegacySQL and UseStandardSQL options for queries.
if client == nil {
t.Skip("Integration tests skipped")
}
ctx := context.Background()
for _, test := range useLegacySqlTests {
q := client.Query(fmt.Sprintf("select word from %s limit 1", test.t))
q.UseStandardSQL = test.std
q.UseLegacySQL = test.legacy
_, err := q.Read(ctx)
gotErr := err != nil
if gotErr && !test.err {
t.Errorf("%+v:\nunexpected error: %v", test, err)
} else if !gotErr && test.err {
t.Errorf("%+v:\nsucceeded, but want error", test)
}
}
}

func TestIntegration_TableUseLegacySQL(t *testing.T) {
// Test the UseLegacySQL and UseStandardSQL options for CreateTable.
if client == nil {
t.Skip("Integration tests skipped")
}
ctx := context.Background()
table := newTable(t, schema)
defer table.Delete(ctx)
for i, test := range useLegacySqlTests {
view := dataset.Table(fmt.Sprintf("t_view_%d", i))
vq := ViewQuery(fmt.Sprintf("SELECT word from %s", test.t))
opts := []CreateTableOption{vq}
if test.std {
opts = append(opts, UseStandardSQL())
}
if test.legacy {
opts = append(opts, UseLegacySQL())
}
err := view.Create(ctx, opts...)
gotErr := err != nil
if gotErr && !test.err {
t.Errorf("%+v:\nunexpected error: %v", test, err)
} else if !gotErr && test.err {
t.Errorf("%+v:\nsucceeded, but want error", test)
}
view.Delete(ctx)
}
}

// Creates a new, temporary table with a unique name and the given schema.
func newTable(t *testing.T, s Schema) *Table {
name := fmt.Sprintf("t%d", time.Now().UnixNano())
Expand Down
15 changes: 14 additions & 1 deletion bigquery/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package bigquery

import (
"errors"

"golang.org/x/net/context"
bq "google.golang.org/api/bigquery/v2"
)
Expand Down Expand Up @@ -89,6 +91,9 @@ type QueryConfig struct {
// The default is false (using legacy SQL).
UseStandardSQL bool

// UseLegacySQL causes the query to use legacy SQL.
UseLegacySQL bool

// Parameters is a list of query parameters. The presence of parameters
// implies the use of standard SQL.
// If the query uses positional syntax ("?"), then no parameter may have a name.
Expand Down Expand Up @@ -177,11 +182,19 @@ func (q *QueryConfig) populateJobQueryConfig(conf *bq.JobConfigurationQuery) err
if q.MaxBytesBilled >= 1 {
conf.MaximumBytesBilled = q.MaxBytesBilled
}
if q.UseStandardSQL && q.UseLegacySQL {
return errors.New("bigquery: cannot provide both UseStandardSQL and UseLegacySQL")
}
if len(q.Parameters) > 0 && q.UseLegacySQL {
return errors.New("bigquery: cannot provide both Parameters (implying standard SQL) and UseLegacySQL")
}
if q.UseStandardSQL || len(q.Parameters) > 0 {
conf.UseLegacySql = false
conf.ForceSendFields = append(conf.ForceSendFields, "UseLegacySql")
}

if q.UseLegacySQL {
conf.UseLegacySql = true
}
if q.Dst != nil && !q.Dst.implicitTable() {
conf.DestinationTable = q.Dst.tableRefProto()
}
Expand Down
24 changes: 24 additions & 0 deletions bigquery/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package bigquery

import (
"fmt"
"testing"

"cloud.google.com/go/internal/testutil"
Expand Down Expand Up @@ -304,3 +305,26 @@ func TestConfiguringQuery(t *testing.T) {
t.Errorf("querying: got:\n%v\nwant:\n%v", s.Job, want)
}
}

func TestQueryLegacySQL(t *testing.T) {
c := &Client{
projectID: "project-id",
service: &testService{},
}
q := c.Query("q")
q.UseStandardSQL = true
q.UseLegacySQL = true
_, err := q.Run(context.Background())
if err == nil {
t.Error("UseStandardSQL and UseLegacySQL: got nil, want error")
}
q = c.Query("q")
q.Parameters = []QueryParameter{{Name: "p", Value: 3}}
q.UseLegacySQL = true
_, err = q.Run(context.Background())
if err == nil {
t.Error("Parameters and UseLegacySQL: got nil, want error")
} else {
fmt.Println(err)
}
}
8 changes: 8 additions & 0 deletions bigquery/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package bigquery

import (
"errors"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -501,6 +502,7 @@ type createTableConf struct {
viewQuery string
schema *bq.TableSchema
useStandardSQL bool
useLegacySQL bool
timePartitioning *TimePartitioning
}

Expand All @@ -510,6 +512,9 @@ type createTableConf struct {
// Note: expiration can only be set during table creation.
// Note: after table creation, a view can be modified only if its table was initially created with a view.
func (s *bigqueryService) createTable(ctx context.Context, conf *createTableConf) error {
if conf.useStandardSQL && conf.useLegacySQL {
return errors.New("bigquery: cannot provide both UseStandardSQL and UseLegacySQL")
}
table := &bq.Table{
// TODO(jba): retry? Is this always idempotent?
TableReference: &bq.TableReference{
Expand All @@ -530,6 +535,9 @@ func (s *bigqueryService) createTable(ctx context.Context, conf *createTableConf
table.View.UseLegacySql = false
table.View.ForceSendFields = append(table.View.ForceSendFields, "UseLegacySql")
}
if conf.useLegacySQL {
table.View.UseLegacySql = true
}
}
if conf.schema != nil {
table.Schema = conf.schema
Expand Down
10 changes: 10 additions & 0 deletions bigquery/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,16 @@ func (opt useStandardSQL) customizeCreateTable(conf *createTableConf) {
conf.useStandardSQL = true
}

type useLegacySQL struct{}

// UseLegacySQL returns a CreateTableOption to set the table to use legacy SQL.
// This is currently the default.
func UseLegacySQL() CreateTableOption { return useLegacySQL{} }

func (opt useLegacySQL) customizeCreateTable(conf *createTableConf) {
conf.useLegacySQL = true
}

// TimePartitioning is a CreateTableOption that can be used to set time-based
// date partitioning on a table.
// For more information see: https://cloud.google.com/bigquery/docs/creating-partitioned-tables
Expand Down

0 comments on commit 1816f0b

Please sign in to comment.