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,51 @@
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
+ class Dataset
19
+ ##
20
+ # Dataset::List is a special case Array with additional values.
21
+ class List < DelegateClass(::Array)
22
+ ##
23
+ # If not empty, indicates that there are more records that match
24
+ # the request and this value should be passed to continue.
25
+ attr_accessor :token
26
+
27
+ # A hash of this page of results.
28
+ attr_accessor :etag
29
+
30
+ ##
31
+ # Create a new Dataset::List with an array of datasets.
32
+ def initialize arr = []
33
+ super arr
34
+ end
35
+
36
+ ##
37
+ # New Dataset::List from a response object.
38
+ def self.from_resp resp, conn #:nodoc:
39
+ datasets = List.new(Array(resp.data["datasets"]).map do |gapi_object|
40
+ Dataset.from_gapi gapi_object, conn
41
+ end)
42
+ datasets.instance_eval do
43
+ @token = resp.data["nextPageToken"]
44
+ @etag = resp.data["etag"]
45
+ end
46
+ datasets
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,62 @@
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
+ # Base BigQuery exception class.
20
+ class Error < Gcloud::Error
21
+ end
22
+
23
+ ##
24
+ # Raised when an API call is not successful.
25
+ class ApiError < Error
26
+ ##
27
+ # The code of the error.
28
+ attr_reader :code
29
+
30
+ ##
31
+ # The errors encountered.
32
+ attr_reader :errors
33
+
34
+ def initialize message, code, errors = [] #:nodoc:
35
+ super message
36
+ @code = code
37
+ @errors = errors
38
+ end
39
+
40
+ def self.from_response resp #:nodoc:
41
+ if resp.data? && resp.data["error"]
42
+ from_response_data resp.data["error"]
43
+ else
44
+ from_response_status resp
45
+ end
46
+ end
47
+
48
+ def self.from_response_data error #:nodoc:
49
+ new error["message"], error["code"], error["errors"]
50
+ end
51
+
52
+ def self.from_response_status resp #:nodoc:
53
+ if resp.status == 404
54
+ new "#{resp.error_message}: #{resp.request.uri.request_uri}",
55
+ resp.status
56
+ else
57
+ new resp.error_message, resp.status
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,117 @@
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
+ # = ExtractJob
20
+ #
21
+ # A Job subclass representing an export operation that may be performed
22
+ # on a Table. A ExtractJob instance is created when you call Table#extract.
23
+ #
24
+ # See {Exporting Data From
25
+ # BigQuery}[https://cloud.google.com/bigquery/exporting-data-from-bigquery]
26
+ # and the {Jobs API
27
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs]
28
+ # for details.
29
+ #
30
+ class ExtractJob < Job
31
+ ##
32
+ # The URI or URIs representing the Google Cloud Storage files to which
33
+ # the data is exported.
34
+ def destinations
35
+ Array config["extract"]["destinationUris"]
36
+ end
37
+
38
+ ##
39
+ # The table from which the data is exported. This is the table upon
40
+ # which Table#extract was called. Returns a Table instance.
41
+ def source
42
+ table = config["extract"]["sourceTable"]
43
+ return nil unless table
44
+ retrieve_table table["projectId"],
45
+ table["datasetId"],
46
+ table["tableId"]
47
+ end
48
+
49
+ ##
50
+ # Checks if the export operation compresses the data using gzip. The
51
+ # default is +false+.
52
+ def compression?
53
+ val = config["extract"]["compression"]
54
+ val == "GZIP"
55
+ end
56
+
57
+ ##
58
+ # Checks if the destination format for the data is {newline-delimited
59
+ # JSON}[http://jsonlines.org/]. The default is +false+.
60
+ def json?
61
+ val = config["extract"]["destinationFormat"]
62
+ val == "NEWLINE_DELIMITED_JSON"
63
+ end
64
+
65
+ ##
66
+ # Checks if the destination format for the data is CSV. Tables with nested
67
+ # or repeated fields cannot be exported as CSV. The default is +true+.
68
+ def csv?
69
+ val = config["extract"]["destinationFormat"]
70
+ return true if val.nil?
71
+ val == "CSV"
72
+ end
73
+
74
+ ##
75
+ # Checks if the destination format for the data is
76
+ # {Avro}[http://avro.apache.org/]. The default is +false+.
77
+ def avro?
78
+ val = config["extract"]["destinationFormat"]
79
+ val == "AVRO"
80
+ end
81
+
82
+ ##
83
+ # The symbol the operation uses to delimit fields in the exported data.
84
+ # The default is a comma (,).
85
+ def delimiter
86
+ val = config["extract"]["fieldDelimiter"]
87
+ val = "," if val.nil?
88
+ val
89
+ end
90
+
91
+ ##
92
+ # Checks if the exported data contains a header row. The default is
93
+ # +true+.
94
+ def print_header?
95
+ val = config["extract"]["printHeader"]
96
+ val = true if val.nil?
97
+ val
98
+ end
99
+
100
+ ##
101
+ # The count of files per destination URI or URI pattern specified in
102
+ # #destinations. Returns an Array of values in the same order as the URI
103
+ # patterns.
104
+ def destinations_file_counts
105
+ Array stats["extract"]["destinationUriFileCounts"]
106
+ end
107
+
108
+ ##
109
+ # The count of files per destination URI or URI pattern specified in
110
+ # #destinations. Returns a Hash with the URI patterns as keys and the
111
+ # counts as values.
112
+ def destinations_counts
113
+ Hash[destinations.zip destinations_file_counts]
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,80 @@
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
+ # InsertResponse
20
+ class InsertResponse
21
+ def initialize rows, gapi #:nodoc:
22
+ @rows = rows
23
+ @gapi = gapi
24
+ end
25
+
26
+ def success?
27
+ error_count.zero?
28
+ end
29
+
30
+ def insert_count
31
+ @insert_count ||= @rows.count - error_count
32
+ end
33
+
34
+ def error_count
35
+ @error_count ||= Array(@gapi["insertErrors"]).count
36
+ end
37
+
38
+ def insert_errors
39
+ @insert_errors ||= begin
40
+ Array(@gapi["insertErrors"]).map do |ie|
41
+ row = @rows[ie["index"]]
42
+ errors = ie["errors"]
43
+ InsertError.new row, errors
44
+ end
45
+ end
46
+ end
47
+
48
+ def error_rows
49
+ @error_rows ||= begin
50
+ Array(@gapi["insertErrors"]).map do |ie|
51
+ @rows[ie["index"]]
52
+ end
53
+ end
54
+ end
55
+
56
+ def errors_for row
57
+ ie = insert_errors.detect { |e| e.row == row }
58
+ return ie.errors if ie
59
+ []
60
+ end
61
+
62
+ def self.from_gapi rows, gapi #:nodoc:
63
+ gapi = gapi.to_hash if gapi.respond_to? :to_hash
64
+ new rows, gapi
65
+ end
66
+
67
+ ##
68
+ # InsertError
69
+ class InsertError
70
+ attr_reader :row
71
+ attr_reader :errors
72
+
73
+ def initialize row, errors #:nodoc:
74
+ @row = row
75
+ @errors = errors
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,283 @@
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/query_data"
17
+ require "gcloud/bigquery/job/list"
18
+ require "gcloud/bigquery/errors"
19
+
20
+ module Gcloud
21
+ module Bigquery
22
+ ##
23
+ # = Job
24
+ #
25
+ # Represents a generic Job that may be performed on a Table.
26
+ #
27
+ # See {Managing Jobs, Datasets, and Projects
28
+ # }[https://cloud.google.com/bigquery/docs/managing_jobs_datasets_projects]
29
+ # for an overview of BigQuery jobs, and the {Jobs API
30
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs]
31
+ # for details.
32
+ #
33
+ # The subclasses of Job represent the specific BigQuery job types: CopyJob,
34
+ # ExtractJob, LoadJob, and QueryJob.
35
+ #
36
+ # A job instance is created when you call Project#query_job,
37
+ # Dataset#query_job, Table#copy, Table#extract, Table#load, or View#data.
38
+ #
39
+ # require "gcloud"
40
+ #
41
+ # gcloud = Gcloud.new
42
+ # bigquery = gcloud.bigquery
43
+ #
44
+ # q = "SELECT COUNT(word) as count FROM publicdata:samples.shakespeare"
45
+ # job = bigquery.query_job q
46
+ #
47
+ # loop do
48
+ # break if job.done?
49
+ # sleep 1
50
+ # job.refresh!
51
+ # end
52
+ #
53
+ # if job.failed?
54
+ # puts job.error
55
+ # else
56
+ # puts job.query_results.first
57
+ # end
58
+ #
59
+ class Job
60
+ ##
61
+ # The Connection object.
62
+ attr_accessor :connection #:nodoc:
63
+
64
+ ##
65
+ # The Google API Client object.
66
+ attr_accessor :gapi #:nodoc:
67
+
68
+ ##
69
+ # Create an empty Job object.
70
+ def initialize #:nodoc:
71
+ @connection = nil
72
+ @gapi = {}
73
+ end
74
+
75
+ ##
76
+ # The ID of the job.
77
+ def job_id
78
+ @gapi["jobReference"]["jobId"]
79
+ end
80
+
81
+ ##
82
+ # The ID of the project containing the job.
83
+ def project_id
84
+ @gapi["jobReference"]["projectId"]
85
+ end
86
+
87
+ ##
88
+ # The current state of the job. The possible values are +PENDING+,
89
+ # +RUNNING+, and +DONE+. A +DONE+ state does not mean that the job
90
+ # completed successfully. Use #failed? to discover if an error occurred
91
+ # or if the job was successful.
92
+ def state
93
+ return nil if @gapi["status"].nil?
94
+ @gapi["status"]["state"]
95
+ end
96
+
97
+ ##
98
+ # Checks if the job's state is +RUNNING+.
99
+ def running?
100
+ return false if state.nil?
101
+ "running".casecmp(state).zero?
102
+ end
103
+
104
+ ##
105
+ # Checks if the job's state is +PENDING+.
106
+ def pending?
107
+ return false if state.nil?
108
+ "pending".casecmp(state).zero?
109
+ end
110
+
111
+ ##
112
+ # Checks if the job's state is +DONE+. When +true+, the job has stopped
113
+ # running. However, a +DONE+ state does not mean that the job completed
114
+ # successfully. Use #failed? to detect if an error occurred or if the
115
+ # job was successful.
116
+ def done?
117
+ return false if state.nil?
118
+ "done".casecmp(state).zero?
119
+ end
120
+
121
+ ##
122
+ # Checks if an error is present.
123
+ def failed?
124
+ !error.nil?
125
+ end
126
+
127
+ ##
128
+ # The time when the job was created.
129
+ def created_at
130
+ return nil if @gapi["statistics"].nil?
131
+ return nil if @gapi["statistics"]["creationTime"].nil?
132
+ Time.at(@gapi["statistics"]["creationTime"] / 1000.0)
133
+ end
134
+
135
+ ##
136
+ # The time when the job was started.
137
+ # This field is present after the job's state changes from +PENDING+
138
+ # to either +RUNNING+ or +DONE+.
139
+ def started_at
140
+ return nil if @gapi["statistics"].nil?
141
+ return nil if @gapi["statistics"]["startTime"].nil?
142
+ Time.at(@gapi["statistics"]["startTime"] / 1000.0)
143
+ end
144
+
145
+ ##
146
+ # The time when the job ended.
147
+ # This field is present when the job's state is +DONE+.
148
+ def ended_at
149
+ return nil if @gapi["statistics"].nil?
150
+ return nil if @gapi["statistics"]["endTime"].nil?
151
+ Time.at(@gapi["statistics"]["endTime"] / 1000.0)
152
+ end
153
+
154
+ ##
155
+ # The configuration for the job. Returns a hash. See the {Jobs API
156
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs].
157
+ def configuration
158
+ hash = @gapi["configuration"] || {}
159
+ hash = hash.to_hash if hash.respond_to? :to_hash
160
+ hash
161
+ end
162
+ alias_method :config, :configuration
163
+
164
+ ##
165
+ # The statistics for the job. Returns a hash. See the {Jobs API
166
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs].
167
+ def statistics
168
+ hash = @gapi["statistics"] || {}
169
+ hash = hash.to_hash if hash.respond_to? :to_hash
170
+ hash
171
+ end
172
+ alias_method :stats, :statistics
173
+
174
+ ##
175
+ # The job's status. Returns a hash. The values contained in the hash are
176
+ # also exposed by #state, #error, and #errors.
177
+ def status
178
+ hash = @gapi["status"] || {}
179
+ hash = hash.to_hash if hash.respond_to? :to_hash
180
+ hash
181
+ end
182
+
183
+ ##
184
+ # The last error for the job, if any errors have occurred. Returns a
185
+ # hash. See the {Jobs API
186
+ # reference}[https://cloud.google.com/bigquery/docs/reference/v2/jobs].
187
+ #
188
+ # === Returns
189
+ #
190
+ # +Hash+
191
+ #
192
+ # {
193
+ # "reason"=>"notFound",
194
+ # "message"=>"Not found: Table publicdata:samples.BAD_ID"
195
+ # }
196
+ #
197
+ def error
198
+ status["errorResult"]
199
+ end
200
+
201
+ ##
202
+ # The errors for the job, if any errors have occurred. Returns an array
203
+ # of hash objects. See #error.
204
+ def errors
205
+ Array status["errors"]
206
+ end
207
+
208
+ ##
209
+ # Created a new job with the current configuration.
210
+ def rerun!
211
+ ensure_connection!
212
+ resp = connection.insert_job configuration
213
+ if resp.success?
214
+ Job.from_gapi resp.data, connection
215
+ else
216
+ fail ApiError.from_response(resp)
217
+ end
218
+ end
219
+
220
+ ##
221
+ # Reloads the job with current data from the BigQuery service.
222
+ def refresh!
223
+ ensure_connection!
224
+ resp = connection.get_job job_id
225
+ if resp.success?
226
+ @gapi = resp.data
227
+ else
228
+ fail ApiError.from_response(resp)
229
+ end
230
+ end
231
+
232
+ ##
233
+ # New Job from a Google API Client object.
234
+ def self.from_gapi gapi, conn #:nodoc:
235
+ klass = klass_for gapi
236
+ klass.new.tap do |f|
237
+ f.gapi = gapi
238
+ f.connection = conn
239
+ end
240
+ end
241
+
242
+ protected
243
+
244
+ ##
245
+ # Raise an error unless an active connection is available.
246
+ def ensure_connection!
247
+ fail "Must have active connection" unless connection
248
+ end
249
+
250
+ ##
251
+ # Get the subclass for a job type
252
+ def self.klass_for gapi
253
+ if gapi["configuration"]["copy"]
254
+ return CopyJob
255
+ elsif gapi["configuration"]["extract"]
256
+ return ExtractJob
257
+ elsif gapi["configuration"]["load"]
258
+ return LoadJob
259
+ elsif gapi["configuration"]["query"]
260
+ return QueryJob
261
+ end
262
+ Job
263
+ end
264
+
265
+ def retrieve_table project_id, dataset_id, table_id
266
+ ensure_connection!
267
+ resp = connection.get_project_table project_id, dataset_id, table_id
268
+ if resp.success?
269
+ Table.from_gapi resp.data, connection
270
+ else
271
+ return nil if resp.status == 404
272
+ fail ApiError.from_response(resp)
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ # We need Job to be defined before loading these.
280
+ require "gcloud/bigquery/copy_job"
281
+ require "gcloud/bigquery/extract_job"
282
+ require "gcloud/bigquery/load_job"
283
+ require "gcloud/bigquery/query_job"