google-cloud-bigquery 1.11.2 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fda568083e129056b970a84c51457de70ee1074698efcfcb1c7d2b569e3a2f4e
4
- data.tar.gz: 00f0df1a1c99a772dccea807556dfb58defce2fe585d11f88dcbd94fcc75007c
3
+ metadata.gz: 4fb1722765075dded454afdeea337efe2c088c118f6f966c55a84ff9bb065df1
4
+ data.tar.gz: 2884b2a12b6a5137234dd4a3fd1fc2420ed2c05eaae223ebeae285caa1ba44b8
5
5
  SHA512:
6
- metadata.gz: a100f2ad6e60402e1b4fdfc93db21e0331a68eeaff458ed633515766630bc9a985ff0a1a6847b9004b252027ffe1e8018a1da6eef16c9b02a200c3d79bb9650c
7
- data.tar.gz: 68e929461695fdf232ca89b8ed9af7e21647b0c6ea93c1552319b7f3e5cc9583ddf07466edcfe753764d0ed383100aa70da9fc16ff1ad45e8296753cb0bdebbc
6
+ metadata.gz: 851560e73e7e1f48b58e08f99ada6f2f822f33f9dfe246e19b6d3abec4b0bfb2ef4802f402e2951ca0e40cb966be2981930fb62608f81d5a57edfa6ae5626d1f
7
+ data.tar.gz: b1a0dc302400edfe2a499d0299cc913cf85e4f34e29b460b8f5b3564a33b0e9a13c99d37539cc65afee07b4cbcfd7103bb07b4898866ea68dcab2187105ead1a
@@ -1,5 +1,14 @@
1
1
  # Release History
2
2
 
3
+ ### 1.12.0 / 2019-07-10
4
+
5
+ * Add BigQuery Model API
6
+ * Add Model
7
+ * Add StandardSql Field, DataType, StructType
8
+ * Add Dataset#model and Dataset#models
9
+ * Correct Float value conversion
10
+ * Ensure that NaN, Infinity, and -Infinity are converted correctly.
11
+
3
12
  ### 1.11.2 / 2019-06-11
4
13
 
5
14
  * Update "Loading data" link
@@ -85,7 +85,15 @@ module Google
85
85
  elsif field.type == "INTEGER"
86
86
  Integer value[:v]
87
87
  elsif field.type == "FLOAT"
88
- Float value[:v]
88
+ if value[:v] == "Infinity"
89
+ Float::INFINITY
90
+ elsif value[:v] == "-Infinity"
91
+ -Float::INFINITY
92
+ elsif value[:v] == "NaN"
93
+ Float::NAN
94
+ else
95
+ Float value[:v]
96
+ end
89
97
  elsif field.type == "NUMERIC"
90
98
  BigDecimal value[:v]
91
99
  elsif field.type == "BOOLEAN"
@@ -370,9 +378,19 @@ module Google
370
378
  # is nil.
371
379
  def self.millis_to_time time_millis
372
380
  return nil unless time_millis
373
- time_millis = Integer time_millis
374
- time_secs = time_millis / 1000.0
375
- ::Time.at time_secs
381
+ ::Time.at Rational(time_millis, 1000)
382
+ end
383
+
384
+ ##
385
+ # @private
386
+ #
387
+ # Converts a Ruby Time object to a primitive time value in milliseconds.
388
+ #
389
+ # @return [Integer, nil] The primitive time value in milliseconds, or
390
+ # nil if the given argument is nil.
391
+ def self.time_to_millis time_obj
392
+ return nil unless time_obj
393
+ (time_obj.to_i * 1000) + (time_obj.nsec / 1000000)
376
394
  end
377
395
  end
378
396
 
@@ -17,6 +17,7 @@ require "json"
17
17
  require "google/cloud/errors"
18
18
  require "google/cloud/bigquery/service"
19
19
  require "google/cloud/bigquery/table"
20
+ require "google/cloud/bigquery/model"
20
21
  require "google/cloud/bigquery/external"
21
22
  require "google/cloud/bigquery/dataset/list"
22
23
  require "google/cloud/bigquery/dataset/access"
@@ -668,6 +669,89 @@ module Google
668
669
  Table::List.from_gapi gapi, service, dataset_id, max
669
670
  end
670
671
 
672
+ ##
673
+ # Retrieves an existing model by ID.
674
+ #
675
+ # @param [String] model_id The ID of a model.
676
+ # @param [Boolean] skip_lookup Optionally create just a local reference
677
+ # object without verifying that the resource exists on the BigQuery
678
+ # service. Calls made on this object will raise errors if the resource
679
+ # does not exist. Default is `false`. Optional.
680
+ #
681
+ # @return [Google::Cloud::Bigquery::Model, nil] Returns `nil` if the
682
+ # model does not exist.
683
+ #
684
+ # @example
685
+ # require "google/cloud/bigquery"
686
+ #
687
+ # bigquery = Google::Cloud::Bigquery.new
688
+ # dataset = bigquery.dataset "my_dataset"
689
+ #
690
+ # model = dataset.model "my_model"
691
+ # puts model.model_id
692
+ #
693
+ # @example Avoid retrieving the model resource with `skip_lookup`:
694
+ # require "google/cloud/bigquery"
695
+ #
696
+ # bigquery = Google::Cloud::Bigquery.new
697
+ #
698
+ # dataset = bigquery.dataset "my_dataset"
699
+ #
700
+ # model = dataset.model "my_model", skip_lookup: true
701
+ #
702
+ # @!group Model
703
+ #
704
+ def model model_id, skip_lookup: nil
705
+ ensure_service!
706
+ if skip_lookup
707
+ return Model.new_reference project_id, dataset_id, model_id, service
708
+ end
709
+ gapi = service.get_model dataset_id, model_id
710
+ Model.from_gapi_json gapi, service
711
+ rescue Google::Cloud::NotFoundError
712
+ nil
713
+ end
714
+
715
+ ##
716
+ # Retrieves the list of models belonging to the dataset.
717
+ #
718
+ # @param [String] token A previously-returned page token representing
719
+ # part of the larger set of results to view.
720
+ # @param [Integer] max Maximum number of models to return.
721
+ #
722
+ # @return [Array<Google::Cloud::Bigquery::Model>] An array of models
723
+ # (See {Google::Cloud::Bigquery::Model::List})
724
+ #
725
+ # @example
726
+ # require "google/cloud/bigquery"
727
+ #
728
+ # bigquery = Google::Cloud::Bigquery.new
729
+ # dataset = bigquery.dataset "my_dataset"
730
+ #
731
+ # models = dataset.models
732
+ # models.each do |model|
733
+ # puts model.model_id
734
+ # end
735
+ #
736
+ # @example Retrieve all models: (See {Model::List#all})
737
+ # require "google/cloud/bigquery"
738
+ #
739
+ # bigquery = Google::Cloud::Bigquery.new
740
+ # dataset = bigquery.dataset "my_dataset"
741
+ #
742
+ # models = dataset.models
743
+ # models.all do |model|
744
+ # puts model.model_id
745
+ # end
746
+ #
747
+ # @!group Model
748
+ #
749
+ def models token: nil, max: nil
750
+ ensure_service!
751
+ gapi = service.list_models dataset_id, token: token, max: max
752
+ Model::List.from_gapi gapi, service, dataset_id, max
753
+ end
754
+
671
755
  ##
672
756
  # Queries data by creating a [query
673
757
  # job](https://cloud.google.com/bigquery/docs/query-overview#query_jobs).
@@ -0,0 +1,665 @@
1
+ # Copyright 2019 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/errors"
17
+ require "google/cloud/bigquery/service"
18
+ require "google/cloud/bigquery/model/list"
19
+ require "google/cloud/bigquery/standard_sql"
20
+ require "google/cloud/bigquery/convert"
21
+ require "google/apis/bigquery_v2"
22
+
23
+ module Google
24
+ module Cloud
25
+ module Bigquery
26
+ ##
27
+ # # Model
28
+ #
29
+ # A model in BigQuery ML represents what an ML system has learned from the
30
+ # training data.
31
+ #
32
+ # The following types of models are supported by BigQuery ML:
33
+ #
34
+ # * Linear regression for forecasting; for example, the sales of an item
35
+ # on a given day. Labels are real-valued (they cannot be +/- infinity or
36
+ # NaN).
37
+ # * Binary logistic regression for classification; for example,
38
+ # determining whether a customer will make a purchase. Labels must only
39
+ # have two possible values.
40
+ # * Multiclass logistic regression for classification. These models can be
41
+ # used to predict multiple possible values such as whether an input is
42
+ # "low-value," "medium-value," or "high-value." Labels can have up to 50
43
+ # unique values. In BigQuery ML, multiclass logistic regression training
44
+ # uses a multinomial classifier with a cross entropy loss function.
45
+ # * K-means clustering for data segmentation (beta); for example,
46
+ # identifying customer segments. K-means is an unsupervised learning
47
+ # technique, so model training does not require labels nor split data
48
+ # for training or evaluation.
49
+ #
50
+ # In BigQuery ML, a model can be used with data from multiple BigQuery
51
+ # datasets for training and for prediction.
52
+ #
53
+ # @see https://cloud.google.com/bigquery-ml/docs/bigqueryml-intro
54
+ # Introduction to BigQuery ML
55
+ # @see https://cloud.google.com/bigquery-ml/docs/getting-model-metadata
56
+ # Getting model metadata
57
+ #
58
+ # @example
59
+ # require "google/cloud/bigquery"
60
+ #
61
+ # bigquery = Google::Cloud::Bigquery.new
62
+ # dataset = bigquery.dataset "my_dataset"
63
+ #
64
+ # model = dataset.model "my_model"
65
+ #
66
+ class Model
67
+ ##
68
+ # @private The Service object.
69
+ attr_accessor :service
70
+
71
+ ##
72
+ # @private The Google API Client JSON Hash.
73
+ attr_accessor :gapi_json
74
+
75
+ ##
76
+ # @private A Google API Client Model Reference object.
77
+ attr_reader :reference
78
+
79
+ ##
80
+ # @private Create an empty Model object.
81
+ def initialize
82
+ @service = nil
83
+ @gapi_json = nil
84
+ @reference = nil
85
+ end
86
+
87
+ ##
88
+ # A unique ID for this model.
89
+ #
90
+ # @return [String] The ID must contain only letters (a-z, A-Z), numbers
91
+ # (0-9), or underscores (_). The maximum length is 1,024 characters.
92
+ #
93
+ # @!group Attributes
94
+ #
95
+ def model_id
96
+ return @reference.model_id if reference?
97
+ @gapi_json[:modelReference][:modelId]
98
+ end
99
+
100
+ ##
101
+ # The ID of the `Dataset` containing this model.
102
+ #
103
+ # @return [String] The ID must contain only letters (a-z, A-Z), numbers
104
+ # (0-9), or underscores (_). The maximum length is 1,024 characters.
105
+ #
106
+ # @!group Attributes
107
+ #
108
+ def dataset_id
109
+ return @reference.dataset_id if reference?
110
+ @gapi_json[:modelReference][:datasetId]
111
+ end
112
+
113
+ ##
114
+ # The ID of the `Project` containing this model.
115
+ #
116
+ # @return [String] The project ID.
117
+ #
118
+ # @!group Attributes
119
+ #
120
+ def project_id
121
+ return @reference.project_id if reference?
122
+ @gapi_json[:modelReference][:projectId]
123
+ end
124
+
125
+ ##
126
+ # @private The gapi_json fragment containing the Project ID, Dataset ID,
127
+ # and Model ID.
128
+ #
129
+ # @return [Google::Apis::BigqueryV2::ModelReference]
130
+ #
131
+ def model_ref
132
+ return @reference if reference?
133
+ Google::Apis::BigqueryV2::ModelReference.new(
134
+ project_id: project_id,
135
+ dataset_id: dataset_id,
136
+ model_id: model_id
137
+ )
138
+ end
139
+
140
+ ##
141
+ # Type of the model resource. Expected to be one of the following:
142
+ #
143
+ # * LINEAR_REGRESSION - Linear regression model.
144
+ # * LOGISTIC_REGRESSION - Logistic regression based classification
145
+ # model.
146
+ # * KMEANS - K-means clustering model (beta).
147
+ # * TENSORFLOW - An imported TensorFlow model (beta).
148
+ #
149
+ # @return [String, nil] The model type, or `nil` if the object is a
150
+ # reference (see {#reference?}).
151
+ #
152
+ # @!group Attributes
153
+ #
154
+ def model_type
155
+ return nil if reference?
156
+ @gapi_json[:modelType]
157
+ end
158
+
159
+ ##
160
+ # The name of the model.
161
+ #
162
+ # @return [String, nil] The friendly name, or `nil` if the object is a
163
+ # reference (see {#reference?}).
164
+ #
165
+ # @!group Attributes
166
+ #
167
+ def name
168
+ return nil if reference?
169
+ ensure_full_data!
170
+ @gapi_json[:friendlyName]
171
+ end
172
+
173
+ ##
174
+ # Updates the name of the model.
175
+ #
176
+ # If the model is not a full resource representation (see
177
+ # {#resource_full?}), the full representation will be retrieved before
178
+ # the update to comply with ETag-based optimistic concurrency control.
179
+ #
180
+ # @param [String] new_name The new friendly name.
181
+ #
182
+ # @!group Attributes
183
+ #
184
+ def name= new_name
185
+ ensure_full_data!
186
+ patch_gapi! friendlyName: new_name
187
+ end
188
+
189
+ ##
190
+ # The ETag hash of the model.
191
+ #
192
+ # @return [String, nil] The ETag hash, or `nil` if the object is a
193
+ # reference (see {#reference?}).
194
+ #
195
+ # @!group Attributes
196
+ #
197
+ def etag
198
+ return nil if reference?
199
+ ensure_full_data!
200
+ @gapi_json[:etag]
201
+ end
202
+
203
+ ##
204
+ # A user-friendly description of the model.
205
+ #
206
+ # @return [String, nil] The description, or `nil` if the object is a
207
+ # reference (see {#reference?}).
208
+ #
209
+ # @!group Attributes
210
+ #
211
+ def description
212
+ return nil if reference?
213
+ ensure_full_data!
214
+ @gapi_json[:description]
215
+ end
216
+
217
+ ##
218
+ # Updates the user-friendly description of the model.
219
+ #
220
+ # If the model is not a full resource representation (see
221
+ # {#resource_full?}), the full representation will be retrieved before
222
+ # the update to comply with ETag-based optimistic concurrency control.
223
+ #
224
+ # @param [String] new_description The new user-friendly description.
225
+ #
226
+ # @!group Attributes
227
+ #
228
+ def description= new_description
229
+ ensure_full_data!
230
+ patch_gapi! description: new_description
231
+ end
232
+
233
+ ##
234
+ # The time when this model was created.
235
+ #
236
+ # @return [Time, nil] The creation time, or `nil` if the object is a
237
+ # reference (see {#reference?}).
238
+ #
239
+ # @!group Attributes
240
+ #
241
+ def created_at
242
+ return nil if reference?
243
+ Convert.millis_to_time @gapi_json[:creationTime]
244
+ end
245
+
246
+ ##
247
+ # The date when this model was last modified.
248
+ #
249
+ # @return [Time, nil] The last modified time, or `nil` if not present or
250
+ # the object is a reference (see {#reference?}).
251
+ #
252
+ # @!group Attributes
253
+ #
254
+ def modified_at
255
+ return nil if reference?
256
+ Convert.millis_to_time @gapi_json[:lastModifiedTime]
257
+ end
258
+
259
+ ##
260
+ # The time when this model expires.
261
+ # If not present, the model will persist indefinitely.
262
+ # Expired models will be deleted and their storage reclaimed.
263
+ #
264
+ # @return [Time, nil] The expiration time, or `nil` if not present or
265
+ # the object is a reference (see {#reference?}).
266
+ #
267
+ # @!group Attributes
268
+ #
269
+ def expires_at
270
+ return nil if reference?
271
+ ensure_full_data!
272
+ Convert.millis_to_time @gapi_json[:expirationTime]
273
+ end
274
+
275
+ ##
276
+ # Updates time when this model expires.
277
+ #
278
+ # If the model is not a full resource representation (see
279
+ # {#resource_full?}), the full representation will be retrieved before
280
+ # the update to comply with ETag-based optimistic concurrency control.
281
+ #
282
+ # @param [Integer] new_expires_at The new time when this model expires.
283
+ #
284
+ # @!group Attributes
285
+ #
286
+ def expires_at= new_expires_at
287
+ ensure_full_data!
288
+ new_expires_millis = Convert.time_to_millis new_expires_at
289
+ patch_gapi! expirationTime: new_expires_millis
290
+ end
291
+
292
+ ##
293
+ # The geographic location where the model should reside. Possible
294
+ # values include `EU` and `US`. The default value is `US`.
295
+ #
296
+ # @return [String, nil] The location code.
297
+ #
298
+ # @!group Attributes
299
+ #
300
+ def location
301
+ return nil if reference?
302
+ ensure_full_data!
303
+ @gapi_json[:location]
304
+ end
305
+
306
+ ##
307
+ # A hash of user-provided labels associated with this model. Labels
308
+ # are used to organize and group models. See [Using
309
+ # Labels](https://cloud.google.com/bigquery/docs/labels).
310
+ #
311
+ # The returned hash is frozen and changes are not allowed. Use
312
+ # {#labels=} to replace the entire hash.
313
+ #
314
+ # @return [Hash<String, String>, nil] A hash containing key/value pairs.
315
+ #
316
+ # @example
317
+ # require "google/cloud/bigquery"
318
+ #
319
+ # bigquery = Google::Cloud::Bigquery.new
320
+ # dataset = bigquery.dataset "my_dataset"
321
+ # model = dataset.model "my_model"
322
+ #
323
+ # labels = model.labels
324
+ #
325
+ # @!group Attributes
326
+ #
327
+ def labels
328
+ return nil if reference?
329
+ m = @gapi_json[:labels]
330
+ m = m.to_h if m.respond_to? :to_h
331
+ m.dup.freeze
332
+ end
333
+
334
+ ##
335
+ # Updates the hash of user-provided labels associated with this model.
336
+ # Labels are used to organize and group models. See [Using
337
+ # Labels](https://cloud.google.com/bigquery/docs/labels).
338
+ #
339
+ # If the model is not a full resource representation (see
340
+ # {#resource_full?}), the full representation will be retrieved before
341
+ # the update to comply with ETag-based optimistic concurrency control.
342
+ #
343
+ # @param [Hash<String, String>] new_labels A hash containing key/value
344
+ # pairs.
345
+ #
346
+ # * Label keys and values can be no longer than 63 characters.
347
+ # * Label keys and values can contain only lowercase letters, numbers,
348
+ # underscores, hyphens, and international characters.
349
+ # * Label keys and values cannot exceed 128 bytes in size.
350
+ # * Label keys must begin with a letter.
351
+ # * Label keys must be unique within a model.
352
+ #
353
+ # @example
354
+ # require "google/cloud/bigquery"
355
+ #
356
+ # bigquery = Google::Cloud::Bigquery.new
357
+ # dataset = bigquery.dataset "my_dataset"
358
+ # model = dataset.model "my_model"
359
+ #
360
+ # model.labels = { "env" => "production" }
361
+ #
362
+ # @!group Attributes
363
+ #
364
+ def labels= new_labels
365
+ ensure_full_data!
366
+ patch_gapi! labels: new_labels
367
+ end
368
+
369
+ ##
370
+ # The input feature columns that were used to train this model.
371
+ #
372
+ # @return [Array<StandardSql::Field>]
373
+ #
374
+ # @!group Attributes
375
+ #
376
+ def feature_columns
377
+ ensure_full_data!
378
+ Array(@gapi_json[:featureColumns]).map do |field_gapi_json|
379
+ StandardSql::Field.from_gapi_json field_gapi_json
380
+ end
381
+ end
382
+
383
+ ##
384
+ # The label columns that were used to train this model. The output of
385
+ # the model will have a "predicted_" prefix to these columns.
386
+ #
387
+ # @return [Array<StandardSql::Field>]
388
+ #
389
+ # @!group Attributes
390
+ #
391
+ def label_columns
392
+ ensure_full_data!
393
+ Array(@gapi_json[:labelColumns]).map do |field_gapi_json|
394
+ StandardSql::Field.from_gapi_json field_gapi_json
395
+ end
396
+ end
397
+
398
+ ##
399
+ # Information for all training runs in increasing order of startTime.
400
+ #
401
+ # @return [Array<Google::Cloud::Bigquery::Model::TrainingRun>]
402
+ #
403
+ # @!group Attributes
404
+ #
405
+ def training_runs
406
+ ensure_full_data!
407
+ Array @gapi_json[:trainingRuns]
408
+ end
409
+
410
+ ##
411
+ # Permanently deletes the model.
412
+ #
413
+ # @return [Boolean] Returns `true` if the model was deleted.
414
+ #
415
+ # @example
416
+ # require "google/cloud/bigquery"
417
+ #
418
+ # bigquery = Google::Cloud::Bigquery.new
419
+ # dataset = bigquery.dataset "my_dataset"
420
+ # model = dataset.model "my_model"
421
+ #
422
+ # model.delete
423
+ #
424
+ # @!group Lifecycle
425
+ #
426
+ def delete
427
+ ensure_service!
428
+ service.delete_model dataset_id, model_id
429
+ # Set flag for #exists?
430
+ @exists = false
431
+ true
432
+ end
433
+
434
+ ##
435
+ # Reloads the model with current data from the BigQuery service.
436
+ #
437
+ # @return [Google::Cloud::Bigquery::Model] Returns the reloaded
438
+ # model.
439
+ #
440
+ # @example Skip retrieving the model from the service, then load it:
441
+ # require "google/cloud/bigquery"
442
+ #
443
+ # bigquery = Google::Cloud::Bigquery.new
444
+ #
445
+ # dataset = bigquery.dataset "my_dataset"
446
+ # model = dataset.model "my_model", skip_lookup: true
447
+ #
448
+ # model.reference? #=> true
449
+ # model.reload!
450
+ # model.resource? #=> true
451
+ #
452
+ # @!group Lifecycle
453
+ #
454
+ def reload!
455
+ ensure_service!
456
+ @gapi_json = service.get_model dataset_id, model_id
457
+ @reference = nil
458
+ @exists = nil
459
+ self
460
+ end
461
+ alias refresh! reload!
462
+
463
+ ##
464
+ # Determines whether the model exists in the BigQuery service. The
465
+ # result is cached locally. To refresh state, set `force` to `true`.
466
+ #
467
+ # @param [Boolean] force Force the latest resource representation to be
468
+ # retrieved from the BigQuery service when `true`. Otherwise the
469
+ # return value of this method will be memoized to reduce the number of
470
+ # API calls made to the BigQuery service. The default is `false`.
471
+ #
472
+ # @return [Boolean] `true` when the model exists in the BigQuery
473
+ # service, `false` otherwise.
474
+ #
475
+ # @example
476
+ # require "google/cloud/bigquery"
477
+ #
478
+ # bigquery = Google::Cloud::Bigquery.new
479
+ #
480
+ # dataset = bigquery.dataset "my_dataset"
481
+ # model = dataset.model "my_model", skip_lookup: true
482
+ # model.exists? #=> true
483
+ #
484
+ def exists? force: nil
485
+ return resource_exists? if force
486
+ # If we have a value, return it
487
+ return @exists unless @exists.nil?
488
+ # Always true if we have a gapi_json object
489
+ return true if resource?
490
+ resource_exists?
491
+ end
492
+
493
+ ##
494
+ # Whether the model was created without retrieving the resource
495
+ # representation from the BigQuery service.
496
+ #
497
+ # @return [Boolean] `true` when the model is just a local reference
498
+ # object, `false` otherwise.
499
+ #
500
+ # @example
501
+ # require "google/cloud/bigquery"
502
+ #
503
+ # bigquery = Google::Cloud::Bigquery.new
504
+ #
505
+ # dataset = bigquery.dataset "my_dataset"
506
+ # model = dataset.model "my_model", skip_lookup: true
507
+ #
508
+ # model.reference? #=> true
509
+ # model.reload!
510
+ # model.reference? #=> false
511
+ #
512
+ def reference?
513
+ @gapi_json.nil?
514
+ end
515
+
516
+ ##
517
+ # Whether the model was created with a resource representation from
518
+ # the BigQuery service.
519
+ #
520
+ # @return [Boolean] `true` when the model was created with a resource
521
+ # representation, `false` otherwise.
522
+ #
523
+ # @example
524
+ # require "google/cloud/bigquery"
525
+ #
526
+ # bigquery = Google::Cloud::Bigquery.new
527
+ #
528
+ # dataset = bigquery.dataset "my_dataset"
529
+ # model = dataset.model "my_model", skip_lookup: true
530
+ #
531
+ # model.resource? #=> false
532
+ # model.reload!
533
+ # model.resource? #=> true
534
+ #
535
+ def resource?
536
+ !@gapi_json.nil?
537
+ end
538
+
539
+ ##
540
+ # Whether the model was created with a partial resource representation
541
+ # from the BigQuery service by retrieval through {Dataset#models}.
542
+ # See [Models: list
543
+ # response](https://cloud.google.com/bigquery/docs/reference/rest/v2/models/list#response)
544
+ # for the contents of the partial representation. Accessing any
545
+ # attribute outside of the partial representation will result in loading
546
+ # the full representation.
547
+ #
548
+ # @return [Boolean] `true` when the model was created with a partial
549
+ # resource representation, `false` otherwise.
550
+ #
551
+ # @example
552
+ # require "google/cloud/bigquery"
553
+ #
554
+ # bigquery = Google::Cloud::Bigquery.new
555
+ #
556
+ # dataset = bigquery.dataset "my_dataset"
557
+ # model = dataset.models.first
558
+ #
559
+ # model.resource_partial? #=> true
560
+ # model.description # Loads the full resource.
561
+ # model.resource_partial? #=> false
562
+ #
563
+ def resource_partial?
564
+ resource? && !resource_full?
565
+ end
566
+
567
+ ##
568
+ # Whether the model was created with a full resource representation
569
+ # from the BigQuery service.
570
+ #
571
+ # @return [Boolean] `true` when the model was created with a full
572
+ # resource representation, `false` otherwise.
573
+ #
574
+ # @example
575
+ # require "google/cloud/bigquery"
576
+ #
577
+ # bigquery = Google::Cloud::Bigquery.new
578
+ #
579
+ # dataset = bigquery.dataset "my_dataset"
580
+ # model = dataset.model "my_model"
581
+ #
582
+ # model.resource_full? #=> true
583
+ #
584
+ def resource_full?
585
+ resource? && @gapi_json.key?(:friendlyName)
586
+ end
587
+
588
+ ##
589
+ # @private New Model from a Google API Client object.
590
+ def self.from_gapi_json gapi_json, service
591
+ new.tap do |m|
592
+ m.instance_variable_set :@gapi_json, gapi_json
593
+ m.instance_variable_set :@service, service
594
+ end
595
+ end
596
+
597
+ ##
598
+ # @private New lazy Model object without making an HTTP request.
599
+ def self.new_reference project_id, dataset_id, model_id, service
600
+ raise ArgumentError, "project_id is required" unless project_id
601
+ raise ArgumentError, "dataset_id is required" unless dataset_id
602
+ raise ArgumentError, "model_id is required" unless model_id
603
+ raise ArgumentError, "service is required" unless service
604
+
605
+ new.tap do |m|
606
+ reference_gapi_json = Google::Apis::BigqueryV2::ModelReference.new(
607
+ project_id: project_id,
608
+ dataset_id: dataset_id,
609
+ model_id: model_id
610
+ )
611
+ m.instance_variable_set :@reference, reference_gapi_json
612
+ m.instance_variable_set :@service, service
613
+ end
614
+ end
615
+
616
+ protected
617
+
618
+ ##
619
+ # Raise an error unless an active service is available.
620
+ def ensure_service!
621
+ raise "Must have active connection" unless service
622
+ end
623
+
624
+ ##
625
+ # Ensures the Google::Apis::BigqueryV2::Model object has been loaded
626
+ # from the service.
627
+ def ensure_gapi_json!
628
+ ensure_service!
629
+ return unless reference?
630
+ reload!
631
+ end
632
+
633
+ ##
634
+ # Fetch gapi_json and memoize whether resource exists.
635
+ def resource_exists?
636
+ reload!
637
+ @exists = true
638
+ rescue Google::Cloud::NotFoundError
639
+ @exists = false
640
+ end
641
+
642
+ def patch_gapi! **changes
643
+ return if changes.empty?
644
+ ensure_service!
645
+ patch_gapi = Google::Apis::BigqueryV2::Model.from_json changes.to_json
646
+ patch_gapi.model_reference = model_ref
647
+ @gapi_json = service.patch_model \
648
+ dataset_id, model_id, patch_gapi, etag
649
+ @reference = nil
650
+
651
+ # TODO: restore original impl after acceptance test indicates that
652
+ # service etag bug is fixed
653
+ reload!
654
+ end
655
+
656
+ ##
657
+ # Load the complete representation of the model if it has been
658
+ # only partially loaded by a request to the API list method.
659
+ def ensure_full_data!
660
+ reload! unless resource_full?
661
+ end
662
+ end
663
+ end
664
+ end
665
+ end