google-cloud-bigquery 1.11.2 → 1.12.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.
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