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,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"