Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
Zhou Yaochen
goActuator
Commits
420350df
Commit
420350df
authored
Nov 12, 2021
by
Zhou Yaochen
Browse files
update ignore
parent
fc1f6363
Changes
723
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
0 additions
and
6908 deletions
+0
-6908
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/error_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/error_test.go
+0
-109
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/examples_test.go
...mod/cloud.google.com/go@v0.34.0/bigquery/examples_test.go
+0
-829
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/external.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/external.go
+0
-400
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/external_test.go
...mod/cloud.google.com/go@v0.34.0/bigquery/external_test.go
+0
-143
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract.go
+0
-110
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract_test.go
+0
-116
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file.go
+0
-137
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file_test.go
+0
-98
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/gcs.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/gcs.go
+0
-75
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/inserter.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/inserter.go
+0
-238
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/inserter_test.go
...mod/cloud.google.com/go@v0.34.0/bigquery/inserter_test.go
+0
-210
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/integration_test.go
.../cloud.google.com/go@v0.34.0/bigquery/integration_test.go
+0
-2141
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/iterator.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/iterator.go
+0
-222
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/iterator_test.go
...mod/cloud.google.com/go@v0.34.0/bigquery/iterator_test.go
+0
-362
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job.go
+0
-821
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job_test.go
+0
-95
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load.go
+0
-146
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load_test.go
+0
-263
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls.go
+0
-320
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls_test.go
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls_test.go
+0
-73
No files found.
Too many changes to show.
To preserve performance only
723 of 723+
files are displayed.
Plain diff
Email patch
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/error_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"errors"
"strings"
"testing"
"cloud.google.com/go/internal/testutil"
bq
"google.golang.org/api/bigquery/v2"
)
func
rowInsertionError
(
msg
string
)
RowInsertionError
{
return
RowInsertionError
{
Errors
:
[]
error
{
errors
.
New
(
msg
)}}
}
func
TestPutMultiErrorString
(
t
*
testing
.
T
)
{
testCases
:=
[]
struct
{
errs
PutMultiError
want
string
}{
{
errs
:
PutMultiError
{},
want
:
"0 row insertions failed"
,
},
{
errs
:
PutMultiError
{
rowInsertionError
(
"a"
)},
want
:
"1 row insertion failed"
,
},
{
errs
:
PutMultiError
{
rowInsertionError
(
"a"
),
rowInsertionError
(
"b"
)},
want
:
"2 row insertions failed"
,
},
}
for
_
,
tc
:=
range
testCases
{
if
tc
.
errs
.
Error
()
!=
tc
.
want
{
t
.
Errorf
(
"PutMultiError string: got:
\n
%v
\n
want:
\n
%v"
,
tc
.
errs
.
Error
(),
tc
.
want
)
}
}
}
func
TestMultiErrorString
(
t
*
testing
.
T
)
{
testCases
:=
[]
struct
{
errs
MultiError
want
string
}{
{
errs
:
MultiError
{},
want
:
"(0 errors)"
,
},
{
errs
:
MultiError
{
errors
.
New
(
"a"
)},
want
:
"a"
,
},
{
errs
:
MultiError
{
errors
.
New
(
"a"
),
errors
.
New
(
"b"
)},
want
:
"a (and 1 other error)"
,
},
{
errs
:
MultiError
{
errors
.
New
(
"a"
),
errors
.
New
(
"b"
),
errors
.
New
(
"c"
)},
want
:
"a (and 2 other errors)"
,
},
}
for
_
,
tc
:=
range
testCases
{
if
tc
.
errs
.
Error
()
!=
tc
.
want
{
t
.
Errorf
(
"PutMultiError string: got:
\n
%v
\n
want:
\n
%v"
,
tc
.
errs
.
Error
(),
tc
.
want
)
}
}
}
func
TestErrorFromErrorProto
(
t
*
testing
.
T
)
{
for
_
,
test
:=
range
[]
struct
{
in
*
bq
.
ErrorProto
want
*
Error
}{
{
nil
,
nil
},
{
in
:
&
bq
.
ErrorProto
{
Location
:
"L"
,
Message
:
"M"
,
Reason
:
"R"
},
want
:
&
Error
{
Location
:
"L"
,
Message
:
"M"
,
Reason
:
"R"
},
},
}
{
if
got
:=
bqToError
(
test
.
in
);
!
testutil
.
Equal
(
got
,
test
.
want
)
{
t
.
Errorf
(
"%v: got %v, want %v"
,
test
.
in
,
got
,
test
.
want
)
}
}
}
func
TestErrorString
(
t
*
testing
.
T
)
{
e
:=
&
Error
{
Location
:
"<L>"
,
Message
:
"<M>"
,
Reason
:
"<R>"
}
got
:=
e
.
Error
()
if
!
strings
.
Contains
(
got
,
"<L>"
)
||
!
strings
.
Contains
(
got
,
"<M>"
)
||
!
strings
.
Contains
(
got
,
"<R>"
)
{
t
.
Errorf
(
`got %q, expected to see "<L>", "<M>" and "<R>"`
,
got
)
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/examples_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2016 Google LLC
//
// Licensed 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
bigquery_test
import
(
"context"
"fmt"
"os"
"time"
"cloud.google.com/go/bigquery"
"google.golang.org/api/iterator"
)
func
ExampleNewClient
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
_
=
client
// TODO: Use client.
}
func
ExampleClient_Dataset
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
fmt
.
Println
(
ds
)
}
func
ExampleClient_DatasetInProject
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
DatasetInProject
(
"their-project-id"
,
"their-dataset"
)
fmt
.
Println
(
ds
)
}
func
ExampleClient_Datasets
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Datasets
(
ctx
)
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
ExampleClient_DatasetsInProject
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
DatasetsInProject
(
ctx
,
"their-project-id"
)
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
getJobID
()
string
{
return
""
}
func
ExampleClient_JobFromID
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
jobID
:=
getJobID
()
// Get a job ID using Job.ID, the console or elsewhere.
job
,
err
:=
client
.
JobFromID
(
ctx
,
jobID
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
job
.
LastStatus
())
// Display the job's status.
}
func
ExampleClient_Jobs
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Jobs
(
ctx
)
it
.
State
=
bigquery
.
Running
// list only running jobs.
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
ExampleNewGCSReference
()
{
gcsRef
:=
bigquery
.
NewGCSReference
(
"gs://my-bucket/my-object"
)
fmt
.
Println
(
gcsRef
)
}
func
ExampleClient_Query
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select name, num from t1"
)
q
.
DefaultProjectID
=
"project-id"
// TODO: set other options on the Query.
// TODO: Call Query.Run or Query.Read.
}
func
ExampleClient_Query_parameters
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select num from t1 where name = @user"
)
q
.
Parameters
=
[]
bigquery
.
QueryParameter
{
{
Name
:
"user"
,
Value
:
"Elizabeth"
},
}
// TODO: set other options on the Query.
// TODO: Call Query.Run or Query.Read.
}
// This example demonstrates how to run a query job on a table
// with a customer-managed encryption key. The same
// applies to load and copy jobs as well.
func
ExampleClient_Query_encryptionKey
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select name, num from t1"
)
// TODO: Replace this key with a key you have created in Cloud KMS.
keyName
:=
"projects/P/locations/L/keyRings/R/cryptoKeys/K"
q
.
DestinationEncryptionConfig
=
&
bigquery
.
EncryptionConfig
{
KMSKeyName
:
keyName
}
// TODO: set other options on the Query.
// TODO: Call Query.Run or Query.Read.
}
func
ExampleQuery_Read
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select name, num from t1"
)
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
ExampleRowIterator_Next
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select name, num from t1"
)
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
for
{
var
row
[]
bigquery
.
Value
err
:=
it
.
Next
(
&
row
)
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
row
)
}
}
func
ExampleRowIterator_Next_struct
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
type
score
struct
{
Name
string
Num
int
}
q
:=
client
.
Query
(
"select name, num from t1"
)
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
for
{
var
s
score
err
:=
it
.
Next
(
&
s
)
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
s
)
}
}
func
ExampleJob_Read
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
q
:=
client
.
Query
(
"select name, num from t1"
)
// Call Query.Run to get a Job, then call Read on the job.
// Note: Query.Read is a shorthand for this.
job
,
err
:=
q
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
,
err
:=
job
.
Read
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
ExampleJob_Wait
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
job
,
err
:=
ds
.
Table
(
"t1"
)
.
CopierFrom
(
ds
.
Table
(
"t2"
))
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
status
.
Err
()
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleJob_Config
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
job
,
err
:=
ds
.
Table
(
"t1"
)
.
CopierFrom
(
ds
.
Table
(
"t2"
))
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
jc
,
err
:=
job
.
Config
()
if
err
!=
nil
{
// TODO: Handle error.
}
copyConfig
:=
jc
.
(
*
bigquery
.
CopyConfig
)
fmt
.
Println
(
copyConfig
.
Dst
,
copyConfig
.
CreateDisposition
)
}
func
ExampleDataset_Create
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
if
err
:=
ds
.
Create
(
ctx
,
&
bigquery
.
DatasetMetadata
{
Location
:
"EU"
});
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleDataset_Delete
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
err
:=
client
.
Dataset
(
"my_dataset"
)
.
Delete
(
ctx
);
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleDataset_Metadata
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
md
,
err
:=
client
.
Dataset
(
"my_dataset"
)
.
Metadata
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
md
)
}
// This example illustrates how to perform a read-modify-write sequence on dataset
// metadata. Passing the metadata's ETag to the Update call ensures that the call
// will fail if the metadata was changed since the read.
func
ExampleDataset_Update_readModifyWrite
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
md
,
err
:=
ds
.
Metadata
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
md2
,
err
:=
ds
.
Update
(
ctx
,
bigquery
.
DatasetMetadataToUpdate
{
Name
:
"new "
+
md
.
Name
},
md
.
ETag
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
md2
)
}
// To perform a blind write, ignoring the existing state (and possibly overwriting
// other updates), pass the empty string as the etag.
func
ExampleDataset_Update_blindWrite
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
md
,
err
:=
client
.
Dataset
(
"my_dataset"
)
.
Update
(
ctx
,
bigquery
.
DatasetMetadataToUpdate
{
Name
:
"blind"
},
""
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
md
)
}
func
ExampleDataset_Table
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
// Table creates a reference to the table. It does not create the actual
// table in BigQuery; to do so, use Table.Create.
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
fmt
.
Println
(
t
)
}
func
ExampleDataset_Tables
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Dataset
(
"my_dataset"
)
.
Tables
(
ctx
)
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
func
ExampleDatasetIterator_Next
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Datasets
(
ctx
)
for
{
ds
,
err
:=
it
.
Next
()
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
ds
)
}
}
func
ExampleInferSchema
()
{
type
Item
struct
{
Name
string
Size
float64
Count
int
}
schema
,
err
:=
bigquery
.
InferSchema
(
Item
{})
if
err
!=
nil
{
fmt
.
Println
(
err
)
// TODO: Handle error.
}
for
_
,
fs
:=
range
schema
{
fmt
.
Println
(
fs
.
Name
,
fs
.
Type
)
}
// Output:
// Name STRING
// Size FLOAT
// Count INTEGER
}
func
ExampleInferSchema_tags
()
{
type
Item
struct
{
Name
string
Size
float64
Count
int
`bigquery:"number"`
Secret
[]
byte
`bigquery:"-"`
Optional
bigquery
.
NullBool
OptBytes
[]
byte
`bigquery:",nullable"`
}
schema
,
err
:=
bigquery
.
InferSchema
(
Item
{})
if
err
!=
nil
{
fmt
.
Println
(
err
)
// TODO: Handle error.
}
for
_
,
fs
:=
range
schema
{
fmt
.
Println
(
fs
.
Name
,
fs
.
Type
,
fs
.
Required
)
}
// Output:
// Name STRING true
// Size FLOAT true
// number INTEGER true
// Optional BOOLEAN false
// OptBytes BYTES false
}
func
ExampleTable_Create
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"new-table"
)
if
err
:=
t
.
Create
(
ctx
,
nil
);
err
!=
nil
{
// TODO: Handle error.
}
}
// Initialize a new table by passing TableMetadata to Table.Create.
func
ExampleTable_Create_initialize
()
{
ctx
:=
context
.
Background
()
// Infer table schema from a Go type.
schema
,
err
:=
bigquery
.
InferSchema
(
Item
{})
if
err
!=
nil
{
// TODO: Handle error.
}
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"new-table"
)
if
err
:=
t
.
Create
(
ctx
,
&
bigquery
.
TableMetadata
{
Name
:
"My New Table"
,
Schema
:
schema
,
ExpirationTime
:
time
.
Now
()
.
Add
(
24
*
time
.
Hour
),
});
err
!=
nil
{
// TODO: Handle error.
}
}
// This example demonstrates how to create a table with
// a customer-managed encryption key.
func
ExampleTable_Create_encryptionKey
()
{
ctx
:=
context
.
Background
()
// Infer table schema from a Go type.
schema
,
err
:=
bigquery
.
InferSchema
(
Item
{})
if
err
!=
nil
{
// TODO: Handle error.
}
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"new-table"
)
// TODO: Replace this key with a key you have created in Cloud KMS.
keyName
:=
"projects/P/locations/L/keyRings/R/cryptoKeys/K"
if
err
:=
t
.
Create
(
ctx
,
&
bigquery
.
TableMetadata
{
Name
:
"My New Table"
,
Schema
:
schema
,
EncryptionConfig
:
&
bigquery
.
EncryptionConfig
{
KMSKeyName
:
keyName
},
});
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_Delete
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
err
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Delete
(
ctx
);
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_Metadata
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
md
,
err
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Metadata
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
md
)
}
func
ExampleTable_Inserter
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
_
=
ins
// TODO: Use ins.
}
func
ExampleTable_Inserter_options
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
ins
.
SkipInvalidRows
=
true
ins
.
IgnoreUnknownValues
=
true
_
=
ins
// TODO: Use ins.
}
func
ExampleTable_CopierFrom
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ds
:=
client
.
Dataset
(
"my_dataset"
)
c
:=
ds
.
Table
(
"combined"
)
.
CopierFrom
(
ds
.
Table
(
"t1"
),
ds
.
Table
(
"t2"
))
c
.
WriteDisposition
=
bigquery
.
WriteTruncate
// TODO: set other options on the Copier.
job
,
err
:=
c
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
status
.
Err
()
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_ExtractorTo
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
gcsRef
:=
bigquery
.
NewGCSReference
(
"gs://my-bucket/my-object"
)
gcsRef
.
FieldDelimiter
=
":"
// TODO: set other options on the GCSReference.
ds
:=
client
.
Dataset
(
"my_dataset"
)
extractor
:=
ds
.
Table
(
"my_table"
)
.
ExtractorTo
(
gcsRef
)
extractor
.
DisableHeader
=
true
// TODO: set other options on the Extractor.
job
,
err
:=
extractor
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
status
.
Err
()
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_LoaderFrom
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
gcsRef
:=
bigquery
.
NewGCSReference
(
"gs://my-bucket/my-object"
)
gcsRef
.
AllowJaggedRows
=
true
gcsRef
.
MaxBadRecords
=
5
gcsRef
.
Schema
=
schema
// TODO: set other options on the GCSReference.
ds
:=
client
.
Dataset
(
"my_dataset"
)
loader
:=
ds
.
Table
(
"my_table"
)
.
LoaderFrom
(
gcsRef
)
loader
.
CreateDisposition
=
bigquery
.
CreateNever
// TODO: set other options on the Loader.
job
,
err
:=
loader
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
status
.
Err
()
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_LoaderFrom_reader
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
f
,
err
:=
os
.
Open
(
"data.csv"
)
if
err
!=
nil
{
// TODO: Handle error.
}
rs
:=
bigquery
.
NewReaderSource
(
f
)
rs
.
AllowJaggedRows
=
true
rs
.
MaxBadRecords
=
5
rs
.
Schema
=
schema
// TODO: set other options on the GCSReference.
ds
:=
client
.
Dataset
(
"my_dataset"
)
loader
:=
ds
.
Table
(
"my_table"
)
.
LoaderFrom
(
rs
)
loader
.
CreateDisposition
=
bigquery
.
CreateNever
// TODO: set other options on the Loader.
job
,
err
:=
loader
.
Run
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
if
status
.
Err
()
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleTable_Read
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Read
(
ctx
)
_
=
it
// TODO: iterate using Next or iterator.Pager.
}
// This example illustrates how to perform a read-modify-write sequence on table
// metadata. Passing the metadata's ETag to the Update call ensures that the call
// will fail if the metadata was changed since the read.
func
ExampleTable_Update_readModifyWrite
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
md
,
err
:=
t
.
Metadata
(
ctx
)
if
err
!=
nil
{
// TODO: Handle error.
}
md2
,
err
:=
t
.
Update
(
ctx
,
bigquery
.
TableMetadataToUpdate
{
Name
:
"new "
+
md
.
Name
},
md
.
ETag
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
md2
)
}
// To perform a blind write, ignoring the existing state (and possibly overwriting
// other updates), pass the empty string as the etag.
func
ExampleTable_Update_blindWrite
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
t
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
tm
,
err
:=
t
.
Update
(
ctx
,
bigquery
.
TableMetadataToUpdate
{
Description
:
"my favorite table"
,
},
""
)
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
tm
)
}
func
ExampleTableIterator_Next
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
it
:=
client
.
Dataset
(
"my_dataset"
)
.
Tables
(
ctx
)
for
{
t
,
err
:=
it
.
Next
()
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
// TODO: Handle error.
}
fmt
.
Println
(
t
)
}
}
type
Item
struct
{
Name
string
Size
float64
Count
int
}
// Save implements the ValueSaver interface.
func
(
i
*
Item
)
Save
()
(
map
[
string
]
bigquery
.
Value
,
string
,
error
)
{
return
map
[
string
]
bigquery
.
Value
{
"Name"
:
i
.
Name
,
"Size"
:
i
.
Size
,
"Count"
:
i
.
Count
,
},
""
,
nil
}
func
ExampleInserter_Put
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
// Item implements the ValueSaver interface.
items
:=
[]
*
Item
{
{
Name
:
"n1"
,
Size
:
32.6
,
Count
:
7
},
{
Name
:
"n2"
,
Size
:
4
,
Count
:
2
},
{
Name
:
"n3"
,
Size
:
101.5
,
Count
:
1
},
}
if
err
:=
ins
.
Put
(
ctx
,
items
);
err
!=
nil
{
// TODO: Handle error.
}
}
var
schema
bigquery
.
Schema
func
ExampleInserter_Put_structSaver
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
type
score
struct
{
Name
string
Num
int
}
// Assume schema holds the table's schema.
savers
:=
[]
*
bigquery
.
StructSaver
{
{
Struct
:
score
{
Name
:
"n1"
,
Num
:
12
},
Schema
:
schema
,
InsertID
:
"id1"
},
{
Struct
:
score
{
Name
:
"n2"
,
Num
:
31
},
Schema
:
schema
,
InsertID
:
"id2"
},
{
Struct
:
score
{
Name
:
"n3"
,
Num
:
7
},
Schema
:
schema
,
InsertID
:
"id3"
},
}
if
err
:=
ins
.
Put
(
ctx
,
savers
);
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleInserter_Put_struct
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
type
score
struct
{
Name
string
Num
int
}
scores
:=
[]
score
{
{
Name
:
"n1"
,
Num
:
12
},
{
Name
:
"n2"
,
Num
:
31
},
{
Name
:
"n3"
,
Num
:
7
},
}
// Schema is inferred from the score type.
if
err
:=
ins
.
Put
(
ctx
,
scores
);
err
!=
nil
{
// TODO: Handle error.
}
}
func
ExampleInserter_Put_valuesSaver
()
{
ctx
:=
context
.
Background
()
client
,
err
:=
bigquery
.
NewClient
(
ctx
,
"project-id"
)
if
err
!=
nil
{
// TODO: Handle error.
}
ins
:=
client
.
Dataset
(
"my_dataset"
)
.
Table
(
"my_table"
)
.
Inserter
()
var
vss
[]
*
bigquery
.
ValuesSaver
for
i
,
name
:=
range
[]
string
{
"n1"
,
"n2"
,
"n3"
}
{
// Assume schema holds the table's schema.
vss
=
append
(
vss
,
&
bigquery
.
ValuesSaver
{
Schema
:
schema
,
InsertID
:
name
,
Row
:
[]
bigquery
.
Value
{
name
,
int64
(
i
)},
})
}
if
err
:=
ins
.
Put
(
ctx
,
vss
);
err
!=
nil
{
// TODO: Handle error.
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/external.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2017 Google LLC
//
// Licensed 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
bigquery
import
(
"encoding/base64"
"unicode/utf8"
bq
"google.golang.org/api/bigquery/v2"
)
// DataFormat describes the format of BigQuery table data.
type
DataFormat
string
// Constants describing the format of BigQuery table data.
const
(
CSV
DataFormat
=
"CSV"
Avro
DataFormat
=
"AVRO"
JSON
DataFormat
=
"NEWLINE_DELIMITED_JSON"
DatastoreBackup
DataFormat
=
"DATASTORE_BACKUP"
GoogleSheets
DataFormat
=
"GOOGLE_SHEETS"
Bigtable
DataFormat
=
"BIGTABLE"
Parquet
DataFormat
=
"PARQUET"
ORC
DataFormat
=
"ORC"
)
// ExternalData is a table which is stored outside of BigQuery. It is implemented by
// *ExternalDataConfig.
// GCSReference also implements it, for backwards compatibility.
type
ExternalData
interface
{
toBQ
()
bq
.
ExternalDataConfiguration
}
// ExternalDataConfig describes data external to BigQuery that can be used
// in queries and to create external tables.
type
ExternalDataConfig
struct
{
// The format of the data. Required.
SourceFormat
DataFormat
// The fully-qualified URIs that point to your
// data in Google Cloud. Required.
//
// For Google Cloud Storage URIs, each URI can contain one '*' wildcard character
// and it must come after the 'bucket' name. Size limits related to load jobs
// apply to external data sources.
//
// For Google Cloud Bigtable URIs, exactly one URI can be specified and it has be
// a fully specified and valid HTTPS URL for a Google Cloud Bigtable table.
//
// For Google Cloud Datastore backups, exactly one URI can be specified. Also,
// the '*' wildcard character is not allowed.
SourceURIs
[]
string
// The schema of the data. Required for CSV and JSON; disallowed for the
// other formats.
Schema
Schema
// Try to detect schema and format options automatically.
// Any option specified explicitly will be honored.
AutoDetect
bool
// The compression type of the data.
Compression
Compression
// IgnoreUnknownValues causes values not matching the schema to be
// tolerated. Unknown values are ignored. For CSV this ignores extra values
// at the end of a line. For JSON this ignores named values that do not
// match any column name. If this field is not set, records containing
// unknown values are treated as bad records. The MaxBadRecords field can
// be used to customize how bad records are handled.
IgnoreUnknownValues
bool
// MaxBadRecords is the maximum number of bad records that will be ignored
// when reading data.
MaxBadRecords
int64
// Additional options for CSV, GoogleSheets and Bigtable formats.
Options
ExternalDataConfigOptions
}
func
(
e
*
ExternalDataConfig
)
toBQ
()
bq
.
ExternalDataConfiguration
{
q
:=
bq
.
ExternalDataConfiguration
{
SourceFormat
:
string
(
e
.
SourceFormat
),
SourceUris
:
e
.
SourceURIs
,
Autodetect
:
e
.
AutoDetect
,
Compression
:
string
(
e
.
Compression
),
IgnoreUnknownValues
:
e
.
IgnoreUnknownValues
,
MaxBadRecords
:
e
.
MaxBadRecords
,
}
if
e
.
Schema
!=
nil
{
q
.
Schema
=
e
.
Schema
.
toBQ
()
}
if
e
.
Options
!=
nil
{
e
.
Options
.
populateExternalDataConfig
(
&
q
)
}
return
q
}
func
bqToExternalDataConfig
(
q
*
bq
.
ExternalDataConfiguration
)
(
*
ExternalDataConfig
,
error
)
{
e
:=
&
ExternalDataConfig
{
SourceFormat
:
DataFormat
(
q
.
SourceFormat
),
SourceURIs
:
q
.
SourceUris
,
AutoDetect
:
q
.
Autodetect
,
Compression
:
Compression
(
q
.
Compression
),
IgnoreUnknownValues
:
q
.
IgnoreUnknownValues
,
MaxBadRecords
:
q
.
MaxBadRecords
,
Schema
:
bqToSchema
(
q
.
Schema
),
}
switch
{
case
q
.
CsvOptions
!=
nil
:
e
.
Options
=
bqToCSVOptions
(
q
.
CsvOptions
)
case
q
.
GoogleSheetsOptions
!=
nil
:
e
.
Options
=
bqToGoogleSheetsOptions
(
q
.
GoogleSheetsOptions
)
case
q
.
BigtableOptions
!=
nil
:
var
err
error
e
.
Options
,
err
=
bqToBigtableOptions
(
q
.
BigtableOptions
)
if
err
!=
nil
{
return
nil
,
err
}
}
return
e
,
nil
}
// ExternalDataConfigOptions are additional options for external data configurations.
// This interface is implemented by CSVOptions, GoogleSheetsOptions and BigtableOptions.
type
ExternalDataConfigOptions
interface
{
populateExternalDataConfig
(
*
bq
.
ExternalDataConfiguration
)
}
// CSVOptions are additional options for CSV external data sources.
type
CSVOptions
struct
{
// AllowJaggedRows causes missing trailing optional columns to be tolerated
// when reading CSV data. Missing values are treated as nulls.
AllowJaggedRows
bool
// AllowQuotedNewlines sets whether quoted data sections containing
// newlines are allowed when reading CSV data.
AllowQuotedNewlines
bool
// Encoding is the character encoding of data to be read.
Encoding
Encoding
// FieldDelimiter is the separator for fields in a CSV file, used when
// reading or exporting data. The default is ",".
FieldDelimiter
string
// Quote is the value used to quote data sections in a CSV file. The
// default quotation character is the double quote ("), which is used if
// both Quote and ForceZeroQuote are unset.
// To specify that no character should be interpreted as a quotation
// character, set ForceZeroQuote to true.
// Only used when reading data.
Quote
string
ForceZeroQuote
bool
// The number of rows at the top of a CSV file that BigQuery will skip when
// reading data.
SkipLeadingRows
int64
}
func
(
o
*
CSVOptions
)
populateExternalDataConfig
(
c
*
bq
.
ExternalDataConfiguration
)
{
c
.
CsvOptions
=
&
bq
.
CsvOptions
{
AllowJaggedRows
:
o
.
AllowJaggedRows
,
AllowQuotedNewlines
:
o
.
AllowQuotedNewlines
,
Encoding
:
string
(
o
.
Encoding
),
FieldDelimiter
:
o
.
FieldDelimiter
,
Quote
:
o
.
quote
(),
SkipLeadingRows
:
o
.
SkipLeadingRows
,
}
}
// quote returns the CSV quote character, or nil if unset.
func
(
o
*
CSVOptions
)
quote
()
*
string
{
if
o
.
ForceZeroQuote
{
quote
:=
""
return
&
quote
}
if
o
.
Quote
==
""
{
return
nil
}
return
&
o
.
Quote
}
func
(
o
*
CSVOptions
)
setQuote
(
ps
*
string
)
{
if
ps
!=
nil
{
o
.
Quote
=
*
ps
if
o
.
Quote
==
""
{
o
.
ForceZeroQuote
=
true
}
}
}
func
bqToCSVOptions
(
q
*
bq
.
CsvOptions
)
*
CSVOptions
{
o
:=
&
CSVOptions
{
AllowJaggedRows
:
q
.
AllowJaggedRows
,
AllowQuotedNewlines
:
q
.
AllowQuotedNewlines
,
Encoding
:
Encoding
(
q
.
Encoding
),
FieldDelimiter
:
q
.
FieldDelimiter
,
SkipLeadingRows
:
q
.
SkipLeadingRows
,
}
o
.
setQuote
(
q
.
Quote
)
return
o
}
// GoogleSheetsOptions are additional options for GoogleSheets external data sources.
type
GoogleSheetsOptions
struct
{
// The number of rows at the top of a sheet that BigQuery will skip when
// reading data.
SkipLeadingRows
int64
}
func
(
o
*
GoogleSheetsOptions
)
populateExternalDataConfig
(
c
*
bq
.
ExternalDataConfiguration
)
{
c
.
GoogleSheetsOptions
=
&
bq
.
GoogleSheetsOptions
{
SkipLeadingRows
:
o
.
SkipLeadingRows
,
}
}
func
bqToGoogleSheetsOptions
(
q
*
bq
.
GoogleSheetsOptions
)
*
GoogleSheetsOptions
{
return
&
GoogleSheetsOptions
{
SkipLeadingRows
:
q
.
SkipLeadingRows
,
}
}
// BigtableOptions are additional options for Bigtable external data sources.
type
BigtableOptions
struct
{
// A list of column families to expose in the table schema along with their
// types. If omitted, all column families are present in the table schema and
// their values are read as BYTES.
ColumnFamilies
[]
*
BigtableColumnFamily
// If true, then the column families that are not specified in columnFamilies
// list are not exposed in the table schema. Otherwise, they are read with BYTES
// type values. The default is false.
IgnoreUnspecifiedColumnFamilies
bool
// If true, then the rowkey column families will be read and converted to string.
// Otherwise they are read with BYTES type values and users need to manually cast
// them with CAST if necessary. The default is false.
ReadRowkeyAsString
bool
}
func
(
o
*
BigtableOptions
)
populateExternalDataConfig
(
c
*
bq
.
ExternalDataConfiguration
)
{
q
:=
&
bq
.
BigtableOptions
{
IgnoreUnspecifiedColumnFamilies
:
o
.
IgnoreUnspecifiedColumnFamilies
,
ReadRowkeyAsString
:
o
.
ReadRowkeyAsString
,
}
for
_
,
f
:=
range
o
.
ColumnFamilies
{
q
.
ColumnFamilies
=
append
(
q
.
ColumnFamilies
,
f
.
toBQ
())
}
c
.
BigtableOptions
=
q
}
func
bqToBigtableOptions
(
q
*
bq
.
BigtableOptions
)
(
*
BigtableOptions
,
error
)
{
b
:=
&
BigtableOptions
{
IgnoreUnspecifiedColumnFamilies
:
q
.
IgnoreUnspecifiedColumnFamilies
,
ReadRowkeyAsString
:
q
.
ReadRowkeyAsString
,
}
for
_
,
f
:=
range
q
.
ColumnFamilies
{
f2
,
err
:=
bqToBigtableColumnFamily
(
f
)
if
err
!=
nil
{
return
nil
,
err
}
b
.
ColumnFamilies
=
append
(
b
.
ColumnFamilies
,
f2
)
}
return
b
,
nil
}
// BigtableColumnFamily describes how BigQuery should access a Bigtable column family.
type
BigtableColumnFamily
struct
{
// Identifier of the column family.
FamilyID
string
// Lists of columns that should be exposed as individual fields as opposed to a
// list of (column name, value) pairs. All columns whose qualifier matches a
// qualifier in this list can be accessed as .. Other columns can be accessed as
// a list through .Column field.
Columns
[]
*
BigtableColumn
// The encoding of the values when the type is not STRING. Acceptable encoding values are:
// - TEXT - indicates values are alphanumeric text strings.
// - BINARY - indicates values are encoded using HBase Bytes.toBytes family of functions.
// This can be overridden for a specific column by listing that column in 'columns' and
// specifying an encoding for it.
Encoding
string
// If true, only the latest version of values are exposed for all columns in this
// column family. This can be overridden for a specific column by listing that
// column in 'columns' and specifying a different setting for that column.
OnlyReadLatest
bool
// The type to convert the value in cells of this
// column family. The values are expected to be encoded using HBase
// Bytes.toBytes function when using the BINARY encoding value.
// Following BigQuery types are allowed (case-sensitive):
// BYTES STRING INTEGER FLOAT BOOLEAN.
// The default type is BYTES. This can be overridden for a specific column by
// listing that column in 'columns' and specifying a type for it.
Type
string
}
func
(
b
*
BigtableColumnFamily
)
toBQ
()
*
bq
.
BigtableColumnFamily
{
q
:=
&
bq
.
BigtableColumnFamily
{
FamilyId
:
b
.
FamilyID
,
Encoding
:
b
.
Encoding
,
OnlyReadLatest
:
b
.
OnlyReadLatest
,
Type
:
b
.
Type
,
}
for
_
,
col
:=
range
b
.
Columns
{
q
.
Columns
=
append
(
q
.
Columns
,
col
.
toBQ
())
}
return
q
}
func
bqToBigtableColumnFamily
(
q
*
bq
.
BigtableColumnFamily
)
(
*
BigtableColumnFamily
,
error
)
{
b
:=
&
BigtableColumnFamily
{
FamilyID
:
q
.
FamilyId
,
Encoding
:
q
.
Encoding
,
OnlyReadLatest
:
q
.
OnlyReadLatest
,
Type
:
q
.
Type
,
}
for
_
,
col
:=
range
q
.
Columns
{
c
,
err
:=
bqToBigtableColumn
(
col
)
if
err
!=
nil
{
return
nil
,
err
}
b
.
Columns
=
append
(
b
.
Columns
,
c
)
}
return
b
,
nil
}
// BigtableColumn describes how BigQuery should access a Bigtable column.
type
BigtableColumn
struct
{
// Qualifier of the column. Columns in the parent column family that have this
// exact qualifier are exposed as . field. The column field name is the
// same as the column qualifier.
Qualifier
string
// If the qualifier is not a valid BigQuery field identifier i.e. does not match
// [a-zA-Z][a-zA-Z0-9_]*, a valid identifier must be provided as the column field
// name and is used as field name in queries.
FieldName
string
// If true, only the latest version of values are exposed for this column.
// See BigtableColumnFamily.OnlyReadLatest.
OnlyReadLatest
bool
// The encoding of the values when the type is not STRING.
// See BigtableColumnFamily.Encoding
Encoding
string
// The type to convert the value in cells of this column.
// See BigtableColumnFamily.Type
Type
string
}
func
(
b
*
BigtableColumn
)
toBQ
()
*
bq
.
BigtableColumn
{
q
:=
&
bq
.
BigtableColumn
{
FieldName
:
b
.
FieldName
,
OnlyReadLatest
:
b
.
OnlyReadLatest
,
Encoding
:
b
.
Encoding
,
Type
:
b
.
Type
,
}
if
utf8
.
ValidString
(
b
.
Qualifier
)
{
q
.
QualifierString
=
b
.
Qualifier
}
else
{
q
.
QualifierEncoded
=
base64
.
RawStdEncoding
.
EncodeToString
([]
byte
(
b
.
Qualifier
))
}
return
q
}
func
bqToBigtableColumn
(
q
*
bq
.
BigtableColumn
)
(
*
BigtableColumn
,
error
)
{
b
:=
&
BigtableColumn
{
FieldName
:
q
.
FieldName
,
OnlyReadLatest
:
q
.
OnlyReadLatest
,
Encoding
:
q
.
Encoding
,
Type
:
q
.
Type
,
}
if
q
.
QualifierString
!=
""
{
b
.
Qualifier
=
q
.
QualifierString
}
else
{
bytes
,
err
:=
base64
.
RawStdEncoding
.
DecodeString
(
q
.
QualifierEncoded
)
if
err
!=
nil
{
return
nil
,
err
}
b
.
Qualifier
=
string
(
bytes
)
}
return
b
,
nil
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/external_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2017 Google LLC
//
// Licensed 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
bigquery
import
(
"testing"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
)
func
TestExternalDataConfig
(
t
*
testing
.
T
)
{
// Round-trip of ExternalDataConfig to underlying representation.
for
i
,
want
:=
range
[]
*
ExternalDataConfig
{
{
SourceFormat
:
CSV
,
SourceURIs
:
[]
string
{
"uri"
},
Schema
:
Schema
{{
Name
:
"n"
,
Type
:
IntegerFieldType
}},
AutoDetect
:
true
,
Compression
:
Gzip
,
IgnoreUnknownValues
:
true
,
MaxBadRecords
:
17
,
Options
:
&
CSVOptions
{
AllowJaggedRows
:
true
,
AllowQuotedNewlines
:
true
,
Encoding
:
UTF_8
,
FieldDelimiter
:
"f"
,
Quote
:
"q"
,
SkipLeadingRows
:
3
,
},
},
{
SourceFormat
:
GoogleSheets
,
Options
:
&
GoogleSheetsOptions
{
SkipLeadingRows
:
4
},
},
{
SourceFormat
:
Bigtable
,
Options
:
&
BigtableOptions
{
IgnoreUnspecifiedColumnFamilies
:
true
,
ReadRowkeyAsString
:
true
,
ColumnFamilies
:
[]
*
BigtableColumnFamily
{
{
FamilyID
:
"f1"
,
Encoding
:
"TEXT"
,
OnlyReadLatest
:
true
,
Type
:
"FLOAT"
,
Columns
:
[]
*
BigtableColumn
{
{
Qualifier
:
"valid-utf-8"
,
FieldName
:
"fn"
,
OnlyReadLatest
:
true
,
Encoding
:
"BINARY"
,
Type
:
"STRING"
,
},
},
},
},
},
},
}
{
q
:=
want
.
toBQ
()
got
,
err
:=
bqToExternalDataConfig
(
&
q
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
diff
:=
testutil
.
Diff
(
got
,
want
);
diff
!=
""
{
t
.
Errorf
(
"#%d: got=-, want=+:
\n
%s"
,
i
,
diff
)
}
}
}
func
TestQuote
(
t
*
testing
.
T
)
{
ptr
:=
func
(
s
string
)
*
string
{
return
&
s
}
for
_
,
test
:=
range
[]
struct
{
quote
string
force
bool
want
*
string
}{
{
""
,
false
,
nil
},
{
""
,
true
,
ptr
(
""
)},
{
"-"
,
false
,
ptr
(
"-"
)},
{
"-"
,
true
,
ptr
(
""
)},
}
{
o
:=
CSVOptions
{
Quote
:
test
.
quote
,
ForceZeroQuote
:
test
.
force
,
}
got
:=
o
.
quote
()
if
(
got
==
nil
)
!=
(
test
.
want
==
nil
)
{
t
.
Errorf
(
"%+v
\n
got %v
\n
want %v"
,
test
,
pretty
.
Value
(
got
),
pretty
.
Value
(
test
.
want
))
}
if
got
!=
nil
&&
test
.
want
!=
nil
&&
*
got
!=
*
test
.
want
{
t
.
Errorf
(
"%+v: got %q, want %q"
,
test
,
*
got
,
*
test
.
want
)
}
}
}
func
TestQualifier
(
t
*
testing
.
T
)
{
b
:=
BigtableColumn
{
Qualifier
:
"a"
}
q
:=
b
.
toBQ
()
if
q
.
QualifierString
!=
b
.
Qualifier
||
q
.
QualifierEncoded
!=
""
{
t
.
Errorf
(
"got (%q, %q), want (%q, %q)"
,
q
.
QualifierString
,
q
.
QualifierEncoded
,
b
.
Qualifier
,
""
)
}
b2
,
err
:=
bqToBigtableColumn
(
q
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
b2
.
Qualifier
,
b
.
Qualifier
;
got
!=
want
{
t
.
Errorf
(
"got %q, want %q"
,
got
,
want
)
}
const
(
invalidUTF8
=
"
\xDF\xFF
"
invalidEncoded
=
"3/8"
)
b
=
BigtableColumn
{
Qualifier
:
invalidUTF8
}
q
=
b
.
toBQ
()
if
q
.
QualifierString
!=
""
||
q
.
QualifierEncoded
!=
invalidEncoded
{
t
.
Errorf
(
"got (%q, %q), want (%q, %q)"
,
q
.
QualifierString
,
""
,
b
.
Qualifier
,
invalidEncoded
)
}
b2
,
err
=
bqToBigtableColumn
(
q
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
b2
.
Qualifier
,
b
.
Qualifier
;
got
!=
want
{
t
.
Errorf
(
"got %q, want %q"
,
got
,
want
)
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2016 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"cloud.google.com/go/internal/trace"
bq
"google.golang.org/api/bigquery/v2"
)
// ExtractConfig holds the configuration for an extract job.
type
ExtractConfig
struct
{
// Src is the table from which data will be extracted.
Src
*
Table
// Dst is the destination into which the data will be extracted.
Dst
*
GCSReference
// DisableHeader disables the printing of a header row in exported data.
DisableHeader
bool
// The labels associated with this job.
Labels
map
[
string
]
string
}
func
(
e
*
ExtractConfig
)
toBQ
()
*
bq
.
JobConfiguration
{
var
printHeader
*
bool
if
e
.
DisableHeader
{
f
:=
false
printHeader
=
&
f
}
return
&
bq
.
JobConfiguration
{
Labels
:
e
.
Labels
,
Extract
:
&
bq
.
JobConfigurationExtract
{
DestinationUris
:
append
([]
string
{},
e
.
Dst
.
URIs
...
),
Compression
:
string
(
e
.
Dst
.
Compression
),
DestinationFormat
:
string
(
e
.
Dst
.
DestinationFormat
),
FieldDelimiter
:
e
.
Dst
.
FieldDelimiter
,
SourceTable
:
e
.
Src
.
toBQ
(),
PrintHeader
:
printHeader
,
},
}
}
func
bqToExtractConfig
(
q
*
bq
.
JobConfiguration
,
c
*
Client
)
*
ExtractConfig
{
qe
:=
q
.
Extract
return
&
ExtractConfig
{
Labels
:
q
.
Labels
,
Dst
:
&
GCSReference
{
URIs
:
qe
.
DestinationUris
,
Compression
:
Compression
(
qe
.
Compression
),
DestinationFormat
:
DataFormat
(
qe
.
DestinationFormat
),
FileConfig
:
FileConfig
{
CSVOptions
:
CSVOptions
{
FieldDelimiter
:
qe
.
FieldDelimiter
,
},
},
},
DisableHeader
:
qe
.
PrintHeader
!=
nil
&&
!*
qe
.
PrintHeader
,
Src
:
bqToTable
(
qe
.
SourceTable
,
c
),
}
}
// An Extractor extracts data from a BigQuery table into Google Cloud Storage.
type
Extractor
struct
{
JobIDConfig
ExtractConfig
c
*
Client
}
// ExtractorTo returns an Extractor which can be used to extract data from a
// BigQuery table into Google Cloud Storage.
// The returned Extractor may optionally be further configured before its Run method is called.
func
(
t
*
Table
)
ExtractorTo
(
dst
*
GCSReference
)
*
Extractor
{
return
&
Extractor
{
c
:
t
.
c
,
ExtractConfig
:
ExtractConfig
{
Src
:
t
,
Dst
:
dst
,
},
}
}
// Run initiates an extract job.
func
(
e
*
Extractor
)
Run
(
ctx
context
.
Context
)
(
j
*
Job
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Extractor.Run"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
return
e
.
c
.
insertJob
(
ctx
,
e
.
newJob
(),
nil
)
}
func
(
e
*
Extractor
)
newJob
()
*
bq
.
Job
{
return
&
bq
.
Job
{
JobReference
:
e
.
JobIDConfig
.
createJobRef
(
e
.
c
),
Configuration
:
e
.
ExtractConfig
.
toBQ
(),
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/extract_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"testing"
"cloud.google.com/go/internal/testutil"
"github.com/google/go-cmp/cmp"
bq
"google.golang.org/api/bigquery/v2"
)
func
defaultExtractJob
()
*
bq
.
Job
{
return
&
bq
.
Job
{
JobReference
:
&
bq
.
JobReference
{
JobId
:
"RANDOM"
,
ProjectId
:
"client-project-id"
},
Configuration
:
&
bq
.
JobConfiguration
{
Extract
:
&
bq
.
JobConfigurationExtract
{
SourceTable
:
&
bq
.
TableReference
{
ProjectId
:
"client-project-id"
,
DatasetId
:
"dataset-id"
,
TableId
:
"table-id"
,
},
DestinationUris
:
[]
string
{
"uri"
},
},
},
}
}
func
defaultGCS
()
*
GCSReference
{
return
&
GCSReference
{
URIs
:
[]
string
{
"uri"
},
}
}
func
TestExtract
(
t
*
testing
.
T
)
{
defer
fixRandomID
(
"RANDOM"
)()
c
:=
&
Client
{
projectID
:
"client-project-id"
,
}
testCases
:=
[]
struct
{
dst
*
GCSReference
src
*
Table
config
ExtractConfig
want
*
bq
.
Job
}{
{
dst
:
defaultGCS
(),
src
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
want
:
defaultExtractJob
(),
},
{
dst
:
defaultGCS
(),
src
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
config
:
ExtractConfig
{
DisableHeader
:
true
,
Labels
:
map
[
string
]
string
{
"a"
:
"b"
},
},
want
:
func
()
*
bq
.
Job
{
j
:=
defaultExtractJob
()
j
.
Configuration
.
Labels
=
map
[
string
]
string
{
"a"
:
"b"
}
f
:=
false
j
.
Configuration
.
Extract
.
PrintHeader
=
&
f
return
j
}(),
},
{
dst
:
func
()
*
GCSReference
{
g
:=
NewGCSReference
(
"uri"
)
g
.
Compression
=
Gzip
g
.
DestinationFormat
=
JSON
g
.
FieldDelimiter
=
"
\t
"
return
g
}(),
src
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultExtractJob
()
j
.
Configuration
.
Extract
.
Compression
=
"GZIP"
j
.
Configuration
.
Extract
.
DestinationFormat
=
"NEWLINE_DELIMITED_JSON"
j
.
Configuration
.
Extract
.
FieldDelimiter
=
"
\t
"
return
j
}(),
},
}
for
i
,
tc
:=
range
testCases
{
ext
:=
tc
.
src
.
ExtractorTo
(
tc
.
dst
)
tc
.
config
.
Src
=
ext
.
Src
tc
.
config
.
Dst
=
ext
.
Dst
ext
.
ExtractConfig
=
tc
.
config
got
:=
ext
.
newJob
()
checkJob
(
t
,
i
,
got
,
tc
.
want
)
jc
,
err
:=
bqToJobConfig
(
got
.
Configuration
,
c
)
if
err
!=
nil
{
t
.
Fatalf
(
"#%d: %v"
,
i
,
err
)
}
diff
:=
testutil
.
Diff
(
jc
,
&
ext
.
ExtractConfig
,
cmp
.
AllowUnexported
(
Table
{},
Client
{}))
if
diff
!=
""
{
t
.
Errorf
(
"#%d: (got=-, want=+:
\n
%s"
,
i
,
diff
)
}
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2016 Google LLC
//
// Licensed 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
bigquery
import
(
"io"
bq
"google.golang.org/api/bigquery/v2"
)
// A ReaderSource is a source for a load operation that gets
// data from an io.Reader.
//
// When a ReaderSource is part of a LoadConfig obtained via Job.Config,
// its internal io.Reader will be nil, so it cannot be used for a
// subsequent load operation.
type
ReaderSource
struct
{
r
io
.
Reader
FileConfig
}
// NewReaderSource creates a ReaderSource from an io.Reader. You may
// optionally configure properties on the ReaderSource that describe the
// data being read, before passing it to Table.LoaderFrom.
func
NewReaderSource
(
r
io
.
Reader
)
*
ReaderSource
{
return
&
ReaderSource
{
r
:
r
}
}
func
(
r
*
ReaderSource
)
populateLoadConfig
(
lc
*
bq
.
JobConfigurationLoad
)
io
.
Reader
{
r
.
FileConfig
.
populateLoadConfig
(
lc
)
return
r
.
r
}
// FileConfig contains configuration options that pertain to files, typically
// text files that require interpretation to be used as a BigQuery table. A
// file may live in Google Cloud Storage (see GCSReference), or it may be
// loaded into a table via the Table.LoaderFromReader.
type
FileConfig
struct
{
// SourceFormat is the format of the data to be read.
// Allowed values are: Avro, CSV, DatastoreBackup, JSON, ORC, and Parquet. The default is CSV.
SourceFormat
DataFormat
// Indicates if we should automatically infer the options and
// schema for CSV and JSON sources.
AutoDetect
bool
// MaxBadRecords is the maximum number of bad records that will be ignored
// when reading data.
MaxBadRecords
int64
// IgnoreUnknownValues causes values not matching the schema to be
// tolerated. Unknown values are ignored. For CSV this ignores extra values
// at the end of a line. For JSON this ignores named values that do not
// match any column name. If this field is not set, records containing
// unknown values are treated as bad records. The MaxBadRecords field can
// be used to customize how bad records are handled.
IgnoreUnknownValues
bool
// Schema describes the data. It is required when reading CSV or JSON data,
// unless the data is being loaded into a table that already exists.
Schema
Schema
// Additional options for CSV files.
CSVOptions
}
func
(
fc
*
FileConfig
)
populateLoadConfig
(
conf
*
bq
.
JobConfigurationLoad
)
{
conf
.
SkipLeadingRows
=
fc
.
SkipLeadingRows
conf
.
SourceFormat
=
string
(
fc
.
SourceFormat
)
conf
.
Autodetect
=
fc
.
AutoDetect
conf
.
AllowJaggedRows
=
fc
.
AllowJaggedRows
conf
.
AllowQuotedNewlines
=
fc
.
AllowQuotedNewlines
conf
.
Encoding
=
string
(
fc
.
Encoding
)
conf
.
FieldDelimiter
=
fc
.
FieldDelimiter
conf
.
IgnoreUnknownValues
=
fc
.
IgnoreUnknownValues
conf
.
MaxBadRecords
=
fc
.
MaxBadRecords
if
fc
.
Schema
!=
nil
{
conf
.
Schema
=
fc
.
Schema
.
toBQ
()
}
conf
.
Quote
=
fc
.
quote
()
}
func
bqPopulateFileConfig
(
conf
*
bq
.
JobConfigurationLoad
,
fc
*
FileConfig
)
{
fc
.
SourceFormat
=
DataFormat
(
conf
.
SourceFormat
)
fc
.
AutoDetect
=
conf
.
Autodetect
fc
.
MaxBadRecords
=
conf
.
MaxBadRecords
fc
.
IgnoreUnknownValues
=
conf
.
IgnoreUnknownValues
fc
.
Schema
=
bqToSchema
(
conf
.
Schema
)
fc
.
SkipLeadingRows
=
conf
.
SkipLeadingRows
fc
.
AllowJaggedRows
=
conf
.
AllowJaggedRows
fc
.
AllowQuotedNewlines
=
conf
.
AllowQuotedNewlines
fc
.
Encoding
=
Encoding
(
conf
.
Encoding
)
fc
.
FieldDelimiter
=
conf
.
FieldDelimiter
fc
.
CSVOptions
.
setQuote
(
conf
.
Quote
)
}
func
(
fc
*
FileConfig
)
populateExternalDataConfig
(
conf
*
bq
.
ExternalDataConfiguration
)
{
format
:=
fc
.
SourceFormat
if
format
==
""
{
// Format must be explicitly set for external data sources.
format
=
CSV
}
conf
.
Autodetect
=
fc
.
AutoDetect
conf
.
IgnoreUnknownValues
=
fc
.
IgnoreUnknownValues
conf
.
MaxBadRecords
=
fc
.
MaxBadRecords
conf
.
SourceFormat
=
string
(
format
)
if
fc
.
Schema
!=
nil
{
conf
.
Schema
=
fc
.
Schema
.
toBQ
()
}
if
format
==
CSV
{
fc
.
CSVOptions
.
populateExternalDataConfig
(
conf
)
}
}
// Encoding specifies the character encoding of data to be loaded into BigQuery.
// See https://cloud.google.com/bigquery/docs/reference/v2/jobs#configuration.load.encoding
// for more details about how this is used.
type
Encoding
string
const
(
// UTF_8 specifies the UTF-8 encoding type.
UTF_8
Encoding
=
"UTF-8"
// ISO_8859_1 specifies the ISO-8859-1 encoding type.
ISO_8859_1
Encoding
=
"ISO-8859-1"
)
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/file_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2016 Google LLC
//
// Licensed 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
bigquery
import
(
"testing"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
bq
"google.golang.org/api/bigquery/v2"
)
var
(
hyphen
=
"-"
fc
=
FileConfig
{
SourceFormat
:
CSV
,
AutoDetect
:
true
,
MaxBadRecords
:
7
,
IgnoreUnknownValues
:
true
,
Schema
:
Schema
{
stringFieldSchema
(),
nestedFieldSchema
(),
},
CSVOptions
:
CSVOptions
{
Quote
:
hyphen
,
FieldDelimiter
:
"
\t
"
,
SkipLeadingRows
:
8
,
AllowJaggedRows
:
true
,
AllowQuotedNewlines
:
true
,
Encoding
:
UTF_8
,
},
}
)
func
TestFileConfigPopulateLoadConfig
(
t
*
testing
.
T
)
{
want
:=
&
bq
.
JobConfigurationLoad
{
SourceFormat
:
"CSV"
,
FieldDelimiter
:
"
\t
"
,
SkipLeadingRows
:
8
,
AllowJaggedRows
:
true
,
AllowQuotedNewlines
:
true
,
Autodetect
:
true
,
Encoding
:
"UTF-8"
,
MaxBadRecords
:
7
,
IgnoreUnknownValues
:
true
,
Schema
:
&
bq
.
TableSchema
{
Fields
:
[]
*
bq
.
TableFieldSchema
{
bqStringFieldSchema
(),
bqNestedFieldSchema
(),
}},
Quote
:
&
hyphen
,
}
got
:=
&
bq
.
JobConfigurationLoad
{}
fc
.
populateLoadConfig
(
got
)
if
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"got:
\n
%v
\n
want:
\n
%v"
,
pretty
.
Value
(
got
),
pretty
.
Value
(
want
))
}
}
func
TestFileConfigPopulateExternalDataConfig
(
t
*
testing
.
T
)
{
got
:=
&
bq
.
ExternalDataConfiguration
{}
fc
.
populateExternalDataConfig
(
got
)
want
:=
&
bq
.
ExternalDataConfiguration
{
SourceFormat
:
"CSV"
,
Autodetect
:
true
,
MaxBadRecords
:
7
,
IgnoreUnknownValues
:
true
,
Schema
:
&
bq
.
TableSchema
{
Fields
:
[]
*
bq
.
TableFieldSchema
{
bqStringFieldSchema
(),
bqNestedFieldSchema
(),
}},
CsvOptions
:
&
bq
.
CsvOptions
{
AllowJaggedRows
:
true
,
AllowQuotedNewlines
:
true
,
Encoding
:
"UTF-8"
,
FieldDelimiter
:
"
\t
"
,
Quote
:
&
hyphen
,
SkipLeadingRows
:
8
,
},
}
if
diff
:=
testutil
.
Diff
(
got
,
want
);
diff
!=
""
{
t
.
Errorf
(
"got=-, want=+:
\n
%s"
,
diff
)
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/gcs.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"io"
bq
"google.golang.org/api/bigquery/v2"
)
// GCSReference is a reference to one or more Google Cloud Storage objects, which together constitute
// an input or output to a BigQuery operation.
type
GCSReference
struct
{
// URIs refer to Google Cloud Storage objects.
URIs
[]
string
FileConfig
// DestinationFormat is the format to use when writing exported files.
// Allowed values are: CSV, Avro, JSON. The default is CSV.
// CSV is not supported for tables with nested or repeated fields.
DestinationFormat
DataFormat
// Compression specifies the type of compression to apply when writing data
// to Google Cloud Storage, or using this GCSReference as an ExternalData
// source with CSV or JSON SourceFormat. Default is None.
Compression
Compression
}
// NewGCSReference constructs a reference to one or more Google Cloud Storage objects, which together constitute a data source or destination.
// In the simple case, a single URI in the form gs://bucket/object may refer to a single GCS object.
// Data may also be split into mutiple files, if multiple URIs or URIs containing wildcards are provided.
// Each URI may contain one '*' wildcard character, which (if present) must come after the bucket name.
// For more information about the treatment of wildcards and multiple URIs,
// see https://cloud.google.com/bigquery/exporting-data-from-bigquery#exportingmultiple
func
NewGCSReference
(
uri
...
string
)
*
GCSReference
{
return
&
GCSReference
{
URIs
:
uri
}
}
// Compression is the type of compression to apply when writing data to Google Cloud Storage.
type
Compression
string
const
(
// None specifies no compression.
None
Compression
=
"NONE"
// Gzip specifies gzip compression.
Gzip
Compression
=
"GZIP"
)
func
(
gcs
*
GCSReference
)
populateLoadConfig
(
lc
*
bq
.
JobConfigurationLoad
)
io
.
Reader
{
lc
.
SourceUris
=
gcs
.
URIs
gcs
.
FileConfig
.
populateLoadConfig
(
lc
)
return
nil
}
func
(
gcs
*
GCSReference
)
toBQ
()
bq
.
ExternalDataConfiguration
{
conf
:=
bq
.
ExternalDataConfiguration
{
Compression
:
string
(
gcs
.
Compression
),
SourceUris
:
append
([]
string
{},
gcs
.
URIs
...
),
}
gcs
.
FileConfig
.
populateExternalDataConfig
(
&
conf
)
return
conf
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/inserter.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"errors"
"fmt"
"reflect"
"cloud.google.com/go/internal/trace"
bq
"google.golang.org/api/bigquery/v2"
)
// An Inserter does streaming inserts into a BigQuery table.
// It is safe for concurrent use.
type
Inserter
struct
{
t
*
Table
// SkipInvalidRows causes rows containing invalid data to be silently
// ignored. The default value is false, which causes the entire request to
// fail if there is an attempt to insert an invalid row.
SkipInvalidRows
bool
// IgnoreUnknownValues causes values not matching the schema to be ignored.
// The default value is false, which causes records containing such values
// to be treated as invalid records.
IgnoreUnknownValues
bool
// A TableTemplateSuffix allows Inserters to create tables automatically.
//
// Experimental: this option is experimental and may be modified or removed in future versions,
// regardless of any other documented package stability guarantees.
//
// When you specify a suffix, the table you upload data to
// will be used as a template for creating a new table, with the same schema,
// called <table> + <suffix>.
//
// More information is available at
// https://cloud.google.com/bigquery/streaming-data-into-bigquery#template-tables
TableTemplateSuffix
string
}
// Inserter returns an Inserter that can be used to append rows to t.
// The returned Inserter may optionally be further configured before its Put method is called.
//
// To stream rows into a date-partitioned table at a particular date, add the
// $yyyymmdd suffix to the table name when constructing the Table.
func
(
t
*
Table
)
Inserter
()
*
Inserter
{
return
&
Inserter
{
t
:
t
}
}
// Uploader calls Inserter.
// Deprecated: use Table.Inserter instead.
func
(
t
*
Table
)
Uploader
()
*
Inserter
{
return
t
.
Inserter
()
}
// Put uploads one or more rows to the BigQuery service.
//
// If src is ValueSaver, then its Save method is called to produce a row for uploading.
//
// If src is a struct or pointer to a struct, then a schema is inferred from it
// and used to create a StructSaver. The InsertID of the StructSaver will be
// empty.
//
// If src is a slice of ValueSavers, structs, or struct pointers, then each
// element of the slice is treated as above, and multiple rows are uploaded.
//
// Put returns a PutMultiError if one or more rows failed to be uploaded.
// The PutMultiError contains a RowInsertionError for each failed row.
//
// Put will retry on temporary errors (see
// https://cloud.google.com/bigquery/troubleshooting-errors). This can result
// in duplicate rows if you do not use insert IDs. Also, if the error persists,
// the call will run indefinitely. Pass a context with a timeout to prevent
// hanging calls.
func
(
u
*
Inserter
)
Put
(
ctx
context
.
Context
,
src
interface
{})
(
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Inserter.Put"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
savers
,
err
:=
valueSavers
(
src
)
if
err
!=
nil
{
return
err
}
return
u
.
putMulti
(
ctx
,
savers
)
}
func
valueSavers
(
src
interface
{})
([]
ValueSaver
,
error
)
{
saver
,
ok
,
err
:=
toValueSaver
(
src
)
if
err
!=
nil
{
return
nil
,
err
}
if
ok
{
return
[]
ValueSaver
{
saver
},
nil
}
srcVal
:=
reflect
.
ValueOf
(
src
)
if
srcVal
.
Kind
()
!=
reflect
.
Slice
{
return
nil
,
fmt
.
Errorf
(
"%T is not a ValueSaver, struct, struct pointer, or slice"
,
src
)
}
var
savers
[]
ValueSaver
for
i
:=
0
;
i
<
srcVal
.
Len
();
i
++
{
s
:=
srcVal
.
Index
(
i
)
.
Interface
()
saver
,
ok
,
err
:=
toValueSaver
(
s
)
if
err
!=
nil
{
return
nil
,
err
}
if
!
ok
{
return
nil
,
fmt
.
Errorf
(
"src[%d] has type %T, which is not a ValueSaver, struct or struct pointer"
,
i
,
s
)
}
savers
=
append
(
savers
,
saver
)
}
return
savers
,
nil
}
// Make a ValueSaver from x, which must implement ValueSaver already
// or be a struct or pointer to struct.
func
toValueSaver
(
x
interface
{})
(
ValueSaver
,
bool
,
error
)
{
if
_
,
ok
:=
x
.
(
StructSaver
);
ok
{
return
nil
,
false
,
errors
.
New
(
"bigquery: use &StructSaver, not StructSaver"
)
}
var
insertID
string
// Handle StructSavers specially so we can infer the schema if necessary.
if
ss
,
ok
:=
x
.
(
*
StructSaver
);
ok
&&
ss
.
Schema
==
nil
{
x
=
ss
.
Struct
insertID
=
ss
.
InsertID
// Fall through so we can infer the schema.
}
if
saver
,
ok
:=
x
.
(
ValueSaver
);
ok
{
return
saver
,
ok
,
nil
}
v
:=
reflect
.
ValueOf
(
x
)
// Support Put with []interface{}
if
v
.
Kind
()
==
reflect
.
Interface
{
v
=
v
.
Elem
()
}
if
v
.
Kind
()
==
reflect
.
Ptr
{
v
=
v
.
Elem
()
}
if
v
.
Kind
()
!=
reflect
.
Struct
{
return
nil
,
false
,
nil
}
schema
,
err
:=
inferSchemaReflectCached
(
v
.
Type
())
if
err
!=
nil
{
return
nil
,
false
,
err
}
return
&
StructSaver
{
Struct
:
x
,
InsertID
:
insertID
,
Schema
:
schema
,
},
true
,
nil
}
func
(
u
*
Inserter
)
putMulti
(
ctx
context
.
Context
,
src
[]
ValueSaver
)
error
{
req
,
err
:=
u
.
newInsertRequest
(
src
)
if
err
!=
nil
{
return
err
}
if
req
==
nil
{
return
nil
}
call
:=
u
.
t
.
c
.
bqs
.
Tabledata
.
InsertAll
(
u
.
t
.
ProjectID
,
u
.
t
.
DatasetID
,
u
.
t
.
TableID
,
req
)
call
=
call
.
Context
(
ctx
)
setClientHeader
(
call
.
Header
())
var
res
*
bq
.
TableDataInsertAllResponse
err
=
runWithRetry
(
ctx
,
func
()
(
err
error
)
{
res
,
err
=
call
.
Do
()
return
err
})
if
err
!=
nil
{
return
err
}
return
handleInsertErrors
(
res
.
InsertErrors
,
req
.
Rows
)
}
func
(
u
*
Inserter
)
newInsertRequest
(
savers
[]
ValueSaver
)
(
*
bq
.
TableDataInsertAllRequest
,
error
)
{
if
savers
==
nil
{
// If there are no rows, do nothing.
return
nil
,
nil
}
req
:=
&
bq
.
TableDataInsertAllRequest
{
TemplateSuffix
:
u
.
TableTemplateSuffix
,
IgnoreUnknownValues
:
u
.
IgnoreUnknownValues
,
SkipInvalidRows
:
u
.
SkipInvalidRows
,
}
for
_
,
saver
:=
range
savers
{
row
,
insertID
,
err
:=
saver
.
Save
()
if
err
!=
nil
{
return
nil
,
err
}
if
insertID
==
""
{
insertID
=
randomIDFn
()
}
m
:=
make
(
map
[
string
]
bq
.
JsonValue
)
for
k
,
v
:=
range
row
{
m
[
k
]
=
bq
.
JsonValue
(
v
)
}
req
.
Rows
=
append
(
req
.
Rows
,
&
bq
.
TableDataInsertAllRequestRows
{
InsertId
:
insertID
,
Json
:
m
,
})
}
return
req
,
nil
}
func
handleInsertErrors
(
ierrs
[]
*
bq
.
TableDataInsertAllResponseInsertErrors
,
rows
[]
*
bq
.
TableDataInsertAllRequestRows
)
error
{
if
len
(
ierrs
)
==
0
{
return
nil
}
var
errs
PutMultiError
for
_
,
e
:=
range
ierrs
{
if
int
(
e
.
Index
)
>
len
(
rows
)
{
return
fmt
.
Errorf
(
"internal error: unexpected row index: %v"
,
e
.
Index
)
}
rie
:=
RowInsertionError
{
InsertID
:
rows
[
e
.
Index
]
.
InsertId
,
RowIndex
:
int
(
e
.
Index
),
}
for
_
,
errp
:=
range
e
.
Errors
{
rie
.
Errors
=
append
(
rie
.
Errors
,
bqToError
(
errp
))
}
errs
=
append
(
errs
,
rie
)
}
return
errs
}
// Uploader is an obsolete name for Inserter.
type
Uploader
=
Inserter
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/inserter_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"errors"
"strconv"
"testing"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
"github.com/google/go-cmp/cmp"
bq
"google.golang.org/api/bigquery/v2"
)
type
testSaver
struct
{
row
map
[
string
]
Value
insertID
string
err
error
}
func
(
ts
testSaver
)
Save
()
(
map
[
string
]
Value
,
string
,
error
)
{
return
ts
.
row
,
ts
.
insertID
,
ts
.
err
}
func
TestNewInsertRequest
(
t
*
testing
.
T
)
{
prev
:=
randomIDFn
n
:=
0
randomIDFn
=
func
()
string
{
n
++
;
return
strconv
.
Itoa
(
n
)
}
defer
func
()
{
randomIDFn
=
prev
}()
tests
:=
[]
struct
{
ul
*
Uploader
savers
[]
ValueSaver
req
*
bq
.
TableDataInsertAllRequest
}{
{
ul
:
&
Uploader
{},
savers
:
nil
,
req
:
nil
,
},
{
ul
:
&
Uploader
{},
savers
:
[]
ValueSaver
{
testSaver
{
row
:
map
[
string
]
Value
{
"one"
:
1
}},
testSaver
{
row
:
map
[
string
]
Value
{
"two"
:
2
}},
},
req
:
&
bq
.
TableDataInsertAllRequest
{
Rows
:
[]
*
bq
.
TableDataInsertAllRequestRows
{
{
InsertId
:
"1"
,
Json
:
map
[
string
]
bq
.
JsonValue
{
"one"
:
1
}},
{
InsertId
:
"2"
,
Json
:
map
[
string
]
bq
.
JsonValue
{
"two"
:
2
}},
},
},
},
{
ul
:
&
Uploader
{
TableTemplateSuffix
:
"suffix"
,
IgnoreUnknownValues
:
true
,
SkipInvalidRows
:
true
,
},
savers
:
[]
ValueSaver
{
testSaver
{
insertID
:
"a"
,
row
:
map
[
string
]
Value
{
"one"
:
1
}},
testSaver
{
insertID
:
""
,
row
:
map
[
string
]
Value
{
"two"
:
2
}},
},
req
:
&
bq
.
TableDataInsertAllRequest
{
Rows
:
[]
*
bq
.
TableDataInsertAllRequestRows
{
{
InsertId
:
"a"
,
Json
:
map
[
string
]
bq
.
JsonValue
{
"one"
:
1
}},
{
InsertId
:
"3"
,
Json
:
map
[
string
]
bq
.
JsonValue
{
"two"
:
2
}},
},
TemplateSuffix
:
"suffix"
,
SkipInvalidRows
:
true
,
IgnoreUnknownValues
:
true
,
},
},
}
for
i
,
tc
:=
range
tests
{
got
,
err
:=
tc
.
ul
.
newInsertRequest
(
tc
.
savers
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
want
:=
tc
.
req
if
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"%d: %#v: got %#v, want %#v"
,
i
,
tc
.
ul
,
got
,
want
)
}
}
}
func
TestNewInsertRequestErrors
(
t
*
testing
.
T
)
{
var
u
Uploader
_
,
err
:=
u
.
newInsertRequest
([]
ValueSaver
{
testSaver
{
err
:
errors
.
New
(
"bang"
)}})
if
err
==
nil
{
t
.
Error
(
"got nil, want error"
)
}
}
func
TestHandleInsertErrors
(
t
*
testing
.
T
)
{
rows
:=
[]
*
bq
.
TableDataInsertAllRequestRows
{
{
InsertId
:
"a"
},
{
InsertId
:
"b"
},
}
for
_
,
test
:=
range
[]
struct
{
in
[]
*
bq
.
TableDataInsertAllResponseInsertErrors
want
error
}{
{
in
:
nil
,
want
:
nil
,
},
{
in
:
[]
*
bq
.
TableDataInsertAllResponseInsertErrors
{{
Index
:
1
}},
want
:
PutMultiError
{
RowInsertionError
{
InsertID
:
"b"
,
RowIndex
:
1
}},
},
{
in
:
[]
*
bq
.
TableDataInsertAllResponseInsertErrors
{{
Index
:
1
}},
want
:
PutMultiError
{
RowInsertionError
{
InsertID
:
"b"
,
RowIndex
:
1
}},
},
{
in
:
[]
*
bq
.
TableDataInsertAllResponseInsertErrors
{
{
Errors
:
[]
*
bq
.
ErrorProto
{{
Message
:
"m0"
}},
Index
:
0
},
{
Errors
:
[]
*
bq
.
ErrorProto
{{
Message
:
"m1"
}},
Index
:
1
},
},
want
:
PutMultiError
{
RowInsertionError
{
InsertID
:
"a"
,
RowIndex
:
0
,
Errors
:
[]
error
{
&
Error
{
Message
:
"m0"
}}},
RowInsertionError
{
InsertID
:
"b"
,
RowIndex
:
1
,
Errors
:
[]
error
{
&
Error
{
Message
:
"m1"
}}},
},
},
}
{
got
:=
handleInsertErrors
(
test
.
in
,
rows
)
if
!
testutil
.
Equal
(
got
,
test
.
want
)
{
t
.
Errorf
(
"%#v:
\n
got
\n
%#v
\n
want
\n
%#v"
,
test
.
in
,
got
,
test
.
want
)
}
}
}
func
TestValueSavers
(
t
*
testing
.
T
)
{
ts
:=
&
testSaver
{}
type
T
struct
{
I
int
}
schema
,
err
:=
InferSchema
(
T
{})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
for
_
,
test
:=
range
[]
struct
{
in
interface
{}
want
[]
ValueSaver
}{
{[]
interface
{}(
nil
),
nil
},
{[]
interface
{}{},
nil
},
{
ts
,
[]
ValueSaver
{
ts
}},
{
T
{
I
:
1
},
[]
ValueSaver
{
&
StructSaver
{
Schema
:
schema
,
Struct
:
T
{
I
:
1
}}}},
{[]
ValueSaver
{
ts
,
ts
},
[]
ValueSaver
{
ts
,
ts
}},
{[]
interface
{}{
ts
,
ts
},
[]
ValueSaver
{
ts
,
ts
}},
{[]
T
{{
I
:
1
},
{
I
:
2
}},
[]
ValueSaver
{
&
StructSaver
{
Schema
:
schema
,
Struct
:
T
{
I
:
1
}},
&
StructSaver
{
Schema
:
schema
,
Struct
:
T
{
I
:
2
}},
}},
{[]
interface
{}{
T
{
I
:
1
},
&
T
{
I
:
2
}},
[]
ValueSaver
{
&
StructSaver
{
Schema
:
schema
,
Struct
:
T
{
I
:
1
}},
&
StructSaver
{
Schema
:
schema
,
Struct
:
&
T
{
I
:
2
}},
}},
{
&
StructSaver
{
Struct
:
T
{
I
:
3
},
InsertID
:
"foo"
},
[]
ValueSaver
{
&
StructSaver
{
Schema
:
schema
,
Struct
:
T
{
I
:
3
},
InsertID
:
"foo"
},
}},
}
{
got
,
err
:=
valueSavers
(
test
.
in
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
!
testutil
.
Equal
(
got
,
test
.
want
,
cmp
.
AllowUnexported
(
testSaver
{}))
{
t
.
Errorf
(
"%+v: got %v, want %v"
,
test
.
in
,
pretty
.
Value
(
got
),
pretty
.
Value
(
test
.
want
))
}
// Make sure Save is successful.
for
i
,
vs
:=
range
got
{
_
,
_
,
err
:=
vs
.
Save
()
if
err
!=
nil
{
t
.
Fatalf
(
"%+v, #%d: got error %v, want nil"
,
test
.
in
,
i
,
err
)
}
}
}
}
func
TestValueSaversErrors
(
t
*
testing
.
T
)
{
inputs
:=
[]
interface
{}{
nil
,
1
,
[]
int
{
1
,
2
},
[]
interface
{}{
testSaver
{
row
:
map
[
string
]
Value
{
"one"
:
1
},
insertID
:
"a"
},
1
,
},
StructSaver
{},
}
for
_
,
in
:=
range
inputs
{
if
_
,
err
:=
valueSavers
(
in
);
err
==
nil
{
t
.
Errorf
(
"%#v: got nil, want error"
,
in
)
}
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/integration_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"math/big"
"net/http"
"os"
"sort"
"strings"
"testing"
"time"
"cloud.google.com/go/civil"
"cloud.google.com/go/httpreplay"
"cloud.google.com/go/internal"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
"cloud.google.com/go/internal/uid"
"cloud.google.com/go/storage"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
gax
"github.com/googleapis/gax-go"
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
const
replayFilename
=
"bigquery.replay"
var
record
=
flag
.
Bool
(
"record"
,
false
,
"record RPCs"
)
var
(
client
*
Client
storageClient
*
storage
.
Client
dataset
*
Dataset
schema
=
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
},
{
Name
:
"nums"
,
Type
:
IntegerFieldType
,
Repeated
:
true
},
{
Name
:
"rec"
,
Type
:
RecordFieldType
,
Schema
:
Schema
{
{
Name
:
"bool"
,
Type
:
BooleanFieldType
},
}},
}
testTableExpiration
time
.
Time
datasetIDs
,
tableIDs
*
uid
.
Space
)
// Note: integration tests cannot be run in parallel, because TestIntegration_Location
// modifies the client.
func
TestMain
(
m
*
testing
.
M
)
{
cleanup
:=
initIntegrationTest
()
r
:=
m
.
Run
()
cleanup
()
os
.
Exit
(
r
)
}
func
getClient
(
t
*
testing
.
T
)
*
Client
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
return
client
}
// If integration tests will be run, create a unique dataset for them.
// Return a cleanup function.
func
initIntegrationTest
()
func
()
{
ctx
:=
context
.
Background
()
flag
.
Parse
()
// needed for testing.Short()
projID
:=
testutil
.
ProjID
()
switch
{
case
testing
.
Short
()
&&
*
record
:
log
.
Fatal
(
"cannot combine -short and -record"
)
return
func
()
{}
case
testing
.
Short
()
&&
httpreplay
.
Supported
()
&&
testutil
.
CanReplay
(
replayFilename
)
&&
projID
!=
""
:
// go test -short with a replay file will replay the integration tests if the
// environment variables are set.
log
.
Printf
(
"replaying from %s"
,
replayFilename
)
httpreplay
.
DebugHeaders
()
replayer
,
err
:=
httpreplay
.
NewReplayer
(
replayFilename
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
var
t
time
.
Time
if
err
:=
json
.
Unmarshal
(
replayer
.
Initial
(),
&
t
);
err
!=
nil
{
log
.
Fatal
(
err
)
}
hc
,
err
:=
replayer
.
Client
(
ctx
)
// no creds needed
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
client
,
err
=
NewClient
(
ctx
,
projID
,
option
.
WithHTTPClient
(
hc
))
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
storageClient
,
err
=
storage
.
NewClient
(
ctx
,
option
.
WithHTTPClient
(
hc
))
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
cleanup
:=
initTestState
(
client
,
t
)
return
func
()
{
cleanup
()
_
=
replayer
.
Close
()
// No actionable error returned.
}
case
testing
.
Short
()
:
// go test -short without a replay file skips the integration tests.
if
testutil
.
CanReplay
(
replayFilename
)
&&
projID
!=
""
{
log
.
Print
(
"replay not supported for Go versions before 1.8"
)
}
client
=
nil
storageClient
=
nil
return
func
()
{}
default
:
// Run integration tests against a real backend.
ts
:=
testutil
.
TokenSource
(
ctx
,
Scope
)
if
ts
==
nil
{
log
.
Println
(
"Integration tests skipped. See CONTRIBUTING.md for details"
)
return
func
()
{}
}
bqOpt
:=
option
.
WithTokenSource
(
ts
)
sOpt
:=
option
.
WithTokenSource
(
testutil
.
TokenSource
(
ctx
,
storage
.
ScopeFullControl
))
cleanup
:=
func
()
{}
now
:=
time
.
Now
()
.
UTC
()
if
*
record
{
if
!
httpreplay
.
Supported
()
{
log
.
Print
(
"record not supported for Go versions before 1.8"
)
}
else
{
nowBytes
,
err
:=
json
.
Marshal
(
now
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
recorder
,
err
:=
httpreplay
.
NewRecorder
(
replayFilename
,
nowBytes
)
if
err
!=
nil
{
log
.
Fatalf
(
"could not record: %v"
,
err
)
}
log
.
Printf
(
"recording to %s"
,
replayFilename
)
hc
,
err
:=
recorder
.
Client
(
ctx
,
bqOpt
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
bqOpt
=
option
.
WithHTTPClient
(
hc
)
hc
,
err
=
recorder
.
Client
(
ctx
,
sOpt
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
sOpt
=
option
.
WithHTTPClient
(
hc
)
cleanup
=
func
()
{
if
err
:=
recorder
.
Close
();
err
!=
nil
{
log
.
Printf
(
"saving recording: %v"
,
err
)
}
}
}
}
var
err
error
client
,
err
=
NewClient
(
ctx
,
projID
,
bqOpt
)
if
err
!=
nil
{
log
.
Fatalf
(
"NewClient: %v"
,
err
)
}
storageClient
,
err
=
storage
.
NewClient
(
ctx
,
sOpt
)
if
err
!=
nil
{
log
.
Fatalf
(
"storage.NewClient: %v"
,
err
)
}
c
:=
initTestState
(
client
,
now
)
return
func
()
{
c
();
cleanup
()
}
}
}
func
initTestState
(
client
*
Client
,
t
time
.
Time
)
func
()
{
// BigQuery does not accept hyphens in dataset or table IDs, so we create IDs
// with underscores.
ctx
:=
context
.
Background
()
opts
:=
&
uid
.
Options
{
Sep
:
'_'
,
Time
:
t
}
datasetIDs
=
uid
.
NewSpace
(
"dataset"
,
opts
)
tableIDs
=
uid
.
NewSpace
(
"table"
,
opts
)
testTableExpiration
=
t
.
Add
(
10
*
time
.
Minute
)
.
Round
(
time
.
Second
)
// For replayability, seed the random source with t.
Seed
(
t
.
UnixNano
())
dataset
=
client
.
Dataset
(
datasetIDs
.
New
())
if
err
:=
dataset
.
Create
(
ctx
,
nil
);
err
!=
nil
{
log
.
Fatalf
(
"creating dataset %s: %v"
,
dataset
.
DatasetID
,
err
)
}
return
func
()
{
if
err
:=
dataset
.
DeleteWithContents
(
ctx
);
err
!=
nil
{
log
.
Printf
(
"could not delete %s"
,
dataset
.
DatasetID
)
}
}
}
func
TestIntegration_TableCreate
(
t
*
testing
.
T
)
{
// Check that creating a record field with an empty schema is an error.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
table
:=
dataset
.
Table
(
"t_bad"
)
schema
:=
Schema
{
{
Name
:
"rec"
,
Type
:
RecordFieldType
,
Schema
:
Schema
{}},
}
err
:=
table
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
schema
,
ExpirationTime
:
testTableExpiration
.
Add
(
5
*
time
.
Minute
),
})
if
err
==
nil
{
t
.
Fatal
(
"want error, got nil"
)
}
if
!
hasStatusCode
(
err
,
http
.
StatusBadRequest
)
{
t
.
Fatalf
(
"want a 400 error, got %v"
,
err
)
}
}
func
TestIntegration_TableCreateView
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Test that standard SQL views work.
view
:=
dataset
.
Table
(
"t_view_standardsql"
)
query
:=
fmt
.
Sprintf
(
"SELECT APPROX_COUNT_DISTINCT(name) FROM `%s.%s.%s`"
,
dataset
.
ProjectID
,
dataset
.
DatasetID
,
table
.
TableID
)
err
:=
view
.
Create
(
context
.
Background
(),
&
TableMetadata
{
ViewQuery
:
query
,
UseStandardSQL
:
true
,
})
if
err
!=
nil
{
t
.
Fatalf
(
"table.create: Did not expect an error, got: %v"
,
err
)
}
if
err
:=
view
.
Delete
(
ctx
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
func
TestIntegration_TableMetadata
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Check table metadata.
md
,
err
:=
table
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// TODO(jba): check md more thorougly.
if
got
,
want
:=
md
.
FullID
,
fmt
.
Sprintf
(
"%s:%s.%s"
,
dataset
.
ProjectID
,
dataset
.
DatasetID
,
table
.
TableID
);
got
!=
want
{
t
.
Errorf
(
"metadata.FullID: got %q, want %q"
,
got
,
want
)
}
if
got
,
want
:=
md
.
Type
,
RegularTable
;
got
!=
want
{
t
.
Errorf
(
"metadata.Type: got %v, want %v"
,
got
,
want
)
}
if
got
,
want
:=
md
.
ExpirationTime
,
testTableExpiration
;
!
got
.
Equal
(
want
)
{
t
.
Errorf
(
"metadata.Type: got %v, want %v"
,
got
,
want
)
}
// Check that timePartitioning is nil by default
if
md
.
TimePartitioning
!=
nil
{
t
.
Errorf
(
"metadata.TimePartitioning: got %v, want %v"
,
md
.
TimePartitioning
,
nil
)
}
// Create tables that have time partitioning
partitionCases
:=
[]
struct
{
timePartitioning
TimePartitioning
wantExpiration
time
.
Duration
wantField
string
wantPruneFilter
bool
}{
{
TimePartitioning
{},
time
.
Duration
(
0
),
""
,
false
},
{
TimePartitioning
{
Expiration
:
time
.
Second
},
time
.
Second
,
""
,
false
},
{
TimePartitioning
{
RequirePartitionFilter
:
true
},
time
.
Duration
(
0
),
""
,
true
},
{
TimePartitioning
{
Expiration
:
time
.
Second
,
Field
:
"date"
,
RequirePartitionFilter
:
true
,
},
time
.
Second
,
"date"
,
true
},
}
schema2
:=
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
},
{
Name
:
"date"
,
Type
:
DateFieldType
},
}
clustering
:=
&
Clustering
{
Fields
:
[]
string
{
"name"
},
}
// Currently, clustering depends on partitioning. Interleave testing of the two features.
for
i
,
c
:=
range
partitionCases
{
table
:=
dataset
.
Table
(
fmt
.
Sprintf
(
"t_metadata_partition_nocluster_%v"
,
i
))
clusterTable
:=
dataset
.
Table
(
fmt
.
Sprintf
(
"t_metadata_partition_cluster_%v"
,
i
))
// Create unclustered, partitioned variant and get metadata.
err
=
table
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
schema2
,
TimePartitioning
:
&
c
.
timePartitioning
,
ExpirationTime
:
testTableExpiration
,
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
defer
table
.
Delete
(
ctx
)
md
,
err
:=
table
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Created clustered table and get metadata.
err
=
clusterTable
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
schema2
,
TimePartitioning
:
&
c
.
timePartitioning
,
ExpirationTime
:
testTableExpiration
,
Clustering
:
clustering
,
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
clusterMD
,
err
:=
clusterTable
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
for
_
,
v
:=
range
[]
*
TableMetadata
{
md
,
clusterMD
}
{
got
:=
v
.
TimePartitioning
want
:=
&
TimePartitioning
{
Expiration
:
c
.
wantExpiration
,
Field
:
c
.
wantField
,
RequirePartitionFilter
:
c
.
wantPruneFilter
,
}
if
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"metadata.TimePartitioning: got %v, want %v"
,
got
,
want
)
}
// check that RequirePartitionFilter can be inverted.
mdUpdate
:=
TableMetadataToUpdate
{
TimePartitioning
:
&
TimePartitioning
{
Expiration
:
v
.
TimePartitioning
.
Expiration
,
RequirePartitionFilter
:
!
want
.
RequirePartitionFilter
,
},
}
newmd
,
err
:=
table
.
Update
(
ctx
,
mdUpdate
,
""
)
if
err
!=
nil
{
t
.
Errorf
(
"failed to invert RequirePartitionFilter on %s: %v"
,
table
.
FullyQualifiedName
(),
err
)
}
if
newmd
.
TimePartitioning
.
RequirePartitionFilter
==
want
.
RequirePartitionFilter
{
t
.
Errorf
(
"inverting RequirePartitionFilter on %s failed, want %t got %t"
,
table
.
FullyQualifiedName
(),
!
want
.
RequirePartitionFilter
,
newmd
.
TimePartitioning
.
RequirePartitionFilter
)
}
}
if
md
.
Clustering
!=
nil
{
t
.
Errorf
(
"metadata.Clustering was not nil on unclustered table %s"
,
table
.
TableID
)
}
got
:=
clusterMD
.
Clustering
want
:=
clustering
if
clusterMD
.
Clustering
!=
clustering
{
if
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"metadata.Clustering: got %v, want %v"
,
got
,
want
)
}
}
}
}
func
TestIntegration_DatasetCreate
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
ds
:=
client
.
Dataset
(
datasetIDs
.
New
())
wmd
:=
&
DatasetMetadata
{
Name
:
"name"
,
Location
:
"EU"
}
err
:=
ds
.
Create
(
ctx
,
wmd
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
gmd
,
err
:=
ds
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
gmd
.
Name
,
wmd
.
Name
;
got
!=
want
{
t
.
Errorf
(
"name: got %q, want %q"
,
got
,
want
)
}
if
got
,
want
:=
gmd
.
Location
,
wmd
.
Location
;
got
!=
want
{
t
.
Errorf
(
"location: got %q, want %q"
,
got
,
want
)
}
if
err
:=
ds
.
Delete
(
ctx
);
err
!=
nil
{
t
.
Fatalf
(
"deleting dataset %v: %v"
,
ds
,
err
)
}
}
func
TestIntegration_DatasetMetadata
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
md
,
err
:=
dataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
md
.
FullID
,
fmt
.
Sprintf
(
"%s:%s"
,
dataset
.
ProjectID
,
dataset
.
DatasetID
);
got
!=
want
{
t
.
Errorf
(
"FullID: got %q, want %q"
,
got
,
want
)
}
jan2016
:=
time
.
Date
(
2016
,
1
,
1
,
0
,
0
,
0
,
0
,
time
.
UTC
)
if
md
.
CreationTime
.
Before
(
jan2016
)
{
t
.
Errorf
(
"CreationTime: got %s, want > 2016-1-1"
,
md
.
CreationTime
)
}
if
md
.
LastModifiedTime
.
Before
(
jan2016
)
{
t
.
Errorf
(
"LastModifiedTime: got %s, want > 2016-1-1"
,
md
.
LastModifiedTime
)
}
// Verify that we get a NotFound for a nonexistent dataset.
_
,
err
=
client
.
Dataset
(
"does_not_exist"
)
.
Metadata
(
ctx
)
if
err
==
nil
||
!
hasStatusCode
(
err
,
http
.
StatusNotFound
)
{
t
.
Errorf
(
"got %v, want NotFound error"
,
err
)
}
}
func
TestIntegration_DatasetDelete
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
ds
:=
client
.
Dataset
(
datasetIDs
.
New
())
if
err
:=
ds
.
Create
(
ctx
,
nil
);
err
!=
nil
{
t
.
Fatalf
(
"creating dataset %s: %v"
,
ds
.
DatasetID
,
err
)
}
if
err
:=
ds
.
Delete
(
ctx
);
err
!=
nil
{
t
.
Fatalf
(
"deleting dataset %s: %v"
,
ds
.
DatasetID
,
err
)
}
}
func
TestIntegration_DatasetDeleteWithContents
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
ds
:=
client
.
Dataset
(
datasetIDs
.
New
())
if
err
:=
ds
.
Create
(
ctx
,
nil
);
err
!=
nil
{
t
.
Fatalf
(
"creating dataset %s: %v"
,
ds
.
DatasetID
,
err
)
}
table
:=
ds
.
Table
(
tableIDs
.
New
())
if
err
:=
table
.
Create
(
ctx
,
nil
);
err
!=
nil
{
t
.
Fatalf
(
"creating table %s in dataset %s: %v"
,
table
.
TableID
,
table
.
DatasetID
,
err
)
}
// We expect failure here
if
err
:=
ds
.
Delete
(
ctx
);
err
==
nil
{
t
.
Fatalf
(
"non-recursive delete of dataset %s succeeded unexpectedly."
,
ds
.
DatasetID
)
}
if
err
:=
ds
.
DeleteWithContents
(
ctx
);
err
!=
nil
{
t
.
Fatalf
(
"deleting recursively dataset %s: %v"
,
ds
.
DatasetID
,
err
)
}
}
func
TestIntegration_DatasetUpdateETags
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
check
:=
func
(
md
*
DatasetMetadata
,
wantDesc
,
wantName
string
)
{
if
md
.
Description
!=
wantDesc
{
t
.
Errorf
(
"description: got %q, want %q"
,
md
.
Description
,
wantDesc
)
}
if
md
.
Name
!=
wantName
{
t
.
Errorf
(
"name: got %q, want %q"
,
md
.
Name
,
wantName
)
}
}
ctx
:=
context
.
Background
()
md
,
err
:=
dataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
md
.
ETag
==
""
{
t
.
Fatal
(
"empty ETag"
)
}
// Write without ETag succeeds.
desc
:=
md
.
Description
+
"d2"
name
:=
md
.
Name
+
"n2"
md2
,
err
:=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
Description
:
desc
,
Name
:
name
},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
check
(
md2
,
desc
,
name
)
// Write with original ETag fails because of intervening write.
_
,
err
=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
Description
:
"d"
,
Name
:
"n"
},
md
.
ETag
)
if
err
==
nil
{
t
.
Fatal
(
"got nil, want error"
)
}
// Write with most recent ETag succeeds.
md3
,
err
:=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
Description
:
""
,
Name
:
""
},
md2
.
ETag
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
check
(
md3
,
""
,
""
)
}
func
TestIntegration_DatasetUpdateDefaultExpiration
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
_
,
err
:=
dataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Set the default expiration time.
md
,
err
:=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
DefaultTableExpiration
:
time
.
Hour
},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
md
.
DefaultTableExpiration
!=
time
.
Hour
{
t
.
Fatalf
(
"got %s, want 1h"
,
md
.
DefaultTableExpiration
)
}
// Omitting DefaultTableExpiration doesn't change it.
md
,
err
=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
Name
:
"xyz"
},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
md
.
DefaultTableExpiration
!=
time
.
Hour
{
t
.
Fatalf
(
"got %s, want 1h"
,
md
.
DefaultTableExpiration
)
}
// Setting it to 0 deletes it (which looks like a 0 duration).
md
,
err
=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
DefaultTableExpiration
:
time
.
Duration
(
0
)},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
md
.
DefaultTableExpiration
!=
0
{
t
.
Fatalf
(
"got %s, want 0"
,
md
.
DefaultTableExpiration
)
}
}
func
TestIntegration_DatasetUpdateAccess
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
md
,
err
:=
dataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
origAccess
:=
append
([]
*
AccessEntry
(
nil
),
md
.
Access
...
)
newEntry
:=
&
AccessEntry
{
Role
:
ReaderRole
,
Entity
:
"Joe@example.com"
,
EntityType
:
UserEmailEntity
,
}
newAccess
:=
append
(
md
.
Access
,
newEntry
)
dm
:=
DatasetMetadataToUpdate
{
Access
:
newAccess
}
md
,
err
=
dataset
.
Update
(
ctx
,
dm
,
md
.
ETag
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
defer
func
()
{
_
,
err
:=
dataset
.
Update
(
ctx
,
DatasetMetadataToUpdate
{
Access
:
origAccess
},
md
.
ETag
)
if
err
!=
nil
{
t
.
Log
(
"could not restore dataset access list"
)
}
}()
if
diff
:=
testutil
.
Diff
(
md
.
Access
,
newAccess
);
diff
!=
""
{
t
.
Fatalf
(
"got=-, want=+:
\n
%s"
,
diff
)
}
}
func
TestIntegration_DatasetUpdateLabels
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
_
,
err
:=
dataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
var
dm
DatasetMetadataToUpdate
dm
.
SetLabel
(
"label"
,
"value"
)
md
,
err
:=
dataset
.
Update
(
ctx
,
dm
,
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
md
.
Labels
[
"label"
],
"value"
;
got
!=
want
{
t
.
Errorf
(
"got %q, want %q"
,
got
,
want
)
}
dm
=
DatasetMetadataToUpdate
{}
dm
.
DeleteLabel
(
"label"
)
md
,
err
=
dataset
.
Update
(
ctx
,
dm
,
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
_
,
ok
:=
md
.
Labels
[
"label"
];
ok
{
t
.
Error
(
"label still present after deletion"
)
}
}
func
TestIntegration_TableUpdateLabels
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
var
tm
TableMetadataToUpdate
tm
.
SetLabel
(
"label"
,
"value"
)
md
,
err
:=
table
.
Update
(
ctx
,
tm
,
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
md
.
Labels
[
"label"
],
"value"
;
got
!=
want
{
t
.
Errorf
(
"got %q, want %q"
,
got
,
want
)
}
tm
=
TableMetadataToUpdate
{}
tm
.
DeleteLabel
(
"label"
)
md
,
err
=
table
.
Update
(
ctx
,
tm
,
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
_
,
ok
:=
md
.
Labels
[
"label"
];
ok
{
t
.
Error
(
"label still present after deletion"
)
}
}
func
TestIntegration_Tables
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
wantName
:=
table
.
FullyQualifiedName
()
// This test is flaky due to eventual consistency.
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
10
*
time
.
Second
)
defer
cancel
()
err
:=
internal
.
Retry
(
ctx
,
gax
.
Backoff
{},
func
()
(
stop
bool
,
err
error
)
{
// Iterate over tables in the dataset.
it
:=
dataset
.
Tables
(
ctx
)
var
tableNames
[]
string
for
{
tbl
,
err
:=
it
.
Next
()
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
return
false
,
err
}
tableNames
=
append
(
tableNames
,
tbl
.
FullyQualifiedName
())
}
// Other tests may be running with this dataset, so there might be more
// than just our table in the list. So don't try for an exact match; just
// make sure that our table is there somewhere.
for
_
,
tn
:=
range
tableNames
{
if
tn
==
wantName
{
return
true
,
nil
}
}
return
false
,
fmt
.
Errorf
(
"got %v
\n
want %s in the list"
,
tableNames
,
wantName
)
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
func
TestIntegration_InsertAndRead
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Populate the table.
ins
:=
table
.
Inserter
()
var
(
wantRows
[][]
Value
saverRows
[]
*
ValuesSaver
)
for
i
,
name
:=
range
[]
string
{
"a"
,
"b"
,
"c"
}
{
row
:=
[]
Value
{
name
,
[]
Value
{
int64
(
i
)},
[]
Value
{
true
}}
wantRows
=
append
(
wantRows
,
row
)
saverRows
=
append
(
saverRows
,
&
ValuesSaver
{
Schema
:
schema
,
InsertID
:
name
,
Row
:
row
,
})
}
if
err
:=
ins
.
Put
(
ctx
,
saverRows
);
err
!=
nil
{
t
.
Fatal
(
putError
(
err
))
}
// Wait until the data has been uploaded. This can take a few seconds, according
// to https://cloud.google.com/bigquery/streaming-data-into-bigquery.
if
err
:=
waitForRow
(
ctx
,
table
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Read the table.
checkRead
(
t
,
"upload"
,
table
.
Read
(
ctx
),
wantRows
)
// Query the table.
q
:=
client
.
Query
(
fmt
.
Sprintf
(
"select name, nums, rec from %s"
,
table
.
TableID
))
q
.
DefaultProjectID
=
dataset
.
ProjectID
q
.
DefaultDatasetID
=
dataset
.
DatasetID
rit
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkRead
(
t
,
"query"
,
rit
,
wantRows
)
// Query the long way.
job1
,
err
:=
q
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
job1
.
LastStatus
()
==
nil
{
t
.
Error
(
"no LastStatus"
)
}
job2
,
err
:=
client
.
JobFromID
(
ctx
,
job1
.
ID
())
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
job2
.
LastStatus
()
==
nil
{
t
.
Error
(
"no LastStatus"
)
}
rit
,
err
=
job2
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkRead
(
t
,
"job.Read"
,
rit
,
wantRows
)
// Get statistics.
jobStatus
,
err
:=
job2
.
Status
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
jobStatus
.
Statistics
==
nil
{
t
.
Fatal
(
"jobStatus missing statistics"
)
}
if
_
,
ok
:=
jobStatus
.
Statistics
.
Details
.
(
*
QueryStatistics
);
!
ok
{
t
.
Errorf
(
"expected QueryStatistics, got %T"
,
jobStatus
.
Statistics
.
Details
)
}
// Test reading directly into a []Value.
valueLists
,
schema
,
_
,
err
:=
readAll
(
table
.
Read
(
ctx
))
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
it
:=
table
.
Read
(
ctx
)
for
i
,
vl
:=
range
valueLists
{
var
got
[]
Value
if
err
:=
it
.
Next
(
&
got
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
!
testutil
.
Equal
(
it
.
Schema
,
schema
)
{
t
.
Fatalf
(
"got schema %v, want %v"
,
it
.
Schema
,
schema
)
}
want
:=
[]
Value
(
vl
)
if
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"%d: got %v, want %v"
,
i
,
got
,
want
)
}
}
// Test reading into a map.
it
=
table
.
Read
(
ctx
)
for
_
,
vl
:=
range
valueLists
{
var
vm
map
[
string
]
Value
if
err
:=
it
.
Next
(
&
vm
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
len
(
vm
),
len
(
vl
);
got
!=
want
{
t
.
Fatalf
(
"valueMap len: got %d, want %d"
,
got
,
want
)
}
// With maps, structs become nested maps.
vl
[
2
]
=
map
[
string
]
Value
{
"bool"
:
vl
[
2
]
.
([]
Value
)[
0
]}
for
i
,
v
:=
range
vl
{
if
got
,
want
:=
vm
[
schema
[
i
]
.
Name
],
v
;
!
testutil
.
Equal
(
got
,
want
)
{
t
.
Errorf
(
"%d, name=%s: got %#v, want %#v"
,
i
,
schema
[
i
]
.
Name
,
got
,
want
)
}
}
}
}
type
SubSubTestStruct
struct
{
Integer
int64
}
type
SubTestStruct
struct
{
String
string
Record
SubSubTestStruct
RecordArray
[]
SubSubTestStruct
}
type
TestStruct
struct
{
Name
string
Bytes
[]
byte
Integer
int64
Float
float64
Boolean
bool
Timestamp
time
.
Time
Date
civil
.
Date
Time
civil
.
Time
DateTime
civil
.
DateTime
Numeric
*
big
.
Rat
StringArray
[]
string
IntegerArray
[]
int64
FloatArray
[]
float64
BooleanArray
[]
bool
TimestampArray
[]
time
.
Time
DateArray
[]
civil
.
Date
TimeArray
[]
civil
.
Time
DateTimeArray
[]
civil
.
DateTime
NumericArray
[]
*
big
.
Rat
Record
SubTestStruct
RecordArray
[]
SubTestStruct
}
// Round times to the microsecond for comparison purposes.
var
roundToMicros
=
cmp
.
Transformer
(
"RoundToMicros"
,
func
(
t
time
.
Time
)
time
.
Time
{
return
t
.
Round
(
time
.
Microsecond
)
})
func
TestIntegration_InsertAndReadStructs
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
schema
,
err
:=
InferSchema
(
TestStruct
{})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
d
:=
civil
.
Date
{
Year
:
2016
,
Month
:
3
,
Day
:
20
}
tm
:=
civil
.
Time
{
Hour
:
15
,
Minute
:
4
,
Second
:
5
,
Nanosecond
:
6000
}
ts
:=
time
.
Date
(
2016
,
3
,
20
,
15
,
4
,
5
,
6000
,
time
.
UTC
)
dtm
:=
civil
.
DateTime
{
Date
:
d
,
Time
:
tm
}
d2
:=
civil
.
Date
{
Year
:
1994
,
Month
:
5
,
Day
:
15
}
tm2
:=
civil
.
Time
{
Hour
:
1
,
Minute
:
2
,
Second
:
4
,
Nanosecond
:
0
}
ts2
:=
time
.
Date
(
1994
,
5
,
15
,
1
,
2
,
4
,
0
,
time
.
UTC
)
dtm2
:=
civil
.
DateTime
{
Date
:
d2
,
Time
:
tm2
}
// Populate the table.
ins
:=
table
.
Inserter
()
want
:=
[]
*
TestStruct
{
{
"a"
,
[]
byte
(
"byte"
),
42
,
3.14
,
true
,
ts
,
d
,
tm
,
dtm
,
big
.
NewRat
(
57
,
100
),
[]
string
{
"a"
,
"b"
},
[]
int64
{
1
,
2
},
[]
float64
{
1
,
1.41
},
[]
bool
{
true
,
false
},
[]
time
.
Time
{
ts
,
ts2
},
[]
civil
.
Date
{
d
,
d2
},
[]
civil
.
Time
{
tm
,
tm2
},
[]
civil
.
DateTime
{
dtm
,
dtm2
},
[]
*
big
.
Rat
{
big
.
NewRat
(
1
,
2
),
big
.
NewRat
(
3
,
5
)},
SubTestStruct
{
"string"
,
SubSubTestStruct
{
24
},
[]
SubSubTestStruct
{{
1
},
{
2
}},
},
[]
SubTestStruct
{
{
String
:
"empty"
},
{
"full"
,
SubSubTestStruct
{
1
},
[]
SubSubTestStruct
{{
1
},
{
2
}},
},
},
},
{
Name
:
"b"
,
Bytes
:
[]
byte
(
"byte2"
),
Integer
:
24
,
Float
:
4.13
,
Boolean
:
false
,
Timestamp
:
ts
,
Date
:
d
,
Time
:
tm
,
DateTime
:
dtm
,
Numeric
:
big
.
NewRat
(
4499
,
10000
),
},
}
var
savers
[]
*
StructSaver
for
_
,
s
:=
range
want
{
savers
=
append
(
savers
,
&
StructSaver
{
Schema
:
schema
,
Struct
:
s
})
}
if
err
:=
ins
.
Put
(
ctx
,
savers
);
err
!=
nil
{
t
.
Fatal
(
putError
(
err
))
}
// Wait until the data has been uploaded. This can take a few seconds, according
// to https://cloud.google.com/bigquery/streaming-data-into-bigquery.
if
err
:=
waitForRow
(
ctx
,
table
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Test iteration with structs.
it
:=
table
.
Read
(
ctx
)
var
got
[]
*
TestStruct
for
{
var
g
TestStruct
err
:=
it
.
Next
(
&
g
)
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
got
=
append
(
got
,
&
g
)
}
sort
.
Sort
(
byName
(
got
))
// BigQuery does not elide nils. It reports an error for nil fields.
for
i
,
g
:=
range
got
{
if
i
>=
len
(
want
)
{
t
.
Errorf
(
"%d: got %v, past end of want"
,
i
,
pretty
.
Value
(
g
))
}
else
if
diff
:=
testutil
.
Diff
(
g
,
want
[
i
],
roundToMicros
);
diff
!=
""
{
t
.
Errorf
(
"%d: got=-, want=+:
\n
%s"
,
i
,
diff
)
}
}
}
type
byName
[]
*
TestStruct
func
(
b
byName
)
Len
()
int
{
return
len
(
b
)
}
func
(
b
byName
)
Swap
(
i
,
j
int
)
{
b
[
i
],
b
[
j
]
=
b
[
j
],
b
[
i
]
}
func
(
b
byName
)
Less
(
i
,
j
int
)
bool
{
return
b
[
i
]
.
Name
<
b
[
j
]
.
Name
}
func
TestIntegration_InsertAndReadNullable
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctm
:=
civil
.
Time
{
Hour
:
15
,
Minute
:
4
,
Second
:
5
,
Nanosecond
:
6000
}
cdt
:=
civil
.
DateTime
{
Date
:
testDate
,
Time
:
ctm
}
rat
:=
big
.
NewRat
(
33
,
100
)
testInsertAndReadNullable
(
t
,
testStructNullable
{},
make
([]
Value
,
len
(
testStructNullableSchema
)))
testInsertAndReadNullable
(
t
,
testStructNullable
{
String
:
NullString
{
"x"
,
true
},
Bytes
:
[]
byte
{
1
,
2
,
3
},
Integer
:
NullInt64
{
1
,
true
},
Float
:
NullFloat64
{
2.3
,
true
},
Boolean
:
NullBool
{
true
,
true
},
Timestamp
:
NullTimestamp
{
testTimestamp
,
true
},
Date
:
NullDate
{
testDate
,
true
},
Time
:
NullTime
{
ctm
,
true
},
DateTime
:
NullDateTime
{
cdt
,
true
},
Numeric
:
rat
,
Record
:
&
subNullable
{
X
:
NullInt64
{
4
,
true
}},
},
[]
Value
{
"x"
,
[]
byte
{
1
,
2
,
3
},
int64
(
1
),
2.3
,
true
,
testTimestamp
,
testDate
,
ctm
,
cdt
,
rat
,
[]
Value
{
int64
(
4
)}})
}
func
testInsertAndReadNullable
(
t
*
testing
.
T
,
ts
testStructNullable
,
wantRow
[]
Value
)
{
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
testStructNullableSchema
)
defer
table
.
Delete
(
ctx
)
// Populate the table.
ins
:=
table
.
Inserter
()
if
err
:=
ins
.
Put
(
ctx
,
[]
*
StructSaver
{{
Schema
:
testStructNullableSchema
,
Struct
:
ts
}});
err
!=
nil
{
t
.
Fatal
(
putError
(
err
))
}
// Wait until the data has been uploaded. This can take a few seconds, according
// to https://cloud.google.com/bigquery/streaming-data-into-bigquery.
if
err
:=
waitForRow
(
ctx
,
table
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Read into a []Value.
iter
:=
table
.
Read
(
ctx
)
gotRows
,
_
,
_
,
err
:=
readAll
(
iter
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
len
(
gotRows
)
!=
1
{
t
.
Fatalf
(
"got %d rows, want 1"
,
len
(
gotRows
))
}
if
diff
:=
testutil
.
Diff
(
gotRows
[
0
],
wantRow
,
roundToMicros
);
diff
!=
""
{
t
.
Error
(
diff
)
}
// Read into a struct.
want
:=
ts
var
sn
testStructNullable
it
:=
table
.
Read
(
ctx
)
if
err
:=
it
.
Next
(
&
sn
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
diff
:=
testutil
.
Diff
(
sn
,
want
,
roundToMicros
);
diff
!=
""
{
t
.
Error
(
diff
)
}
}
func
TestIntegration_TableUpdate
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Test Update of non-schema fields.
tm
,
err
:=
table
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
wantDescription
:=
tm
.
Description
+
"more"
wantName
:=
tm
.
Name
+
"more"
wantExpiration
:=
tm
.
ExpirationTime
.
Add
(
time
.
Hour
*
24
)
got
,
err
:=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Description
:
wantDescription
,
Name
:
wantName
,
ExpirationTime
:
wantExpiration
,
},
tm
.
ETag
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
.
Description
!=
wantDescription
{
t
.
Errorf
(
"Description: got %q, want %q"
,
got
.
Description
,
wantDescription
)
}
if
got
.
Name
!=
wantName
{
t
.
Errorf
(
"Name: got %q, want %q"
,
got
.
Name
,
wantName
)
}
if
got
.
ExpirationTime
!=
wantExpiration
{
t
.
Errorf
(
"ExpirationTime: got %q, want %q"
,
got
.
ExpirationTime
,
wantExpiration
)
}
if
!
testutil
.
Equal
(
got
.
Schema
,
schema
)
{
t
.
Errorf
(
"Schema: got %v, want %v"
,
pretty
.
Value
(
got
.
Schema
),
pretty
.
Value
(
schema
))
}
// Blind write succeeds.
_
,
err
=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Name
:
"x"
},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Write with old etag fails.
_
,
err
=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Name
:
"y"
},
got
.
ETag
)
if
err
==
nil
{
t
.
Fatal
(
"Update with old ETag succeeded, wanted failure"
)
}
// Test schema update.
// Columns can be added. schema2 is the same as schema, except for the
// added column in the middle.
nested
:=
Schema
{
{
Name
:
"nested"
,
Type
:
BooleanFieldType
},
{
Name
:
"other"
,
Type
:
StringFieldType
},
}
schema2
:=
Schema
{
schema
[
0
],
{
Name
:
"rec2"
,
Type
:
RecordFieldType
,
Schema
:
nested
},
schema
[
1
],
schema
[
2
],
}
got
,
err
=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Schema
:
schema2
},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Wherever you add the column, it appears at the end.
schema3
:=
Schema
{
schema2
[
0
],
schema2
[
2
],
schema2
[
3
],
schema2
[
1
]}
if
!
testutil
.
Equal
(
got
.
Schema
,
schema3
)
{
t
.
Errorf
(
"add field:
\n
got %v
\n
want %v"
,
pretty
.
Value
(
got
.
Schema
),
pretty
.
Value
(
schema3
))
}
// Updating with the empty schema succeeds, but is a no-op.
got
,
err
=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Schema
:
Schema
{}},
""
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
!
testutil
.
Equal
(
got
.
Schema
,
schema3
)
{
t
.
Errorf
(
"empty schema:
\n
got %v
\n
want %v"
,
pretty
.
Value
(
got
.
Schema
),
pretty
.
Value
(
schema3
))
}
// Error cases when updating schema.
for
_
,
test
:=
range
[]
struct
{
desc
string
fields
Schema
}{
{
"change from optional to required"
,
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
,
Required
:
true
},
schema3
[
1
],
schema3
[
2
],
schema3
[
3
],
}},
{
"add a required field"
,
Schema
{
schema3
[
0
],
schema3
[
1
],
schema3
[
2
],
schema3
[
3
],
{
Name
:
"req"
,
Type
:
StringFieldType
,
Required
:
true
},
}},
{
"remove a field"
,
Schema
{
schema3
[
0
],
schema3
[
1
],
schema3
[
2
]}},
{
"remove a nested field"
,
Schema
{
schema3
[
0
],
schema3
[
1
],
schema3
[
2
],
{
Name
:
"rec2"
,
Type
:
RecordFieldType
,
Schema
:
Schema
{
nested
[
0
]}}}},
{
"remove all nested fields"
,
Schema
{
schema3
[
0
],
schema3
[
1
],
schema3
[
2
],
{
Name
:
"rec2"
,
Type
:
RecordFieldType
,
Schema
:
Schema
{}}}},
}
{
_
,
err
=
table
.
Update
(
ctx
,
TableMetadataToUpdate
{
Schema
:
Schema
(
test
.
fields
)},
""
)
if
err
==
nil
{
t
.
Errorf
(
"%s: want error, got nil"
,
test
.
desc
)
}
else
if
!
hasStatusCode
(
err
,
400
)
{
t
.
Errorf
(
"%s: want 400, got %v"
,
test
.
desc
,
err
)
}
}
}
func
TestIntegration_Load
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
// CSV data can't be loaded into a repeated field, so we use a different schema.
table
:=
newTable
(
t
,
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
},
{
Name
:
"nums"
,
Type
:
IntegerFieldType
},
})
defer
table
.
Delete
(
ctx
)
// Load the table from a reader.
r
:=
strings
.
NewReader
(
"a,0
\n
b,1
\n
c,2
\n
"
)
wantRows
:=
[][]
Value
{
{
"a"
,
int64
(
0
)},
{
"b"
,
int64
(
1
)},
{
"c"
,
int64
(
2
)},
}
rs
:=
NewReaderSource
(
r
)
loader
:=
table
.
LoaderFrom
(
rs
)
loader
.
WriteDisposition
=
WriteTruncate
loader
.
Labels
=
map
[
string
]
string
{
"test"
:
"go"
}
job
,
err
:=
loader
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
job
.
LastStatus
()
==
nil
{
t
.
Error
(
"no LastStatus"
)
}
conf
,
err
:=
job
.
Config
()
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
config
,
ok
:=
conf
.
(
*
LoadConfig
)
if
!
ok
{
t
.
Fatalf
(
"got %T, want LoadConfig"
,
conf
)
}
diff
:=
testutil
.
Diff
(
config
,
&
loader
.
LoadConfig
,
cmp
.
AllowUnexported
(
Table
{}),
cmpopts
.
IgnoreUnexported
(
Client
{},
ReaderSource
{}),
// returned schema is at top level, not in the config
cmpopts
.
IgnoreFields
(
FileConfig
{},
"Schema"
))
if
diff
!=
""
{
t
.
Errorf
(
"got=-, want=+:
\n
%s"
,
diff
)
}
if
err
:=
wait
(
ctx
,
job
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkReadAndTotalRows
(
t
,
"reader load"
,
table
.
Read
(
ctx
),
wantRows
)
}
func
TestIntegration_DML
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
sql
:=
fmt
.
Sprintf
(
`INSERT %s.%s (name, nums, rec)
VALUES ('a', [0], STRUCT<BOOL>(TRUE)),
('b', [1], STRUCT<BOOL>(FALSE)),
('c', [2], STRUCT<BOOL>(TRUE))`
,
table
.
DatasetID
,
table
.
TableID
)
if
err
:=
runDML
(
ctx
,
sql
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
wantRows
:=
[][]
Value
{
{
"a"
,
[]
Value
{
int64
(
0
)},
[]
Value
{
true
}},
{
"b"
,
[]
Value
{
int64
(
1
)},
[]
Value
{
false
}},
{
"c"
,
[]
Value
{
int64
(
2
)},
[]
Value
{
true
}},
}
checkRead
(
t
,
"DML"
,
table
.
Read
(
ctx
),
wantRows
)
}
func
runDML
(
ctx
context
.
Context
,
sql
string
)
error
{
// Retry insert; sometimes it fails with INTERNAL.
return
internal
.
Retry
(
ctx
,
gax
.
Backoff
{},
func
()
(
stop
bool
,
err
error
)
{
ri
,
err
:=
client
.
Query
(
sql
)
.
Read
(
ctx
)
if
err
!=
nil
{
if
e
,
ok
:=
err
.
(
*
googleapi
.
Error
);
ok
&&
e
.
Code
<
500
{
return
true
,
err
// fail on 4xx
}
return
false
,
err
}
// It is OK to try to iterate over DML results. The first call to Next
// will return iterator.Done.
err
=
ri
.
Next
(
nil
)
if
err
==
nil
{
return
true
,
errors
.
New
(
"want iterator.Done on the first call, got nil"
)
}
if
err
==
iterator
.
Done
{
return
true
,
nil
}
if
e
,
ok
:=
err
.
(
*
googleapi
.
Error
);
ok
&&
e
.
Code
<
500
{
return
true
,
err
// fail on 4xx
}
return
false
,
err
})
}
func
TestIntegration_TimeTypes
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
dtSchema
:=
Schema
{
{
Name
:
"d"
,
Type
:
DateFieldType
},
{
Name
:
"t"
,
Type
:
TimeFieldType
},
{
Name
:
"dt"
,
Type
:
DateTimeFieldType
},
{
Name
:
"ts"
,
Type
:
TimestampFieldType
},
}
table
:=
newTable
(
t
,
dtSchema
)
defer
table
.
Delete
(
ctx
)
d
:=
civil
.
Date
{
Year
:
2016
,
Month
:
3
,
Day
:
20
}
tm
:=
civil
.
Time
{
Hour
:
12
,
Minute
:
30
,
Second
:
0
,
Nanosecond
:
6000
}
dtm
:=
civil
.
DateTime
{
Date
:
d
,
Time
:
tm
}
ts
:=
time
.
Date
(
2016
,
3
,
20
,
15
,
04
,
05
,
0
,
time
.
UTC
)
wantRows
:=
[][]
Value
{
{
d
,
tm
,
dtm
,
ts
},
}
ins
:=
table
.
Inserter
()
if
err
:=
ins
.
Put
(
ctx
,
[]
*
ValuesSaver
{
{
Schema
:
dtSchema
,
Row
:
wantRows
[
0
]},
});
err
!=
nil
{
t
.
Fatal
(
putError
(
err
))
}
if
err
:=
waitForRow
(
ctx
,
table
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// SQL wants DATETIMEs with a space between date and time, but the service
// returns them in RFC3339 form, with a "T" between.
query
:=
fmt
.
Sprintf
(
"INSERT %s.%s (d, t, dt, ts) "
+
"VALUES ('%s', '%s', '%s', '%s')"
,
table
.
DatasetID
,
table
.
TableID
,
d
,
CivilTimeString
(
tm
),
CivilDateTimeString
(
dtm
),
ts
.
Format
(
"2006-01-02 15:04:05"
))
if
err
:=
runDML
(
ctx
,
query
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
wantRows
=
append
(
wantRows
,
wantRows
[
0
])
checkRead
(
t
,
"TimeTypes"
,
table
.
Read
(
ctx
),
wantRows
)
}
func
TestIntegration_StandardQuery
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
d
:=
civil
.
Date
{
Year
:
2016
,
Month
:
3
,
Day
:
20
}
tm
:=
civil
.
Time
{
Hour
:
15
,
Minute
:
04
,
Second
:
05
,
Nanosecond
:
0
}
ts
:=
time
.
Date
(
2016
,
3
,
20
,
15
,
04
,
05
,
0
,
time
.
UTC
)
dtm
:=
ts
.
Format
(
"2006-01-02 15:04:05"
)
// Constructs Value slices made up of int64s.
ints
:=
func
(
args
...
int
)
[]
Value
{
vals
:=
make
([]
Value
,
len
(
args
))
for
i
,
arg
:=
range
args
{
vals
[
i
]
=
int64
(
arg
)
}
return
vals
}
testCases
:=
[]
struct
{
query
string
wantRow
[]
Value
}{
{
"SELECT 1"
,
ints
(
1
)},
{
"SELECT 1.3"
,
[]
Value
{
1.3
}},
{
"SELECT CAST(1.3 AS NUMERIC)"
,
[]
Value
{
big
.
NewRat
(
13
,
10
)}},
{
"SELECT NUMERIC '0.25'"
,
[]
Value
{
big
.
NewRat
(
1
,
4
)}},
{
"SELECT TRUE"
,
[]
Value
{
true
}},
{
"SELECT 'ABC'"
,
[]
Value
{
"ABC"
}},
{
"SELECT CAST('foo' AS BYTES)"
,
[]
Value
{[]
byte
(
"foo"
)}},
{
fmt
.
Sprintf
(
"SELECT TIMESTAMP '%s'"
,
dtm
),
[]
Value
{
ts
}},
{
fmt
.
Sprintf
(
"SELECT [TIMESTAMP '%s', TIMESTAMP '%s']"
,
dtm
,
dtm
),
[]
Value
{[]
Value
{
ts
,
ts
}}},
{
fmt
.
Sprintf
(
"SELECT ('hello', TIMESTAMP '%s')"
,
dtm
),
[]
Value
{[]
Value
{
"hello"
,
ts
}}},
{
fmt
.
Sprintf
(
"SELECT DATETIME(TIMESTAMP '%s')"
,
dtm
),
[]
Value
{
civil
.
DateTime
{
Date
:
d
,
Time
:
tm
}}},
{
fmt
.
Sprintf
(
"SELECT DATE(TIMESTAMP '%s')"
,
dtm
),
[]
Value
{
d
}},
{
fmt
.
Sprintf
(
"SELECT TIME(TIMESTAMP '%s')"
,
dtm
),
[]
Value
{
tm
}},
{
"SELECT (1, 2)"
,
[]
Value
{
ints
(
1
,
2
)}},
{
"SELECT [1, 2, 3]"
,
[]
Value
{
ints
(
1
,
2
,
3
)}},
{
"SELECT ([1, 2], 3, [4, 5])"
,
[]
Value
{[]
Value
{
ints
(
1
,
2
),
int64
(
3
),
ints
(
4
,
5
)}}},
{
"SELECT [(1, 2, 3), (4, 5, 6)]"
,
[]
Value
{[]
Value
{
ints
(
1
,
2
,
3
),
ints
(
4
,
5
,
6
)}}},
{
"SELECT [([1, 2, 3], 4), ([5, 6], 7)]"
,
[]
Value
{[]
Value
{[]
Value
{
ints
(
1
,
2
,
3
),
int64
(
4
)},
[]
Value
{
ints
(
5
,
6
),
int64
(
7
)}}}},
{
"SELECT ARRAY(SELECT STRUCT([1, 2]))"
,
[]
Value
{[]
Value
{[]
Value
{
ints
(
1
,
2
)}}}},
}
for
_
,
c
:=
range
testCases
{
q
:=
client
.
Query
(
c
.
query
)
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkRead
(
t
,
"StandardQuery"
,
it
,
[][]
Value
{
c
.
wantRow
})
}
}
func
TestIntegration_LegacyQuery
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
ts
:=
time
.
Date
(
2016
,
3
,
20
,
15
,
04
,
05
,
0
,
time
.
UTC
)
dtm
:=
ts
.
Format
(
"2006-01-02 15:04:05"
)
testCases
:=
[]
struct
{
query
string
wantRow
[]
Value
}{
{
"SELECT 1"
,
[]
Value
{
int64
(
1
)}},
{
"SELECT 1.3"
,
[]
Value
{
1.3
}},
{
"SELECT TRUE"
,
[]
Value
{
true
}},
{
"SELECT 'ABC'"
,
[]
Value
{
"ABC"
}},
{
"SELECT CAST('foo' AS BYTES)"
,
[]
Value
{[]
byte
(
"foo"
)}},
{
fmt
.
Sprintf
(
"SELECT TIMESTAMP('%s')"
,
dtm
),
[]
Value
{
ts
}},
{
fmt
.
Sprintf
(
"SELECT DATE(TIMESTAMP('%s'))"
,
dtm
),
[]
Value
{
"2016-03-20"
}},
{
fmt
.
Sprintf
(
"SELECT TIME(TIMESTAMP('%s'))"
,
dtm
),
[]
Value
{
"15:04:05"
}},
}
for
_
,
c
:=
range
testCases
{
q
:=
client
.
Query
(
c
.
query
)
q
.
UseLegacySQL
=
true
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkRead
(
t
,
"LegacyQuery"
,
it
,
[][]
Value
{
c
.
wantRow
})
}
}
func
TestIntegration_QueryParameters
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
d
:=
civil
.
Date
{
Year
:
2016
,
Month
:
3
,
Day
:
20
}
tm
:=
civil
.
Time
{
Hour
:
15
,
Minute
:
04
,
Second
:
05
,
Nanosecond
:
3008
}
rtm
:=
tm
rtm
.
Nanosecond
=
3000
// round to microseconds
dtm
:=
civil
.
DateTime
{
Date
:
d
,
Time
:
tm
}
ts
:=
time
.
Date
(
2016
,
3
,
20
,
15
,
04
,
05
,
0
,
time
.
UTC
)
rat
:=
big
.
NewRat
(
13
,
10
)
type
ss
struct
{
String
string
}
type
s
struct
{
Timestamp
time
.
Time
StringArray
[]
string
SubStruct
ss
SubStructArray
[]
ss
}
testCases
:=
[]
struct
{
query
string
parameters
[]
QueryParameter
wantRow
[]
Value
wantConfig
interface
{}
}{
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
1
}},
[]
Value
{
int64
(
1
)},
int64
(
1
),
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
1.3
}},
[]
Value
{
1.3
},
1.3
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
rat
}},
[]
Value
{
rat
},
rat
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
true
}},
[]
Value
{
true
},
true
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
"ABC"
}},
[]
Value
{
"ABC"
},
"ABC"
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
[]
byte
(
"foo"
)}},
[]
Value
{[]
byte
(
"foo"
)},
[]
byte
(
"foo"
),
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
ts
}},
[]
Value
{
ts
},
ts
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
[]
time
.
Time
{
ts
,
ts
}}},
[]
Value
{[]
Value
{
ts
,
ts
}},
[]
interface
{}{
ts
,
ts
},
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
dtm
}},
[]
Value
{
civil
.
DateTime
{
Date
:
d
,
Time
:
rtm
}},
civil
.
DateTime
{
Date
:
d
,
Time
:
rtm
},
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
d
}},
[]
Value
{
d
},
d
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
tm
}},
[]
Value
{
rtm
},
rtm
,
},
{
"SELECT @val"
,
[]
QueryParameter
{{
"val"
,
s
{
ts
,
[]
string
{
"a"
,
"b"
},
ss
{
"c"
},
[]
ss
{{
"d"
},
{
"e"
}}}}},
[]
Value
{[]
Value
{
ts
,
[]
Value
{
"a"
,
"b"
},
[]
Value
{
"c"
},
[]
Value
{[]
Value
{
"d"
},
[]
Value
{
"e"
}}}},
map
[
string
]
interface
{}{
"Timestamp"
:
ts
,
"StringArray"
:
[]
interface
{}{
"a"
,
"b"
},
"SubStruct"
:
map
[
string
]
interface
{}{
"String"
:
"c"
},
"SubStructArray"
:
[]
interface
{}{
map
[
string
]
interface
{}{
"String"
:
"d"
},
map
[
string
]
interface
{}{
"String"
:
"e"
},
},
},
},
{
"SELECT @val.Timestamp, @val.SubStruct.String"
,
[]
QueryParameter
{{
"val"
,
s
{
Timestamp
:
ts
,
SubStruct
:
ss
{
"a"
}}}},
[]
Value
{
ts
,
"a"
},
map
[
string
]
interface
{}{
"Timestamp"
:
ts
,
"SubStruct"
:
map
[
string
]
interface
{}{
"String"
:
"a"
},
"StringArray"
:
nil
,
"SubStructArray"
:
nil
,
},
},
}
for
_
,
c
:=
range
testCases
{
q
:=
client
.
Query
(
c
.
query
)
q
.
Parameters
=
c
.
parameters
job
,
err
:=
q
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
job
.
LastStatus
()
==
nil
{
t
.
Error
(
"no LastStatus"
)
}
it
,
err
:=
job
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkRead
(
t
,
"QueryParameters"
,
it
,
[][]
Value
{
c
.
wantRow
})
config
,
err
:=
job
.
Config
()
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
got
:=
config
.
(
*
QueryConfig
)
.
Parameters
[
0
]
.
Value
if
!
testutil
.
Equal
(
got
,
c
.
wantConfig
)
{
t
.
Errorf
(
"param %[1]v (%[1]T): config:
\n
got %[2]v (%[2]T)
\n
want %[3]v (%[3]T)"
,
c
.
parameters
[
0
]
.
Value
,
got
,
c
.
wantConfig
)
}
}
}
func
TestIntegration_QueryDryRun
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
q
:=
client
.
Query
(
"SELECT word from "
+
stdName
+
" LIMIT 10"
)
q
.
DryRun
=
true
job
,
err
:=
q
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
s
:=
job
.
LastStatus
()
if
s
.
State
!=
Done
{
t
.
Errorf
(
"state is %v, expected Done"
,
s
.
State
)
}
if
s
.
Statistics
==
nil
{
t
.
Fatal
(
"no statistics"
)
}
if
s
.
Statistics
.
Details
.
(
*
QueryStatistics
)
.
Schema
==
nil
{
t
.
Fatal
(
"no schema"
)
}
}
func
TestIntegration_ExtractExternal
(
t
*
testing
.
T
)
{
// Create a table, extract it to GCS, then query it externally.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
schema
:=
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
},
{
Name
:
"num"
,
Type
:
IntegerFieldType
},
}
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Insert table data.
sql
:=
fmt
.
Sprintf
(
`INSERT %s.%s (name, num)
VALUES ('a', 1), ('b', 2), ('c', 3)`
,
table
.
DatasetID
,
table
.
TableID
)
if
err
:=
runDML
(
ctx
,
sql
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Extract to a GCS object as CSV.
bucketName
:=
testutil
.
ProjID
()
objectName
:=
fmt
.
Sprintf
(
"bq-test-%s.csv"
,
table
.
TableID
)
uri
:=
fmt
.
Sprintf
(
"gs://%s/%s"
,
bucketName
,
objectName
)
defer
storageClient
.
Bucket
(
bucketName
)
.
Object
(
objectName
)
.
Delete
(
ctx
)
gr
:=
NewGCSReference
(
uri
)
gr
.
DestinationFormat
=
CSV
e
:=
table
.
ExtractorTo
(
gr
)
job
,
err
:=
e
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
conf
,
err
:=
job
.
Config
()
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
config
,
ok
:=
conf
.
(
*
ExtractConfig
)
if
!
ok
{
t
.
Fatalf
(
"got %T, want ExtractConfig"
,
conf
)
}
diff
:=
testutil
.
Diff
(
config
,
&
e
.
ExtractConfig
,
cmp
.
AllowUnexported
(
Table
{}),
cmpopts
.
IgnoreUnexported
(
Client
{}))
if
diff
!=
""
{
t
.
Errorf
(
"got=-, want=+:
\n
%s"
,
diff
)
}
if
err
:=
wait
(
ctx
,
job
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
edc
:=
&
ExternalDataConfig
{
SourceFormat
:
CSV
,
SourceURIs
:
[]
string
{
uri
},
Schema
:
schema
,
Options
:
&
CSVOptions
{
SkipLeadingRows
:
1
},
}
// Query that CSV file directly.
q
:=
client
.
Query
(
"SELECT * FROM csv"
)
q
.
TableDefinitions
=
map
[
string
]
ExternalData
{
"csv"
:
edc
}
wantRows
:=
[][]
Value
{
{
"a"
,
int64
(
1
)},
{
"b"
,
int64
(
2
)},
{
"c"
,
int64
(
3
)},
}
iter
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkReadAndTotalRows
(
t
,
"external query"
,
iter
,
wantRows
)
// Make a table pointing to the file, and query it.
// BigQuery does not allow a Table.Read on an external table.
table
=
dataset
.
Table
(
tableIDs
.
New
())
err
=
table
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
schema
,
ExpirationTime
:
testTableExpiration
,
ExternalDataConfig
:
edc
,
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
q
=
client
.
Query
(
fmt
.
Sprintf
(
"SELECT * FROM %s.%s"
,
table
.
DatasetID
,
table
.
TableID
))
iter
,
err
=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
checkReadAndTotalRows
(
t
,
"external table"
,
iter
,
wantRows
)
// While we're here, check that the table metadata is correct.
md
,
err
:=
table
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
// One difference: since BigQuery returns the schema as part of the ordinary
// table metadata, it does not populate ExternalDataConfig.Schema.
md
.
ExternalDataConfig
.
Schema
=
md
.
Schema
if
diff
:=
testutil
.
Diff
(
md
.
ExternalDataConfig
,
edc
);
diff
!=
""
{
t
.
Errorf
(
"got=-, want=+
\n
%s"
,
diff
)
}
}
func
TestIntegration_ReadNullIntoStruct
(
t
*
testing
.
T
)
{
// Reading a null into a struct field should return an error (not panic).
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
ins
:=
table
.
Inserter
()
row
:=
&
ValuesSaver
{
Schema
:
schema
,
Row
:
[]
Value
{
nil
,
[]
Value
{},
[]
Value
{
nil
}},
}
if
err
:=
ins
.
Put
(
ctx
,
[]
*
ValuesSaver
{
row
});
err
!=
nil
{
t
.
Fatal
(
putError
(
err
))
}
if
err
:=
waitForRow
(
ctx
,
table
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
q
:=
client
.
Query
(
fmt
.
Sprintf
(
"select name from %s"
,
table
.
TableID
))
q
.
DefaultProjectID
=
dataset
.
ProjectID
q
.
DefaultDatasetID
=
dataset
.
DatasetID
it
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
type
S
struct
{
Name
string
}
var
s
S
if
err
:=
it
.
Next
(
&
s
);
err
==
nil
{
t
.
Fatal
(
"got nil, want error"
)
}
}
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
:
true
},
// standard 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
:
false
},
// standard 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:
\n
unexpected error: %v"
,
test
,
err
)
}
else
if
!
gotErr
&&
test
.
err
{
t
.
Errorf
(
"%+v:
\n
succeeded, but want error"
,
test
)
}
}
}
func
TestIntegration_TableUseLegacySQL
(
t
*
testing
.
T
)
{
// Test UseLegacySQL and UseStandardSQL for Table.Create.
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
))
tm
:=
&
TableMetadata
{
ViewQuery
:
fmt
.
Sprintf
(
"SELECT word from %s"
,
test
.
t
),
UseStandardSQL
:
test
.
std
,
UseLegacySQL
:
test
.
legacy
,
}
err
:=
view
.
Create
(
ctx
,
tm
)
gotErr
:=
err
!=
nil
if
gotErr
&&
!
test
.
err
{
t
.
Errorf
(
"%+v:
\n
unexpected error: %v"
,
test
,
err
)
}
else
if
!
gotErr
&&
test
.
err
{
t
.
Errorf
(
"%+v:
\n
succeeded, but want error"
,
test
)
}
_
=
view
.
Delete
(
ctx
)
}
}
func
TestIntegration_ListJobs
(
t
*
testing
.
T
)
{
// It's difficult to test the list of jobs, because we can't easily
// control what's in it. Also, there are many jobs in the test project,
// and it takes considerable time to list them all.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
// About all we can do is list a few jobs.
const
max
=
20
var
jobs
[]
*
Job
it
:=
client
.
Jobs
(
ctx
)
for
{
job
,
err
:=
it
.
Next
()
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
jobs
=
append
(
jobs
,
job
)
if
len
(
jobs
)
>=
max
{
break
}
}
// We expect that there is at least one job in the last few months.
if
len
(
jobs
)
==
0
{
t
.
Fatal
(
"did not get any jobs"
)
}
}
const
tokyo
=
"asia-northeast1"
func
TestIntegration_Location
(
t
*
testing
.
T
)
{
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
client
.
Location
=
""
testLocation
(
t
,
tokyo
)
client
.
Location
=
tokyo
defer
func
()
{
client
.
Location
=
""
}()
testLocation
(
t
,
""
)
}
func
testLocation
(
t
*
testing
.
T
,
loc
string
)
{
ctx
:=
context
.
Background
()
tokyoDataset
:=
client
.
Dataset
(
"tokyo"
)
err
:=
tokyoDataset
.
Create
(
ctx
,
&
DatasetMetadata
{
Location
:
loc
})
if
err
!=
nil
&&
!
hasStatusCode
(
err
,
409
)
{
// 409 = already exists
t
.
Fatal
(
err
)
}
md
,
err
:=
tokyoDataset
.
Metadata
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
md
.
Location
!=
tokyo
{
t
.
Fatalf
(
"dataset location: got %s, want %s"
,
md
.
Location
,
tokyo
)
}
table
:=
tokyoDataset
.
Table
(
tableIDs
.
New
())
err
=
table
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
Schema
{
{
Name
:
"name"
,
Type
:
StringFieldType
},
{
Name
:
"nums"
,
Type
:
IntegerFieldType
},
},
ExpirationTime
:
testTableExpiration
,
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
defer
table
.
Delete
(
ctx
)
loader
:=
table
.
LoaderFrom
(
NewReaderSource
(
strings
.
NewReader
(
"a,0
\n
b,1
\n
c,2
\n
"
)))
loader
.
Location
=
loc
job
,
err
:=
loader
.
Run
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
"loader.Run"
,
err
)
}
if
job
.
Location
()
!=
tokyo
{
t
.
Fatalf
(
"job location: got %s, want %s"
,
job
.
Location
(),
tokyo
)
}
_
,
err
=
client
.
JobFromID
(
ctx
,
job
.
ID
())
if
client
.
Location
==
""
&&
err
==
nil
{
t
.
Error
(
"JobFromID with Tokyo job, no client location: want error, got nil"
)
}
if
client
.
Location
!=
""
&&
err
!=
nil
{
t
.
Errorf
(
"JobFromID with Tokyo job, with client location: want nil, got %v"
,
err
)
}
_
,
err
=
client
.
JobFromIDLocation
(
ctx
,
job
.
ID
(),
"US"
)
if
err
==
nil
{
t
.
Error
(
"JobFromIDLocation with US: want error, got nil"
)
}
job2
,
err
:=
client
.
JobFromIDLocation
(
ctx
,
job
.
ID
(),
loc
)
if
loc
==
tokyo
&&
err
!=
nil
{
t
.
Errorf
(
"loc=tokyo: %v"
,
err
)
}
if
loc
==
""
&&
err
==
nil
{
t
.
Error
(
"loc empty: got nil, want error"
)
}
if
job2
!=
nil
&&
(
job2
.
ID
()
!=
job
.
ID
()
||
job2
.
Location
()
!=
tokyo
)
{
t
.
Errorf
(
"got id %s loc %s, want id%s loc %s"
,
job2
.
ID
(),
job2
.
Location
(),
job
.
ID
(),
tokyo
)
}
if
err
:=
wait
(
ctx
,
job
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
// Cancel should succeed even if the job is done.
if
err
:=
job
.
Cancel
(
ctx
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
q
:=
client
.
Query
(
fmt
.
Sprintf
(
"SELECT * FROM %s.%s"
,
table
.
DatasetID
,
table
.
TableID
))
q
.
Location
=
loc
iter
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
wantRows
:=
[][]
Value
{
{
"a"
,
int64
(
0
)},
{
"b"
,
int64
(
1
)},
{
"c"
,
int64
(
2
)},
}
checkRead
(
t
,
"location"
,
iter
,
wantRows
)
table2
:=
tokyoDataset
.
Table
(
tableIDs
.
New
())
copier
:=
table2
.
CopierFrom
(
table
)
copier
.
Location
=
loc
if
_
,
err
:=
copier
.
Run
(
ctx
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
bucketName
:=
testutil
.
ProjID
()
objectName
:=
fmt
.
Sprintf
(
"bq-test-%s.csv"
,
table
.
TableID
)
uri
:=
fmt
.
Sprintf
(
"gs://%s/%s"
,
bucketName
,
objectName
)
defer
storageClient
.
Bucket
(
bucketName
)
.
Object
(
objectName
)
.
Delete
(
ctx
)
gr
:=
NewGCSReference
(
uri
)
gr
.
DestinationFormat
=
CSV
e
:=
table
.
ExtractorTo
(
gr
)
e
.
Location
=
loc
if
_
,
err
:=
e
.
Run
(
ctx
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
func
TestIntegration_NumericErrors
(
t
*
testing
.
T
)
{
// Verify that the service returns an error for a big.Rat that's too large.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
schema
:=
Schema
{{
Name
:
"n"
,
Type
:
NumericFieldType
}}
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
tooBigRat
:=
&
big
.
Rat
{}
if
_
,
ok
:=
tooBigRat
.
SetString
(
"1e40"
);
!
ok
{
t
.
Fatal
(
"big.Rat.SetString failed"
)
}
ins
:=
table
.
Inserter
()
err
:=
ins
.
Put
(
ctx
,
[]
*
ValuesSaver
{{
Schema
:
schema
,
Row
:
[]
Value
{
tooBigRat
}}})
if
err
==
nil
{
t
.
Fatal
(
"got nil, want error"
)
}
}
func
TestIntegration_QueryErrors
(
t
*
testing
.
T
)
{
// Verify that a bad query returns an appropriate error.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
q
:=
client
.
Query
(
"blah blah broken"
)
_
,
err
:=
q
.
Read
(
ctx
)
const
want
=
"invalidQuery"
if
!
strings
.
Contains
(
err
.
Error
(),
want
)
{
t
.
Fatalf
(
"got %q, want substring %q"
,
err
,
want
)
}
}
func
TestIntegration_Model
(
t
*
testing
.
T
)
{
// Create an ML model.
if
client
==
nil
{
t
.
Skip
(
"Integration tests skipped"
)
}
ctx
:=
context
.
Background
()
schema
:=
Schema
{
{
Name
:
"input"
,
Type
:
IntegerFieldType
},
{
Name
:
"label"
,
Type
:
IntegerFieldType
},
}
table
:=
newTable
(
t
,
schema
)
defer
table
.
Delete
(
ctx
)
// Insert table data.
tableName
:=
fmt
.
Sprintf
(
"%s.%s"
,
table
.
DatasetID
,
table
.
TableID
)
sql
:=
fmt
.
Sprintf
(
`INSERT %s (input, label)
VALUES (1, 0), (2, 1), (3, 0), (4, 1)`
,
tableName
)
wantNumRows
:=
4
if
err
:=
runDML
(
ctx
,
sql
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
model
:=
dataset
.
Table
(
"my_model"
)
modelName
:=
fmt
.
Sprintf
(
"%s.%s"
,
model
.
DatasetID
,
model
.
TableID
)
sql
=
fmt
.
Sprintf
(
`CREATE MODEL %s OPTIONS (model_type='logistic_reg') AS SELECT input, label FROM %s`
,
modelName
,
tableName
)
if
err
:=
runDML
(
ctx
,
sql
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
defer
model
.
Delete
(
ctx
)
sql
=
fmt
.
Sprintf
(
`SELECT * FROM ml.PREDICT(MODEL %s, TABLE %s)`
,
modelName
,
tableName
)
q
:=
client
.
Query
(
sql
)
ri
,
err
:=
q
.
Read
(
ctx
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
rows
,
_
,
_
,
err
:=
readAll
(
ri
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
:=
len
(
rows
);
got
!=
wantNumRows
{
t
.
Fatalf
(
"got %d rows in prediction table, want %d"
,
got
,
wantNumRows
)
}
iter
:=
dataset
.
Tables
(
ctx
)
seen
:=
false
for
{
tbl
,
err
:=
iter
.
Next
()
if
err
==
iterator
.
Done
{
break
}
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
tbl
.
TableID
==
"my_model"
{
seen
=
true
}
}
if
!
seen
{
t
.
Fatal
(
"model not listed in dataset"
)
}
if
err
:=
model
.
Delete
(
ctx
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
// Creates a new, temporary table with a unique name and the given schema.
func
newTable
(
t
*
testing
.
T
,
s
Schema
)
*
Table
{
table
:=
dataset
.
Table
(
tableIDs
.
New
())
err
:=
table
.
Create
(
context
.
Background
(),
&
TableMetadata
{
Schema
:
s
,
ExpirationTime
:
testTableExpiration
,
})
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
return
table
}
func
checkRead
(
t
*
testing
.
T
,
msg
string
,
it
*
RowIterator
,
want
[][]
Value
)
{
if
msg2
,
ok
:=
compareRead
(
it
,
want
,
false
);
!
ok
{
t
.
Errorf
(
"%s: %s"
,
msg
,
msg2
)
}
}
func
checkReadAndTotalRows
(
t
*
testing
.
T
,
msg
string
,
it
*
RowIterator
,
want
[][]
Value
)
{
if
msg2
,
ok
:=
compareRead
(
it
,
want
,
true
);
!
ok
{
t
.
Errorf
(
"%s: %s"
,
msg
,
msg2
)
}
}
func
compareRead
(
it
*
RowIterator
,
want
[][]
Value
,
compareTotalRows
bool
)
(
msg
string
,
ok
bool
)
{
got
,
_
,
totalRows
,
err
:=
readAll
(
it
)
if
err
!=
nil
{
return
err
.
Error
(),
false
}
if
len
(
got
)
!=
len
(
want
)
{
return
fmt
.
Sprintf
(
"got %d rows, want %d"
,
len
(
got
),
len
(
want
)),
false
}
if
compareTotalRows
&&
len
(
got
)
!=
int
(
totalRows
)
{
return
fmt
.
Sprintf
(
"got %d rows, but totalRows = %d"
,
len
(
got
),
totalRows
),
false
}
sort
.
Sort
(
byCol0
(
got
))
for
i
,
r
:=
range
got
{
gotRow
:=
[]
Value
(
r
)
wantRow
:=
want
[
i
]
if
!
testutil
.
Equal
(
gotRow
,
wantRow
)
{
return
fmt
.
Sprintf
(
"#%d: got %#v, want %#v"
,
i
,
gotRow
,
wantRow
),
false
}
}
return
""
,
true
}
func
readAll
(
it
*
RowIterator
)
([][]
Value
,
Schema
,
uint64
,
error
)
{
var
(
rows
[][]
Value
schema
Schema
totalRows
uint64
)
for
{
var
vals
[]
Value
err
:=
it
.
Next
(
&
vals
)
if
err
==
iterator
.
Done
{
return
rows
,
schema
,
totalRows
,
nil
}
if
err
!=
nil
{
return
nil
,
nil
,
0
,
err
}
rows
=
append
(
rows
,
vals
)
schema
=
it
.
Schema
totalRows
=
it
.
TotalRows
}
}
type
byCol0
[][]
Value
func
(
b
byCol0
)
Len
()
int
{
return
len
(
b
)
}
func
(
b
byCol0
)
Swap
(
i
,
j
int
)
{
b
[
i
],
b
[
j
]
=
b
[
j
],
b
[
i
]
}
func
(
b
byCol0
)
Less
(
i
,
j
int
)
bool
{
switch
a
:=
b
[
i
][
0
]
.
(
type
)
{
case
string
:
return
a
<
b
[
j
][
0
]
.
(
string
)
case
civil
.
Date
:
return
a
.
Before
(
b
[
j
][
0
]
.
(
civil
.
Date
))
default
:
panic
(
"unknown type"
)
}
}
func
hasStatusCode
(
err
error
,
code
int
)
bool
{
if
e
,
ok
:=
err
.
(
*
googleapi
.
Error
);
ok
&&
e
.
Code
==
code
{
return
true
}
return
false
}
// wait polls the job until it is complete or an error is returned.
func
wait
(
ctx
context
.
Context
,
job
*
Job
)
error
{
status
,
err
:=
job
.
Wait
(
ctx
)
if
err
!=
nil
{
return
err
}
if
status
.
Err
()
!=
nil
{
return
fmt
.
Errorf
(
"job status error: %#v"
,
status
.
Err
())
}
if
status
.
Statistics
==
nil
{
return
errors
.
New
(
"nil Statistics"
)
}
if
status
.
Statistics
.
EndTime
.
IsZero
()
{
return
errors
.
New
(
"EndTime is zero"
)
}
if
status
.
Statistics
.
Details
==
nil
{
return
errors
.
New
(
"nil Statistics.Details"
)
}
return
nil
}
// waitForRow polls the table until it contains a row.
// TODO(jba): use internal.Retry.
func
waitForRow
(
ctx
context
.
Context
,
table
*
Table
)
error
{
for
{
it
:=
table
.
Read
(
ctx
)
var
v
[]
Value
err
:=
it
.
Next
(
&
v
)
if
err
==
nil
{
return
nil
}
if
err
!=
iterator
.
Done
{
return
err
}
time
.
Sleep
(
1
*
time
.
Second
)
}
}
func
putError
(
err
error
)
string
{
pme
,
ok
:=
err
.
(
PutMultiError
)
if
!
ok
{
return
err
.
Error
()
}
var
msgs
[]
string
for
_
,
err
:=
range
pme
{
msgs
=
append
(
msgs
,
err
.
Error
())
}
return
strings
.
Join
(
msgs
,
"
\n
"
)
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/iterator.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"fmt"
"reflect"
bq
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/iterator"
)
// Construct a RowIterator.
// If pf is nil, there are no rows in the result set.
func
newRowIterator
(
ctx
context
.
Context
,
t
*
Table
,
pf
pageFetcher
)
*
RowIterator
{
it
:=
&
RowIterator
{
ctx
:
ctx
,
table
:
t
,
pf
:
pf
,
}
if
pf
!=
nil
{
it
.
pageInfo
,
it
.
nextFunc
=
iterator
.
NewPageInfo
(
it
.
fetch
,
func
()
int
{
return
len
(
it
.
rows
)
},
func
()
interface
{}
{
r
:=
it
.
rows
;
it
.
rows
=
nil
;
return
r
})
}
return
it
}
// A RowIterator provides access to the result of a BigQuery lookup.
type
RowIterator
struct
{
ctx
context
.
Context
table
*
Table
pf
pageFetcher
pageInfo
*
iterator
.
PageInfo
nextFunc
func
()
error
// StartIndex can be set before the first call to Next. If PageInfo().Token
// is also set, StartIndex is ignored.
StartIndex
uint64
// The schema of the table. Available after the first call to Next.
Schema
Schema
// The total number of rows in the result. Available after the first call to Next.
// May be zero just after rows were inserted.
TotalRows
uint64
rows
[][]
Value
structLoader
structLoader
// used to populate a pointer to a struct
}
// Next loads the next row into dst. Its return value is iterator.Done if there
// are no more results. Once Next returns iterator.Done, all subsequent calls
// will return iterator.Done.
//
// dst may implement ValueLoader, or may be a *[]Value, *map[string]Value, or struct pointer.
//
// If dst is a *[]Value, it will be set to new []Value whose i'th element
// will be populated with the i'th column of the row.
//
// If dst is a *map[string]Value, a new map will be created if dst is nil. Then
// for each schema column name, the map key of that name will be set to the column's
// value. STRUCT types (RECORD types or nested schemas) become nested maps.
//
// If dst is pointer to a struct, each column in the schema will be matched
// with an exported field of the struct that has the same name, ignoring case.
// Unmatched schema columns and struct fields will be ignored.
//
// Each BigQuery column type corresponds to one or more Go types; a matching struct
// field must be of the correct type. The correspondences are:
//
// STRING string
// BOOL bool
// INTEGER int, int8, int16, int32, int64, uint8, uint16, uint32
// FLOAT float32, float64
// BYTES []byte
// TIMESTAMP time.Time
// DATE civil.Date
// TIME civil.Time
// DATETIME civil.DateTime
//
// A repeated field corresponds to a slice or array of the element type. A STRUCT
// type (RECORD or nested schema) corresponds to a nested struct or struct pointer.
// All calls to Next on the same iterator must use the same struct type.
//
// It is an error to attempt to read a BigQuery NULL value into a struct field,
// unless the field is of type []byte or is one of the special Null types: NullInt64,
// NullFloat64, NullBool, NullString, NullTimestamp, NullDate, NullTime or
// NullDateTime. You can also use a *[]Value or *map[string]Value to read from a
// table with NULLs.
func
(
it
*
RowIterator
)
Next
(
dst
interface
{})
error
{
if
it
.
pf
==
nil
{
// There are no rows in the result set.
return
iterator
.
Done
}
var
vl
ValueLoader
switch
dst
:=
dst
.
(
type
)
{
case
ValueLoader
:
vl
=
dst
case
*
[]
Value
:
vl
=
(
*
valueList
)(
dst
)
case
*
map
[
string
]
Value
:
vl
=
(
*
valueMap
)(
dst
)
default
:
if
!
isStructPtr
(
dst
)
{
return
fmt
.
Errorf
(
"bigquery: cannot convert %T to ValueLoader (need pointer to []Value, map[string]Value, or struct)"
,
dst
)
}
}
if
err
:=
it
.
nextFunc
();
err
!=
nil
{
return
err
}
row
:=
it
.
rows
[
0
]
it
.
rows
=
it
.
rows
[
1
:
]
if
vl
==
nil
{
// This can only happen if dst is a pointer to a struct. We couldn't
// set vl above because we need the schema.
if
err
:=
it
.
structLoader
.
set
(
dst
,
it
.
Schema
);
err
!=
nil
{
return
err
}
vl
=
&
it
.
structLoader
}
return
vl
.
Load
(
row
,
it
.
Schema
)
}
func
isStructPtr
(
x
interface
{})
bool
{
t
:=
reflect
.
TypeOf
(
x
)
return
t
.
Kind
()
==
reflect
.
Ptr
&&
t
.
Elem
()
.
Kind
()
==
reflect
.
Struct
}
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
func
(
it
*
RowIterator
)
PageInfo
()
*
iterator
.
PageInfo
{
return
it
.
pageInfo
}
func
(
it
*
RowIterator
)
fetch
(
pageSize
int
,
pageToken
string
)
(
string
,
error
)
{
res
,
err
:=
it
.
pf
(
it
.
ctx
,
it
.
table
,
it
.
Schema
,
it
.
StartIndex
,
int64
(
pageSize
),
pageToken
)
if
err
!=
nil
{
return
""
,
err
}
it
.
rows
=
append
(
it
.
rows
,
res
.
rows
...
)
it
.
Schema
=
res
.
schema
it
.
TotalRows
=
res
.
totalRows
return
res
.
pageToken
,
nil
}
// A pageFetcher returns a page of rows from a destination table.
type
pageFetcher
func
(
ctx
context
.
Context
,
_
*
Table
,
_
Schema
,
startIndex
uint64
,
pageSize
int64
,
pageToken
string
)
(
*
fetchPageResult
,
error
)
type
fetchPageResult
struct
{
pageToken
string
rows
[][]
Value
totalRows
uint64
schema
Schema
}
// fetchPage gets a page of rows from t.
func
fetchPage
(
ctx
context
.
Context
,
t
*
Table
,
schema
Schema
,
startIndex
uint64
,
pageSize
int64
,
pageToken
string
)
(
*
fetchPageResult
,
error
)
{
// Fetch the table schema in the background, if necessary.
errc
:=
make
(
chan
error
,
1
)
if
schema
!=
nil
{
errc
<-
nil
}
else
{
go
func
()
{
var
bqt
*
bq
.
Table
err
:=
runWithRetry
(
ctx
,
func
()
(
err
error
)
{
bqt
,
err
=
t
.
c
.
bqs
.
Tables
.
Get
(
t
.
ProjectID
,
t
.
DatasetID
,
t
.
TableID
)
.
Fields
(
"schema"
)
.
Context
(
ctx
)
.
Do
()
return
err
})
if
err
==
nil
&&
bqt
.
Schema
!=
nil
{
schema
=
bqToSchema
(
bqt
.
Schema
)
}
errc
<-
err
}()
}
call
:=
t
.
c
.
bqs
.
Tabledata
.
List
(
t
.
ProjectID
,
t
.
DatasetID
,
t
.
TableID
)
setClientHeader
(
call
.
Header
())
if
pageToken
!=
""
{
call
.
PageToken
(
pageToken
)
}
else
{
call
.
StartIndex
(
startIndex
)
}
if
pageSize
>
0
{
call
.
MaxResults
(
pageSize
)
}
var
res
*
bq
.
TableDataList
err
:=
runWithRetry
(
ctx
,
func
()
(
err
error
)
{
res
,
err
=
call
.
Context
(
ctx
)
.
Do
()
return
err
})
if
err
!=
nil
{
return
nil
,
err
}
err
=
<-
errc
if
err
!=
nil
{
return
nil
,
err
}
rows
,
err
:=
convertRows
(
res
.
Rows
,
schema
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
fetchPageResult
{
pageToken
:
res
.
PageToken
,
rows
:
rows
,
totalRows
:
uint64
(
res
.
TotalRows
),
schema
:
schema
,
},
nil
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/iterator_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"errors"
"fmt"
"testing"
"cloud.google.com/go/internal/testutil"
"google.golang.org/api/iterator"
)
type
fetchResponse
struct
{
result
*
fetchPageResult
// The result to return.
err
error
// The error to return.
}
// pageFetcherStub services fetch requests by returning data from an in-memory list of values.
type
pageFetcherStub
struct
{
fetchResponses
map
[
string
]
fetchResponse
err
error
}
func
(
pf
*
pageFetcherStub
)
fetchPage
(
ctx
context
.
Context
,
_
*
Table
,
_
Schema
,
_
uint64
,
_
int64
,
pageToken
string
)
(
*
fetchPageResult
,
error
)
{
call
,
ok
:=
pf
.
fetchResponses
[
pageToken
]
if
!
ok
{
pf
.
err
=
fmt
.
Errorf
(
"Unexpected page token: %q"
,
pageToken
)
}
return
call
.
result
,
call
.
err
}
func
TestIterator
(
t
*
testing
.
T
)
{
var
(
iiSchema
=
Schema
{
{
Type
:
IntegerFieldType
},
{
Type
:
IntegerFieldType
},
}
siSchema
=
Schema
{
{
Type
:
StringFieldType
},
{
Type
:
IntegerFieldType
},
}
)
fetchFailure
:=
errors
.
New
(
"fetch failure"
)
testCases
:=
[]
struct
{
desc
string
pageToken
string
fetchResponses
map
[
string
]
fetchResponse
want
[][]
Value
wantErr
error
wantSchema
Schema
wantTotalRows
uint64
}{
{
desc
:
"Iteration over single empty page"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{},
schema
:
Schema
{},
},
},
},
want
:
[][]
Value
{},
wantSchema
:
Schema
{},
},
{
desc
:
"Iteration over single page"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
totalRows
:
4
,
},
},
},
want
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
wantSchema
:
iiSchema
,
wantTotalRows
:
4
,
},
{
desc
:
"Iteration over single page with different schema"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
"1"
,
2
},
{
"11"
,
12
}},
schema
:
siSchema
,
},
},
},
want
:
[][]
Value
{{
"1"
,
2
},
{
"11"
,
12
}},
wantSchema
:
siSchema
,
},
{
desc
:
"Iteration over two pages"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
"a"
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
totalRows
:
4
,
},
},
"a"
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
schema
:
iiSchema
,
totalRows
:
4
,
},
},
},
want
:
[][]
Value
{{
1
,
2
},
{
11
,
12
},
{
101
,
102
},
{
111
,
112
}},
wantSchema
:
iiSchema
,
wantTotalRows
:
4
,
},
{
desc
:
"Server response includes empty page"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
"a"
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
},
},
"a"
:
{
result
:
&
fetchPageResult
{
pageToken
:
"b"
,
rows
:
[][]
Value
{},
schema
:
iiSchema
,
},
},
"b"
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
schema
:
iiSchema
,
},
},
},
want
:
[][]
Value
{{
1
,
2
},
{
11
,
12
},
{
101
,
102
},
{
111
,
112
}},
wantSchema
:
iiSchema
,
},
{
desc
:
"Fetch error"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
"a"
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
},
},
"a"
:
{
// We returns some data from this fetch, but also an error.
// So the end result should include only data from the previous fetch.
err
:
fetchFailure
,
result
:
&
fetchPageResult
{
pageToken
:
"b"
,
rows
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
schema
:
iiSchema
,
},
},
},
want
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
wantErr
:
fetchFailure
,
wantSchema
:
iiSchema
,
},
{
desc
:
"Skip over an entire page"
,
pageToken
:
"a"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
"a"
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
},
},
"a"
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
schema
:
iiSchema
,
},
},
},
want
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
wantSchema
:
iiSchema
,
},
{
desc
:
"Skip beyond all data"
,
pageToken
:
"b"
,
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
"a"
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
schema
:
iiSchema
,
},
},
"a"
:
{
result
:
&
fetchPageResult
{
pageToken
:
"b"
,
rows
:
[][]
Value
{{
101
,
102
},
{
111
,
112
}},
schema
:
iiSchema
,
},
},
"b"
:
{
result
:
&
fetchPageResult
{},
},
},
// In this test case, Next will return false on its first call,
// so we won't even attempt to call Get.
want
:
[][]
Value
{},
wantSchema
:
Schema
{},
},
}
for
_
,
tc
:=
range
testCases
{
pf
:=
&
pageFetcherStub
{
fetchResponses
:
tc
.
fetchResponses
,
}
it
:=
newRowIterator
(
context
.
Background
(),
nil
,
pf
.
fetchPage
)
it
.
PageInfo
()
.
Token
=
tc
.
pageToken
values
,
schema
,
totalRows
,
err
:=
consumeRowIterator
(
it
)
if
err
!=
tc
.
wantErr
{
t
.
Fatalf
(
"%s: got %v, want %v"
,
tc
.
desc
,
err
,
tc
.
wantErr
)
}
if
(
len
(
values
)
!=
0
||
len
(
tc
.
want
)
!=
0
)
&&
!
testutil
.
Equal
(
values
,
tc
.
want
)
{
t
.
Errorf
(
"%s: values:
\n
got: %v
\n
want:%v"
,
tc
.
desc
,
values
,
tc
.
want
)
}
if
(
len
(
schema
)
!=
0
||
len
(
tc
.
wantSchema
)
!=
0
)
&&
!
testutil
.
Equal
(
schema
,
tc
.
wantSchema
)
{
t
.
Errorf
(
"%s: iterator.Schema:
\n
got: %v
\n
want: %v"
,
tc
.
desc
,
schema
,
tc
.
wantSchema
)
}
if
totalRows
!=
tc
.
wantTotalRows
{
t
.
Errorf
(
"%s: totalRows: got %d, want %d"
,
tc
.
desc
,
totalRows
,
tc
.
wantTotalRows
)
}
}
}
// consumeRowIterator reads the schema and all values from a RowIterator and returns them.
func
consumeRowIterator
(
it
*
RowIterator
)
([][]
Value
,
Schema
,
uint64
,
error
)
{
var
(
got
[][]
Value
schema
Schema
totalRows
uint64
)
for
{
var
vls
[]
Value
err
:=
it
.
Next
(
&
vls
)
if
err
==
iterator
.
Done
{
return
got
,
schema
,
totalRows
,
nil
}
if
err
!=
nil
{
return
got
,
schema
,
totalRows
,
err
}
got
=
append
(
got
,
vls
)
schema
=
it
.
Schema
totalRows
=
it
.
TotalRows
}
}
func
TestNextDuringErrorState
(
t
*
testing
.
T
)
{
pf
:=
&
pageFetcherStub
{
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
err
:
errors
.
New
(
"bang"
)},
},
}
it
:=
newRowIterator
(
context
.
Background
(),
nil
,
pf
.
fetchPage
)
var
vals
[]
Value
if
err
:=
it
.
Next
(
&
vals
);
err
==
nil
{
t
.
Errorf
(
"Expected error after calling Next"
)
}
if
err
:=
it
.
Next
(
&
vals
);
err
==
nil
{
t
.
Errorf
(
"Expected error calling Next again when iterator has a non-nil error."
)
}
}
func
TestNextAfterFinished
(
t
*
testing
.
T
)
{
testCases
:=
[]
struct
{
fetchResponses
map
[
string
]
fetchResponse
want
[][]
Value
}{
{
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
},
},
},
want
:
[][]
Value
{{
1
,
2
},
{
11
,
12
}},
},
{
fetchResponses
:
map
[
string
]
fetchResponse
{
""
:
{
result
:
&
fetchPageResult
{
pageToken
:
""
,
rows
:
[][]
Value
{},
},
},
},
want
:
[][]
Value
{},
},
}
for
_
,
tc
:=
range
testCases
{
pf
:=
&
pageFetcherStub
{
fetchResponses
:
tc
.
fetchResponses
,
}
it
:=
newRowIterator
(
context
.
Background
(),
nil
,
pf
.
fetchPage
)
values
,
_
,
_
,
err
:=
consumeRowIterator
(
it
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
(
len
(
values
)
!=
0
||
len
(
tc
.
want
)
!=
0
)
&&
!
testutil
.
Equal
(
values
,
tc
.
want
)
{
t
.
Errorf
(
"values: got:
\n
%v
\n
want:
\n
%v"
,
values
,
tc
.
want
)
}
// Try calling Get again.
var
vals
[]
Value
if
err
:=
it
.
Next
(
&
vals
);
err
!=
iterator
.
Done
{
t
.
Errorf
(
"Expected Done calling Next when there are no more values"
)
}
}
}
func
TestIteratorNextTypes
(
t
*
testing
.
T
)
{
it
:=
newRowIterator
(
context
.
Background
(),
nil
,
nil
)
for
_
,
v
:=
range
[]
interface
{}{
3
,
"s"
,
[]
int
{},
&
[]
int
{},
map
[
string
]
Value
{},
&
map
[
string
]
interface
{}{},
struct
{}{},
}
{
if
err
:=
it
.
Next
(
v
);
err
==
nil
{
t
.
Errorf
(
"%v: want error, got nil"
,
v
)
}
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"errors"
"fmt"
"time"
"cloud.google.com/go/internal"
"cloud.google.com/go/internal/trace"
gax
"github.com/googleapis/gax-go"
bq
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/googleapi"
"google.golang.org/api/iterator"
)
// A Job represents an operation which has been submitted to BigQuery for processing.
type
Job
struct
{
c
*
Client
projectID
string
jobID
string
location
string
email
string
config
*
bq
.
JobConfiguration
lastStatus
*
JobStatus
}
// JobFromID creates a Job which refers to an existing BigQuery job. The job
// need not have been created by this package. For example, the job may have
// been created in the BigQuery console.
//
// For jobs whose location is other than "US" or "EU", set Client.Location or use
// JobFromIDLocation.
func
(
c
*
Client
)
JobFromID
(
ctx
context
.
Context
,
id
string
)
(
*
Job
,
error
)
{
return
c
.
JobFromIDLocation
(
ctx
,
id
,
c
.
Location
)
}
// JobFromIDLocation creates a Job which refers to an existing BigQuery job. The job
// need not have been created by this package (for example, it may have
// been created in the BigQuery console), but it must exist in the specified location.
func
(
c
*
Client
)
JobFromIDLocation
(
ctx
context
.
Context
,
id
,
location
string
)
(
j
*
Job
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.JobFromIDLocation"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
bqjob
,
err
:=
c
.
getJobInternal
(
ctx
,
id
,
location
,
"configuration"
,
"jobReference"
,
"status"
,
"statistics"
)
if
err
!=
nil
{
return
nil
,
err
}
return
bqToJob
(
bqjob
,
c
)
}
// ID returns the job's ID.
func
(
j
*
Job
)
ID
()
string
{
return
j
.
jobID
}
// Location returns the job's location.
func
(
j
*
Job
)
Location
()
string
{
return
j
.
location
}
// Email returns the email of the job's creator.
func
(
j
*
Job
)
Email
()
string
{
return
j
.
email
}
// State is one of a sequence of states that a Job progresses through as it is processed.
type
State
int
const
(
// StateUnspecified is the default JobIterator state.
StateUnspecified
State
=
iota
// Pending is a state that describes that the job is pending.
Pending
// Running is a state that describes that the job is running.
Running
// Done is a state that describes that the job is done.
Done
)
// JobStatus contains the current State of a job, and errors encountered while processing that job.
type
JobStatus
struct
{
State
State
err
error
// All errors encountered during the running of the job.
// Not all Errors are fatal, so errors here do not necessarily mean that the job has completed or was unsuccessful.
Errors
[]
*
Error
// Statistics about the job.
Statistics
*
JobStatistics
}
// JobConfig contains configuration information for a job. It is implemented by
// *CopyConfig, *ExtractConfig, *LoadConfig and *QueryConfig.
type
JobConfig
interface
{
isJobConfig
()
}
func
(
*
CopyConfig
)
isJobConfig
()
{}
func
(
*
ExtractConfig
)
isJobConfig
()
{}
func
(
*
LoadConfig
)
isJobConfig
()
{}
func
(
*
QueryConfig
)
isJobConfig
()
{}
// Config returns the configuration information for j.
func
(
j
*
Job
)
Config
()
(
JobConfig
,
error
)
{
return
bqToJobConfig
(
j
.
config
,
j
.
c
)
}
func
bqToJobConfig
(
q
*
bq
.
JobConfiguration
,
c
*
Client
)
(
JobConfig
,
error
)
{
switch
{
case
q
==
nil
:
return
nil
,
nil
case
q
.
Copy
!=
nil
:
return
bqToCopyConfig
(
q
,
c
),
nil
case
q
.
Extract
!=
nil
:
return
bqToExtractConfig
(
q
,
c
),
nil
case
q
.
Load
!=
nil
:
return
bqToLoadConfig
(
q
,
c
),
nil
case
q
.
Query
!=
nil
:
return
bqToQueryConfig
(
q
,
c
)
default
:
return
nil
,
nil
}
}
// JobIDConfig describes how to create an ID for a job.
type
JobIDConfig
struct
{
// JobID is the ID to use for the job. If empty, a random job ID will be generated.
JobID
string
// If AddJobIDSuffix is true, then a random string will be appended to JobID.
AddJobIDSuffix
bool
// Location is the location for the job.
Location
string
}
// createJobRef creates a JobReference.
func
(
j
*
JobIDConfig
)
createJobRef
(
c
*
Client
)
*
bq
.
JobReference
{
// We don't check whether projectID is empty; the server will return an
// error when it encounters the resulting JobReference.
loc
:=
j
.
Location
if
loc
==
""
{
// Use Client.Location as a default.
loc
=
c
.
Location
}
jr
:=
&
bq
.
JobReference
{
ProjectId
:
c
.
projectID
,
Location
:
loc
}
if
j
.
JobID
==
""
{
jr
.
JobId
=
randomIDFn
()
}
else
if
j
.
AddJobIDSuffix
{
jr
.
JobId
=
j
.
JobID
+
"-"
+
randomIDFn
()
}
else
{
jr
.
JobId
=
j
.
JobID
}
return
jr
}
// Done reports whether the job has completed.
// After Done returns true, the Err method will return an error if the job completed unsuccessfully.
func
(
s
*
JobStatus
)
Done
()
bool
{
return
s
.
State
==
Done
}
// Err returns the error that caused the job to complete unsuccessfully (if any).
func
(
s
*
JobStatus
)
Err
()
error
{
return
s
.
err
}
// Status retrieves the current status of the job from BigQuery. It fails if the Status could not be determined.
func
(
j
*
Job
)
Status
(
ctx
context
.
Context
)
(
js
*
JobStatus
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Job.Status"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
bqjob
,
err
:=
j
.
c
.
getJobInternal
(
ctx
,
j
.
jobID
,
j
.
location
,
"status"
,
"statistics"
)
if
err
!=
nil
{
return
nil
,
err
}
if
err
:=
j
.
setStatus
(
bqjob
.
Status
);
err
!=
nil
{
return
nil
,
err
}
j
.
setStatistics
(
bqjob
.
Statistics
,
j
.
c
)
return
j
.
lastStatus
,
nil
}
// LastStatus returns the most recently retrieved status of the job. The status is
// retrieved when a new job is created, or when JobFromID or Job.Status is called.
// Call Job.Status to get the most up-to-date information about a job.
func
(
j
*
Job
)
LastStatus
()
*
JobStatus
{
return
j
.
lastStatus
}
// Cancel requests that a job be cancelled. This method returns without waiting for
// cancellation to take effect. To check whether the job has terminated, use Job.Status.
// Cancelled jobs may still incur costs.
func
(
j
*
Job
)
Cancel
(
ctx
context
.
Context
)
error
{
// Jobs.Cancel returns a job entity, but the only relevant piece of
// data it may contain (the status of the job) is unreliable. From the
// docs: "This call will return immediately, and the client will need
// to poll for the job status to see if the cancel completed
// successfully". So it would be misleading to return a status.
call
:=
j
.
c
.
bqs
.
Jobs
.
Cancel
(
j
.
projectID
,
j
.
jobID
)
.
Location
(
j
.
location
)
.
Fields
()
.
// We don't need any of the response data.
Context
(
ctx
)
setClientHeader
(
call
.
Header
())
return
runWithRetry
(
ctx
,
func
()
error
{
_
,
err
:=
call
.
Do
()
return
err
})
}
// Wait blocks until the job or the context is done. It returns the final status
// of the job.
// If an error occurs while retrieving the status, Wait returns that error. But
// Wait returns nil if the status was retrieved successfully, even if
// status.Err() != nil. So callers must check both errors. See the example.
func
(
j
*
Job
)
Wait
(
ctx
context
.
Context
)
(
js
*
JobStatus
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Job.Wait"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
if
j
.
isQuery
()
{
// We can avoid polling for query jobs.
if
_
,
_
,
err
:=
j
.
waitForQuery
(
ctx
,
j
.
projectID
);
err
!=
nil
{
return
nil
,
err
}
// Note: extra RPC even if you just want to wait for the query to finish.
js
,
err
:=
j
.
Status
(
ctx
)
if
err
!=
nil
{
return
nil
,
err
}
return
js
,
nil
}
// Non-query jobs must poll.
err
=
internal
.
Retry
(
ctx
,
gax
.
Backoff
{},
func
()
(
stop
bool
,
err
error
)
{
js
,
err
=
j
.
Status
(
ctx
)
if
err
!=
nil
{
return
true
,
err
}
if
js
.
Done
()
{
return
true
,
nil
}
return
false
,
nil
})
if
err
!=
nil
{
return
nil
,
err
}
return
js
,
nil
}
// Read fetches the results of a query job.
// If j is not a query job, Read returns an error.
func
(
j
*
Job
)
Read
(
ctx
context
.
Context
)
(
ri
*
RowIterator
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Job.Read"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
return
j
.
read
(
ctx
,
j
.
waitForQuery
,
fetchPage
)
}
func
(
j
*
Job
)
read
(
ctx
context
.
Context
,
waitForQuery
func
(
context
.
Context
,
string
)
(
Schema
,
uint64
,
error
),
pf
pageFetcher
)
(
*
RowIterator
,
error
)
{
if
!
j
.
isQuery
()
{
return
nil
,
errors
.
New
(
"bigquery: cannot read from a non-query job"
)
}
destTable
:=
j
.
config
.
Query
.
DestinationTable
// The destination table should only be nil if there was a query error.
projectID
:=
j
.
projectID
if
destTable
!=
nil
&&
projectID
!=
destTable
.
ProjectId
{
return
nil
,
fmt
.
Errorf
(
"bigquery: job project ID is %q, but destination table's is %q"
,
projectID
,
destTable
.
ProjectId
)
}
schema
,
totalRows
,
err
:=
waitForQuery
(
ctx
,
projectID
)
if
err
!=
nil
{
return
nil
,
err
}
if
destTable
==
nil
{
return
nil
,
errors
.
New
(
"bigquery: query job missing destination table"
)
}
dt
:=
bqToTable
(
destTable
,
j
.
c
)
if
totalRows
==
0
{
pf
=
nil
}
it
:=
newRowIterator
(
ctx
,
dt
,
pf
)
it
.
Schema
=
schema
it
.
TotalRows
=
totalRows
return
it
,
nil
}
// waitForQuery waits for the query job to complete and returns its schema. It also
// returns the total number of rows in the result set.
func
(
j
*
Job
)
waitForQuery
(
ctx
context
.
Context
,
projectID
string
)
(
Schema
,
uint64
,
error
)
{
// Use GetQueryResults only to wait for completion, not to read results.
call
:=
j
.
c
.
bqs
.
Jobs
.
GetQueryResults
(
projectID
,
j
.
jobID
)
.
Location
(
j
.
location
)
.
Context
(
ctx
)
.
MaxResults
(
0
)
setClientHeader
(
call
.
Header
())
backoff
:=
gax
.
Backoff
{
Initial
:
1
*
time
.
Second
,
Multiplier
:
2
,
Max
:
60
*
time
.
Second
,
}
var
res
*
bq
.
GetQueryResultsResponse
err
:=
internal
.
Retry
(
ctx
,
backoff
,
func
()
(
stop
bool
,
err
error
)
{
res
,
err
=
call
.
Do
()
if
err
!=
nil
{
return
!
retryableError
(
err
),
err
}
if
!
res
.
JobComplete
{
// GetQueryResults may return early without error; retry.
return
false
,
nil
}
return
true
,
nil
})
if
err
!=
nil
{
return
nil
,
0
,
err
}
return
bqToSchema
(
res
.
Schema
),
res
.
TotalRows
,
nil
}
// JobStatistics contains statistics about a job.
type
JobStatistics
struct
{
CreationTime
time
.
Time
StartTime
time
.
Time
EndTime
time
.
Time
TotalBytesProcessed
int64
Details
Statistics
}
// Statistics is one of ExtractStatistics, LoadStatistics or QueryStatistics.
type
Statistics
interface
{
implementsStatistics
()
}
// ExtractStatistics contains statistics about an extract job.
type
ExtractStatistics
struct
{
// The number of files per destination URI or URI pattern specified in the
// extract configuration. These values will be in the same order as the
// URIs specified in the 'destinationUris' field.
DestinationURIFileCounts
[]
int64
}
// LoadStatistics contains statistics about a load job.
type
LoadStatistics
struct
{
// The number of bytes of source data in a load job.
InputFileBytes
int64
// The number of source files in a load job.
InputFiles
int64
// Size of the loaded data in bytes. Note that while a load job is in the
// running state, this value may change.
OutputBytes
int64
// The number of rows imported in a load job. Note that while an import job is
// in the running state, this value may change.
OutputRows
int64
}
// QueryStatistics contains statistics about a query job.
type
QueryStatistics
struct
{
// Billing tier for the job.
BillingTier
int64
// Whether the query result was fetched from the query cache.
CacheHit
bool
// The type of query statement, if valid.
StatementType
string
// Total bytes billed for the job.
TotalBytesBilled
int64
// Total bytes processed for the job.
TotalBytesProcessed
int64
// Describes execution plan for the query.
QueryPlan
[]
*
ExplainQueryStage
// The number of rows affected by a DML statement. Present only for DML
// statements INSERT, UPDATE or DELETE.
NumDMLAffectedRows
int64
// Describes a timeline of job execution.
Timeline
[]
*
QueryTimelineSample
// ReferencedTables: [Output-only, Experimental] Referenced tables for
// the job. Queries that reference more than 50 tables will not have a
// complete list.
ReferencedTables
[]
*
Table
// The schema of the results. Present only for successful dry run of
// non-legacy SQL queries.
Schema
Schema
// Slot-milliseconds consumed by this query job.
SlotMillis
int64
// Standard SQL: list of undeclared query parameter names detected during a
// dry run validation.
UndeclaredQueryParameterNames
[]
string
// DDL target table.
DDLTargetTable
*
Table
// DDL Operation performed on the target table. Used to report how the
// query impacted the DDL target table.
DDLOperationPerformed
string
}
// ExplainQueryStage describes one stage of a query.
type
ExplainQueryStage
struct
{
// CompletedParallelInputs: Number of parallel input segments completed.
CompletedParallelInputs
int64
// ComputeAvg: Duration the average shard spent on CPU-bound tasks.
ComputeAvg
time
.
Duration
// ComputeMax: Duration the slowest shard spent on CPU-bound tasks.
ComputeMax
time
.
Duration
// Relative amount of the total time the average shard spent on CPU-bound tasks.
ComputeRatioAvg
float64
// Relative amount of the total time the slowest shard spent on CPU-bound tasks.
ComputeRatioMax
float64
// EndTime: Stage end time.
EndTime
time
.
Time
// Unique ID for stage within plan.
ID
int64
// InputStages: IDs for stages that are inputs to this stage.
InputStages
[]
int64
// Human-readable name for stage.
Name
string
// ParallelInputs: Number of parallel input segments to be processed.
ParallelInputs
int64
// ReadAvg: Duration the average shard spent reading input.
ReadAvg
time
.
Duration
// ReadMax: Duration the slowest shard spent reading input.
ReadMax
time
.
Duration
// Relative amount of the total time the average shard spent reading input.
ReadRatioAvg
float64
// Relative amount of the total time the slowest shard spent reading input.
ReadRatioMax
float64
// Number of records read into the stage.
RecordsRead
int64
// Number of records written by the stage.
RecordsWritten
int64
// ShuffleOutputBytes: Total number of bytes written to shuffle.
ShuffleOutputBytes
int64
// ShuffleOutputBytesSpilled: Total number of bytes written to shuffle
// and spilled to disk.
ShuffleOutputBytesSpilled
int64
// StartTime: Stage start time.
StartTime
time
.
Time
// Current status for the stage.
Status
string
// List of operations within the stage in dependency order (approximately
// chronological).
Steps
[]
*
ExplainQueryStep
// WaitAvg: Duration the average shard spent waiting to be scheduled.
WaitAvg
time
.
Duration
// WaitMax: Duration the slowest shard spent waiting to be scheduled.
WaitMax
time
.
Duration
// Relative amount of the total time the average shard spent waiting to be scheduled.
WaitRatioAvg
float64
// Relative amount of the total time the slowest shard spent waiting to be scheduled.
WaitRatioMax
float64
// WriteAvg: Duration the average shard spent on writing output.
WriteAvg
time
.
Duration
// WriteMax: Duration the slowest shard spent on writing output.
WriteMax
time
.
Duration
// Relative amount of the total time the average shard spent on writing output.
WriteRatioAvg
float64
// Relative amount of the total time the slowest shard spent on writing output.
WriteRatioMax
float64
}
// ExplainQueryStep describes one step of a query stage.
type
ExplainQueryStep
struct
{
// Machine-readable operation type.
Kind
string
// Human-readable stage descriptions.
Substeps
[]
string
}
// QueryTimelineSample represents a sample of execution statistics at a point in time.
type
QueryTimelineSample
struct
{
// Total number of units currently being processed by workers, represented as largest value since last sample.
ActiveUnits
int64
// Total parallel units of work completed by this query.
CompletedUnits
int64
// Time elapsed since start of query execution.
Elapsed
time
.
Duration
// Total parallel units of work remaining for the active stages.
PendingUnits
int64
// Cumulative slot-milliseconds consumed by the query.
SlotMillis
int64
}
func
(
*
ExtractStatistics
)
implementsStatistics
()
{}
func
(
*
LoadStatistics
)
implementsStatistics
()
{}
func
(
*
QueryStatistics
)
implementsStatistics
()
{}
// Jobs lists jobs within a project.
func
(
c
*
Client
)
Jobs
(
ctx
context
.
Context
)
*
JobIterator
{
it
:=
&
JobIterator
{
ctx
:
ctx
,
c
:
c
,
ProjectID
:
c
.
projectID
,
}
it
.
pageInfo
,
it
.
nextFunc
=
iterator
.
NewPageInfo
(
it
.
fetch
,
func
()
int
{
return
len
(
it
.
items
)
},
func
()
interface
{}
{
b
:=
it
.
items
;
it
.
items
=
nil
;
return
b
})
return
it
}
// JobIterator iterates over jobs in a project.
type
JobIterator
struct
{
ProjectID
string
// Project ID of the jobs to list. Default is the client's project.
AllUsers
bool
// Whether to list jobs owned by all users in the project, or just the current caller.
State
State
// List only jobs in the given state. Defaults to all states.
MinCreationTime
time
.
Time
// List only jobs created after this time.
MaxCreationTime
time
.
Time
// List only jobs created before this time.
ctx
context
.
Context
c
*
Client
pageInfo
*
iterator
.
PageInfo
nextFunc
func
()
error
items
[]
*
Job
}
// PageInfo is a getter for the JobIterator's PageInfo.
func
(
it
*
JobIterator
)
PageInfo
()
*
iterator
.
PageInfo
{
return
it
.
pageInfo
}
// Next returns the next Job. Its second return value is iterator.Done if
// there are no more results. Once Next returns Done, all subsequent calls will
// return Done.
func
(
it
*
JobIterator
)
Next
()
(
*
Job
,
error
)
{
if
err
:=
it
.
nextFunc
();
err
!=
nil
{
return
nil
,
err
}
item
:=
it
.
items
[
0
]
it
.
items
=
it
.
items
[
1
:
]
return
item
,
nil
}
func
(
it
*
JobIterator
)
fetch
(
pageSize
int
,
pageToken
string
)
(
string
,
error
)
{
var
st
string
switch
it
.
State
{
case
StateUnspecified
:
st
=
""
case
Pending
:
st
=
"pending"
case
Running
:
st
=
"running"
case
Done
:
st
=
"done"
default
:
return
""
,
fmt
.
Errorf
(
"bigquery: invalid value for JobIterator.State: %d"
,
it
.
State
)
}
req
:=
it
.
c
.
bqs
.
Jobs
.
List
(
it
.
ProjectID
)
.
Context
(
it
.
ctx
)
.
PageToken
(
pageToken
)
.
Projection
(
"full"
)
.
AllUsers
(
it
.
AllUsers
)
if
st
!=
""
{
req
.
StateFilter
(
st
)
}
if
!
it
.
MinCreationTime
.
IsZero
()
{
req
.
MinCreationTime
(
uint64
(
it
.
MinCreationTime
.
UnixNano
()
/
1e6
))
}
if
!
it
.
MaxCreationTime
.
IsZero
()
{
req
.
MaxCreationTime
(
uint64
(
it
.
MaxCreationTime
.
UnixNano
()
/
1e6
))
}
setClientHeader
(
req
.
Header
())
if
pageSize
>
0
{
req
.
MaxResults
(
int64
(
pageSize
))
}
res
,
err
:=
req
.
Do
()
if
err
!=
nil
{
return
""
,
err
}
for
_
,
j
:=
range
res
.
Jobs
{
job
,
err
:=
convertListedJob
(
j
,
it
.
c
)
if
err
!=
nil
{
return
""
,
err
}
it
.
items
=
append
(
it
.
items
,
job
)
}
return
res
.
NextPageToken
,
nil
}
func
convertListedJob
(
j
*
bq
.
JobListJobs
,
c
*
Client
)
(
*
Job
,
error
)
{
return
bqToJob2
(
j
.
JobReference
,
j
.
Configuration
,
j
.
Status
,
j
.
Statistics
,
j
.
UserEmail
,
c
)
}
func
(
c
*
Client
)
getJobInternal
(
ctx
context
.
Context
,
jobID
,
location
string
,
fields
...
googleapi
.
Field
)
(
*
bq
.
Job
,
error
)
{
var
job
*
bq
.
Job
call
:=
c
.
bqs
.
Jobs
.
Get
(
c
.
projectID
,
jobID
)
.
Context
(
ctx
)
if
location
!=
""
{
call
=
call
.
Location
(
location
)
}
if
len
(
fields
)
>
0
{
call
=
call
.
Fields
(
fields
...
)
}
setClientHeader
(
call
.
Header
())
err
:=
runWithRetry
(
ctx
,
func
()
(
err
error
)
{
job
,
err
=
call
.
Do
()
return
err
})
if
err
!=
nil
{
return
nil
,
err
}
return
job
,
nil
}
func
bqToJob
(
q
*
bq
.
Job
,
c
*
Client
)
(
*
Job
,
error
)
{
return
bqToJob2
(
q
.
JobReference
,
q
.
Configuration
,
q
.
Status
,
q
.
Statistics
,
q
.
UserEmail
,
c
)
}
func
bqToJob2
(
qr
*
bq
.
JobReference
,
qc
*
bq
.
JobConfiguration
,
qs
*
bq
.
JobStatus
,
qt
*
bq
.
JobStatistics
,
email
string
,
c
*
Client
)
(
*
Job
,
error
)
{
j
:=
&
Job
{
projectID
:
qr
.
ProjectId
,
jobID
:
qr
.
JobId
,
location
:
qr
.
Location
,
c
:
c
,
email
:
email
,
}
j
.
setConfig
(
qc
)
if
err
:=
j
.
setStatus
(
qs
);
err
!=
nil
{
return
nil
,
err
}
j
.
setStatistics
(
qt
,
c
)
return
j
,
nil
}
func
(
j
*
Job
)
setConfig
(
config
*
bq
.
JobConfiguration
)
{
if
config
==
nil
{
return
}
j
.
config
=
config
}
func
(
j
*
Job
)
isQuery
()
bool
{
return
j
.
config
!=
nil
&&
j
.
config
.
Query
!=
nil
}
var
stateMap
=
map
[
string
]
State
{
"PENDING"
:
Pending
,
"RUNNING"
:
Running
,
"DONE"
:
Done
}
func
(
j
*
Job
)
setStatus
(
qs
*
bq
.
JobStatus
)
error
{
if
qs
==
nil
{
return
nil
}
state
,
ok
:=
stateMap
[
qs
.
State
]
if
!
ok
{
return
fmt
.
Errorf
(
"unexpected job state: %v"
,
qs
.
State
)
}
j
.
lastStatus
=
&
JobStatus
{
State
:
state
,
err
:
nil
,
}
if
err
:=
bqToError
(
qs
.
ErrorResult
);
state
==
Done
&&
err
!=
nil
{
j
.
lastStatus
.
err
=
err
}
for
_
,
ep
:=
range
qs
.
Errors
{
j
.
lastStatus
.
Errors
=
append
(
j
.
lastStatus
.
Errors
,
bqToError
(
ep
))
}
return
nil
}
func
(
j
*
Job
)
setStatistics
(
s
*
bq
.
JobStatistics
,
c
*
Client
)
{
if
s
==
nil
||
j
.
lastStatus
==
nil
{
return
}
js
:=
&
JobStatistics
{
CreationTime
:
unixMillisToTime
(
s
.
CreationTime
),
StartTime
:
unixMillisToTime
(
s
.
StartTime
),
EndTime
:
unixMillisToTime
(
s
.
EndTime
),
TotalBytesProcessed
:
s
.
TotalBytesProcessed
,
}
switch
{
case
s
.
Extract
!=
nil
:
js
.
Details
=
&
ExtractStatistics
{
DestinationURIFileCounts
:
[]
int64
(
s
.
Extract
.
DestinationUriFileCounts
),
}
case
s
.
Load
!=
nil
:
js
.
Details
=
&
LoadStatistics
{
InputFileBytes
:
s
.
Load
.
InputFileBytes
,
InputFiles
:
s
.
Load
.
InputFiles
,
OutputBytes
:
s
.
Load
.
OutputBytes
,
OutputRows
:
s
.
Load
.
OutputRows
,
}
case
s
.
Query
!=
nil
:
var
names
[]
string
for
_
,
qp
:=
range
s
.
Query
.
UndeclaredQueryParameters
{
names
=
append
(
names
,
qp
.
Name
)
}
var
tables
[]
*
Table
for
_
,
tr
:=
range
s
.
Query
.
ReferencedTables
{
tables
=
append
(
tables
,
bqToTable
(
tr
,
c
))
}
js
.
Details
=
&
QueryStatistics
{
BillingTier
:
s
.
Query
.
BillingTier
,
CacheHit
:
s
.
Query
.
CacheHit
,
DDLTargetTable
:
bqToTable
(
s
.
Query
.
DdlTargetTable
,
c
),
DDLOperationPerformed
:
s
.
Query
.
DdlOperationPerformed
,
StatementType
:
s
.
Query
.
StatementType
,
TotalBytesBilled
:
s
.
Query
.
TotalBytesBilled
,
TotalBytesProcessed
:
s
.
Query
.
TotalBytesProcessed
,
NumDMLAffectedRows
:
s
.
Query
.
NumDmlAffectedRows
,
QueryPlan
:
queryPlanFromProto
(
s
.
Query
.
QueryPlan
),
Schema
:
bqToSchema
(
s
.
Query
.
Schema
),
SlotMillis
:
s
.
Query
.
TotalSlotMs
,
Timeline
:
timelineFromProto
(
s
.
Query
.
Timeline
),
ReferencedTables
:
tables
,
UndeclaredQueryParameterNames
:
names
,
}
}
j
.
lastStatus
.
Statistics
=
js
}
func
queryPlanFromProto
(
stages
[]
*
bq
.
ExplainQueryStage
)
[]
*
ExplainQueryStage
{
var
res
[]
*
ExplainQueryStage
for
_
,
s
:=
range
stages
{
var
steps
[]
*
ExplainQueryStep
for
_
,
p
:=
range
s
.
Steps
{
steps
=
append
(
steps
,
&
ExplainQueryStep
{
Kind
:
p
.
Kind
,
Substeps
:
p
.
Substeps
,
})
}
res
=
append
(
res
,
&
ExplainQueryStage
{
CompletedParallelInputs
:
s
.
CompletedParallelInputs
,
ComputeAvg
:
time
.
Duration
(
s
.
ComputeMsAvg
)
*
time
.
Millisecond
,
ComputeMax
:
time
.
Duration
(
s
.
ComputeMsMax
)
*
time
.
Millisecond
,
ComputeRatioAvg
:
s
.
ComputeRatioAvg
,
ComputeRatioMax
:
s
.
ComputeRatioMax
,
EndTime
:
time
.
Unix
(
0
,
s
.
EndMs
*
1e6
),
ID
:
s
.
Id
,
InputStages
:
s
.
InputStages
,
Name
:
s
.
Name
,
ParallelInputs
:
s
.
ParallelInputs
,
ReadAvg
:
time
.
Duration
(
s
.
ReadMsAvg
)
*
time
.
Millisecond
,
ReadMax
:
time
.
Duration
(
s
.
ReadMsMax
)
*
time
.
Millisecond
,
ReadRatioAvg
:
s
.
ReadRatioAvg
,
ReadRatioMax
:
s
.
ReadRatioMax
,
RecordsRead
:
s
.
RecordsRead
,
RecordsWritten
:
s
.
RecordsWritten
,
ShuffleOutputBytes
:
s
.
ShuffleOutputBytes
,
ShuffleOutputBytesSpilled
:
s
.
ShuffleOutputBytesSpilled
,
StartTime
:
time
.
Unix
(
0
,
s
.
StartMs
*
1e6
),
Status
:
s
.
Status
,
Steps
:
steps
,
WaitAvg
:
time
.
Duration
(
s
.
WaitMsAvg
)
*
time
.
Millisecond
,
WaitMax
:
time
.
Duration
(
s
.
WaitMsMax
)
*
time
.
Millisecond
,
WaitRatioAvg
:
s
.
WaitRatioAvg
,
WaitRatioMax
:
s
.
WaitRatioMax
,
WriteAvg
:
time
.
Duration
(
s
.
WriteMsAvg
)
*
time
.
Millisecond
,
WriteMax
:
time
.
Duration
(
s
.
WriteMsMax
)
*
time
.
Millisecond
,
WriteRatioAvg
:
s
.
WriteRatioAvg
,
WriteRatioMax
:
s
.
WriteRatioMax
,
})
}
return
res
}
func
timelineFromProto
(
timeline
[]
*
bq
.
QueryTimelineSample
)
[]
*
QueryTimelineSample
{
var
res
[]
*
QueryTimelineSample
for
_
,
s
:=
range
timeline
{
res
=
append
(
res
,
&
QueryTimelineSample
{
ActiveUnits
:
s
.
ActiveUnits
,
CompletedUnits
:
s
.
CompletedUnits
,
Elapsed
:
time
.
Duration
(
s
.
ElapsedMs
)
*
time
.
Millisecond
,
PendingUnits
:
s
.
PendingUnits
,
SlotMillis
:
s
.
TotalSlotMs
,
})
}
return
res
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/job_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2017 Google LLC
//
// Licensed 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
bigquery
import
(
"testing"
"cloud.google.com/go/internal/testutil"
bq
"google.golang.org/api/bigquery/v2"
)
func
TestCreateJobRef
(
t
*
testing
.
T
)
{
defer
fixRandomID
(
"RANDOM"
)()
cNoLoc
:=
&
Client
{
projectID
:
"projectID"
}
cLoc
:=
&
Client
{
projectID
:
"projectID"
,
Location
:
"defaultLoc"
}
for
_
,
test
:=
range
[]
struct
{
in
JobIDConfig
client
*
Client
want
*
bq
.
JobReference
}{
{
in
:
JobIDConfig
{
JobID
:
"foo"
},
want
:
&
bq
.
JobReference
{
JobId
:
"foo"
},
},
{
in
:
JobIDConfig
{},
want
:
&
bq
.
JobReference
{
JobId
:
"RANDOM"
},
},
{
in
:
JobIDConfig
{
AddJobIDSuffix
:
true
},
want
:
&
bq
.
JobReference
{
JobId
:
"RANDOM"
},
},
{
in
:
JobIDConfig
{
JobID
:
"foo"
,
AddJobIDSuffix
:
true
},
want
:
&
bq
.
JobReference
{
JobId
:
"foo-RANDOM"
},
},
{
in
:
JobIDConfig
{
JobID
:
"foo"
,
Location
:
"loc"
},
want
:
&
bq
.
JobReference
{
JobId
:
"foo"
,
Location
:
"loc"
},
},
{
in
:
JobIDConfig
{
JobID
:
"foo"
},
client
:
cLoc
,
want
:
&
bq
.
JobReference
{
JobId
:
"foo"
,
Location
:
"defaultLoc"
},
},
{
in
:
JobIDConfig
{
JobID
:
"foo"
,
Location
:
"loc"
},
client
:
cLoc
,
want
:
&
bq
.
JobReference
{
JobId
:
"foo"
,
Location
:
"loc"
},
},
}
{
client
:=
test
.
client
if
client
==
nil
{
client
=
cNoLoc
}
got
:=
test
.
in
.
createJobRef
(
client
)
test
.
want
.
ProjectId
=
"projectID"
if
!
testutil
.
Equal
(
got
,
test
.
want
)
{
t
.
Errorf
(
"%+v: got %+v, want %+v"
,
test
.
in
,
got
,
test
.
want
)
}
}
}
func
fixRandomID
(
s
string
)
func
()
{
prev
:=
randomIDFn
randomIDFn
=
func
()
string
{
return
s
}
return
func
()
{
randomIDFn
=
prev
}
}
func
checkJob
(
t
*
testing
.
T
,
i
int
,
got
,
want
*
bq
.
Job
)
{
if
got
.
JobReference
==
nil
{
t
.
Errorf
(
"#%d: empty job reference"
,
i
)
return
}
if
got
.
JobReference
.
JobId
==
""
{
t
.
Errorf
(
"#%d: empty job ID"
,
i
)
return
}
d
:=
testutil
.
Diff
(
got
,
want
)
if
d
!=
""
{
t
.
Errorf
(
"#%d: (got=-, want=+) %s"
,
i
,
d
)
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2016 Google LLC
//
// Licensed 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
bigquery
import
(
"context"
"io"
"cloud.google.com/go/internal/trace"
bq
"google.golang.org/api/bigquery/v2"
)
// LoadConfig holds the configuration for a load job.
type
LoadConfig
struct
{
// Src is the source from which data will be loaded.
Src
LoadSource
// Dst is the table into which the data will be loaded.
Dst
*
Table
// CreateDisposition specifies the circumstances under which the destination table will be created.
// The default is CreateIfNeeded.
CreateDisposition
TableCreateDisposition
// WriteDisposition specifies how existing data in the destination table is treated.
// The default is WriteAppend.
WriteDisposition
TableWriteDisposition
// The labels associated with this job.
Labels
map
[
string
]
string
// If non-nil, the destination table is partitioned by time.
TimePartitioning
*
TimePartitioning
// Clustering specifies the data clustering configuration for the destination table.
Clustering
*
Clustering
// Custom encryption configuration (e.g., Cloud KMS keys).
DestinationEncryptionConfig
*
EncryptionConfig
// Allows the schema of the destination table to be updated as a side effect of
// the load job.
SchemaUpdateOptions
[]
string
}
func
(
l
*
LoadConfig
)
toBQ
()
(
*
bq
.
JobConfiguration
,
io
.
Reader
)
{
config
:=
&
bq
.
JobConfiguration
{
Labels
:
l
.
Labels
,
Load
:
&
bq
.
JobConfigurationLoad
{
CreateDisposition
:
string
(
l
.
CreateDisposition
),
WriteDisposition
:
string
(
l
.
WriteDisposition
),
DestinationTable
:
l
.
Dst
.
toBQ
(),
TimePartitioning
:
l
.
TimePartitioning
.
toBQ
(),
Clustering
:
l
.
Clustering
.
toBQ
(),
DestinationEncryptionConfiguration
:
l
.
DestinationEncryptionConfig
.
toBQ
(),
SchemaUpdateOptions
:
l
.
SchemaUpdateOptions
,
},
}
media
:=
l
.
Src
.
populateLoadConfig
(
config
.
Load
)
return
config
,
media
}
func
bqToLoadConfig
(
q
*
bq
.
JobConfiguration
,
c
*
Client
)
*
LoadConfig
{
lc
:=
&
LoadConfig
{
Labels
:
q
.
Labels
,
CreateDisposition
:
TableCreateDisposition
(
q
.
Load
.
CreateDisposition
),
WriteDisposition
:
TableWriteDisposition
(
q
.
Load
.
WriteDisposition
),
Dst
:
bqToTable
(
q
.
Load
.
DestinationTable
,
c
),
TimePartitioning
:
bqToTimePartitioning
(
q
.
Load
.
TimePartitioning
),
Clustering
:
bqToClustering
(
q
.
Load
.
Clustering
),
DestinationEncryptionConfig
:
bqToEncryptionConfig
(
q
.
Load
.
DestinationEncryptionConfiguration
),
SchemaUpdateOptions
:
q
.
Load
.
SchemaUpdateOptions
,
}
var
fc
*
FileConfig
if
len
(
q
.
Load
.
SourceUris
)
==
0
{
s
:=
NewReaderSource
(
nil
)
fc
=
&
s
.
FileConfig
lc
.
Src
=
s
}
else
{
s
:=
NewGCSReference
(
q
.
Load
.
SourceUris
...
)
fc
=
&
s
.
FileConfig
lc
.
Src
=
s
}
bqPopulateFileConfig
(
q
.
Load
,
fc
)
return
lc
}
// A Loader loads data from Google Cloud Storage into a BigQuery table.
type
Loader
struct
{
JobIDConfig
LoadConfig
c
*
Client
}
// A LoadSource represents a source of data that can be loaded into
// a BigQuery table.
//
// This package defines two LoadSources: GCSReference, for Google Cloud Storage
// objects, and ReaderSource, for data read from an io.Reader.
type
LoadSource
interface
{
// populates config, returns media
populateLoadConfig
(
*
bq
.
JobConfigurationLoad
)
io
.
Reader
}
// LoaderFrom returns a Loader which can be used to load data into a BigQuery table.
// The returned Loader may optionally be further configured before its Run method is called.
// See GCSReference and ReaderSource for additional configuration options that
// affect loading.
func
(
t
*
Table
)
LoaderFrom
(
src
LoadSource
)
*
Loader
{
return
&
Loader
{
c
:
t
.
c
,
LoadConfig
:
LoadConfig
{
Src
:
src
,
Dst
:
t
,
},
}
}
// Run initiates a load job.
func
(
l
*
Loader
)
Run
(
ctx
context
.
Context
)
(
j
*
Job
,
err
error
)
{
ctx
=
trace
.
StartSpan
(
ctx
,
"cloud.google.com/go/bigquery.Load.Run"
)
defer
func
()
{
trace
.
EndSpan
(
ctx
,
err
)
}()
job
,
media
:=
l
.
newJob
()
return
l
.
c
.
insertJob
(
ctx
,
job
,
media
)
}
func
(
l
*
Loader
)
newJob
()
(
*
bq
.
Job
,
io
.
Reader
)
{
config
,
media
:=
l
.
LoadConfig
.
toBQ
()
return
&
bq
.
Job
{
JobReference
:
l
.
JobIDConfig
.
createJobRef
(
l
.
c
),
Configuration
:
config
,
},
media
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/load_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"strings"
"testing"
"time"
"cloud.google.com/go/internal/testutil"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
bq
"google.golang.org/api/bigquery/v2"
)
func
defaultLoadJob
()
*
bq
.
Job
{
return
&
bq
.
Job
{
JobReference
:
&
bq
.
JobReference
{
JobId
:
"RANDOM"
,
ProjectId
:
"client-project-id"
},
Configuration
:
&
bq
.
JobConfiguration
{
Load
:
&
bq
.
JobConfigurationLoad
{
DestinationTable
:
&
bq
.
TableReference
{
ProjectId
:
"client-project-id"
,
DatasetId
:
"dataset-id"
,
TableId
:
"table-id"
,
},
SourceUris
:
[]
string
{
"uri"
},
},
},
}
}
func
stringFieldSchema
()
*
FieldSchema
{
return
&
FieldSchema
{
Name
:
"fieldname"
,
Type
:
StringFieldType
}
}
func
nestedFieldSchema
()
*
FieldSchema
{
return
&
FieldSchema
{
Name
:
"nested"
,
Type
:
RecordFieldType
,
Schema
:
Schema
{
stringFieldSchema
()},
}
}
func
bqStringFieldSchema
()
*
bq
.
TableFieldSchema
{
return
&
bq
.
TableFieldSchema
{
Name
:
"fieldname"
,
Type
:
"STRING"
,
}
}
func
bqNestedFieldSchema
()
*
bq
.
TableFieldSchema
{
return
&
bq
.
TableFieldSchema
{
Name
:
"nested"
,
Type
:
"RECORD"
,
Fields
:
[]
*
bq
.
TableFieldSchema
{
bqStringFieldSchema
()},
}
}
func
TestLoad
(
t
*
testing
.
T
)
{
defer
fixRandomID
(
"RANDOM"
)()
c
:=
&
Client
{
projectID
:
"client-project-id"
}
testCases
:=
[]
struct
{
dst
*
Table
src
LoadSource
jobID
string
location
string
config
LoadConfig
want
*
bq
.
Job
}{
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
NewGCSReference
(
"uri"
),
want
:
defaultLoadJob
(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
NewGCSReference
(
"uri"
),
location
:
"loc"
,
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
JobReference
.
Location
=
"loc"
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
jobID
:
"ajob"
,
config
:
LoadConfig
{
CreateDisposition
:
CreateNever
,
WriteDisposition
:
WriteTruncate
,
Labels
:
map
[
string
]
string
{
"a"
:
"b"
},
TimePartitioning
:
&
TimePartitioning
{
Expiration
:
1234
*
time
.
Millisecond
},
Clustering
:
&
Clustering
{
Fields
:
[]
string
{
"cfield1"
}},
DestinationEncryptionConfig
:
&
EncryptionConfig
{
KMSKeyName
:
"keyName"
},
SchemaUpdateOptions
:
[]
string
{
"ALLOW_FIELD_ADDITION"
},
},
src
:
NewGCSReference
(
"uri"
),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
Configuration
.
Labels
=
map
[
string
]
string
{
"a"
:
"b"
}
j
.
Configuration
.
Load
.
CreateDisposition
=
"CREATE_NEVER"
j
.
Configuration
.
Load
.
WriteDisposition
=
"WRITE_TRUNCATE"
j
.
Configuration
.
Load
.
TimePartitioning
=
&
bq
.
TimePartitioning
{
Type
:
"DAY"
,
ExpirationMs
:
1234
,
}
j
.
Configuration
.
Load
.
Clustering
=
&
bq
.
Clustering
{
Fields
:
[]
string
{
"cfield1"
},
}
j
.
Configuration
.
Load
.
DestinationEncryptionConfiguration
=
&
bq
.
EncryptionConfiguration
{
KmsKeyName
:
"keyName"
}
j
.
JobReference
=
&
bq
.
JobReference
{
JobId
:
"ajob"
,
ProjectId
:
"client-project-id"
,
}
j
.
Configuration
.
Load
.
SchemaUpdateOptions
=
[]
string
{
"ALLOW_FIELD_ADDITION"
}
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
func
()
*
GCSReference
{
g
:=
NewGCSReference
(
"uri"
)
g
.
MaxBadRecords
=
1
g
.
AllowJaggedRows
=
true
g
.
AllowQuotedNewlines
=
true
g
.
IgnoreUnknownValues
=
true
return
g
}(),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
Configuration
.
Load
.
MaxBadRecords
=
1
j
.
Configuration
.
Load
.
AllowJaggedRows
=
true
j
.
Configuration
.
Load
.
AllowQuotedNewlines
=
true
j
.
Configuration
.
Load
.
IgnoreUnknownValues
=
true
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
func
()
*
GCSReference
{
g
:=
NewGCSReference
(
"uri"
)
g
.
Schema
=
Schema
{
stringFieldSchema
(),
nestedFieldSchema
(),
}
return
g
}(),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
Configuration
.
Load
.
Schema
=
&
bq
.
TableSchema
{
Fields
:
[]
*
bq
.
TableFieldSchema
{
bqStringFieldSchema
(),
bqNestedFieldSchema
(),
}}
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
func
()
*
GCSReference
{
g
:=
NewGCSReference
(
"uri"
)
g
.
SkipLeadingRows
=
1
g
.
SourceFormat
=
JSON
g
.
Encoding
=
UTF_8
g
.
FieldDelimiter
=
"
\t
"
g
.
Quote
=
"-"
return
g
}(),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
Configuration
.
Load
.
SkipLeadingRows
=
1
j
.
Configuration
.
Load
.
SourceFormat
=
"NEWLINE_DELIMITED_JSON"
j
.
Configuration
.
Load
.
Encoding
=
"UTF-8"
j
.
Configuration
.
Load
.
FieldDelimiter
=
"
\t
"
hyphen
:=
"-"
j
.
Configuration
.
Load
.
Quote
=
&
hyphen
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
NewGCSReference
(
"uri"
),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
// Quote is left unset in GCSReference, so should be nil here.
j
.
Configuration
.
Load
.
Quote
=
nil
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
func
()
*
GCSReference
{
g
:=
NewGCSReference
(
"uri"
)
g
.
ForceZeroQuote
=
true
return
g
}(),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
empty
:=
""
j
.
Configuration
.
Load
.
Quote
=
&
empty
return
j
}(),
},
{
dst
:
c
.
Dataset
(
"dataset-id"
)
.
Table
(
"table-id"
),
src
:
func
()
*
ReaderSource
{
r
:=
NewReaderSource
(
strings
.
NewReader
(
"foo"
))
r
.
SkipLeadingRows
=
1
r
.
SourceFormat
=
JSON
r
.
Encoding
=
UTF_8
r
.
FieldDelimiter
=
"
\t
"
r
.
Quote
=
"-"
return
r
}(),
want
:
func
()
*
bq
.
Job
{
j
:=
defaultLoadJob
()
j
.
Configuration
.
Load
.
SourceUris
=
nil
j
.
Configuration
.
Load
.
SkipLeadingRows
=
1
j
.
Configuration
.
Load
.
SourceFormat
=
"NEWLINE_DELIMITED_JSON"
j
.
Configuration
.
Load
.
Encoding
=
"UTF-8"
j
.
Configuration
.
Load
.
FieldDelimiter
=
"
\t
"
hyphen
:=
"-"
j
.
Configuration
.
Load
.
Quote
=
&
hyphen
return
j
}(),
},
}
for
i
,
tc
:=
range
testCases
{
loader
:=
tc
.
dst
.
LoaderFrom
(
tc
.
src
)
loader
.
JobID
=
tc
.
jobID
loader
.
Location
=
tc
.
location
tc
.
config
.
Src
=
tc
.
src
tc
.
config
.
Dst
=
tc
.
dst
loader
.
LoadConfig
=
tc
.
config
got
,
_
:=
loader
.
newJob
()
checkJob
(
t
,
i
,
got
,
tc
.
want
)
jc
,
err
:=
bqToJobConfig
(
got
.
Configuration
,
c
)
if
err
!=
nil
{
t
.
Fatalf
(
"#%d: %v"
,
i
,
err
)
}
diff
:=
testutil
.
Diff
(
jc
.
(
*
LoadConfig
),
&
loader
.
LoadConfig
,
cmp
.
AllowUnexported
(
Table
{},
Client
{}),
cmpopts
.
IgnoreUnexported
(
ReaderSource
{}))
if
diff
!=
""
{
t
.
Errorf
(
"#%d: (got=-, want=+:
\n
%s"
,
i
,
diff
)
}
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"time"
"cloud.google.com/go/civil"
)
// NullInt64 represents a BigQuery INT64 that may be NULL.
type
NullInt64
struct
{
Int64
int64
Valid
bool
// Valid is true if Int64 is not NULL.
}
func
(
n
NullInt64
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
Int64
)
}
// NullString represents a BigQuery STRING that may be NULL.
type
NullString
struct
{
StringVal
string
Valid
bool
// Valid is true if StringVal is not NULL.
}
func
(
n
NullString
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
StringVal
)
}
// NullFloat64 represents a BigQuery FLOAT64 that may be NULL.
type
NullFloat64
struct
{
Float64
float64
Valid
bool
// Valid is true if Float64 is not NULL.
}
func
(
n
NullFloat64
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
Float64
)
}
// NullBool represents a BigQuery BOOL that may be NULL.
type
NullBool
struct
{
Bool
bool
Valid
bool
// Valid is true if Bool is not NULL.
}
func
(
n
NullBool
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
Bool
)
}
// NullTimestamp represents a BigQuery TIMESTAMP that may be null.
type
NullTimestamp
struct
{
Timestamp
time
.
Time
Valid
bool
// Valid is true if Time is not NULL.
}
func
(
n
NullTimestamp
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
Timestamp
)
}
// NullDate represents a BigQuery DATE that may be null.
type
NullDate
struct
{
Date
civil
.
Date
Valid
bool
// Valid is true if Date is not NULL.
}
func
(
n
NullDate
)
String
()
string
{
return
nullstr
(
n
.
Valid
,
n
.
Date
)
}
// NullTime represents a BigQuery TIME that may be null.
type
NullTime
struct
{
Time
civil
.
Time
Valid
bool
// Valid is true if Time is not NULL.
}
func
(
n
NullTime
)
String
()
string
{
if
!
n
.
Valid
{
return
"<null>"
}
return
CivilTimeString
(
n
.
Time
)
}
// NullDateTime represents a BigQuery DATETIME that may be null.
type
NullDateTime
struct
{
DateTime
civil
.
DateTime
Valid
bool
// Valid is true if DateTime is not NULL.
}
func
(
n
NullDateTime
)
String
()
string
{
if
!
n
.
Valid
{
return
"<null>"
}
return
CivilDateTimeString
(
n
.
DateTime
)
}
// MarshalJSON converts the NullInt64 to JSON.
func
(
n
NullInt64
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
Int64
)
}
// MarshalJSON converts the NullFloat64 to JSON.
func
(
n
NullFloat64
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
Float64
)
}
// MarshalJSON converts the NullBool to JSON.
func
(
n
NullBool
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
Bool
)
}
// MarshalJSON converts the NullString to JSON.
func
(
n
NullString
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
StringVal
)
}
// MarshalJSON converts the NullTimestamp to JSON.
func
(
n
NullTimestamp
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
Timestamp
)
}
// MarshalJSON converts the NullDate to JSON.
func
(
n
NullDate
)
MarshalJSON
()
([]
byte
,
error
)
{
return
nulljson
(
n
.
Valid
,
n
.
Date
)
}
// MarshalJSON converts the NullTime to JSON.
func
(
n
NullTime
)
MarshalJSON
()
([]
byte
,
error
)
{
if
!
n
.
Valid
{
return
jsonNull
,
nil
}
return
[]
byte
(
`"`
+
CivilTimeString
(
n
.
Time
)
+
`"`
),
nil
}
// MarshalJSON converts the NullDateTime to JSON.
func
(
n
NullDateTime
)
MarshalJSON
()
([]
byte
,
error
)
{
if
!
n
.
Valid
{
return
jsonNull
,
nil
}
return
[]
byte
(
`"`
+
CivilDateTimeString
(
n
.
DateTime
)
+
`"`
),
nil
}
func
nullstr
(
valid
bool
,
v
interface
{})
string
{
if
!
valid
{
return
"NULL"
}
return
fmt
.
Sprint
(
v
)
}
var
jsonNull
=
[]
byte
(
"null"
)
func
nulljson
(
valid
bool
,
v
interface
{})
([]
byte
,
error
)
{
if
!
valid
{
return
jsonNull
,
nil
}
return
json
.
Marshal
(
v
)
}
// UnmarshalJSON converts JSON into a NullInt64.
func
(
n
*
NullInt64
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Int64
=
0
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
Int64
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullFloat64.
func
(
n
*
NullFloat64
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Float64
=
0
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
Float64
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullBool.
func
(
n
*
NullBool
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Bool
=
false
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
Bool
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullString.
func
(
n
*
NullString
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
StringVal
=
""
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
StringVal
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullTimestamp.
func
(
n
*
NullTimestamp
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Timestamp
=
time
.
Time
{}
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
Timestamp
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullDate.
func
(
n
*
NullDate
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Date
=
civil
.
Date
{}
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
if
err
:=
json
.
Unmarshal
(
b
,
&
n
.
Date
);
err
!=
nil
{
return
err
}
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullTime.
func
(
n
*
NullTime
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
Time
=
civil
.
Time
{}
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
s
,
err
:=
strconv
.
Unquote
(
string
(
b
))
if
err
!=
nil
{
return
err
}
t
,
err
:=
civil
.
ParseTime
(
s
)
if
err
!=
nil
{
return
err
}
n
.
Time
=
t
n
.
Valid
=
true
return
nil
}
// UnmarshalJSON converts JSON into a NullDateTime.
func
(
n
*
NullDateTime
)
UnmarshalJSON
(
b
[]
byte
)
error
{
n
.
Valid
=
false
n
.
DateTime
=
civil
.
DateTime
{}
if
bytes
.
Equal
(
b
,
jsonNull
)
{
return
nil
}
s
,
err
:=
strconv
.
Unquote
(
string
(
b
))
if
err
!=
nil
{
return
err
}
dt
,
err
:=
parseCivilDateTime
(
s
)
if
err
!=
nil
{
return
err
}
n
.
DateTime
=
dt
n
.
Valid
=
true
return
nil
}
var
(
typeOfNullInt64
=
reflect
.
TypeOf
(
NullInt64
{})
typeOfNullFloat64
=
reflect
.
TypeOf
(
NullFloat64
{})
typeOfNullBool
=
reflect
.
TypeOf
(
NullBool
{})
typeOfNullString
=
reflect
.
TypeOf
(
NullString
{})
typeOfNullTimestamp
=
reflect
.
TypeOf
(
NullTimestamp
{})
typeOfNullDate
=
reflect
.
TypeOf
(
NullDate
{})
typeOfNullTime
=
reflect
.
TypeOf
(
NullTime
{})
typeOfNullDateTime
=
reflect
.
TypeOf
(
NullDateTime
{})
)
func
nullableFieldType
(
t
reflect
.
Type
)
FieldType
{
switch
t
{
case
typeOfNullInt64
:
return
IntegerFieldType
case
typeOfNullFloat64
:
return
FloatFieldType
case
typeOfNullBool
:
return
BooleanFieldType
case
typeOfNullString
:
return
StringFieldType
case
typeOfNullTimestamp
:
return
TimestampFieldType
case
typeOfNullDate
:
return
DateFieldType
case
typeOfNullTime
:
return
TimeFieldType
case
typeOfNullDateTime
:
return
DateTimeFieldType
default
:
return
""
}
}
pkg/mod/cloud.google.com/go@v0.34.0/bigquery/nulls_test.go
deleted
100644 → 0
View file @
fc1f6363
// Copyright 2015 Google LLC
//
// Licensed 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
bigquery
import
(
"encoding/json"
"reflect"
"testing"
"cloud.google.com/go/civil"
"cloud.google.com/go/internal/testutil"
)
var
(
nullsTestTime
=
civil
.
Time
{
Hour
:
7
,
Minute
:
50
,
Second
:
22
,
Nanosecond
:
1000
}
nullsTestDateTime
=
civil
.
DateTime
{
Date
:
civil
.
Date
{
Year
:
2016
,
Month
:
11
,
Day
:
5
},
Time
:
nullsTestTime
}
)
func
TestNullsJSON
(
t
*
testing
.
T
)
{
for
_
,
test
:=
range
[]
struct
{
in
interface
{}
want
string
}{
{
&
NullInt64
{
Valid
:
true
,
Int64
:
3
},
`3`
},
{
&
NullFloat64
{
Valid
:
true
,
Float64
:
3.14
},
`3.14`
},
{
&
NullBool
{
Valid
:
true
,
Bool
:
true
},
`true`
},
{
&
NullString
{
Valid
:
true
,
StringVal
:
"foo"
},
`"foo"`
},
{
&
NullTimestamp
{
Valid
:
true
,
Timestamp
:
testTimestamp
},
`"2016-11-05T07:50:22.000000008Z"`
},
{
&
NullDate
{
Valid
:
true
,
Date
:
testDate
},
`"2016-11-05"`
},
{
&
NullTime
{
Valid
:
true
,
Time
:
nullsTestTime
},
`"07:50:22.000001"`
},
{
&
NullDateTime
{
Valid
:
true
,
DateTime
:
nullsTestDateTime
},
`"2016-11-05 07:50:22.000001"`
},
{
&
NullInt64
{},
`null`
},
{
&
NullFloat64
{},
`null`
},
{
&
NullBool
{},
`null`
},
{
&
NullString
{},
`null`
},
{
&
NullTimestamp
{},
`null`
},
{
&
NullDate
{},
`null`
},
{
&
NullTime
{},
`null`
},
{
&
NullDateTime
{},
`null`
},
}
{
bytes
,
err
:=
json
.
Marshal
(
test
.
in
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
got
,
want
:=
string
(
bytes
),
test
.
want
;
got
!=
want
{
t
.
Errorf
(
"%#v: got %s, want %s"
,
test
.
in
,
got
,
want
)
}
typ
:=
reflect
.
Indirect
(
reflect
.
ValueOf
(
test
.
in
))
.
Type
()
value
:=
reflect
.
New
(
typ
)
.
Interface
()
err
=
json
.
Unmarshal
(
bytes
,
value
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
!
testutil
.
Equal
(
value
,
test
.
in
)
{
t
.
Errorf
(
"%#v: got %#v, want %#v"
,
test
.
in
,
value
,
test
.
in
)
}
}
}
Prev
1
…
28
29
30
31
32
33
34
35
36
37
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment