Skip to content

Commit abc4548

Browse files
Add type parmeters to bqfakes (#163)
1 parent 740dcf0 commit abc4548

File tree

3 files changed

+56
-34
lines changed

3 files changed

+56
-34
lines changed

cloud/bqfake/bqfake.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import (
1919
"google.golang.org/api/iterator"
2020
)
2121

22+
var (
23+
// ErrTypeAssertionFailed may be returned by the row iterator when the target type is incorrect.
24+
ErrTypeAssertionFailed = errors.New("type assertion failed")
25+
)
26+
2227
// Table implements part of the bqiface.Table interface required for basic testing
2328
// Other parts of the interface should be implemented as needed.
2429
type Table struct {
@@ -99,44 +104,44 @@ func (ds Dataset) Table(name string) bqiface.Table {
99104
}
100105

101106
// ClientConfig contains configuration for injecting result and error values.
102-
type ClientConfig struct {
103-
QueryConfig
107+
type ClientConfig[Row any] struct {
108+
QueryConfig[Row]
104109
}
105110

106111
// QueryConfig contains configuration for injecting query results and error values.
107-
type QueryConfig struct {
112+
type QueryConfig[Row any] struct {
108113
ReadErr error
109-
RowIteratorConfig
114+
RowIteratorConfig[Row]
110115
}
111116

112117
// RowIteratorConfig contains configuration for injecting row iteration results and error values.
113-
type RowIteratorConfig struct {
118+
type RowIteratorConfig[Row any] struct {
114119
IterErr error
115-
Rows []map[string]bigquery.Value
120+
Rows []Row
116121
}
117122

118123
// Query implements parts of bqiface.Query to allow some very basic
119124
// unit tests.
120-
type Query struct {
125+
type Query[Row any] struct {
121126
bqiface.Query
122-
config QueryConfig
127+
config QueryConfig[Row]
123128
}
124129

125130
// SetQueryConfig is used to set the ReadErr or RowIteratorConfig.
126-
func (q Query) SetQueryConfig(bqiface.QueryConfig) {
131+
func (q Query[Row]) SetQueryConfig(bqiface.QueryConfig) {
127132
log.Println("SetQueryConfig not implemented")
128133
}
129134

130-
func (q Query) Run(context.Context) (bqiface.Job, error) {
135+
func (q Query[Row]) Run(context.Context) (bqiface.Job, error) {
131136
log.Println("Run not implemented")
132137
return Job{}, nil
133138
}
134139

135-
func (q Query) Read(context.Context) (bqiface.RowIterator, error) {
140+
func (q Query[Row]) Read(context.Context) (bqiface.RowIterator, error) {
136141
if q.config.ReadErr != nil {
137142
return nil, q.config.ReadErr
138143
}
139-
return &RowIterator{config: q.config.RowIteratorConfig}, nil
144+
return &RowIterator[Row]{config: q.config.RowIteratorConfig}, nil
140145
}
141146

142147
// Job implements parts of bqiface.Job to allow some very basic
@@ -150,13 +155,17 @@ func (j Job) Wait(context.Context) (*bigquery.JobStatus, error) {
150155
return nil, nil
151156
}
152157

153-
type RowIterator struct {
158+
type RowIterator[Row any] struct {
154159
bqiface.RowIterator
155-
config RowIteratorConfig
160+
config RowIteratorConfig[Row]
156161
index int
157162
}
158163

159-
func (r *RowIterator) Next(dst interface{}) error {
164+
func (r *RowIterator[Row]) TotalRows() uint64 {
165+
return uint64(len(r.config.Rows))
166+
}
167+
168+
func (r *RowIterator[Row]) Next(dst interface{}) error {
160169
// Check config for an error.
161170
if r.config.IterErr != nil {
162171
return r.config.IterErr
@@ -165,7 +174,10 @@ func (r *RowIterator) Next(dst interface{}) error {
165174
if r.index >= len(r.config.Rows) {
166175
return iterator.Done
167176
}
168-
v := dst.(*map[string]bigquery.Value)
177+
v, ok := dst.(*Row)
178+
if !ok {
179+
return ErrTypeAssertionFailed
180+
}
169181
*v = r.config.Rows[r.index]
170182
r.index++
171183
return nil

cloud/bqfake/bqfake_test.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func assertDataset(ds bqfake.Dataset) {
2929
}
3030

3131
// This fails to compile if Client does not satisfy the interface.
32-
func assertClient(c bqfake.Client) {
32+
func assertClient[Row any](c bqfake.Client[Row]) {
3333
func(cc bqiface.Client) {}(c)
3434
}
3535

@@ -210,30 +210,32 @@ func TestQuery(t *testing.T) {
210210
func TestNewQueryReadClient(t *testing.T) {
211211
tests := []struct {
212212
name string
213-
config bqfake.QueryConfig
213+
config bqfake.QueryConfig[map[string]bigquery.Value]
214+
rows uint64
214215
wantErr bool
215216
}{
216217
{
217218
name: "success",
218-
config: bqfake.QueryConfig{
219-
RowIteratorConfig: bqfake.RowIteratorConfig{
219+
config: bqfake.QueryConfig[map[string]bigquery.Value]{
220+
RowIteratorConfig: bqfake.RowIteratorConfig[map[string]bigquery.Value]{
220221
Rows: []map[string]bigquery.Value{
221222
{"okay": 1.234},
222223
},
223224
},
224225
},
226+
rows: 1,
225227
},
226228
{
227229
name: "read-error",
228-
config: bqfake.QueryConfig{
230+
config: bqfake.QueryConfig[map[string]bigquery.Value]{
229231
ReadErr: fmt.Errorf("Fake read error"),
230232
},
231233
wantErr: true,
232234
},
233235
{
234236
name: "iter-error",
235-
config: bqfake.QueryConfig{
236-
RowIteratorConfig: bqfake.RowIteratorConfig{
237+
config: bqfake.QueryConfig[map[string]bigquery.Value]{
238+
RowIteratorConfig: bqfake.RowIteratorConfig[map[string]bigquery.Value]{
237239
IterErr: fmt.Errorf("Fake iter error"),
238240
},
239241
},
@@ -252,6 +254,15 @@ func TestNewQueryReadClient(t *testing.T) {
252254
if it == nil {
253255
return
254256
}
257+
if it.TotalRows() != tt.rows {
258+
t.Errorf("RowIterator.TotalRows() = %d, want %d", it.TotalRows(), tt.rows)
259+
}
260+
// Try loading wrong type.
261+
var x string
262+
err = it.Next(&x)
263+
if !tt.wantErr && err != bqfake.ErrTypeAssertionFailed {
264+
t.Errorf("RowIterator.Next() = %v, want %v", err, bqfake.ErrTypeAssertionFailed)
265+
}
255266
var row map[string]bigquery.Value
256267
i := 0
257268
for err = it.Next(&row); err == nil; err = it.Next(&row) {
@@ -265,7 +276,6 @@ func TestNewQueryReadClient(t *testing.T) {
265276
if err == iterator.Done {
266277
return
267278
}
268-
// err != nil.
269279
if err != tt.config.RowIteratorConfig.IterErr {
270280
t.Errorf("Next() error; got %v, want %v", err, tt.config.RowIteratorConfig.IterErr)
271281
return

cloud/bqfake/client.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,40 +66,40 @@ func DryRunClient() (*http.Client, *CountingTransport) {
6666
}
6767

6868
// Client implements a fake client.
69-
type Client struct {
69+
type Client[Row any] struct {
7070
bqiface.Client
7171
ctx context.Context // Just for checking expiration/cancelation
72-
config ClientConfig
72+
config ClientConfig[Row]
7373
}
7474

7575
// NewClient creates a new Client implementing bqiface.Client, with a dry run HTTPClient.
76-
func NewClient(ctx context.Context, project string, opts ...option.ClientOption) (*Client, error) {
76+
func NewClient(ctx context.Context, project string, opts ...option.ClientOption) (*Client[map[string]bigquery.Value], error) {
7777
dryRun, _ := DryRunClient()
7878
opts = append(opts, option.WithHTTPClient(dryRun))
7979
c, err := bigquery.NewClient(ctx, project, opts...)
8080
if err != nil {
8181
return nil, err
8282
}
83-
return &Client{Client: bqiface.AdaptClient(c), ctx: ctx}, nil
83+
return &Client[map[string]bigquery.Value]{Client: bqiface.AdaptClient(c), ctx: ctx}, nil
8484
}
8585

8686
// Dataset creates a Dataset.
8787
// TODO - understand how bqiface adapters/structs work, and make this return a Dataset
8888
// that satisfies bqiface.Dataset interface?
89-
func (client Client) Dataset(ds string) bqiface.Dataset {
89+
func (client Client[Row]) Dataset(ds string) bqiface.Dataset {
9090
return Dataset{Dataset: client.Client.Dataset(ds), tables: make(map[string]*Table)}
9191
}
9292

93-
func (client Client) Query(string) bqiface.Query {
94-
return Query{
93+
func (client Client[Row]) Query(string) bqiface.Query {
94+
return Query[Row]{
9595
config: client.config.QueryConfig,
9696
}
9797
}
9898

99-
func NewQueryReadClient(qc QueryConfig) *Client {
99+
func NewQueryReadClient[Row any](qc QueryConfig[Row]) *Client[Row] {
100100
// NOTE: if all needed functions are implemented by the fake, then a real
101101
// client is unnecessary.
102-
return &Client{
103-
config: ClientConfig{QueryConfig: qc},
102+
return &Client[Row]{
103+
config: ClientConfig[Row]{QueryConfig: qc},
104104
}
105105
}

0 commit comments

Comments
 (0)