google-cloud-bigquery 0.28.0 → 0.29.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,280 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/bigquery/convert"
17
+ require "monitor"
18
+ require "concurrent"
19
+
20
+ module Google
21
+ module Cloud
22
+ module Bigquery
23
+ class Table
24
+ ##
25
+ # # AsyncInserter
26
+ #
27
+ # Used to insert multiple rows in batches to a topic. See
28
+ # {Google::Cloud::Bigquery::Table#insert_async}.
29
+ #
30
+ # @example
31
+ # require "google/cloud/bigquery"
32
+ #
33
+ # bigquery = Google::Cloud::Bigquery.new
34
+ # dataset = bigquery.dataset "my_dataset"
35
+ # table = dataset.table "my_table"
36
+ # inserter = table.insert_async do |response|
37
+ # log_insert "inserted #{response.insert_count} rows " \
38
+ # "with #{response.error_count} errors"
39
+ # end
40
+ #
41
+ # rows = [
42
+ # { "first_name" => "Alice", "age" => 21 },
43
+ # { "first_name" => "Bob", "age" => 22 }
44
+ # ]
45
+ # inserter.insert rows
46
+ #
47
+ # inserter.stop.wait!
48
+ #
49
+ # @attr_reader [Integer] max_bytes The maximum size of rows to be
50
+ # collected before the batch is inserted. Default is 10,000,000
51
+ # (10MB).
52
+ # @attr_reader [Integer] max_rows The maximum number of rows to be
53
+ # collected before the batch is inserted. Default is 500.
54
+ # @attr_reader [Numeric] interval The number of seconds to collect rows
55
+ # before the batch is inserted. Default is 10.
56
+ # @attr_reader [Integer] threads The number of threads used to insert
57
+ # rows. Default is 4.
58
+ #
59
+ class AsyncInserter
60
+ include MonitorMixin
61
+
62
+ attr_reader :max_bytes, :max_rows, :interval, :threads
63
+ ##
64
+ # @private Implementation accessors
65
+ attr_reader :table, :batch
66
+
67
+ ##
68
+ # @private
69
+ def initialize table, skip_invalid: nil, ignore_unknown: nil,
70
+ max_bytes: 10000000, max_rows: 500, interval: 10,
71
+ threads: 4, &block
72
+ @table = table
73
+ @skip_invalid = skip_invalid
74
+ @ignore_unknown = ignore_unknown
75
+
76
+ @max_bytes = max_bytes
77
+ @max_rows = max_rows
78
+ @interval = interval
79
+ @threads = threads
80
+ @callback = block
81
+
82
+ @batch = nil
83
+
84
+ @thread_pool = Concurrent::FixedThreadPool.new @threads
85
+
86
+ @cond = new_cond
87
+
88
+ # init MonitorMixin
89
+ super()
90
+ end
91
+
92
+ ##
93
+ # Adds rows to the async inserter to be inserted. Rows will be
94
+ # collected in batches and inserted together.
95
+ # See {Google::Cloud::Bigquery::Table#insert_async}.
96
+ #
97
+ # @param [Hash, Array<Hash>] rows A hash object or array of hash
98
+ # objects containing the data.
99
+ #
100
+ def insert rows
101
+ return nil if rows.nil?
102
+ return nil if rows.is_a?(Array) && rows.empty?
103
+ rows = [rows] if rows.is_a? Hash
104
+
105
+ synchronize do
106
+ rows.each do |row|
107
+ if @batch.nil?
108
+ @batch = Batch.new max_bytes: @max_bytes, max_rows: @max_rows
109
+ @batch.insert row
110
+ else
111
+ unless @batch.try_insert row
112
+ push_batch_request!
113
+
114
+ @batch = Batch.new max_bytes: @max_bytes,
115
+ max_rows: @max_rows
116
+ @batch.insert row
117
+ end
118
+ end
119
+
120
+ @batch_created_at ||= ::Time.now
121
+ @background_thread ||= Thread.new { run_background }
122
+
123
+ push_batch_request! if @batch.ready?
124
+ end
125
+
126
+ @cond.signal
127
+ end
128
+
129
+ true
130
+ end
131
+
132
+ ##
133
+ # Begins the process of stopping the inserter. Rows already in the
134
+ # queue will be inserted, but no new rows can be added. Use {#wait!}
135
+ # to block until the inserter is fully stopped and all pending rows
136
+ # have been inserted.
137
+ #
138
+ # @return [AsyncInserter] returns self so calls can be chained.
139
+ #
140
+ def stop
141
+ synchronize do
142
+ break if @stopped
143
+
144
+ @stopped = true
145
+ push_batch_request!
146
+ @cond.signal
147
+ end
148
+
149
+ self
150
+ end
151
+
152
+ ##
153
+ # Blocks until the inserter is fully stopped, all pending rows
154
+ # have been inserted, and all callbacks have completed. Does not stop
155
+ # the inserter. To stop the inserter, first call {#stop} and then
156
+ # call {#wait!} to block until the inserter is stopped.
157
+ #
158
+ # @return [AsyncInserter] returns self so calls can be chained.
159
+ #
160
+ def wait! timeout = nil
161
+ synchronize do
162
+ @thread_pool.shutdown
163
+ @thread_pool.wait_for_termination timeout
164
+ end
165
+
166
+ self
167
+ end
168
+
169
+ ##
170
+ # Forces all rows in the current batch to be inserted immediately.
171
+ #
172
+ # @return [AsyncInserter] returns self so calls can be chained.
173
+ #
174
+ def flush
175
+ synchronize do
176
+ push_batch_request!
177
+ @cond.signal
178
+ end
179
+
180
+ self
181
+ end
182
+
183
+ ##
184
+ # Whether the inserter has been started.
185
+ #
186
+ # @return [boolean] `true` when started, `false` otherwise.
187
+ #
188
+ def started?
189
+ !stopped?
190
+ end
191
+
192
+ ##
193
+ # Whether the inserter has been stopped.
194
+ #
195
+ # @return [boolean] `true` when stopped, `false` otherwise.
196
+ #
197
+ def stopped?
198
+ synchronize { @stopped }
199
+ end
200
+
201
+ protected
202
+
203
+ def run_background
204
+ synchronize do
205
+ until @stopped
206
+ if @batch.nil?
207
+ @cond.wait
208
+ next
209
+ end
210
+
211
+ time_since_first_publish = ::Time.now - @batch_created_at
212
+ if time_since_first_publish < @interval
213
+ # still waiting for the interval to insert the batch...
214
+ @cond.wait(@interval - time_since_first_publish)
215
+ else
216
+ # interval met, insert the batch...
217
+ push_batch_request!
218
+ @cond.wait
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ def push_batch_request!
225
+ return unless @batch
226
+
227
+ batch_rows = @batch.rows
228
+ Concurrent::Future.new(executor: @thread_pool) do
229
+ begin
230
+ response = @table.insert batch_rows,
231
+ skip_invalid: @skip_invalid,
232
+ ignore_unknown: @ignore_unknown
233
+ @callback.call response if @callback
234
+ rescue => e
235
+ raise e.inspect
236
+ end
237
+ end.execute
238
+
239
+ @batch = nil
240
+ @batch_created_at = nil
241
+ end
242
+
243
+ ##
244
+ # @private
245
+ class Batch
246
+ attr_reader :max_bytes, :max_rows, :rows
247
+
248
+ def initialize max_bytes: 10000000, max_rows: 500
249
+ @max_bytes = max_bytes
250
+ @max_rows = max_rows
251
+ @rows = []
252
+ end
253
+
254
+ def insert row
255
+ @rows << row
256
+ end
257
+
258
+ def try_insert row
259
+ addl_bytes = row.to_json.bytes.size + 1
260
+ return false if current_bytes + addl_bytes >= @max_bytes
261
+ return false if @rows.count + 1 >= @max_rows
262
+
263
+ insert row
264
+ true
265
+ end
266
+
267
+ def ready?
268
+ current_bytes >= @max_bytes || rows.count >= @max_rows
269
+ end
270
+
271
+ def current_bytes
272
+ # TODO: add to a counter instead of calling #to_json each time
273
+ Convert.to_json_rows(rows).to_json.bytes.size
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Bigquery
19
- VERSION = "0.28.0"
19
+ VERSION = "0.29.0"
20
20
  end
21
21
  end
22
22
  end
@@ -15,7 +15,6 @@
15
15
 
16
16
  require "google/cloud/errors"
17
17
  require "google/cloud/bigquery/service"
18
- require "google/cloud/bigquery/data"
19
18
  require "google/cloud/bigquery/table/list"
20
19
  require "google/apis/bigquery_v2"
21
20
 
@@ -52,16 +51,17 @@ module Google
52
51
  attr_accessor :gapi
53
52
 
54
53
  ##
55
- # @private Create an empty Table object.
54
+ # @private Create an empty View object.
56
55
  def initialize
57
56
  @service = nil
58
57
  @gapi = Google::Apis::BigqueryV2::Table.new
59
58
  end
60
59
 
61
60
  ##
62
- # A unique ID for this table.
63
- # The ID must contain only letters (a-z, A-Z), numbers (0-9),
64
- # or underscores (_). The maximum length is 1,024 characters.
61
+ # A unique ID for this view.
62
+ #
63
+ # @return [String] The ID must contain only letters (a-z, A-Z), numbers
64
+ # (0-9), or underscores (_). The maximum length is 1,024 characters.
65
65
  #
66
66
  # @!group Attributes
67
67
  #
@@ -70,7 +70,10 @@ module Google
70
70
  end
71
71
 
72
72
  ##
73
- # The ID of the `Dataset` containing this table.
73
+ # The ID of the `Dataset` containing this view.
74
+ #
75
+ # @return [String] The ID must contain only letters (a-z, A-Z), numbers
76
+ # (0-9), or underscores (_). The maximum length is 1,024 characters.
74
77
  #
75
78
  # @!group Attributes
76
79
  #
@@ -79,7 +82,9 @@ module Google
79
82
  end
80
83
 
81
84
  ##
82
- # The ID of the `Project` containing this table.
85
+ # The ID of the `Project` containing this view.
86
+ #
87
+ # @return [String] The project ID.
83
88
  #
84
89
  # @!group Attributes
85
90
  #
@@ -97,7 +102,7 @@ module Google
97
102
  end
98
103
 
99
104
  ##
100
- # The combined Project ID, Dataset ID, and Table ID for this table, in
105
+ # The combined Project ID, Dataset ID, and Table ID for this view, in
101
106
  # the format specified by the [Legacy SQL Query
102
107
  # Reference](https://cloud.google.com/bigquery/query-reference#from):
103
108
  # `project_name:datasetId.tableId`. To use this value in queries see
@@ -144,7 +149,9 @@ module Google
144
149
  end
145
150
 
146
151
  ##
147
- # The name of the table.
152
+ # The name of the view.
153
+ #
154
+ # @return [String] The friendly name.
148
155
  #
149
156
  # @!group Attributes
150
157
  #
@@ -153,7 +160,9 @@ module Google
153
160
  end
154
161
 
155
162
  ##
156
- # Updates the name of the table.
163
+ # Updates the name of the view.
164
+ #
165
+ # @param [String] new_name The new friendly name.
157
166
  #
158
167
  # @!group Attributes
159
168
  #
@@ -163,7 +172,9 @@ module Google
163
172
  end
164
173
 
165
174
  ##
166
- # A string hash of the dataset.
175
+ # The ETag hash of the view.
176
+ #
177
+ # @return [String] The ETag hash.
167
178
  #
168
179
  # @!group Attributes
169
180
  #
@@ -173,7 +184,9 @@ module Google
173
184
  end
174
185
 
175
186
  ##
176
- # A URL that can be used to access the dataset using the REST API.
187
+ # A URL that can be used to access the view using the REST API.
188
+ #
189
+ # @return [String] A REST URL for the resource.
177
190
  #
178
191
  # @!group Attributes
179
192
  #
@@ -183,7 +196,9 @@ module Google
183
196
  end
184
197
 
185
198
  ##
186
- # The description of the table.
199
+ # A user-friendly description of the view.
200
+ #
201
+ # @return [String] The description.
187
202
  #
188
203
  # @!group Attributes
189
204
  #
@@ -193,7 +208,9 @@ module Google
193
208
  end
194
209
 
195
210
  ##
196
- # Updates the description of the table.
211
+ # Updates the user-friendly description of the view.
212
+ #
213
+ # @param [String] new_description The new user-friendly description.
197
214
  #
198
215
  # @!group Attributes
199
216
  #
@@ -203,7 +220,9 @@ module Google
203
220
  end
204
221
 
205
222
  ##
206
- # The time when this table was created.
223
+ # The time when this view was created.
224
+ #
225
+ # @return [Time, nil] The creation time.
207
226
  #
208
227
  # @!group Attributes
209
228
  #
@@ -217,9 +236,11 @@ module Google
217
236
  end
218
237
 
219
238
  ##
220
- # The time when this table expires.
221
- # If not present, the table will persist indefinitely.
222
- # Expired tables will be deleted and their storage reclaimed.
239
+ # The time when this view expires.
240
+ # If not present, the view will persist indefinitely.
241
+ # Expired views will be deleted and their storage reclaimed.
242
+ #
243
+ # @return [Time, nil] The expiration time.
223
244
  #
224
245
  # @!group Attributes
225
246
  #
@@ -233,7 +254,9 @@ module Google
233
254
  end
234
255
 
235
256
  ##
236
- # The date when this table was last modified.
257
+ # The date when this view was last modified.
258
+ #
259
+ # @return [Time, nil] The last modified time.
237
260
  #
238
261
  # @!group Attributes
239
262
  #
@@ -247,7 +270,9 @@ module Google
247
270
  end
248
271
 
249
272
  ##
250
- # Checks if the table's type is "TABLE".
273
+ # Checks if the view's type is "TABLE".
274
+ #
275
+ # @return [Boolean] `true` when the type is `TABLE`, `false` otherwise.
251
276
  #
252
277
  # @!group Attributes
253
278
  #
@@ -256,7 +281,9 @@ module Google
256
281
  end
257
282
 
258
283
  ##
259
- # Checks if the table's type is "VIEW".
284
+ # Checks if the view's type is "VIEW".
285
+ #
286
+ # @return [Boolean] `true` when the type is `VIEW`, `false` otherwise.
260
287
  #
261
288
  # @!group Attributes
262
289
  #
@@ -265,8 +292,22 @@ module Google
265
292
  end
266
293
 
267
294
  ##
268
- # The geographic location where the table should reside. Possible
269
- # values include EU and US. The default value is US.
295
+ # Checks if the view's type is "EXTERNAL".
296
+ #
297
+ # @return [Boolean] `true` when the type is `EXTERNAL`, `false`
298
+ # otherwise.
299
+ #
300
+ # @!group Attributes
301
+ #
302
+ def external?
303
+ @gapi.type == "EXTERNAL"
304
+ end
305
+
306
+ ##
307
+ # The geographic location where the view should reside. Possible
308
+ # values include `EU` and `US`. The default value is `US`.
309
+ #
310
+ # @return [String] The location code.
270
311
  #
271
312
  # @!group Attributes
272
313
  #
@@ -275,9 +316,83 @@ module Google
275
316
  @gapi.location
276
317
  end
277
318
 
319
+ ##
320
+ # A hash of user-provided labels associated with this view. Labels
321
+ # are used to organize and group views and views. See [Using
322
+ # Labels](https://cloud.google.com/bigquery/docs/labels).
323
+ #
324
+ # The returned hash is frozen and changes are not allowed. Use
325
+ # {#labels=} to replace the entire hash.
326
+ #
327
+ # @return [Hash<String, String>] A hash containing key/value pairs.
328
+ #
329
+ # @example
330
+ # require "google/cloud/bigquery"
331
+ #
332
+ # bigquery = Google::Cloud::Bigquery.new
333
+ # dataset = bigquery.dataset "my_dataset"
334
+ # view = dataset.table "my_view"
335
+ #
336
+ # labels = view.labels
337
+ # labels["department"] #=> "shipping"
338
+ #
339
+ # @!group Attributes
340
+ #
341
+ def labels
342
+ m = @gapi.labels
343
+ m = m.to_h if m.respond_to? :to_h
344
+ m.dup.freeze
345
+ end
346
+
347
+ ##
348
+ # Updates the hash of user-provided labels associated with this view.
349
+ # Labels are used to organize and group tables and views. See [Using
350
+ # Labels](https://cloud.google.com/bigquery/docs/labels).
351
+ #
352
+ # @param [Hash<String, String>] labels A hash containing key/value
353
+ # pairs.
354
+ #
355
+ # * Label keys and values can be no longer than 63 characters.
356
+ # * Label keys and values can contain only lowercase letters, numbers,
357
+ # underscores, hyphens, and international characters.
358
+ # * Label keys and values cannot exceed 128 bytes in size.
359
+ # * Label keys must begin with a letter.
360
+ # * Label keys must be unique within a view.
361
+ #
362
+ # @example
363
+ # require "google/cloud/bigquery"
364
+ #
365
+ # bigquery = Google::Cloud::Bigquery.new
366
+ # dataset = bigquery.dataset "my_dataset"
367
+ # view = dataset.table "my_view"
368
+ #
369
+ # view.labels = { "department" => "shipping" }
370
+ #
371
+ # @!group Attributes
372
+ #
373
+ def labels= labels
374
+ @gapi.labels = labels
375
+ patch_gapi! :labels
376
+ end
377
+
278
378
  ##
279
379
  # The schema of the view.
280
380
  #
381
+ # The returned object is frozen and changes are not allowed.
382
+ #
383
+ # @return [Schema] A schema object.
384
+ #
385
+ # @example
386
+ # require "google/cloud/bigquery"
387
+ #
388
+ # bigquery = Google::Cloud::Bigquery.new
389
+ # dataset = bigquery.dataset "my_dataset"
390
+ # view = dataset.table "my_view"
391
+ #
392
+ # schema = view.schema
393
+ # field = schema.field "name"
394
+ # field.required? #=> true
395
+ #
281
396
  # @!group Attributes
282
397
  #
283
398
  def schema
@@ -286,7 +401,20 @@ module Google
286
401
  end
287
402
 
288
403
  ##
289
- # The fields of the view.
404
+ # The fields of the view, obtained from its schema.
405
+ #
406
+ # @return [Array<Schema::Field>] An array of field objects.
407
+ #
408
+ # @example
409
+ # require "google/cloud/bigquery"
410
+ #
411
+ # bigquery = Google::Cloud::Bigquery.new
412
+ # dataset = bigquery.dataset "my_dataset"
413
+ # view = dataset.table "my_view"
414
+ #
415
+ # view.fields.each do |field|
416
+ # puts field.name
417
+ # end
290
418
  #
291
419
  # @!group Attributes
292
420
  #
@@ -295,7 +423,20 @@ module Google
295
423
  end
296
424
 
297
425
  ##
298
- # The names of the columns in the view.
426
+ # The names of the columns in the view, obtained from its schema.
427
+ #
428
+ # @return [Array<Symbol>] An array of column names.
429
+ #
430
+ # @example
431
+ # require "google/cloud/bigquery"
432
+ #
433
+ # bigquery = Google::Cloud::Bigquery.new
434
+ # dataset = bigquery.dataset "my_dataset"
435
+ # view = dataset.table "my_view"
436
+ #
437
+ # view.headers.each do |header|
438
+ # puts header
439
+ # end
299
440
  #
300
441
  # @!group Attributes
301
442
  #
@@ -306,6 +447,8 @@ module Google
306
447
  ##
307
448
  # The query that executes each time the view is loaded.
308
449
  #
450
+ # @return [String] The query that defines the view.
451
+ #
309
452
  # @!group Attributes
310
453
  #
311
454
  def query
@@ -315,10 +458,39 @@ module Google
315
458
  ##
316
459
  # Updates the query that executes each time the view is loaded.
317
460
  #
461
+ # This sets the query using standard SQL. To specify legacy SQL or to
462
+ # use user-defined function resources use (#set_query) instead.
463
+ #
318
464
  # @see https://cloud.google.com/bigquery/query-reference BigQuery Query
319
465
  # Reference
320
466
  #
321
467
  # @param [String] new_query The query that defines the view.
468
+ #
469
+ # @example
470
+ # require "google/cloud/bigquery"
471
+ #
472
+ # bigquery = Google::Cloud::Bigquery.new
473
+ # dataset = bigquery.dataset "my_dataset"
474
+ # view = dataset.table "my_view"
475
+ #
476
+ # view.query = "SELECT first_name FROM " \
477
+ # "`my_project.my_dataset.my_table`"
478
+ #
479
+ # @!group Lifecycle
480
+ #
481
+ def query= new_query
482
+ set_query new_query
483
+ end
484
+
485
+ ##
486
+ # Updates the query that executes each time the view is loaded. Allows
487
+ # setting of standard vs. legacy SQL and user-defined function
488
+ # resources.
489
+ #
490
+ # @see https://cloud.google.com/bigquery/query-reference BigQuery Query
491
+ # Reference
492
+ #
493
+ # @param [String] query The query that defines the view.
322
494
  # @param [Boolean] standard_sql Specifies whether to use BigQuery's
323
495
  # [standard
324
496
  # SQL](https://cloud.google.com/bigquery/docs/reference/standard-sql/)
@@ -327,6 +499,13 @@ module Google
327
499
  # [legacy
328
500
  # SQL](https://cloud.google.com/bigquery/docs/reference/legacy-sql)
329
501
  # dialect. Optional. The default value is false.
502
+ # @param [Array<String>, String] udfs User-defined function resources
503
+ # used in the query. May be either a code resource to load from a
504
+ # Google Cloud Storage URI (`gs://bucket/path`), or an inline resource
505
+ # that contains code for a user-defined function (UDF). Providing an
506
+ # inline code resource is equivalent to providing a URI for a file
507
+ # containing the same code. See [User-Defined
508
+ # Functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions).
330
509
  #
331
510
  # @example
332
511
  # require "google/cloud/bigquery"
@@ -335,21 +514,71 @@ module Google
335
514
  # dataset = bigquery.dataset "my_dataset"
336
515
  # view = dataset.table "my_view"
337
516
  #
338
- # view.query = "SELECT first_name FROM " \
339
- # "`my_project.my_dataset.my_table`"
517
+ # view.set_query "SELECT first_name FROM " \
518
+ # "`my_project.my_dataset.my_table`",
519
+ # standard_sql: true
340
520
  #
341
521
  # @!group Lifecycle
342
522
  #
343
- def query= new_query, standard_sql: nil, legacy_sql: nil
344
- @gapi.view ||= Google::Apis::BigqueryV2::ViewDefinition.new
345
- @gapi.view.update! query: new_query
346
- @gapi.view.update! use_legacy_sql: \
347
- Convert.resolve_legacy_sql(standard_sql, legacy_sql)
348
- patch_view_gapi! :query
523
+ def set_query query, standard_sql: nil, legacy_sql: nil, udfs: nil
524
+ @gapi.view = Google::Apis::BigqueryV2::ViewDefinition.new \
525
+ query: query,
526
+ use_legacy_sql: Convert.resolve_legacy_sql(standard_sql,
527
+ legacy_sql),
528
+ user_defined_function_resources: udfs_gapi(udfs)
529
+ patch_view_gapi!
349
530
  end
350
531
 
351
532
  ##
352
- # Runs a query to retrieve all data from the view.
533
+ # Checks if the view's query is using legacy sql.
534
+ #
535
+ # @return [Boolean] `true` when legacy sql is used, `false` otherwise.
536
+ #
537
+ # @!group Attributes
538
+ #
539
+ def query_legacy_sql?
540
+ val = @gapi.view.use_legacy_sql
541
+ return true if val.nil?
542
+ val
543
+ end
544
+
545
+ ##
546
+ # Checks if the view's query is using standard sql.
547
+ #
548
+ # @return [Boolean] `true` when standard sql is used, `false` otherwise.
549
+ #
550
+ # @!group Attributes
551
+ #
552
+ def query_standard_sql?
553
+ !query_legacy_sql?
554
+ end
555
+
556
+ ##
557
+ # The user-defined function resources used in the view's query. May be
558
+ # either a code resource to load from a Google Cloud Storage URI
559
+ # (`gs://bucket/path`), or an inline resource that contains code for a
560
+ # user-defined function (UDF). Providing an inline code resource is
561
+ # equivalent to providing a URI for a file containing the same code. See
562
+ # [User-Defined
563
+ # Functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/user-defined-functions).
564
+ #
565
+ # @return [Array<String>] An array containing Google Cloud Storage URIs
566
+ # and/or inline source code.
567
+ #
568
+ # @!group Attributes
569
+ #
570
+ def query_udfs
571
+ udfs_gapi = @gapi.view.user_defined_function_resources
572
+ return [] if udfs_gapi.nil?
573
+ Array(udfs_gapi).map { |udf| udf.inline_code || udf.resource_uri }
574
+ end
575
+
576
+ ##
577
+ # Runs a query to retrieve all data from the view, in a synchronous
578
+ # method that blocks for a response. In this method, a {QueryJob} is
579
+ # created and its results are saved to a temporary table, then read from
580
+ # the table. Timeouts and transient errors are generally handled as
581
+ # needed to complete the query.
353
582
  #
354
583
  # @param [Integer] max The maximum number of rows of data to return per
355
584
  # page of results. Setting this flag to a small value such as 1000 and
@@ -357,23 +586,13 @@ module Google
357
586
  # result set is large. In addition to this limit, responses are also
358
587
  # limited to 10 MB. By default, there is no maximum row count, and
359
588
  # only the byte limit applies.
360
- # @param [Integer] timeout How long to wait for the query to complete,
361
- # in milliseconds, before the request times out and returns. Note that
362
- # this is only a timeout for the request, not the query. If the query
363
- # takes longer to run than the timeout value, the call returns without
364
- # any results and with QueryData#complete? set to false. The default
365
- # value is 10000 milliseconds (10 seconds).
366
589
  # @param [Boolean] cache Whether to look for the result in the query
367
590
  # cache. The query cache is a best-effort cache that will be flushed
368
591
  # whenever tables in the query are modified. The default value is
369
592
  # true. For more information, see [query
370
593
  # caching](https://developers.google.com/bigquery/querying-data).
371
- # @param [Boolean] dryrun If set to `true`, BigQuery doesn't run the
372
- # job. Instead, if the query is valid, BigQuery returns statistics
373
- # about the job such as how many bytes would be processed. If the
374
- # query is invalid, an error returns. The default value is `false`.
375
594
  #
376
- # @return [Google::Cloud::Bigquery::QueryData]
595
+ # @return [Google::Cloud::Bigquery::Data]
377
596
  #
378
597
  # @example
379
598
  # require "google/cloud/bigquery"
@@ -390,18 +609,31 @@ module Google
390
609
  #
391
610
  # @!group Data
392
611
  #
393
- def data max: nil, timeout: 10000, cache: true, dryrun: nil
612
+ def data max: nil, cache: true
394
613
  sql = "SELECT * FROM #{query_id}"
395
614
  ensure_service!
396
- options = { max: max, timeout: timeout, cache: cache, dryrun: dryrun }
397
- gapi = service.query sql, options
398
- QueryData.from_gapi gapi, service
615
+
616
+ gapi = service.query_job sql, cache: cache
617
+ job = Job.from_gapi gapi, service
618
+ job.wait_until_done!
619
+
620
+ if job.failed?
621
+ begin
622
+ # raise to activate ruby exception cause handling
623
+ fail job.gapi_error
624
+ rescue => e
625
+ # wrap Google::Apis::Error with Google::Cloud::Error
626
+ raise Google::Cloud::Error.from_error(e)
627
+ end
628
+ end
629
+
630
+ job.data max: max
399
631
  end
400
632
 
401
633
  ##
402
- # Permanently deletes the table.
634
+ # Permanently deletes the view.
403
635
  #
404
- # @return [Boolean] Returns `true` if the table was deleted.
636
+ # @return [Boolean] Returns `true` if the view was deleted.
405
637
  #
406
638
  # @example
407
639
  # require "google/cloud/bigquery"
@@ -421,7 +653,7 @@ module Google
421
653
  end
422
654
 
423
655
  ##
424
- # Reloads the table with current data from the BigQuery service.
656
+ # Reloads the view with current data from the BigQuery service.
425
657
  #
426
658
  # @!group Lifecycle
427
659
  #
@@ -449,12 +681,6 @@ module Google
449
681
  fail "Must have active connection" unless service
450
682
  end
451
683
 
452
- def resolve_legacy_sql legacy_sql, standard_sql
453
- return legacy_sql unless legacy_sql.nil?
454
- return !standard_sql unless standard_sql.nil?
455
- false
456
- end
457
-
458
684
  def patch_gapi! *attributes
459
685
  return if attributes.empty?
460
686
  patch_args = Hash[attributes.map do |attr|
@@ -463,21 +689,19 @@ module Google
463
689
  patch_table_gapi patch_args
464
690
  end
465
691
 
466
- def patch_view_gapi! *attributes
467
- return if attributes.empty?
468
- patch_args = Hash[attributes.map do |attr|
469
- [attr, @gapi.view.send(attr)]
470
- end]
471
- patch_view_args = Google::Apis::BigqueryV2::ViewDefinition.new(
472
- patch_args
473
- )
474
- patch_table_gapi view: patch_view_args
475
- end
476
-
477
692
  def patch_table_gapi patch_args
478
693
  ensure_service!
479
694
  patch_gapi = Google::Apis::BigqueryV2::Table.new patch_args
695
+ patch_gapi.etag = etag if etag
480
696
  @gapi = service.patch_table dataset_id, table_id, patch_gapi
697
+
698
+ # TODO: restore original impl after acceptance test indicates that
699
+ # service etag bug is fixed
700
+ reload!
701
+ end
702
+
703
+ def patch_view_gapi!
704
+ patch_table_gapi view: @gapi.view
481
705
  end
482
706
 
483
707
  ##
@@ -496,6 +720,19 @@ module Google
496
720
  def data_complete?
497
721
  @gapi.is_a? Google::Apis::BigqueryV2::Table
498
722
  end
723
+
724
+ def udfs_gapi array_or_str
725
+ return [] if array_or_str.nil?
726
+ Array(array_or_str).map do |uri_or_code|
727
+ resource = Google::Apis::BigqueryV2::UserDefinedFunctionResource.new
728
+ if uri_or_code.start_with?("gs://")
729
+ resource.resource_uri = uri_or_code
730
+ else
731
+ resource.inline_code = uri_or_code
732
+ end
733
+ resource
734
+ end
735
+ end
499
736
  end
500
737
  end
501
738
  end