gcloud 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +8 -8
  2. data/AUTHENTICATION.md +3 -3
  3. data/CHANGELOG.md +12 -0
  4. data/OVERVIEW.md +30 -0
  5. data/lib/gcloud.rb +126 -9
  6. data/lib/gcloud/bigquery.rb +399 -0
  7. data/lib/gcloud/bigquery/connection.rb +592 -0
  8. data/lib/gcloud/bigquery/copy_job.rb +98 -0
  9. data/lib/gcloud/bigquery/credentials.rb +29 -0
  10. data/lib/gcloud/bigquery/data.rb +134 -0
  11. data/lib/gcloud/bigquery/dataset.rb +662 -0
  12. data/lib/gcloud/bigquery/dataset/list.rb +51 -0
  13. data/lib/gcloud/bigquery/errors.rb +62 -0
  14. data/lib/gcloud/bigquery/extract_job.rb +117 -0
  15. data/lib/gcloud/bigquery/insert_response.rb +80 -0
  16. data/lib/gcloud/bigquery/job.rb +283 -0
  17. data/lib/gcloud/bigquery/job/list.rb +55 -0
  18. data/lib/gcloud/bigquery/load_job.rb +199 -0
  19. data/lib/gcloud/bigquery/project.rb +512 -0
  20. data/lib/gcloud/bigquery/query_data.rb +135 -0
  21. data/lib/gcloud/bigquery/query_job.rb +151 -0
  22. data/lib/gcloud/bigquery/table.rb +827 -0
  23. data/lib/gcloud/bigquery/table/list.rb +55 -0
  24. data/lib/gcloud/bigquery/view.rb +419 -0
  25. data/lib/gcloud/credentials.rb +3 -3
  26. data/lib/gcloud/datastore.rb +15 -3
  27. data/lib/gcloud/datastore/credentials.rb +3 -2
  28. data/lib/gcloud/datastore/dataset.rb +5 -1
  29. data/lib/gcloud/datastore/transaction.rb +1 -1
  30. data/lib/gcloud/pubsub.rb +14 -3
  31. data/lib/gcloud/pubsub/credentials.rb +4 -4
  32. data/lib/gcloud/pubsub/project.rb +5 -1
  33. data/lib/gcloud/pubsub/topic.rb +5 -0
  34. data/lib/gcloud/storage.rb +14 -24
  35. data/lib/gcloud/storage/bucket.rb +10 -4
  36. data/lib/gcloud/storage/credentials.rb +3 -2
  37. data/lib/gcloud/storage/file.rb +8 -1
  38. data/lib/gcloud/storage/project.rb +5 -1
  39. data/lib/gcloud/upload.rb +54 -0
  40. data/lib/gcloud/version.rb +1 -1
  41. metadata +78 -2
@@ -0,0 +1,135 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/bigquery/data"
17
+
18
+ module Gcloud
19
+ module Bigquery
20
+ ##
21
+ # = QueryData
22
+ #
23
+ # Represents Data returned from a query a a list of name/value pairs.
24
+ class QueryData < Data
25
+ ##
26
+ # The Connection object.
27
+ attr_accessor :connection #:nodoc:
28
+
29
+ def initialize arr = [] #:nodoc:
30
+ @job = nil
31
+ super
32
+ end
33
+
34
+ # The total number of bytes processed for this query.
35
+ def total_bytes
36
+ @gapi["totalBytesProcessed"]
37
+ end
38
+
39
+ # Whether the query has completed or not. When data is present this will
40
+ # always be +true+. When +false+, +total+ will not be available.
41
+ def complete?
42
+ @gapi["jobComplete"]
43
+ end
44
+
45
+ # Whether the query result was fetched from the query cache.
46
+ def cache_hit?
47
+ @gapi["cacheHit"]
48
+ end
49
+
50
+ ##
51
+ # The schema of the data.
52
+ def schema
53
+ s = @gapi["schema"]
54
+ s = s.to_hash if s.respond_to? :to_hash
55
+ s = {} if s.nil?
56
+ s
57
+ end
58
+
59
+ ##
60
+ # The fields of the data.
61
+ def fields
62
+ f = schema["fields"]
63
+ f = f.to_hash if f.respond_to? :to_hash
64
+ f = [] if f.nil?
65
+ f
66
+ end
67
+
68
+ ##
69
+ # The name of the columns in the data.
70
+ def headers
71
+ fields.map { |f| f["name"] }
72
+ end
73
+
74
+ ##
75
+ # Is there a next page of data?
76
+ def next?
77
+ !token.nil?
78
+ end
79
+
80
+ def next
81
+ return nil unless next?
82
+ ensure_connection!
83
+ resp = connection.job_query_results job_id, token: token
84
+ if resp.success?
85
+ QueryData.from_gapi resp.data, connection
86
+ else
87
+ fail ApiError.from_response(resp)
88
+ end
89
+ end
90
+
91
+ ##
92
+ # The BigQuery Job that was created to run the query.
93
+ def job
94
+ return @job if @job
95
+ return nil unless job?
96
+ ensure_connection!
97
+ resp = connection.get_job job_id
98
+ if resp.success?
99
+ @job = Job.from_gapi resp.data, connection
100
+ else
101
+ return nil if resp.status == 404
102
+ fail ApiError.from_response(resp)
103
+ end
104
+ end
105
+
106
+ ##
107
+ # New Data from a response object.
108
+ def self.from_gapi gapi, connection #:nodoc:
109
+ formatted_rows = format_rows gapi["rows"],
110
+ gapi["schema"]["fields"]
111
+
112
+ data = new formatted_rows
113
+ data.gapi = gapi
114
+ data.connection = connection
115
+ data
116
+ end
117
+
118
+ protected
119
+
120
+ ##
121
+ # Raise an error unless an active connection is available.
122
+ def ensure_connection!
123
+ fail "Must have active connection" unless connection
124
+ end
125
+
126
+ def job?
127
+ @gapi["jobReference"] && @gapi["jobReference"]["jobId"]
128
+ end
129
+
130
+ def job_id
131
+ @gapi["jobReference"]["jobId"]
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,151 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module Gcloud
17
+ module Bigquery
18
+ ##
19
+ # = QueryJob
20
+ #
21
+ # A Job subclass representing a query operation that may be performed
22
+ # on a Table. A QueryJob instance is created when you call
23
+ # Project#query_job, Dataset#query_job, or View#data.
24
+ #
25
+ # See {Querying Data}[https://cloud.google.com/bigquery/querying-data]
26
+ # and the {Jobs API
27
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs]
28
+ # for details.
29
+ #
30
+ class QueryJob < Job
31
+ ##
32
+ # Checks if the priority for the query is +BATCH+.
33
+ def batch?
34
+ val = config["query"]["priority"]
35
+ val == "BATCH"
36
+ end
37
+
38
+ ##
39
+ # Checks if the priority for the query is +INTERACTIVE+.
40
+ def interactive?
41
+ val = config["query"]["priority"]
42
+ return true if val.nil?
43
+ val == "INTERACTIVE"
44
+ end
45
+
46
+ ##
47
+ # Checks if the the query job allows arbitrarily large results at a slight
48
+ # cost to performance.
49
+ def large_results?
50
+ val = config["query"]["preserveNulls"]
51
+ return false if val.nil?
52
+ val
53
+ end
54
+
55
+ ##
56
+ # Checks if the query job looks for an existing result in the query cache.
57
+ # For more information, see {Query
58
+ # Caching}[https://cloud.google.com/bigquery/querying-data#querycaching].
59
+ def cache?
60
+ val = config["query"]["useQueryCache"]
61
+ return false if val.nil?
62
+ val
63
+ end
64
+
65
+ ##
66
+ # Checks if the query job flattens nested and repeated fields in the query
67
+ # results. The default is +true+. If the value is +false+, #large_results?
68
+ # should return +true+.
69
+ def flatten?
70
+ val = config["query"]["flattenResults"]
71
+ return true if val.nil?
72
+ val
73
+ end
74
+
75
+ ##
76
+ # Checks if the query results are from the query cache.
77
+ def cache_hit?
78
+ stats["query"]["cacheHit"]
79
+ end
80
+
81
+ ##
82
+ # The number of bytes processed by the query.
83
+ def bytes_processed
84
+ stats["query"]["totalBytesProcessed"]
85
+ end
86
+
87
+ ##
88
+ # The table in which the query results are stored.
89
+ def destination
90
+ table = config["query"]["destinationTable"]
91
+ return nil unless table
92
+ retrieve_table table["projectId"],
93
+ table["datasetId"],
94
+ table["tableId"]
95
+ end
96
+
97
+ ##
98
+ # Retrieves the query results for the job.
99
+ #
100
+ # === Parameters
101
+ #
102
+ # +options+::
103
+ # An optional Hash for controlling additional behavior. (+Hash+)
104
+ # <code>options[:token]</code>::
105
+ # Page token, returned by a previous call, identifying the result set.
106
+ # (+String+)
107
+ # <code>options[:max]</code>::
108
+ # Maximum number of results to return. (+Integer+)
109
+ # <code>options[:start]</code>::
110
+ # Zero-based index of the starting row to read. (+Integer+)
111
+ # <code>options[:timeout]</code>::
112
+ # How long to wait for the query to complete, in milliseconds, before
113
+ # returning. Default is 10,000 milliseconds (10 seconds). (+Integer+)
114
+ #
115
+ # === Returns
116
+ #
117
+ # Gcloud::Bigquery::QueryData
118
+ #
119
+ # === Example
120
+ #
121
+ # require "gcloud"
122
+ #
123
+ # gcloud = Gcloud.new
124
+ # bigquery = gcloud.bigquery
125
+ #
126
+ # q = "SELECT word FROM publicdata:samples.shakespeare"
127
+ # job = bigquery.query_job q
128
+ #
129
+ # loop do
130
+ # break if job.done?
131
+ # sleep 1
132
+ # job.refresh!
133
+ # end
134
+ # data = job.query_results
135
+ # data.each do |row|
136
+ # puts row["word"]
137
+ # end
138
+ # data = data.next if data.next?
139
+ #
140
+ def query_results options = {}
141
+ ensure_connection!
142
+ resp = connection.job_query_results job_id, options
143
+ if resp.success?
144
+ QueryData.from_gapi resp.data, connection
145
+ else
146
+ fail ApiError.from_response(resp)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,827 @@
1
+ #--
2
+ # Copyright 2015 Google Inc. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require "gcloud/bigquery/view"
17
+ require "gcloud/bigquery/data"
18
+ require "gcloud/bigquery/table/list"
19
+ require "gcloud/bigquery/errors"
20
+ require "gcloud/bigquery/insert_response"
21
+ require "gcloud/upload"
22
+
23
+ module Gcloud
24
+ module Bigquery
25
+ ##
26
+ # = Table
27
+ #
28
+ # A named resource representing a BigQuery table that holds zero or more
29
+ # records. Every table is defined by a schema
30
+ # that may contain nested and repeated fields. (For more information
31
+ # about nested and repeated fields, see {Preparing Data for
32
+ # BigQuery}[https://cloud.google.com/bigquery/preparing-data-for-bigquery].)
33
+ #
34
+ # require "gcloud"
35
+ #
36
+ # gcloud = Gcloud.new
37
+ # bigquery = gcloud.bigquery
38
+ # dataset = bigquery.dataset "my_dataset"
39
+ # table = dataset.create_table "my_table"
40
+ #
41
+ # schema = {
42
+ # "fields" => [
43
+ # {
44
+ # "name" => "first_name",
45
+ # "type" => "STRING",
46
+ # "mode" => "REQUIRED"
47
+ # },
48
+ # {
49
+ # "name" => "cities_lived",
50
+ # "type" => "RECORD",
51
+ # "mode" => "REPEATED",
52
+ # "fields" => [
53
+ # {
54
+ # "name" => "place",
55
+ # "type" => "STRING",
56
+ # "mode" => "REQUIRED"
57
+ # },
58
+ # {
59
+ # "name" => "number_of_years",
60
+ # "type" => "INTEGER",
61
+ # "mode" => "REQUIRED"
62
+ # }
63
+ # ]
64
+ # }
65
+ # ]
66
+ # }
67
+ # table.schema = schema
68
+ #
69
+ # row = {
70
+ # "first_name" => "Alice",
71
+ # "cities_lived" => [
72
+ # {
73
+ # "place": "Seattle",
74
+ # "number_of_years": 5
75
+ # },
76
+ # {
77
+ # "place": "Stockholm",
78
+ # "number_of_years": 6
79
+ # }
80
+ # ]
81
+ # }
82
+ # table.insert row
83
+ #
84
+ class Table
85
+ ##
86
+ # The Connection object.
87
+ attr_accessor :connection #:nodoc:
88
+
89
+ ##
90
+ # The Google API Client object.
91
+ attr_accessor :gapi #:nodoc:
92
+
93
+ ##
94
+ # Create an empty Table object.
95
+ def initialize #:nodoc:
96
+ @connection = nil
97
+ @gapi = {}
98
+ end
99
+
100
+ ##
101
+ # A unique ID for this table.
102
+ # The ID must contain only letters (a-z, A-Z), numbers (0-9),
103
+ # or underscores (_). The maximum length is 1,024 characters.
104
+ #
105
+ # :category: Attributes
106
+ #
107
+ def table_id
108
+ @gapi["tableReference"]["tableId"]
109
+ end
110
+
111
+ ##
112
+ # The ID of the +Dataset+ containing this table.
113
+ #
114
+ # :category: Attributes
115
+ #
116
+ def dataset_id
117
+ @gapi["tableReference"]["datasetId"]
118
+ end
119
+
120
+ ##
121
+ # The ID of the +Project+ containing this table.
122
+ #
123
+ # :category: Attributes
124
+ #
125
+ def project_id
126
+ @gapi["tableReference"]["projectId"]
127
+ end
128
+
129
+ ##
130
+ # The name of the table.
131
+ #
132
+ # :category: Attributes
133
+ #
134
+ def name
135
+ @gapi["friendlyName"]
136
+ end
137
+
138
+ ##
139
+ # Updates the name of the table.
140
+ #
141
+ # :category: Attributes
142
+ #
143
+ def name= new_name
144
+ patch_gapi! name: new_name
145
+ end
146
+
147
+ ##
148
+ # A string hash of the dataset.
149
+ #
150
+ # :category: Attributes
151
+ #
152
+ def etag
153
+ ensure_full_data!
154
+ @gapi["etag"]
155
+ end
156
+
157
+ ##
158
+ # A URL that can be used to access the dataset using the REST API.
159
+ #
160
+ # :category: Attributes
161
+ #
162
+ def url
163
+ ensure_full_data!
164
+ @gapi["selfLink"]
165
+ end
166
+
167
+ ##
168
+ # The description of the table.
169
+ #
170
+ # :category: Attributes
171
+ #
172
+ def description
173
+ ensure_full_data!
174
+ @gapi["description"]
175
+ end
176
+
177
+ ##
178
+ # Updates the description of the table.
179
+ #
180
+ # :category: Attributes
181
+ #
182
+ def description= new_description
183
+ patch_gapi! description: new_description
184
+ end
185
+
186
+ ##
187
+ # The number of bytes in the table.
188
+ #
189
+ # :category: Data
190
+ #
191
+ def bytes_count
192
+ ensure_full_data!
193
+ @gapi["numBytes"]
194
+ end
195
+
196
+ ##
197
+ # The number of rows in the table.
198
+ #
199
+ # :category: Data
200
+ #
201
+ def rows_count
202
+ ensure_full_data!
203
+ @gapi["numRows"]
204
+ end
205
+
206
+ ##
207
+ # The time when this table was created.
208
+ #
209
+ # :category: Attributes
210
+ #
211
+ def created_at
212
+ ensure_full_data!
213
+ Time.at(@gapi["creationTime"] / 1000.0)
214
+ end
215
+
216
+ ##
217
+ # The time when this table expires.
218
+ # If not present, the table will persist indefinitely.
219
+ # Expired tables will be deleted and their storage reclaimed.
220
+ #
221
+ # :category: Attributes
222
+ #
223
+ def expires_at
224
+ ensure_full_data!
225
+ return nil if @gapi["expirationTime"].nil?
226
+ Time.at(@gapi["expirationTime"] / 1000.0)
227
+ end
228
+
229
+ ##
230
+ # The date when this table was last modified.
231
+ #
232
+ # :category: Attributes
233
+ #
234
+ def modified_at
235
+ ensure_full_data!
236
+ Time.at(@gapi["lastModifiedTime"] / 1000.0)
237
+ end
238
+
239
+ ##
240
+ # Checks if the table's type is "TABLE".
241
+ #
242
+ # :category: Attributes
243
+ #
244
+ def table?
245
+ @gapi["type"] == "TABLE"
246
+ end
247
+
248
+ ##
249
+ # Checks if the table's type is "VIEW".
250
+ #
251
+ # :category: Attributes
252
+ #
253
+ def view?
254
+ @gapi["type"] == "VIEW"
255
+ end
256
+
257
+ ##
258
+ # The geographic location where the table should reside. Possible
259
+ # values include EU and US. The default value is US.
260
+ #
261
+ # :category: Attributes
262
+ #
263
+ def location
264
+ ensure_full_data!
265
+ @gapi["location"]
266
+ end
267
+
268
+ ##
269
+ # The schema of the table.
270
+ #
271
+ # :category: Attributes
272
+ #
273
+ def schema
274
+ ensure_full_data!
275
+ s = @gapi["schema"]
276
+ s = s.to_hash if s.respond_to? :to_hash
277
+ s = {} if s.nil?
278
+ s
279
+ end
280
+
281
+ ##
282
+ # Updates the schema of the table.
283
+ #
284
+ # === Example
285
+ #
286
+ # require "gcloud"
287
+ #
288
+ # gcloud = Gcloud.new
289
+ # bigquery = gcloud.bigquery
290
+ # dataset = bigquery.dataset "my_dataset"
291
+ # table = dataset.create_table "my_table"
292
+ #
293
+ # schema = {
294
+ # "fields" => [
295
+ # {
296
+ # "name" => "first_name",
297
+ # "type" => "STRING",
298
+ # "mode" => "REQUIRED"
299
+ # },
300
+ # {
301
+ # "name" => "age",
302
+ # "type" => "INTEGER",
303
+ # "mode" => "REQUIRED"
304
+ # }
305
+ # ]
306
+ # }
307
+ # table.schema = schema
308
+ #
309
+ # :category: Attributes
310
+ #
311
+ def schema= new_schema
312
+ patch_gapi! schema: new_schema
313
+ end
314
+
315
+ ##
316
+ # The fields of the table.
317
+ #
318
+ # :category: Attributes
319
+ #
320
+ def fields
321
+ f = schema["fields"]
322
+ f = f.to_hash if f.respond_to? :to_hash
323
+ f = [] if f.nil?
324
+ f
325
+ end
326
+
327
+ ##
328
+ # The names of the columns in the table.
329
+ #
330
+ # :category: Attributes
331
+ #
332
+ def headers
333
+ fields.map { |f| f["name"] }
334
+ end
335
+
336
+ ##
337
+ # Retrieves data from the table.
338
+ #
339
+ # === Parameters
340
+ #
341
+ # +options+::
342
+ # An optional Hash for controlling additional behavior. (+Hash+)
343
+ # <code>options[:token]</code>::
344
+ # Page token, returned by a previous call, identifying the result set.
345
+ # (+String+)
346
+ # <code>options[:max]</code>::
347
+ # Maximum number of results to return. (+Integer+)
348
+ # <code>options[:start]</code>::
349
+ # Zero-based index of the starting row to read. (+Integer+)
350
+ #
351
+ # === Returns
352
+ #
353
+ # Gcloud::Bigquery::Data
354
+ #
355
+ # === Example
356
+ #
357
+ # require "gcloud"
358
+ #
359
+ # gcloud = Gcloud.new
360
+ # bigquery = gcloud.bigquery
361
+ # dataset = bigquery.dataset "my_dataset"
362
+ # table = dataset.table "my_table"
363
+ #
364
+ # data = table.data
365
+ # data.each do |row|
366
+ # puts row["first_name"]
367
+ # end
368
+ # more_data = table.data token: data.token
369
+ #
370
+ # :category: Data
371
+ #
372
+ def data options = {}
373
+ ensure_connection!
374
+ resp = connection.list_tabledata dataset_id, table_id, options
375
+ if resp.success?
376
+ Data.from_response resp, self
377
+ else
378
+ fail ApiError.from_response(resp)
379
+ end
380
+ end
381
+
382
+ ##
383
+ # Copies the data from the table to another table.
384
+ #
385
+ # === Parameters
386
+ #
387
+ # +destination_table+::
388
+ # The destination for the copied data. (+Table+)
389
+ # +options+::
390
+ # An optional Hash for controlling additional behavior. (+Hash+)
391
+ # <code>options[:create]</code>::
392
+ # Specifies whether the job is allowed to create new tables. (+String+)
393
+ #
394
+ # The following values are supported:
395
+ # * +needed+ - Create the table if it does not exist.
396
+ # * +never+ - The table must already exist. A 'notFound' error is
397
+ # raised if the table does not exist.
398
+ # <code>options[:write]</code>::
399
+ # Specifies how to handle data already present in the destination table.
400
+ # The default value is +empty+. (+String+)
401
+ #
402
+ # The following values are supported:
403
+ # * +truncate+ - BigQuery overwrites the table data.
404
+ # * +append+ - BigQuery appends the data to the table.
405
+ # * +empty+ - An error will be returned if the destination table already
406
+ # contains data.
407
+ #
408
+ # === Returns
409
+ #
410
+ # Gcloud::Bigquery::CopyJob
411
+ #
412
+ # === Example
413
+ #
414
+ # require "gcloud"
415
+ #
416
+ # gcloud = Gcloud.new
417
+ # bigquery = gcloud.bigquery
418
+ # dataset = bigquery.dataset "my_dataset"
419
+ # table = dataset.table "my_table"
420
+ # destination_table = dataset.table "my_destination_table"
421
+ #
422
+ # copy_job = table.copy destination_table
423
+ #
424
+ # :category: Data
425
+ #
426
+ def copy destination_table, options = {}
427
+ ensure_connection!
428
+ resp = connection.copy_table gapi, destination_table.gapi, options
429
+ if resp.success?
430
+ Job.from_gapi resp.data, connection
431
+ else
432
+ fail ApiError.from_response(resp)
433
+ end
434
+ end
435
+
436
+ ##
437
+ # Links the table to a source table identified by a URI.
438
+ #
439
+ # === Parameters
440
+ #
441
+ # +source_url+::
442
+ # The URI of source table to link. (+String+)
443
+ # +options+::
444
+ # An optional Hash for controlling additional behavior. (+Hash+)
445
+ # <code>options[:create]</code>::
446
+ # Specifies whether the job is allowed to create new tables. (+String+)
447
+ #
448
+ # The following values are supported:
449
+ # * +needed+ - Create the table if it does not exist.
450
+ # * +never+ - The table must already exist. A 'notFound' error is
451
+ # raised if the table does not exist.
452
+ # <code>options[:write]</code>::
453
+ # Specifies how to handle data already present in the table.
454
+ # The default value is +empty+. (+String+)
455
+ #
456
+ # The following values are supported:
457
+ # * +truncate+ - BigQuery overwrites the table data.
458
+ # * +append+ - BigQuery appends the data to the table.
459
+ # * +empty+ - An error will be returned if the table already contains
460
+ # data.
461
+ #
462
+ # === Returns
463
+ #
464
+ # Gcloud::Bigquery::Job
465
+ #
466
+ # :category: Data
467
+ #
468
+ def link source_url, options = {} #:nodoc:
469
+ ensure_connection!
470
+ resp = connection.link_table gapi, source_url, options
471
+ if resp.success?
472
+ Job.from_gapi resp.data, connection
473
+ else
474
+ fail ApiError.from_response(resp)
475
+ end
476
+ end
477
+
478
+ ##
479
+ # Extract the data from the table to a Google Cloud Storage file. For
480
+ # more information, see {Exporting Data From BigQuery
481
+ # }[https://cloud.google.com/bigquery/exporting-data-from-bigquery].
482
+ #
483
+ # === Parameters
484
+ #
485
+ # +extract_url+::
486
+ # The Google Storage file or file URI pattern(s) to which BigQuery
487
+ # should extract the table data.
488
+ # (+Gcloud::Storage::File+ or +String+ or +Array+)
489
+ # +options+::
490
+ # An optional Hash for controlling additional behavior. (+Hash+)
491
+ # <code>options[:format]</code>::
492
+ # The exported file format. The default value is +csv+. (+String+)
493
+ #
494
+ # The following values are supported:
495
+ # * +csv+ - CSV
496
+ # * +json+ - {Newline-delimited JSON}[http://jsonlines.org/]
497
+ # * +avro+ - {Avro}[http://avro.apache.org/]
498
+ #
499
+ # === Returns
500
+ #
501
+ # Gcloud::Bigquery::ExtractJob
502
+ #
503
+ # === Example
504
+ #
505
+ # require "gcloud"
506
+ #
507
+ # gcloud = Gcloud.new
508
+ # bigquery = gcloud.bigquery
509
+ # dataset = bigquery.dataset "my_dataset"
510
+ # table = dataset.table "my_table"
511
+ #
512
+ # extract_job = table.extract "gs://my-bucket/file-name.json",
513
+ # format: "json"
514
+ #
515
+ # :category: Data
516
+ #
517
+ def extract extract_url, options = {}
518
+ ensure_connection!
519
+ resp = connection.extract_table gapi, extract_url, options
520
+ if resp.success?
521
+ Job.from_gapi resp.data, connection
522
+ else
523
+ fail ApiError.from_response(resp)
524
+ end
525
+ end
526
+
527
+ ##
528
+ # Loads data into the table.
529
+ #
530
+ # === Parameters
531
+ #
532
+ # +file+::
533
+ # A file or the URI of a Google Cloud Storage file containing
534
+ # data to load into the table.
535
+ # (+File+ or +Gcloud::Storage::File+ or +String+)
536
+ # +options+::
537
+ # An optional Hash for controlling additional behavior. (+Hash+)
538
+ # <code>options[:format]</code>::
539
+ # The exported file format. The default value is +csv+. (+String+)
540
+ #
541
+ # The following values are supported:
542
+ # * +csv+ - CSV
543
+ # * +json+ - {Newline-delimited JSON}[http://jsonlines.org/]
544
+ # * +avro+ - {Avro}[http://avro.apache.org/]
545
+ # <code>options[:create]</code>::
546
+ # Specifies whether the job is allowed to create new tables. (+String+)
547
+ #
548
+ # The following values are supported:
549
+ # * +needed+ - Create the table if it does not exist.
550
+ # * +never+ - The table must already exist. A 'notFound' error is
551
+ # raised if the table does not exist.
552
+ # <code>options[:write]</code>::
553
+ # Specifies how to handle data already present in the table.
554
+ # The default value is +empty+. (+String+)
555
+ #
556
+ # The following values are supported:
557
+ # * +truncate+ - BigQuery overwrites the table data.
558
+ # * +append+ - BigQuery appends the data to the table.
559
+ # * +empty+ - An error will be returned if the table already contains
560
+ # data.
561
+ #
562
+ # === Returns
563
+ #
564
+ # Gcloud::Bigquery::LoadJob
565
+ #
566
+ # === Examples
567
+ #
568
+ # require "gcloud"
569
+ #
570
+ # gcloud = Gcloud.new
571
+ # bigquery = gcloud.bigquery
572
+ # dataset = bigquery.dataset "my_dataset"
573
+ # table = dataset.table "my_table"
574
+ #
575
+ # load_job = table.load "gs://my-bucket/file-name.csv"
576
+ #
577
+ # You can also pass a gcloud storage file instance.
578
+ #
579
+ # require "gcloud"
580
+ # require "gcloud/storage"
581
+ #
582
+ # gcloud = Gcloud.new
583
+ # bigquery = gcloud.bigquery
584
+ # dataset = bigquery.dataset "my_dataset"
585
+ # table = dataset.table "my_table"
586
+ #
587
+ # storage = gcloud.storage
588
+ # bucket = storage.bucket "my-bucket"
589
+ # file = bucket.file "file-name.csv"
590
+ # load_job = table.load file
591
+ #
592
+ # Or, you can upload a smaller file directly.
593
+ # See {Loading Data with a POST Request}[
594
+ # https://cloud.google.com/bigquery/loading-data-post-request#multipart].
595
+ #
596
+ # require "gcloud"
597
+ #
598
+ # gcloud = Gcloud.new
599
+ # bigquery = gcloud.bigquery
600
+ # dataset = bigquery.dataset "my_dataset"
601
+ # table = dataset.table "my_table"
602
+ #
603
+ # file = File.open "my_data.csv"
604
+ # load_job = table.load file
605
+ #
606
+ # :category: Data
607
+ #
608
+ def load file, options = {}
609
+ ensure_connection!
610
+ if storage_url? file
611
+ load_storage file, options
612
+ elsif local_file? file
613
+ load_local file, options
614
+ else
615
+ fail Gcloud::Bigquery::Error, "Don't know how to load #{file}"
616
+ end
617
+ end
618
+
619
+ ##
620
+ # Inserts data into the table for near-immediate querying, without the
621
+ # need to complete a #load operation before the data can appear in query
622
+ # results. See {Streaming Data Into BigQuery
623
+ # }[https://cloud.google.com/bigquery/streaming-data-into-bigquery].
624
+ #
625
+ # === Parameters
626
+ #
627
+ # +rows+::
628
+ # A hash object or array of hash objects containing the data.
629
+ # (+Array+ or +Hash+)
630
+ # +options+::
631
+ # An optional Hash for controlling additional behavior. (+Hash+)
632
+ # <code>options[:skip_invalid]</code>::
633
+ # Insert all valid rows of a request, even if invalid rows exist. The
634
+ # default value is +false+, which causes the entire request to fail if
635
+ # any invalid rows exist. (+Boolean+)
636
+ # <code>options[:ignore_unknown]</code>::
637
+ # Accept rows that contain values that do not match the schema. The
638
+ # unknown values are ignored. Default is false, which treats unknown
639
+ # values as errors. (+Boolean+)
640
+ #
641
+ # === Returns
642
+ #
643
+ # Gcloud::Bigquery::InsertResponse
644
+ #
645
+ # === Example
646
+ #
647
+ # require "gcloud"
648
+ #
649
+ # gcloud = Gcloud.new
650
+ # bigquery = gcloud.bigquery
651
+ # dataset = bigquery.dataset "my_dataset"
652
+ # table = dataset.table "my_table"
653
+ #
654
+ # rows = [
655
+ # { "first_name" => "Alice", "age" => 21 },
656
+ # { "first_name" => "Bob", "age" => 22 }
657
+ # ]
658
+ # table.insert rows
659
+ #
660
+ # :category: Data
661
+ #
662
+ def insert rows, options = {}
663
+ rows = [rows] if rows.is_a? Hash
664
+ ensure_connection!
665
+ resp = connection.insert_tabledata dataset_id, table_id, rows, options
666
+ if resp.success?
667
+ InsertResponse.from_gapi rows, resp.data
668
+ else
669
+ fail ApiError.from_response(resp)
670
+ end
671
+ end
672
+
673
+ ##
674
+ # Permanently deletes the table.
675
+ #
676
+ # === Returns
677
+ #
678
+ # +true+ if the table was deleted.
679
+ #
680
+ # === Example
681
+ #
682
+ # require "gcloud"
683
+ #
684
+ # gcloud = Gcloud.new
685
+ # bigquery = gcloud.bigquery
686
+ # dataset = bigquery.dataset "my_dataset"
687
+ # table = dataset.table "my_table"
688
+ #
689
+ # table.delete
690
+ #
691
+ # :category: Lifecycle
692
+ #
693
+ def delete
694
+ ensure_connection!
695
+ resp = connection.delete_table dataset_id, table_id
696
+ if resp.success?
697
+ true
698
+ else
699
+ fail ApiError.from_response(resp)
700
+ end
701
+ end
702
+
703
+ ##
704
+ # New Table from a Google API Client object.
705
+ def self.from_gapi gapi, conn #:nodoc:
706
+ klass = class_for gapi
707
+ klass.new.tap do |f|
708
+ f.gapi = gapi
709
+ f.connection = conn
710
+ end
711
+ end
712
+
713
+ protected
714
+
715
+ ##
716
+ # Raise an error unless an active connection is available.
717
+ def ensure_connection!
718
+ fail "Must have active connection" unless connection
719
+ end
720
+
721
+ def patch_gapi! options = {}
722
+ ensure_connection!
723
+ resp = connection.patch_table dataset_id, table_id, options
724
+ if resp.success?
725
+ @gapi = resp.data
726
+ else
727
+ fail ApiError.from_response(resp)
728
+ end
729
+ end
730
+
731
+ def self.class_for gapi
732
+ return View if gapi["type"] == "VIEW"
733
+ self
734
+ end
735
+
736
+ def load_storage file, options = {}
737
+ # Convert to storage URL
738
+ file = file.to_gs_url if file.respond_to? :to_gs_url
739
+
740
+ resp = connection.load_table gapi, file, options
741
+ if resp.success?
742
+ Job.from_gapi resp.data, connection
743
+ else
744
+ fail ApiError.from_response(resp)
745
+ end
746
+ end
747
+
748
+ def load_local file, options = {}
749
+ if resumable_upload? file
750
+ load_resumable file, options
751
+ else
752
+ load_multipart file, options
753
+ end
754
+ end
755
+
756
+ def load_resumable file, options = {}
757
+ chunk_size = verify_chunk_size! options[:chunk_size]
758
+ resp = connection.load_resumable gapi, file, chunk_size, options
759
+ if resp.success?
760
+ Job.from_gapi resp.data, connection
761
+ else
762
+ fail ApiError.from_response(resp)
763
+ end
764
+ end
765
+
766
+ def load_multipart file, options = {}
767
+ resp = connection.load_multipart gapi, file, options
768
+ if resp.success?
769
+ Job.from_gapi resp.data, connection
770
+ else
771
+ fail ApiError.from_response(resp)
772
+ end
773
+ end
774
+
775
+ ##
776
+ # Determines if a resumable upload should be used.
777
+ def resumable_upload? file #:nodoc:
778
+ ::File.size?(file).to_i > Upload.resumable_threshold
779
+ end
780
+
781
+ def storage_url? file
782
+ file.respond_to?(:to_gs_url) ||
783
+ (file.respond_to?(:to_str) &&
784
+ file.to_str.downcase.start_with?("gs://"))
785
+ end
786
+
787
+ def local_file? file
788
+ ::File.file? file
789
+ rescue
790
+ false
791
+ end
792
+
793
+ ##
794
+ # Determines if a chunk_size is valid.
795
+ def verify_chunk_size! chunk_size
796
+ chunk_size = chunk_size.to_i
797
+ chunk_mod = 256 * 1024 # 256KB
798
+ if (chunk_size.to_i % chunk_mod) != 0
799
+ chunk_size = (chunk_size / chunk_mod) * chunk_mod
800
+ end
801
+ return if chunk_size.zero?
802
+ chunk_size
803
+ end
804
+
805
+ ##
806
+ # Load the complete representation of the table if it has been
807
+ # only partially loaded by a request to the API list method.
808
+ def ensure_full_data!
809
+ reload_gapi! unless data_complete?
810
+ end
811
+
812
+ def reload_gapi!
813
+ ensure_connection!
814
+ resp = connection.get_table dataset_id, table_id
815
+ if resp.success?
816
+ @gapi = resp.data
817
+ else
818
+ fail ApiError.from_response(resp)
819
+ end
820
+ end
821
+
822
+ def data_complete?
823
+ !@gapi["creationTime"].nil?
824
+ end
825
+ end
826
+ end
827
+ end