google-cloud-bigquery 0.23.0 → 0.24.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.
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ require "google/cloud/bigquery/schema/field"
17
+
16
18
  module Google
17
19
  module Cloud
18
20
  module Bigquery
@@ -42,69 +44,35 @@ module Google
42
44
  # end
43
45
  #
44
46
  class Schema
45
- def initialize
46
- @nested = nil
47
- end
48
-
47
+ ##
48
+ # The fields of the table schema.
49
49
  def fields
50
- @fields ||= @gapi.fields.map { |f| Field.from_gapi f }
51
- end
52
-
53
- def fields= new_fields
54
- @gapi.fields = Array(new_fields).map(&:to_gapi)
55
- @fields = @gapi.fields.map { |f| Field.from_gapi f }
56
- end
57
-
58
- def empty?
59
- fields.empty?
60
- end
61
-
62
- # @private
63
- def changed?
64
- return false if frozen?
65
- check_for_mutated_schema!
66
- @original_json != @gapi.to_json
67
- end
68
-
69
- # @private
70
- def freeze
71
- @gapi = @gapi.dup.freeze
72
- @gapi.fields.freeze
73
- @fields = @gapi.fields.map { |f| Field.from_gapi(f).freeze }
74
- @fields.freeze
75
- super
50
+ if frozen?
51
+ Array(@gapi.fields).map { |f| Field.from_gapi(f).freeze }.freeze
52
+ else
53
+ Array(@gapi.fields).map { |f| Field.from_gapi f }
54
+ end
76
55
  end
77
56
 
78
57
  ##
79
- # @private Make sure any changes are saved.
80
- def check_for_mutated_schema!
81
- return if frozen?
82
- return if @gapi.frozen?
83
- return if @fields.nil?
84
- gapi_fields = Array(@fields).map(&:to_gapi)
85
- @gapi.update! fields: gapi_fields
86
- end
87
-
88
- # @private
89
- def self.from_gapi gapi
90
- gapi ||= Google::Apis::BigqueryV2::TableSchema.new fields: []
91
- gapi.fields ||= []
92
- new.tap do |s|
93
- s.instance_variable_set :@gapi, gapi
94
- s.instance_variable_set :@original_json, gapi.to_json
95
- end
58
+ # The names of the fields as symbols.
59
+ def headers
60
+ fields.map(&:name).map(&:to_sym)
96
61
  end
97
62
 
98
- # @private
99
- def to_gapi
100
- check_for_mutated_schema!
101
- @gapi
63
+ ##
64
+ # Retreive a fields by name.
65
+ def field name
66
+ f = fields.find { |fld| fld.name == name.to_s }
67
+ return nil if f.nil?
68
+ yield f if block_given?
69
+ f
102
70
  end
103
71
 
104
- # @private
105
- def == other
106
- return false unless other.is_a? Schema
107
- to_gapi.to_h == other.to_gapi.to_h
72
+ ##
73
+ # Whether the schema has no fields defined.
74
+ def empty?
75
+ fields.empty?
108
76
  end
109
77
 
110
78
  ##
@@ -119,7 +87,7 @@ module Google
119
87
  # `:nullable`, `:required`, and `:repeated`. The default value is
120
88
  # `:nullable`.
121
89
  def string name, description: nil, mode: :nullable
122
- add_field name, :string, nil, description: description, mode: mode
90
+ add_field name, :string, description: description, mode: mode
123
91
  end
124
92
 
125
93
  ##
@@ -134,7 +102,7 @@ module Google
134
102
  # `:nullable`, `:required`, and `:repeated`. The default value is
135
103
  # `:nullable`.
136
104
  def integer name, description: nil, mode: :nullable
137
- add_field name, :integer, nil, description: description, mode: mode
105
+ add_field name, :integer, description: description, mode: mode
138
106
  end
139
107
 
140
108
  ##
@@ -149,7 +117,7 @@ module Google
149
117
  # `:nullable`, `:required`, and `:repeated`. The default value is
150
118
  # `:nullable`.
151
119
  def float name, description: nil, mode: :nullable
152
- add_field name, :float, nil, description: description, mode: mode
120
+ add_field name, :float, description: description, mode: mode
153
121
  end
154
122
 
155
123
  ##
@@ -164,7 +132,22 @@ module Google
164
132
  # `:nullable`, `:required`, and `:repeated`. The default value is
165
133
  # `:nullable`.
166
134
  def boolean name, description: nil, mode: :nullable
167
- add_field name, :boolean, nil, description: description, mode: mode
135
+ add_field name, :boolean, description: description, mode: mode
136
+ end
137
+
138
+ ##
139
+ # Adds a bytes field to the schema.
140
+ #
141
+ # @param [String] name The field name. The name must contain only
142
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
143
+ # start with a letter or underscore. The maximum length is 128
144
+ # characters.
145
+ # @param [String] description A description of the field.
146
+ # @param [Symbol] mode The field's mode. The possible values are
147
+ # `:nullable`, `:required`, and `:repeated`. The default value is
148
+ # `:nullable`.
149
+ def bytes name, description: nil, mode: :nullable
150
+ add_field name, :bytes, description: description, mode: mode
168
151
  end
169
152
 
170
153
  ##
@@ -179,7 +162,52 @@ module Google
179
162
  # `:nullable`, `:required`, and `:repeated`. The default value is
180
163
  # `:nullable`.
181
164
  def timestamp name, description: nil, mode: :nullable
182
- add_field name, :timestamp, nil, description: description, mode: mode
165
+ add_field name, :timestamp, description: description, mode: mode
166
+ end
167
+
168
+ ##
169
+ # Adds a time field to the schema.
170
+ #
171
+ # @param [String] name The field name. The name must contain only
172
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
173
+ # start with a letter or underscore. The maximum length is 128
174
+ # characters.
175
+ # @param [String] description A description of the field.
176
+ # @param [Symbol] mode The field's mode. The possible values are
177
+ # `:nullable`, `:required`, and `:repeated`. The default value is
178
+ # `:nullable`.
179
+ def time name, description: nil, mode: :nullable
180
+ add_field name, :time, description: description, mode: mode
181
+ end
182
+
183
+ ##
184
+ # Adds a datetime field to the schema.
185
+ #
186
+ # @param [String] name The field name. The name must contain only
187
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
188
+ # start with a letter or underscore. The maximum length is 128
189
+ # characters.
190
+ # @param [String] description A description of the field.
191
+ # @param [Symbol] mode The field's mode. The possible values are
192
+ # `:nullable`, `:required`, and `:repeated`. The default value is
193
+ # `:nullable`.
194
+ def datetime name, description: nil, mode: :nullable
195
+ add_field name, :datetime, description: description, mode: mode
196
+ end
197
+
198
+ ##
199
+ # Adds a date field to the schema.
200
+ #
201
+ # @param [String] name The field name. The name must contain only
202
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
203
+ # start with a letter or underscore. The maximum length is 128
204
+ # characters.
205
+ # @param [String] description A description of the field.
206
+ # @param [Symbol] mode The field's mode. The possible values are
207
+ # `:nullable`, `:required`, and `:repeated`. The default value is
208
+ # `:nullable`.
209
+ def date name, description: nil, mode: :nullable
210
+ add_field name, :date, description: description, mode: mode
183
211
  end
184
212
 
185
213
  ##
@@ -196,8 +224,8 @@ module Google
196
224
  # @param [Symbol] mode The field's mode. The possible values are
197
225
  # `:nullable`, `:required`, and `:repeated`. The default value is
198
226
  # `:nullable`.
199
- # @yield [nested_schema] a block for setting the nested schema
200
- # @yieldparam [Schema] nested_schema the object accepting the
227
+ # @yield [field] a block for setting the nested record's schema
228
+ # @yieldparam [Field] field the object accepting the
201
229
  # nested schema
202
230
  #
203
231
  # @example
@@ -216,142 +244,86 @@ module Google
216
244
  # end
217
245
  #
218
246
  def record name, description: nil, mode: nil
219
- fail ArgumentError, "nested RECORD type is not permitted" if @nested
247
+ # TODO: do we need to fail if no block was given?
220
248
  fail ArgumentError, "a block is required" unless block_given?
221
- empty_schema = Google::Apis::BigqueryV2::TableSchema.new fields: []
222
- nested_schema = self.class.from_gapi(empty_schema).tap do |s|
223
- s.instance_variable_set :@nested, true
224
- end
225
- yield nested_schema
226
- add_field name, :record, nested_schema.fields,
227
- description: description, mode: mode
228
- end
229
-
230
- protected
231
249
 
232
- def add_field name, type, nested_fields, description: nil,
233
- mode: :nullable
234
- # Remove any existing field of this name
235
- fields.reject! { |f| f.name == name }
236
- fields << Field.new(name, type, description: description,
237
- mode: mode, fields: nested_fields)
250
+ nested_field = add_field name, :record, description: description,
251
+ mode: mode
252
+ yield nested_field
253
+ nested_field
238
254
  end
239
255
 
240
- class Field
241
- # @private
242
- MODES = %w( NULLABLE REQUIRED REPEATED )
243
-
244
- # @private
245
- TYPES = %w( STRING INTEGER FLOAT BOOLEAN TIMESTAMP RECORD )
246
-
247
- def initialize name, type, description: nil,
248
- mode: :nullable, fields: nil
249
- @gapi = Google::Apis::BigqueryV2::TableFieldSchema.new
250
- @gapi.update! name: name
251
- @gapi.update! type: verify_type(type)
252
- @gapi.update! description: description if description
253
- @gapi.update! mode: verify_mode(mode) if mode
254
- if fields
255
- @fields = fields
256
- check_for_changed_fields!
257
- end
258
- @original_json = @gapi.to_json
259
- end
260
-
261
- def name
262
- @gapi.name
263
- end
264
-
265
- def name= new_name
266
- @gapi.update! name: new_name
267
- end
268
-
269
- def type
270
- @gapi.type
271
- end
272
-
273
- def type= new_type
274
- @gapi.update! type: verify_type(new_type)
275
- end
276
-
277
- def description
278
- @gapi.description
279
- end
280
-
281
- def description= new_description
282
- @gapi.update! description: new_description
283
- end
256
+ # @private
257
+ def changed?
258
+ return false if frozen?
259
+ @original_json != @gapi.to_json
260
+ end
284
261
 
285
- def mode
286
- @gapi.mode
262
+ # @private
263
+ def self.from_gapi gapi
264
+ gapi ||= Google::Apis::BigqueryV2::TableSchema.new fields: []
265
+ gapi.fields ||= []
266
+ new.tap do |s|
267
+ s.instance_variable_set :@gapi, gapi
268
+ s.instance_variable_set :@original_json, gapi.to_json
287
269
  end
270
+ end
288
271
 
289
- def mode= new_mode
290
- @gapi.update! mode: verify_mode(new_mode)
291
- end
272
+ # @private
273
+ def to_gapi
274
+ @gapi
275
+ end
292
276
 
293
- def fields
294
- @fields ||= Array(@gapi.fields).map { |f| Field.from_gapi f }
295
- end
277
+ # @private
278
+ def == other
279
+ return false unless other.is_a? Schema
280
+ to_gapi.to_json == other.to_gapi.to_json
281
+ end
296
282
 
297
- def fields= new_fields
298
- @fields = new_fields
299
- end
283
+ protected
300
284
 
301
- ##
302
- # @private Make sure any fields are saved.
303
- def check_for_changed_fields!
304
- return if frozen?
305
- fields.each(&:check_for_changed_fields!)
306
- gapi_fields = Array(fields).map(&:to_gapi)
307
- gapi_fields = nil if gapi_fields.empty?
308
- @gapi.update! fields: gapi_fields
309
- end
285
+ def frozen_check!
286
+ return unless frozen?
287
+ fail ArgumentError, "Cannot modify a frozen schema"
288
+ end
310
289
 
311
- # @private
312
- def changed?
313
- @original_json == to_gapi.to_json
314
- end
290
+ def add_field name, type, description: nil, mode: :nullable
291
+ frozen_check!
315
292
 
316
- # @private
317
- def self.from_gapi gapi
318
- new("to-be-replaced", "STRING").tap do |f|
319
- f.instance_variable_set :@gapi, gapi
320
- f.instance_variable_set :@original_json, gapi.to_json
321
- end
322
- end
293
+ new_gapi = Google::Apis::BigqueryV2::TableFieldSchema.new(
294
+ name: String(name),
295
+ type: verify_type(type),
296
+ description: description,
297
+ mode: verify_mode(mode),
298
+ fields: [])
323
299
 
324
- # @private
325
- def to_gapi
326
- # make sure any changes are saved.
327
- check_for_changed_fields!
328
- @gapi
329
- end
300
+ # Remove any existing field of this name
301
+ @gapi.fields ||= []
302
+ @gapi.fields.reject! { |f| f.name == new_gapi.name }
330
303
 
331
- # @private
332
- def == other
333
- return false unless other.is_a? Field
334
- to_gapi.to_h == other.to_gapi.to_h
335
- end
304
+ # Add to the nested fields
305
+ @gapi.fields << new_gapi
336
306
 
337
- protected
307
+ # return the public API object
308
+ Field.from_gapi new_gapi
309
+ end
338
310
 
339
- def verify_type type
340
- upcase_type = type.to_s.upcase
341
- unless TYPES.include? upcase_type
342
- fail ArgumentError,
343
- "Type '#{upcase_type}' not found in #{TYPES.inspect}"
344
- end
345
- upcase_type
311
+ def verify_type type
312
+ type = type.to_s.upcase
313
+ unless Field::TYPES.include? type
314
+ fail ArgumentError,
315
+ "Type '#{type}' not found in #{TYPES.inspect}"
346
316
  end
317
+ type
318
+ end
347
319
 
348
- def verify_mode mode
349
- upcase_mode = mode.to_s.upcase
350
- unless MODES.include? upcase_mode
351
- fail ArgumentError "Unable to determine mode for '#{mode}'"
352
- end
353
- upcase_mode
320
+ def verify_mode mode
321
+ mode = :nullable if mode.nil?
322
+ mode = mode.to_s.upcase
323
+ unless Field::MODES.include? mode
324
+ fail ArgumentError "Unable to determine mode for '#{mode}'"
354
325
  end
326
+ mode
355
327
  end
356
328
  end
357
329
  end
@@ -0,0 +1,498 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
+ module Google
17
+ module Cloud
18
+ module Bigquery
19
+ class Schema
20
+ ##
21
+ # # Schema Field
22
+ #
23
+ # The fields of a table schema.
24
+ #
25
+ # @see https://cloud.google.com/bigquery/preparing-data-for-bigquery
26
+ # Preparing Data for BigQuery
27
+ #
28
+ # @example
29
+ # require "google/cloud/bigquery"
30
+ #
31
+ # bigquery = Google::Cloud::Bigquery.new
32
+ # dataset = bigquery.dataset "my_dataset"
33
+ # table = dataset.table "my_table"
34
+ #
35
+ # name_field = table.schema.field "name"
36
+ # name_field.required? #=> true
37
+ class Field
38
+ # @private
39
+ MODES = %w( NULLABLE REQUIRED REPEATED )
40
+
41
+ # @private
42
+ TYPES = %w( STRING INTEGER FLOAT BOOLEAN BYTES TIMESTAMP TIME DATETIME
43
+ DATE RECORD )
44
+
45
+ ##
46
+ # The name of the field.
47
+ #
48
+ def name
49
+ @gapi.name
50
+ end
51
+
52
+ ##
53
+ # Updates the name of the field.
54
+ #
55
+ def name= new_name
56
+ @gapi.update! name: String(new_name)
57
+ end
58
+
59
+ ##
60
+ # The type of the field.
61
+ #
62
+ def type
63
+ @gapi.type
64
+ end
65
+
66
+ ##
67
+ # Updates the type of the field.
68
+ #
69
+ def type= new_type
70
+ @gapi.update! type: verify_type(new_type)
71
+ end
72
+
73
+ ##
74
+ # Checks if the type of the field is `NULLABLE`.
75
+ def nullable?
76
+ mode == "NULLABLE"
77
+ end
78
+
79
+ ##
80
+ # Checks if the type of the field is `REQUIRED`.
81
+ def required?
82
+ mode == "REQUIRED"
83
+ end
84
+
85
+ ##
86
+ # Checks if the type of the field is `REPEATED`.
87
+ def repeated?
88
+ mode == "REPEATED"
89
+ end
90
+
91
+ ##
92
+ # The description of the field.
93
+ #
94
+ def description
95
+ @gapi.description
96
+ end
97
+
98
+ ##
99
+ # Updates the description of the field.
100
+ #
101
+ def description= new_description
102
+ @gapi.update! description: new_description
103
+ end
104
+
105
+ ##
106
+ # The mode of the field.
107
+ #
108
+ def mode
109
+ @gapi.mode
110
+ end
111
+
112
+ ##
113
+ # Updates the mode of the field.
114
+ #
115
+ def mode= new_mode
116
+ @gapi.update! mode: verify_mode(new_mode)
117
+ end
118
+
119
+ ##
120
+ # Checks if the mode of the field is `STRING`.
121
+ def string?
122
+ mode == "STRING"
123
+ end
124
+
125
+ ##
126
+ # Checks if the mode of the field is `INTEGER`.
127
+ def integer?
128
+ mode == "INTEGER"
129
+ end
130
+
131
+ ##
132
+ # Checks if the mode of the field is `FLOAT`.
133
+ def float?
134
+ mode == "FLOAT"
135
+ end
136
+
137
+ ##
138
+ # Checks if the mode of the field is `BOOLEAN`.
139
+ def boolean?
140
+ mode == "BOOLEAN"
141
+ end
142
+
143
+ ##
144
+ # Checks if the mode of the field is `BYTES`.
145
+ def bytes?
146
+ mode == "BYTES"
147
+ end
148
+
149
+ ##
150
+ # Checks if the mode of the field is `TIMESTAMP`.
151
+ def timestamp?
152
+ mode == "TIMESTAMP"
153
+ end
154
+
155
+ ##
156
+ # Checks if the mode of the field is `TIME`.
157
+ def time?
158
+ mode == "TIME"
159
+ end
160
+
161
+ ##
162
+ # Checks if the mode of the field is `DATETIME`.
163
+ def datetime?
164
+ mode == "DATETIME"
165
+ end
166
+
167
+ ##
168
+ # Checks if the mode of the field is `DATE`.
169
+ def date?
170
+ mode == "DATE"
171
+ end
172
+
173
+ ##
174
+ # Checks if the mode of the field is `RECORD`.
175
+ def record?
176
+ mode == "RECORD"
177
+ end
178
+
179
+ ##
180
+ # The nested fields if the type property is set to `RECORD`. Will be
181
+ # empty otherwise.
182
+ def fields
183
+ if frozen?
184
+ Array(@gapi.fields).map { |f| Field.from_gapi(f).freeze }.freeze
185
+ else
186
+ Array(@gapi.fields).map { |f| Field.from_gapi f }
187
+ end
188
+ end
189
+
190
+ ##
191
+ # The names of the nested fields as symbols if the type property is
192
+ # set to `RECORD`. Will be empty otherwise.
193
+ def headers
194
+ fields.map(&:name).map(&:to_sym)
195
+ end
196
+
197
+ ##
198
+ # Retreive a nested fields by name, if the type property is
199
+ # set to `RECORD`. Will return `nil` otherwise.
200
+ def field name
201
+ f = fields.find { |fld| fld.name == name.to_s }
202
+ return nil if f.nil?
203
+ yield f if block_given?
204
+ f
205
+ end
206
+
207
+ ##
208
+ # Adds a string field to the schema.
209
+ #
210
+ # This can only be called on fields that are of type `RECORD`.
211
+ #
212
+ # @param [String] name The field name. The name must contain only
213
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
214
+ # start with a letter or underscore. The maximum length is 128
215
+ # characters.
216
+ # @param [String] description A description of the field.
217
+ # @param [Symbol] mode The field's mode. The possible values are
218
+ # `:nullable`, `:required`, and `:repeated`. The default value is
219
+ # `:nullable`.
220
+ def string name, description: nil, mode: :nullable
221
+ record_check!
222
+
223
+ add_field name, :string, description: description, mode: mode
224
+ end
225
+
226
+ ##
227
+ # Adds an integer field to the schema.
228
+ #
229
+ # This can only be called on fields that are of type `RECORD`.
230
+ #
231
+ # @param [String] name The field name. The name must contain only
232
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
233
+ # start with a letter or underscore. The maximum length is 128
234
+ # characters.
235
+ # @param [String] description A description of the field.
236
+ # @param [Symbol] mode The field's mode. The possible values are
237
+ # `:nullable`, `:required`, and `:repeated`. The default value is
238
+ # `:nullable`.
239
+ def integer name, description: nil, mode: :nullable
240
+ record_check!
241
+
242
+ add_field name, :integer, description: description, mode: mode
243
+ end
244
+
245
+ ##
246
+ # Adds a floating-point number field to the schema.
247
+ #
248
+ # This can only be called on fields that are of type `RECORD`.
249
+ #
250
+ # @param [String] name The field name. The name must contain only
251
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
252
+ # start with a letter or underscore. The maximum length is 128
253
+ # characters.
254
+ # @param [String] description A description of the field.
255
+ # @param [Symbol] mode The field's mode. The possible values are
256
+ # `:nullable`, `:required`, and `:repeated`. The default value is
257
+ # `:nullable`.
258
+ def float name, description: nil, mode: :nullable
259
+ record_check!
260
+
261
+ add_field name, :float, description: description, mode: mode
262
+ end
263
+
264
+ ##
265
+ # Adds a boolean field to the schema.
266
+ #
267
+ # This can only be called on fields that are of type `RECORD`.
268
+ #
269
+ # @param [String] name The field name. The name must contain only
270
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
271
+ # start with a letter or underscore. The maximum length is 128
272
+ # characters.
273
+ # @param [String] description A description of the field.
274
+ # @param [Symbol] mode The field's mode. The possible values are
275
+ # `:nullable`, `:required`, and `:repeated`. The default value is
276
+ # `:nullable`.
277
+ def boolean name, description: nil, mode: :nullable
278
+ record_check!
279
+
280
+ add_field name, :boolean, description: description, mode: mode
281
+ end
282
+
283
+ ##
284
+ # Adds a bytes field to the schema.
285
+ #
286
+ # This can only be called on fields that are of type `RECORD`.
287
+ #
288
+ # @param [String] name The field name. The name must contain only
289
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
290
+ # start with a letter or underscore. The maximum length is 128
291
+ # characters.
292
+ # @param [String] description A description of the field.
293
+ # @param [Symbol] mode The field's mode. The possible values are
294
+ # `:nullable`, `:required`, and `:repeated`. The default value is
295
+ # `:nullable`.
296
+ def bytes name, description: nil, mode: :nullable
297
+ record_check!
298
+
299
+ add_field name, :bytes, description: description, mode: mode
300
+ end
301
+
302
+ ##
303
+ # Adds a timestamp field to the schema.
304
+ #
305
+ # This can only be called on fields that are of type `RECORD`.
306
+ #
307
+ # @param [String] name The field name. The name must contain only
308
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
309
+ # start with a letter or underscore. The maximum length is 128
310
+ # characters.
311
+ # @param [String] description A description of the field.
312
+ # @param [Symbol] mode The field's mode. The possible values are
313
+ # `:nullable`, `:required`, and `:repeated`. The default value is
314
+ # `:nullable`.
315
+ def timestamp name, description: nil, mode: :nullable
316
+ record_check!
317
+
318
+ add_field name, :timestamp, description: description, mode: mode
319
+ end
320
+
321
+ ##
322
+ # Adds a time field to the schema.
323
+ #
324
+ # This can only be called on fields that are of type `RECORD`.
325
+ #
326
+ # @param [String] name The field name. The name must contain only
327
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
328
+ # start with a letter or underscore. The maximum length is 128
329
+ # characters.
330
+ # @param [String] description A description of the field.
331
+ # @param [Symbol] mode The field's mode. The possible values are
332
+ # `:nullable`, `:required`, and `:repeated`. The default value is
333
+ # `:nullable`.
334
+ def time name, description: nil, mode: :nullable
335
+ record_check!
336
+
337
+ add_field name, :time, description: description, mode: mode
338
+ end
339
+
340
+ ##
341
+ # Adds a datetime field to the schema.
342
+ #
343
+ # This can only be called on fields that are of type `RECORD`.
344
+ #
345
+ # @param [String] name The field name. The name must contain only
346
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
347
+ # start with a letter or underscore. The maximum length is 128
348
+ # characters.
349
+ # @param [String] description A description of the field.
350
+ # @param [Symbol] mode The field's mode. The possible values are
351
+ # `:nullable`, `:required`, and `:repeated`. The default value is
352
+ # `:nullable`.
353
+ def datetime name, description: nil, mode: :nullable
354
+ record_check!
355
+
356
+ add_field name, :datetime, description: description, mode: mode
357
+ end
358
+
359
+ ##
360
+ # Adds a date field to the schema.
361
+ #
362
+ # This can only be called on fields that are of type `RECORD`.
363
+ #
364
+ # @param [String] name The field name. The name must contain only
365
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
366
+ # start with a letter or underscore. The maximum length is 128
367
+ # characters.
368
+ # @param [String] description A description of the field.
369
+ # @param [Symbol] mode The field's mode. The possible values are
370
+ # `:nullable`, `:required`, and `:repeated`. The default value is
371
+ # `:nullable`.
372
+ def date name, description: nil, mode: :nullable
373
+ record_check!
374
+
375
+ add_field name, :date, description: description, mode: mode
376
+ end
377
+
378
+ ##
379
+ # Adds a record field to the schema. A block must be passed describing
380
+ # the nested fields of the record. For more information about nested
381
+ # and repeated records, see [Preparing Data for BigQuery
382
+ # ](https://cloud.google.com/bigquery/preparing-data-for-bigquery).
383
+ #
384
+ # This can only be called on fields that are of type `RECORD`.
385
+ #
386
+ # @param [String] name The field name. The name must contain only
387
+ # letters (a-z, A-Z), numbers (0-9), or underscores (_), and must
388
+ # start with a letter or underscore. The maximum length is 128
389
+ # characters.
390
+ # @param [String] description A description of the field.
391
+ # @param [Symbol] mode The field's mode. The possible values are
392
+ # `:nullable`, `:required`, and `:repeated`. The default value is
393
+ # `:nullable`.
394
+ # @yield [nested_schema] a block for setting the nested schema
395
+ # @yieldparam [Schema] nested_schema the object accepting the
396
+ # nested schema
397
+ #
398
+ # @example
399
+ # require "google/cloud/bigquery"
400
+ #
401
+ # bigquery = Google::Cloud::Bigquery.new
402
+ # dataset = bigquery.dataset "my_dataset"
403
+ # table = dataset.create_table "my_table"
404
+ #
405
+ # table.schema do |schema|
406
+ # schema.string "first_name", mode: :required
407
+ # schema.record "cities_lived", mode: :repeated do |cities_lived|
408
+ # cities_lived.string "place", mode: :required
409
+ # cities_lived.integer "number_of_years", mode: :required
410
+ # end
411
+ # end
412
+ #
413
+ def record name, description: nil, mode: nil
414
+ record_check!
415
+
416
+ # TODO: do we need to fail if no block was given?
417
+ fail ArgumentError, "a block is required" unless block_given?
418
+
419
+ nested_field = add_field name, :record, description: description,
420
+ mode: mode
421
+ yield nested_field
422
+ nested_field
423
+ end
424
+
425
+ # @private
426
+ def self.from_gapi gapi
427
+ new.tap do |f|
428
+ f.instance_variable_set :@gapi, gapi
429
+ f.instance_variable_set :@original_json, gapi.to_json
430
+ end
431
+ end
432
+
433
+ # @private
434
+ def to_gapi
435
+ @gapi
436
+ end
437
+
438
+ # @private
439
+ def == other
440
+ return false unless other.is_a? Field
441
+ to_gapi.to_h == other.to_gapi.to_h
442
+ end
443
+
444
+ protected
445
+
446
+ def frozen_check!
447
+ return unless frozen?
448
+ fail ArgumentError, "Cannot modify a frozen field"
449
+ end
450
+
451
+ def record_check!
452
+ return unless type != "RECORD"
453
+ fail ArgumentError,
454
+ "Cannot add fields to a non-RECORD field (#{type})"
455
+ end
456
+
457
+ def add_field name, type, description: nil, mode: :nullable
458
+ frozen_check!
459
+
460
+ new_gapi = Google::Apis::BigqueryV2::TableFieldSchema.new(
461
+ name: String(name),
462
+ type: verify_type(type),
463
+ description: description,
464
+ mode: verify_mode(mode),
465
+ fields: [])
466
+
467
+ # Remove any existing field of this name
468
+ @gapi.fields ||= []
469
+ @gapi.fields.reject! { |f| f.name == new_gapi.name }
470
+ # Add to the nested fields
471
+ @gapi.fields << new_gapi
472
+
473
+ # return the public API object
474
+ Field.from_gapi new_gapi
475
+ end
476
+
477
+ def verify_type type
478
+ type = type.to_s.upcase
479
+ unless TYPES.include? type
480
+ fail ArgumentError,
481
+ "Type '#{type}' not found in #{TYPES.inspect}"
482
+ end
483
+ type
484
+ end
485
+
486
+ def verify_mode mode
487
+ mode = :nullable if mode.nil?
488
+ mode = mode.to_s.upcase
489
+ unless MODES.include? mode
490
+ fail ArgumentError "Unable to determine mode for '#{mode}'"
491
+ end
492
+ mode
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end
498
+ end