google-cloud-bigquery 1.21.2

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +16 -0
  3. data/AUTHENTICATION.md +158 -0
  4. data/CHANGELOG.md +397 -0
  5. data/CODE_OF_CONDUCT.md +40 -0
  6. data/CONTRIBUTING.md +188 -0
  7. data/LICENSE +201 -0
  8. data/LOGGING.md +27 -0
  9. data/OVERVIEW.md +463 -0
  10. data/TROUBLESHOOTING.md +31 -0
  11. data/lib/google-cloud-bigquery.rb +139 -0
  12. data/lib/google/cloud/bigquery.rb +145 -0
  13. data/lib/google/cloud/bigquery/argument.rb +197 -0
  14. data/lib/google/cloud/bigquery/convert.rb +383 -0
  15. data/lib/google/cloud/bigquery/copy_job.rb +316 -0
  16. data/lib/google/cloud/bigquery/credentials.rb +50 -0
  17. data/lib/google/cloud/bigquery/data.rb +526 -0
  18. data/lib/google/cloud/bigquery/dataset.rb +2845 -0
  19. data/lib/google/cloud/bigquery/dataset/access.rb +1021 -0
  20. data/lib/google/cloud/bigquery/dataset/list.rb +162 -0
  21. data/lib/google/cloud/bigquery/encryption_configuration.rb +123 -0
  22. data/lib/google/cloud/bigquery/external.rb +2432 -0
  23. data/lib/google/cloud/bigquery/extract_job.rb +368 -0
  24. data/lib/google/cloud/bigquery/insert_response.rb +180 -0
  25. data/lib/google/cloud/bigquery/job.rb +657 -0
  26. data/lib/google/cloud/bigquery/job/list.rb +162 -0
  27. data/lib/google/cloud/bigquery/load_job.rb +1704 -0
  28. data/lib/google/cloud/bigquery/model.rb +740 -0
  29. data/lib/google/cloud/bigquery/model/list.rb +164 -0
  30. data/lib/google/cloud/bigquery/project.rb +1655 -0
  31. data/lib/google/cloud/bigquery/project/list.rb +161 -0
  32. data/lib/google/cloud/bigquery/query_job.rb +1695 -0
  33. data/lib/google/cloud/bigquery/routine.rb +1108 -0
  34. data/lib/google/cloud/bigquery/routine/list.rb +165 -0
  35. data/lib/google/cloud/bigquery/schema.rb +564 -0
  36. data/lib/google/cloud/bigquery/schema/field.rb +668 -0
  37. data/lib/google/cloud/bigquery/service.rb +589 -0
  38. data/lib/google/cloud/bigquery/standard_sql.rb +495 -0
  39. data/lib/google/cloud/bigquery/table.rb +3340 -0
  40. data/lib/google/cloud/bigquery/table/async_inserter.rb +520 -0
  41. data/lib/google/cloud/bigquery/table/list.rb +172 -0
  42. data/lib/google/cloud/bigquery/time.rb +65 -0
  43. data/lib/google/cloud/bigquery/version.rb +22 -0
  44. metadata +297 -0
@@ -0,0 +1,165 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "delegate"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Bigquery
21
+ class Routine
22
+ ##
23
+ # Routine::List is a special case Array with additional values.
24
+ class List < DelegateClass(::Array)
25
+ ##
26
+ # If not empty, indicates that there are more records that match
27
+ # the request and this value should be passed to continue.
28
+ attr_accessor :token
29
+
30
+ ##
31
+ # @private Create a new Routine::List with an array of routines.
32
+ def initialize arr = []
33
+ super arr
34
+ end
35
+
36
+ ##
37
+ # Whether there is a next page of routines.
38
+ #
39
+ # @return [Boolean]
40
+ #
41
+ # @example
42
+ # require "google/cloud/bigquery"
43
+ #
44
+ # bigquery = Google::Cloud::Bigquery.new
45
+ # dataset = bigquery.dataset "my_dataset"
46
+ #
47
+ # routines = dataset.routines
48
+ # if routines.next?
49
+ # next_routines = routines.next
50
+ # end
51
+ #
52
+ def next?
53
+ !token.nil?
54
+ end
55
+
56
+ ##
57
+ # Retrieve the next page of routines.
58
+ #
59
+ # @return [Routine::List]
60
+ #
61
+ # @example
62
+ # require "google/cloud/bigquery"
63
+ #
64
+ # bigquery = Google::Cloud::Bigquery.new
65
+ # dataset = bigquery.dataset "my_dataset"
66
+ #
67
+ # routines = dataset.routines
68
+ # if routines.next?
69
+ # next_routines = routines.next
70
+ # end
71
+ #
72
+ def next
73
+ return nil unless next?
74
+ ensure_service!
75
+ gapi = @service.list_routines @dataset_id, token: token, max: @max, filter: @filter
76
+ self.class.from_gapi gapi, @service, @dataset_id, @max, filter: @filter
77
+ end
78
+
79
+ ##
80
+ # Retrieves remaining results by repeatedly invoking {#next} until
81
+ # {#next?} returns `false`. Calls the given block once for each
82
+ # result, which is passed as the argument to the block.
83
+ #
84
+ # An Enumerator is returned if no block is given.
85
+ #
86
+ # This method will make repeated API calls until all remaining results
87
+ # are retrieved. (Unlike `#each`, for example, which merely iterates
88
+ # over the results returned by a single API call.) Use with caution.
89
+ #
90
+ # @param [Integer] request_limit The upper limit of API requests to
91
+ # make to load all routines. Default is no limit.
92
+ # @yield [routine] The block for accessing each routine.
93
+ # @yieldparam [Routine] routine The routine object.
94
+ #
95
+ # @return [Enumerator]
96
+ #
97
+ # @example Iterating each result by passing a block:
98
+ # require "google/cloud/bigquery"
99
+ #
100
+ # bigquery = Google::Cloud::Bigquery.new
101
+ # dataset = bigquery.dataset "my_dataset"
102
+ #
103
+ # dataset.routines.all do |routine|
104
+ # puts routine.routine_id
105
+ # end
106
+ #
107
+ # @example Using the enumerator by not passing a block:
108
+ # require "google/cloud/bigquery"
109
+ #
110
+ # bigquery = Google::Cloud::Bigquery.new
111
+ # dataset = bigquery.dataset "my_dataset"
112
+ #
113
+ # all_names = dataset.routines.all.map do |routine|
114
+ # routine.routine_id
115
+ # end
116
+ #
117
+ # @example Limit the number of API requests made:
118
+ # require "google/cloud/bigquery"
119
+ #
120
+ # bigquery = Google::Cloud::Bigquery.new
121
+ # dataset = bigquery.dataset "my_dataset"
122
+ #
123
+ # dataset.routines.all(request_limit: 10) do |routine|
124
+ # puts routine.routine_id
125
+ # end
126
+ #
127
+ def all request_limit: nil
128
+ request_limit = request_limit.to_i if request_limit
129
+ return enum_for :all, request_limit: request_limit unless block_given?
130
+ results = self
131
+ loop do
132
+ results.each { |r| yield r }
133
+ if request_limit
134
+ request_limit -= 1
135
+ break if request_limit.negative?
136
+ end
137
+ break unless results.next?
138
+ results = results.next
139
+ end
140
+ end
141
+
142
+ ##
143
+ # @private New Routine::List from a response object.
144
+ def self.from_gapi gapi_list, service, dataset_id = nil, max = nil, filter: nil
145
+ routines = List.new(Array(gapi_list.routines).map { |gapi| Routine.from_gapi gapi, service })
146
+ routines.instance_variable_set :@token, gapi_list.next_page_token
147
+ routines.instance_variable_set :@service, service
148
+ routines.instance_variable_set :@dataset_id, dataset_id
149
+ routines.instance_variable_set :@max, max
150
+ routines.instance_variable_set :@filter, filter
151
+ routines
152
+ end
153
+
154
+ protected
155
+
156
+ ##
157
+ # Raise an error unless an active service is available.
158
+ def ensure_service!
159
+ raise "Must have active connection" unless @service
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,564 @@
1
+ # Copyright 2015 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/bigquery/schema/field"
17
+ require "json"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Bigquery
22
+ ##
23
+ # # Table Schema
24
+ #
25
+ # A builder for BigQuery table schemas, passed to block arguments to
26
+ # {Dataset#create_table} and {Table#schema}. Supports nested and
27
+ # repeated fields via a nested block.
28
+ #
29
+ # @see https://cloud.google.com/bigquery/docs/loading-data#loading_denormalized_nested_and_repeated_data
30
+ # Loading denormalized, nested, and repeated data
31
+ #
32
+ # @example
33
+ # require "google/cloud/bigquery"
34
+ #
35
+ # bigquery = Google::Cloud::Bigquery.new
36
+ # dataset = bigquery.dataset "my_dataset"
37
+ # table = dataset.create_table "my_table"
38
+ #
39
+ # table.schema do |schema|
40
+ # schema.string "first_name", mode: :required
41
+ # schema.record "cities_lived", mode: :repeated do |cities_lived|
42
+ # cities_lived.string "place", mode: :required
43
+ # cities_lived.integer "number_of_years", mode: :required
44
+ # end
45
+ # end
46
+ #
47
+ class Schema
48
+ class << self
49
+ ##
50
+ # Load a schema from a JSON file.
51
+ #
52
+ # The JSON schema file is the same as for the [`bq`
53
+ # CLI](https://cloud.google.com/bigquery/docs/schemas#specifying_a_json_schema_file)
54
+ # consisting of an array of JSON objects containing the following:
55
+ # - `name`: The column [name](https://cloud.google.com/bigquery/docs/schemas#column_names)
56
+ # - `type`: The column's [data
57
+ # type](https://cloud.google.com/bigquery/docs/schemas#standard_sql_data_types)
58
+ # - `description`: (Optional) The column's [description](https://cloud.google.com/bigquery/docs/schemas#column_descriptions)
59
+ # - `mode`: (Optional) The column's [mode](https://cloud.google.com/bigquery/docs/schemas#modes)
60
+ # (if unspecified, mode defaults to `NULLABLE`)
61
+ # - `fields`: If `type` is `RECORD`, an array of objects defining
62
+ # child fields with these properties
63
+ #
64
+ # @param [IO, String, Array<Hash>] source An `IO` containing the JSON
65
+ # schema, a `String` containing the JSON schema, or an `Array` of
66
+ # `Hash`es containing the schema details.
67
+ #
68
+ # @return [Schema] A schema.
69
+ #
70
+ # @example
71
+ # require "google/cloud/bigquery"
72
+ #
73
+ # schema = Google::Cloud::Bigquery::Schema.load(
74
+ # File.read("schema.json")
75
+ # )
76
+ #
77
+ def load source
78
+ new.load source
79
+ end
80
+
81
+ ##
82
+ # Write a schema as JSON to a file.
83
+ #
84
+ # The JSON schema file is the same as for the [`bq`
85
+ # CLI](https://cloud.google.com/bigquery/docs/schemas#specifying_a_json_schema_file).
86
+ #
87
+ # @param [Schema] schema A `Google::Cloud::Bigquery::Schema`.
88
+ #
89
+ # @param [IO, String] destination An `IO` to which to write the
90
+ # schema, or a `String` containing the filename to write to.
91
+ #
92
+ # @return [Schema] The schema so that commands are chainable.
93
+ #
94
+ # @example
95
+ # require "google/cloud/bigquery"
96
+ #
97
+ # bigquery = Google::Cloud::Bigquery.new
98
+ # dataset = bigquery.dataset "my_dataset"
99
+ # table = dataset.table "my_table"
100
+ # schema = Google::Cloud::Bigquery::Schema.dump(
101
+ # table.schema,
102
+ # "schema.json"
103
+ # )
104
+ #
105
+ def dump schema, destination
106
+ schema.dump destination
107
+ end
108
+ end
109
+ ##
110
+ # The fields of the table schema.
111
+ #
112
+ # @return [Array<Field>] An array of field objects.
113
+ #
114
+ # @example
115
+ # require "google/cloud/bigquery"
116
+ #
117
+ # bigquery = Google::Cloud::Bigquery.new
118
+ # dataset = bigquery.dataset "my_dataset"
119
+ # table = dataset.table "my_table"
120
+ #
121
+ # schema = table.schema
122
+ #
123
+ # schema.fields.each do |field|
124
+ # puts field.name
125
+ # end
126
+ #
127
+ def fields
128
+ if frozen?
129
+ Array(@gapi.fields).map { |f| Field.from_gapi(f).freeze }.freeze
130
+ else
131
+ Array(@gapi.fields).map { |f| Field.from_gapi f }
132
+ end
133
+ end
134
+
135
+ ##
136
+ # The names of the fields as symbols.
137
+ #
138
+ # @return [Array<Symbol>] An array of column names.
139
+ #
140
+ # @example
141
+ # require "google/cloud/bigquery"
142
+ #
143
+ # bigquery = Google::Cloud::Bigquery.new
144
+ # dataset = bigquery.dataset "my_dataset"
145
+ # table = dataset.create_table "my_table"
146
+ #
147
+ # schema = table.schema
148
+ #
149
+ # schema.headers.each do |header|
150
+ # puts header
151
+ # end
152
+ #
153
+ def headers
154
+ fields.map(&:name).map(&:to_sym)
155
+ end
156
+
157
+ ##
158
+ # The types of the fields, using the same format as the optional query
159
+ # parameter types.
160
+ #
161
+ # @return [Hash] A hash with column names as keys, and types as values.
162
+ #
163
+ # @example
164
+ # require "google/cloud/bigquery"
165
+ #
166
+ # bigquery = Google::Cloud::Bigquery.new
167
+ # dataset = bigquery.dataset "my_dataset"
168
+ # table = dataset.create_table "my_table"
169
+ #
170
+ # schema = table.schema
171
+ #
172
+ # schema.param_types
173
+ #
174
+ def param_types
175
+ Hash[fields.map { |field| [field.name.to_sym, field.param_type] }]
176
+ end
177
+
178
+ ##
179
+ # Retrieve a field by name.
180
+ #
181
+ # @return [Field] A field object.
182
+ #
183
+ # @example
184
+ # require "google/cloud/bigquery"
185
+ #
186
+ # bigquery = Google::Cloud::Bigquery.new
187
+ # dataset = bigquery.dataset "my_dataset"
188
+ # table = dataset.table "my_table"
189
+ #
190
+ # field = table.schema.field "name"
191
+ # field.required? #=> true
192
+ #
193
+ def field name
194
+ f = fields.find { |fld| fld.name == name.to_s }
195
+ return nil if f.nil?
196
+ yield f if block_given?
197
+ f
198
+ end
199
+
200
+ ##
201
+ # Whether the schema has no fields defined.
202
+ #
203
+ # @return [Boolean] `true` when there are no fields, `false` otherwise.
204
+ #
205
+ def empty?
206
+ fields.empty?
207
+ end
208
+
209
+ ##
210
+ # Load the schema from a JSON file.
211
+ #
212
+ # The JSON schema file is the same as for the [`bq`
213
+ # CLI](https://cloud.google.com/bigquery/docs/schemas#specifying_a_json_schema_file)
214
+ # consisting of an array of JSON objects containing the following:
215
+ # - `name`: The column [name](https://cloud.google.com/bigquery/docs/schemas#column_names)
216
+ # - `type`: The column's [data
217
+ # type](https://cloud.google.com/bigquery/docs/schemas#standard_sql_data_types)
218
+ # - `description`: (Optional) The column's [description](https://cloud.google.com/bigquery/docs/schemas#column_descriptions)
219
+ # - `mode`: (Optional) The column's [mode](https://cloud.google.com/bigquery/docs/schemas#modes)
220
+ # (if unspecified, mode defaults to `NULLABLE`)
221
+ # - `fields`: If `type` is `RECORD`, an array of objects defining child
222
+ # fields with these properties
223
+ #
224
+ # @param [IO, String, Array<Hash>] source An `IO` containing the JSON
225
+ # schema, a `String` containing the JSON schema, or an `Array` of
226
+ # `Hash`es containing the schema details.
227
+ #
228
+ # @return [Schema] The schema so that commands are chainable.
229
+ #
230
+ # @example
231
+ # require "google/cloud/bigquery"
232
+ #
233
+ # bigquery = Google::Cloud::Bigquery.new
234
+ # dataset = bigquery.dataset "my_dataset"
235
+ # table = dataset.table "my_table" do |t|
236
+ # t.schema.load File.read("path/to/schema.json")
237
+ # end
238
+ #
239
+ def load source
240
+ if source.respond_to?(:rewind) && source.respond_to?(:read)
241
+ source.rewind
242
+ schema_json = String source.read
243
+ elsif source.is_a? Array
244
+ schema_json = JSON.dump source
245
+ else
246
+ schema_json = String source
247
+ end
248
+
249
+ schema_json = %({"fields":#{schema_json}})
250
+
251
+ @gapi = Google::Apis::BigqueryV2::TableSchema.from_json schema_json
252
+
253
+ self
254
+ end
255
+
256
+ ##
257
+ # Write the schema as JSON to a file.
258
+ #
259
+ # The JSON schema file is the same as for the [`bq`
260
+ # CLI](https://cloud.google.com/bigquery/docs/schemas#specifying_a_json_schema_file).
261
+ #
262
+ # @param [IO, String] destination An `IO` to which to write the schema,
263
+ # or a `String` containing the filename to write to.
264
+ #
265
+ # @return [Schema] The schema so that commands are chainable.
266
+ #
267
+ # @example
268
+ # require "google/cloud/bigquery"
269
+ #
270
+ # bigquery = Google::Cloud::Bigquery.new
271
+ # dataset = bigquery.dataset "my_dataset"
272
+ # table = dataset.table "my_table"
273
+ # table.schema.dump "schema.json"
274
+ #
275
+ def dump destination
276
+ if destination.respond_to?(:rewind) && destination.respond_to?(:write)
277
+ destination.rewind
278
+ destination.write JSON.dump(fields.map(&:to_hash))
279
+ else
280
+ File.write String(destination), JSON.dump(fields.map(&:to_hash))
281
+ end
282
+
283
+ self
284
+ end
285
+
286
+ ##
287
+ # Adds a string field to the schema.
288
+ #
289
+ # @param [String] name The field name. The name must contain only
290
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
291
+ # start with a letter or underscore. The maximum length is 128
292
+ # characters.
293
+ # @param [String] description A description of the field.
294
+ # @param [Symbol] mode The field's mode. The possible values are
295
+ # `:nullable`, `:required`, and `:repeated`. The default value is
296
+ # `:nullable`.
297
+ #
298
+ def string name, description: nil, mode: :nullable
299
+ add_field name, :string, description: description, mode: mode
300
+ end
301
+
302
+ ##
303
+ # Adds an integer field to the schema.
304
+ #
305
+ # @param [String] name The field name. The name must contain only
306
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
307
+ # start with a letter or underscore. The maximum length is 128
308
+ # characters.
309
+ # @param [String] description A description of the field.
310
+ # @param [Symbol] mode The field's mode. The possible values are
311
+ # `:nullable`, `:required`, and `:repeated`. The default value is
312
+ # `:nullable`.
313
+ #
314
+ def integer name, description: nil, mode: :nullable
315
+ add_field name, :integer, description: description, mode: mode
316
+ end
317
+
318
+ ##
319
+ # Adds a floating-point number field to the schema.
320
+ #
321
+ # @param [String] name The field name. The name must contain only
322
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
323
+ # start with a letter or underscore. The maximum length is 128
324
+ # characters.
325
+ # @param [String] description A description of the field.
326
+ # @param [Symbol] mode The field's mode. The possible values are
327
+ # `:nullable`, `:required`, and `:repeated`. The default value is
328
+ # `:nullable`.
329
+ #
330
+ def float name, description: nil, mode: :nullable
331
+ add_field name, :float, description: description, mode: mode
332
+ end
333
+
334
+ ##
335
+ # Adds a numeric number field to the schema. Numeric is a
336
+ # fixed-precision numeric type with 38 decimal digits, 9 that follow the
337
+ # decimal point.
338
+ #
339
+ # @param [String] name The field name. The name must contain only
340
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
341
+ # start with a letter or underscore. The maximum length is 128
342
+ # characters.
343
+ # @param [String] description A description of the field.
344
+ # @param [Symbol] mode The field's mode. The possible values are
345
+ # `:nullable`, `:required`, and `:repeated`. The default value is
346
+ # `:nullable`.
347
+ #
348
+ def numeric name, description: nil, mode: :nullable
349
+ add_field name, :numeric, description: description, mode: mode
350
+ end
351
+
352
+ ##
353
+ # Adds a boolean field to the schema.
354
+ #
355
+ # @param [String] name The field name. The name must contain only
356
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
357
+ # start with a letter or underscore. The maximum length is 128
358
+ # characters.
359
+ # @param [String] description A description of the field.
360
+ # @param [Symbol] mode The field's mode. The possible values are
361
+ # `:nullable`, `:required`, and `:repeated`. The default value is
362
+ # `:nullable`.
363
+ #
364
+ def boolean name, description: nil, mode: :nullable
365
+ add_field name, :boolean, description: description, mode: mode
366
+ end
367
+
368
+ ##
369
+ # Adds a bytes field to the schema.
370
+ #
371
+ # @param [String] name The field name. The name must contain only
372
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
373
+ # start with a letter or underscore. The maximum length is 128
374
+ # characters.
375
+ # @param [String] description A description of the field.
376
+ # @param [Symbol] mode The field's mode. The possible values are
377
+ # `:nullable`, `:required`, and `:repeated`. The default value is
378
+ # `:nullable`.
379
+ #
380
+ def bytes name, description: nil, mode: :nullable
381
+ add_field name, :bytes, description: description, mode: mode
382
+ end
383
+
384
+ ##
385
+ # Adds a timestamp field to the schema.
386
+ #
387
+ # @param [String] name The field name. The name must contain only
388
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
389
+ # start with a letter or underscore. The maximum length is 128
390
+ # characters.
391
+ # @param [String] description A description of the field.
392
+ # @param [Symbol] mode The field's mode. The possible values are
393
+ # `:nullable`, `:required`, and `:repeated`. The default value is
394
+ # `:nullable`.
395
+ def timestamp name, description: nil, mode: :nullable
396
+ add_field name, :timestamp, description: description, mode: mode
397
+ end
398
+
399
+ ##
400
+ # Adds a time field to the schema.
401
+ #
402
+ # @param [String] name The field name. The name must contain only
403
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
404
+ # start with a letter or underscore. The maximum length is 128
405
+ # characters.
406
+ # @param [String] description A description of the field.
407
+ # @param [Symbol] mode The field's mode. The possible values are
408
+ # `:nullable`, `:required`, and `:repeated`. The default value is
409
+ # `:nullable`.
410
+ #
411
+ def time name, description: nil, mode: :nullable
412
+ add_field name, :time, description: description, mode: mode
413
+ end
414
+
415
+ ##
416
+ # Adds a datetime field to the schema.
417
+ #
418
+ # @param [String] name The field name. The name must contain only
419
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
420
+ # start with a letter or underscore. The maximum length is 128
421
+ # characters.
422
+ # @param [String] description A description of the field.
423
+ # @param [Symbol] mode The field's mode. The possible values are
424
+ # `:nullable`, `:required`, and `:repeated`. The default value is
425
+ # `:nullable`.
426
+ #
427
+ def datetime name, description: nil, mode: :nullable
428
+ add_field name, :datetime, description: description, mode: mode
429
+ end
430
+
431
+ ##
432
+ # Adds a date field to the schema.
433
+ #
434
+ # @param [String] name The field name. The name must contain only
435
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
436
+ # start with a letter or underscore. The maximum length is 128
437
+ # characters.
438
+ # @param [String] description A description of the field.
439
+ # @param [Symbol] mode The field's mode. The possible values are
440
+ # `:nullable`, `:required`, and `:repeated`. The default value is
441
+ # `:nullable`.
442
+ #
443
+ def date name, description: nil, mode: :nullable
444
+ add_field name, :date, description: description, mode: mode
445
+ end
446
+
447
+ ##
448
+ # Adds a record field to the schema. A block must be passed describing
449
+ # the nested fields of the record. For more information about nested
450
+ # and repeated records, see [Loading denormalized, nested, and repeated
451
+ # data
452
+ # ](https://cloud.google.com/bigquery/docs/loading-data#loading_denormalized_nested_and_repeated_data).
453
+ #
454
+ # @param [String] name The field name. The name must contain only
455
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
456
+ # start with a letter or underscore. The maximum length is 128
457
+ # characters.
458
+ # @param [String] description A description of the field.
459
+ # @param [Symbol] mode The field's mode. The possible values are
460
+ # `:nullable`, `:required`, and `:repeated`. The default value is
461
+ # `:nullable`.
462
+ # @yield [field] a block for setting the nested record's schema
463
+ # @yieldparam [Field] field the object accepting the
464
+ # nested schema
465
+ #
466
+ # @example
467
+ # require "google/cloud/bigquery"
468
+ #
469
+ # bigquery = Google::Cloud::Bigquery.new
470
+ # dataset = bigquery.dataset "my_dataset"
471
+ # table = dataset.create_table "my_table"
472
+ #
473
+ # table.schema do |schema|
474
+ # schema.string "first_name", mode: :required
475
+ # schema.record "cities_lived", mode: :repeated do |cities_lived|
476
+ # cities_lived.string "place", mode: :required
477
+ # cities_lived.integer "number_of_years", mode: :required
478
+ # end
479
+ # end
480
+ #
481
+ def record name, description: nil, mode: nil
482
+ # TODO: do we need to raise if no block was given?
483
+ raise ArgumentError, "a block is required" unless block_given?
484
+
485
+ nested_field = add_field name, :record, description: description, mode: mode
486
+ yield nested_field
487
+ nested_field
488
+ end
489
+
490
+ # @private
491
+ def changed?
492
+ return false if frozen?
493
+ @original_json != @gapi.to_json
494
+ end
495
+
496
+ # @private
497
+ # @param [Google::Apis::BigqueryV2::TableSchema, nil] gapi Returns an
498
+ # empty schema if nil or no arg is provided. The default is nil.
499
+ #
500
+ def self.from_gapi gapi = nil
501
+ gapi ||= Google::Apis::BigqueryV2::TableSchema.new fields: []
502
+ gapi.fields ||= []
503
+ new.tap do |s|
504
+ s.instance_variable_set :@gapi, gapi
505
+ s.instance_variable_set :@original_json, gapi.to_json
506
+ end
507
+ end
508
+
509
+ # @private
510
+ def to_gapi
511
+ @gapi
512
+ end
513
+
514
+ # @private
515
+ def == other
516
+ return false unless other.is_a? Schema
517
+ to_gapi.to_json == other.to_gapi.to_json
518
+ end
519
+
520
+ protected
521
+
522
+ def frozen_check!
523
+ return unless frozen?
524
+ raise ArgumentError, "Cannot modify a frozen schema"
525
+ end
526
+
527
+ def add_field name, type, description: nil, mode: :nullable
528
+ frozen_check!
529
+
530
+ new_gapi = Google::Apis::BigqueryV2::TableFieldSchema.new(
531
+ name: String(name),
532
+ type: verify_type(type),
533
+ description: description,
534
+ mode: verify_mode(mode),
535
+ fields: []
536
+ )
537
+
538
+ # Remove any existing field of this name
539
+ @gapi.fields ||= []
540
+ @gapi.fields.reject! { |f| f.name == new_gapi.name }
541
+
542
+ # Add to the nested fields
543
+ @gapi.fields << new_gapi
544
+
545
+ # return the public API object
546
+ Field.from_gapi new_gapi
547
+ end
548
+
549
+ def verify_type type
550
+ type = type.to_s.upcase
551
+ raise ArgumentError, "Type '#{type}' not found" unless Field::TYPES.include? type
552
+ type
553
+ end
554
+
555
+ def verify_mode mode
556
+ mode = :nullable if mode.nil?
557
+ mode = mode.to_s.upcase
558
+ raise ArgumentError "Unable to determine mode for '#{mode}'" unless Field::MODES.include? mode
559
+ mode
560
+ end
561
+ end
562
+ end
563
+ end
564
+ end