gcloud 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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