google-cloud-bigquery 1.21.2

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