google-cloud-bigquery 0.23.0 → 0.24.0

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