google-cloud-spanner 1.6.4 → 1.7.1

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: 7a7e4d511e0da87129eb3b33152dda498583baf5832222808867822a7097d79a
4
- data.tar.gz: 18ada9b93ec395b625c1439a90a2968c495c62eb1fdee3f1ecbbcae79c33f329
3
+ metadata.gz: 1f10305755bd4e6a4e31255e8fa54067a6725190a7bd8465321d3db6ab85e5b8
4
+ data.tar.gz: d6d33eaf5b1e84649fd7ab4d65565acf350afa2f073f2697106444f0a07ccdf1
5
5
  SHA512:
6
- metadata.gz: 50c14a7f74a351318c61fe1ad2b2342231f8997db010c5793d5d0112e4e545399476c2a2ab3aa3b0505cdef63430d710d864346bca611be19a78861af82a04d0
7
- data.tar.gz: d244fd5a527e3759536e797bee8354a7263898a5ac6bd7941d4815c02de432d4b83ef72dbe81cd8fbd7c4d259b59b21bc70311d9e81b5f7ed62ea3d5ee8da9ec
6
+ metadata.gz: a17174bd2315f5252835be530aa64b264426eef1ba3285682d6546101127203059fb2878733b5f1056e25181cd592828dfd7113916be667503dc7251c60521d6
7
+ data.tar.gz: 7bce7dd07f441fffd5a56017f92220ad088465dc269a9dc9c967bf24b81e4017487f6bc601e22912c2e0c1206f9dc2488d21788e050f2864c73812f127548547
@@ -1,5 +1,14 @@
1
1
  # Release History
2
2
 
3
+ ### 1.7.1 / 2018-10-08
4
+
5
+ * Add DML and Partitioned DML support
6
+ * Add execute_update to process DML statements
7
+ * Add execute_partition_update for Partitioned DML
8
+ * Rename execute_query method
9
+ * Maintain naming consistency with execute_update method.
10
+ * Maintain compatibility by adding query, execute and execute_sql aliases.
11
+
3
12
  ### 1.6.4 / 2018-09-20
4
13
 
5
14
  * Update Spanner generated files.
@@ -200,7 +200,7 @@ module Google
200
200
 
201
201
  results.partitions.map do |grpc|
202
202
  # Convert partition protos to execute sql request protos
203
- execute_grpc = Google::Spanner::V1::ExecuteSqlRequest.new(
203
+ execute_sql_grpc = Google::Spanner::V1::ExecuteSqlRequest.new(
204
204
  {
205
205
  session: session.path,
206
206
  sql: sql,
@@ -210,7 +210,7 @@ module Google
210
210
  partition_token: grpc.partition_token
211
211
  }.delete_if { |_, v| v.nil? }
212
212
  )
213
- Partition.from_execute_grpc execute_grpc
213
+ Partition.from_execute_sql_grpc execute_sql_grpc
214
214
  end
215
215
  end
216
216
 
@@ -425,7 +425,7 @@ module Google
425
425
  # batch_client = spanner.batch_client "my-instance", "my-database"
426
426
  # batch_snapshot = batch_client.batch_snapshot
427
427
  #
428
- # results = batch_snapshot.execute "SELECT * FROM users"
428
+ # results = batch_snapshot.execute_query "SELECT * FROM users"
429
429
  #
430
430
  # results.rows.each do |row|
431
431
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -438,9 +438,11 @@ module Google
438
438
  # batch_client = spanner.batch_client "my-instance", "my-database"
439
439
  # batch_snapshot = batch_client.batch_snapshot
440
440
  #
441
- # results = batch_snapshot.execute "SELECT * FROM users " \
442
- # "WHERE active = @active",
443
- # params: { active: true }
441
+ # results = batch_snapshot.execute_query(
442
+ # "SELECT * FROM users " \
443
+ # "WHERE active = @active",
444
+ # params: { active: true }
445
+ # )
444
446
  #
445
447
  # results.rows.each do |row|
446
448
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -455,11 +457,13 @@ module Google
455
457
  #
456
458
  # user_hash = { id: 1, name: "Charlie", active: false }
457
459
  #
458
- # results = batch_snapshot.execute "SELECT * FROM users WHERE " \
459
- # "ID = @user_struct.id " \
460
- # "AND name = @user_struct.name " \
461
- # "AND active = @user_struct.active",
462
- # params: { user_struct: user_hash }
460
+ # results = batch_snapshot.execute_query(
461
+ # "SELECT * FROM users WHERE " \
462
+ # "ID = @user_struct.id " \
463
+ # "AND name = @user_struct.name " \
464
+ # "AND active = @user_struct.active",
465
+ # params: { user_struct: user_hash }
466
+ # )
463
467
  #
464
468
  # results.rows.each do |row|
465
469
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -477,12 +481,14 @@ module Google
477
481
  # )
478
482
  # user_hash = { id: 1, name: nil, active: false }
479
483
  #
480
- # results = batch_snapshot.execute "SELECT * FROM users WHERE " \
481
- # "ID = @user_struct.id " \
482
- # "AND name = @user_struct.name " \
483
- # "AND active = @user_struct.active",
484
- # params: { user_struct: user_hash },
485
- # types: { user_struct: user_type }
484
+ # results = batch_snapshot.execute_query(
485
+ # "SELECT * FROM users WHERE " \
486
+ # "ID = @user_struct.id " \
487
+ # "AND name = @user_struct.name " \
488
+ # "AND active = @user_struct.active",
489
+ # params: { user_struct: user_hash },
490
+ # types: { user_struct: user_type }
491
+ # )
486
492
  #
487
493
  # results.rows.each do |row|
488
494
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -500,29 +506,33 @@ module Google
500
506
  # )
501
507
  # user_data = user_type.struct id: 1, name: nil, active: false
502
508
  #
503
- # results = batch_snapshot.execute "SELECT * FROM users WHERE " \
504
- # "ID = @user_struct.id " \
505
- # "AND name = @user_struct.name " \
506
- # "AND active = @user_struct.active",
507
- # params: { user_struct: user_data }
509
+ # results = batch_snapshot.execute_query(
510
+ # "SELECT * FROM users WHERE " \
511
+ # "ID = @user_struct.id " \
512
+ # "AND name = @user_struct.name " \
513
+ # "AND active = @user_struct.active",
514
+ # params: { user_struct: user_data }
515
+ # )
508
516
  #
509
517
  # results.rows.each do |row|
510
518
  # puts "User #{row[:id]} is #{row[:name]}"
511
519
  # end
512
520
  #
513
- def execute sql, params: nil, types: nil
521
+ def execute_query sql, params: nil, types: nil
514
522
  ensure_session!
515
523
 
516
524
  params, types = Convert.to_input_params_and_types params, types
517
525
 
518
- session.execute sql, params: params, types: types,
519
- transaction: tx_selector
526
+ session.execute_query sql, params: params, types: types,
527
+ transaction: tx_selector
520
528
  end
521
- alias query execute
529
+ alias execute execute_query
530
+ alias query execute_query
531
+ alias execute_sql execute_query
522
532
 
523
533
  ##
524
534
  # Read rows from a database table, as a simple alternative to
525
- # {#execute}.
535
+ # {#execute_query}.
526
536
  #
527
537
  # @param [String] table The name of the table in the database to be
528
538
  # read.
@@ -651,11 +661,12 @@ module Google
651
661
  end
652
662
 
653
663
  def execute_partition_query partition
654
- session.execute partition.execute.sql,
655
- params: partition.execute.params,
656
- types: partition.execute.param_types.to_h,
657
- transaction: partition.execute.transaction,
658
- partition_token: partition.execute.partition_token
664
+ session.execute_query \
665
+ partition.execute.sql,
666
+ params: partition.execute.params,
667
+ types: partition.execute.param_types.to_h,
668
+ transaction: partition.execute.transaction,
669
+ partition_token: partition.execute.partition_token
659
670
  end
660
671
 
661
672
  def execute_partition_read partition
@@ -42,7 +42,7 @@ module Google
42
42
  # db = spanner.client "my-instance", "my-database"
43
43
  #
44
44
  # db.transaction do |tx|
45
- # results = tx.execute "SELECT * FROM users"
45
+ # results = tx.execute_query "SELECT * FROM users"
46
46
  #
47
47
  # results.rows.each do |row|
48
48
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -218,7 +218,7 @@ module Google
218
218
  #
219
219
  # db = spanner.client "my-instance", "my-database"
220
220
  #
221
- # results = db.execute "SELECT * FROM users"
221
+ # results = db.execute_query "SELECT * FROM users"
222
222
  #
223
223
  # results.rows.each do |row|
224
224
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -231,8 +231,10 @@ module Google
231
231
  #
232
232
  # db = spanner.client "my-instance", "my-database"
233
233
  #
234
- # results = db.execute "SELECT * FROM users WHERE active = @active",
235
- # params: { active: true }
234
+ # results = db.execute_query(
235
+ # "SELECT * FROM users WHERE active = @active",
236
+ # params: { active: true }
237
+ # )
236
238
  #
237
239
  # results.rows.each do |row|
238
240
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -247,11 +249,13 @@ module Google
247
249
  #
248
250
  # user_hash = { id: 1, name: "Charlie", active: false }
249
251
  #
250
- # results = db.execute "SELECT * FROM users WHERE " \
251
- # "ID = @user_struct.id " \
252
- # "AND name = @user_struct.name " \
253
- # "AND active = @user_struct.active",
254
- # params: { user_struct: user_hash }
252
+ # results = db.execute_query(
253
+ # "SELECT * FROM users WHERE " \
254
+ # "ID = @user_struct.id " \
255
+ # "AND name = @user_struct.name " \
256
+ # "AND active = @user_struct.active",
257
+ # params: { user_struct: user_hash }
258
+ # )
255
259
  #
256
260
  # results.rows.each do |row|
257
261
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -267,12 +271,14 @@ module Google
267
271
  # user_type = db.fields id: :INT64, name: :STRING, active: :BOOL
268
272
  # user_hash = { id: 1, name: nil, active: false }
269
273
  #
270
- # results = db.execute "SELECT * FROM users WHERE " \
271
- # "ID = @user_struct.id " \
272
- # "AND name = @user_struct.name " \
273
- # "AND active = @user_struct.active",
274
- # params: { user_struct: user_hash },
275
- # types: { user_struct: user_type }
274
+ # results = db.execute_query(
275
+ # "SELECT * FROM users WHERE " \
276
+ # "ID = @user_struct.id " \
277
+ # "AND name = @user_struct.name " \
278
+ # "AND active = @user_struct.active",
279
+ # params: { user_struct: user_hash },
280
+ # types: { user_struct: user_type }
281
+ # )
276
282
  #
277
283
  # results.rows.each do |row|
278
284
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -288,17 +294,19 @@ module Google
288
294
  # user_type = db.fields id: :INT64, name: :STRING, active: :BOOL
289
295
  # user_data = user_type.struct id: 1, name: nil, active: false
290
296
  #
291
- # results = db.execute "SELECT * FROM users WHERE " \
292
- # "ID = @user_struct.id " \
293
- # "AND name = @user_struct.name " \
294
- # "AND active = @user_struct.active",
295
- # params: { user_struct: user_data }
297
+ # results = db.execute_query(
298
+ # "SELECT * FROM users WHERE " \
299
+ # "ID = @user_struct.id " \
300
+ # "AND name = @user_struct.name " \
301
+ # "AND active = @user_struct.active",
302
+ # params: { user_struct: user_data }
303
+ # )
296
304
  #
297
305
  # results.rows.each do |row|
298
306
  # puts "User #{row[:id]} is #{row[:name]}"
299
307
  # end
300
308
  #
301
- def execute sql, params: nil, types: nil, single_use: nil
309
+ def execute_query sql, params: nil, types: nil, single_use: nil
302
310
  validate_single_use_args! single_use
303
311
  ensure_service!
304
312
 
@@ -307,16 +315,197 @@ module Google
307
315
  single_use_tx = single_use_transaction single_use
308
316
  results = nil
309
317
  @pool.with_session do |session|
310
- results = session.execute \
318
+ results = session.execute_query \
311
319
  sql, params: params, types: types, transaction: single_use_tx
312
320
  end
313
321
  results
314
322
  end
315
- alias query execute
323
+ alias execute execute_query
324
+ alias query execute_query
325
+ alias execute_sql execute_query
326
+
327
+ ##
328
+ # Executes a Partitioned DML SQL statement.
329
+ #
330
+ # Partitioned DML is an alternate implementation with looser semantics
331
+ # to enable large-scale changes without running into transaction size
332
+ # limits or (accidentally) locking the entire table in one large
333
+ # transaction. At a high level, it partitions the keyspace and executes
334
+ # the statement on each partition in separate internal transactions.
335
+ #
336
+ # Partitioned DML does not guarantee database-wide atomicity of the
337
+ # statement - it guarantees row-based atomicity, which includes updates
338
+ # to any indices. Additionally, it does not guarantee that it will
339
+ # execute exactly one time against each row - it guarantees "at least
340
+ # once" semantics.
341
+ #
342
+ # Where DML statements must be executed using Transaction (see
343
+ # {Transaction#execute_update}), Paritioned DML statements are executed
344
+ # outside of a read/write transaction.
345
+ #
346
+ # Not all DML statements can be executed in the Partitioned DML mode and
347
+ # the backend will return an error for the statements which are not
348
+ # supported.
349
+ #
350
+ # DML statements must be fully-partitionable. Specifically, the
351
+ # statement must be expressible as the union of many statements which
352
+ # each access only a single row of the table.
353
+ # {Google::Cloud::InvalidArgumentError} is raised if the statement does
354
+ # not qualify.
355
+ #
356
+ # The method will block until the update is complete. Running a DML
357
+ # statement with this method does not offer exactly once semantics, and
358
+ # therefore the DML statement should be idempotent. The DML statement
359
+ # must be fully-partitionable. Specifically, the statement must be
360
+ # expressible as the union of many statements which each access only a
361
+ # single row of the table. This is a Partitioned DML transaction in
362
+ # which a single Partitioned DML statement is executed. Partitioned DML
363
+ # partitions the and runs the DML statement over each partition in
364
+ # parallel using separate, internal transactions that commit
365
+ # independently. Partitioned DML transactions do not need to be
366
+ # committed.
367
+ #
368
+ # Partitioned DML updates are used to execute a single DML statement
369
+ # with a different execution strategy that provides different, and often
370
+ # better, scalability properties for large, table-wide operations than
371
+ # DML in a {Transaction#execute_update} transaction. Smaller scoped
372
+ # statements, such as an OLTP workload, should prefer using
373
+ # {Transaction#execute_update}.
374
+ #
375
+ # That said, Partitioned DML is not a drop-in replacement for standard
376
+ # DML used in {Transaction#execute_update}.
377
+ #
378
+ # * The DML statement must be fully-partitionable. Specifically, the
379
+ # statement must be expressible as the union of many statements which
380
+ # each access only a single row of the table.
381
+ # * The statement is not applied atomically to all rows of the table.
382
+ # Rather, the statement is applied atomically to partitions of the
383
+ # table, in independent internal transactions. Secondary index rows
384
+ # are updated atomically with the base table rows.
385
+ # * Partitioned DML does not guarantee exactly-once execution semantics
386
+ # against a partition. The statement will be applied at least once to
387
+ # each partition. It is strongly recommended that the DML statement
388
+ # should be idempotent to avoid unexpected results. For instance, it
389
+ # is potentially dangerous to run a statement such as `UPDATE table
390
+ # SET column = column + 1` as it could be run multiple times against
391
+ # some rows.
392
+ # * The partitions are committed automatically - there is no support for
393
+ # Commit or Rollback. If the call returns an error, or if the client
394
+ # issuing the DML statement dies, it is possible that some rows had
395
+ # the statement executed on them successfully. It is also possible
396
+ # that statement was never executed against other rows.
397
+ # * If any error is encountered during the execution of the partitioned
398
+ # DML operation (for instance, a UNIQUE INDEX violation, division by
399
+ # zero, or a value that cannot be stored due to schema constraints),
400
+ # then the operation is stopped at that point and an error is
401
+ # returned. It is possible that at this point, some partitions have
402
+ # been committed (or even committed multiple times), and other
403
+ # partitions have not been run at all.
404
+ #
405
+ # Given the above, Partitioned DML is good fit for large, database-wide,
406
+ # operations that are idempotent, such as deleting old rows from a very
407
+ # large table.
408
+ #
409
+ # @param [String] sql The Partitioned DML statement string. See [Query
410
+ # syntax](https://cloud.google.com/spanner/docs/query-syntax).
411
+ #
412
+ # The Partitioned DML statement string can contain parameter
413
+ # placeholders. A parameter placeholder consists of "@" followed by
414
+ # the parameter name. Parameter names consist of any combination of
415
+ # letters, numbers, and underscores.
416
+ # @param [Hash] params Parameters for the Partitioned DML statement
417
+ # string. The parameter placeholders, minus the "@", are the the hash
418
+ # keys, and the literal values are the hash values. If the query
419
+ # string contains something like "WHERE id > @msg_id", then the params
420
+ # must contain something like `:msg_id => 1`.
421
+ #
422
+ # Ruby types are mapped to Spanner types as follows:
423
+ #
424
+ # | Spanner | Ruby | Notes |
425
+ # |-------------|----------------|---|
426
+ # | `BOOL` | `true`/`false` | |
427
+ # | `INT64` | `Integer` | |
428
+ # | `FLOAT64` | `Float` | |
429
+ # | `STRING` | `String` | |
430
+ # | `DATE` | `Date` | |
431
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
432
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
433
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
434
+ # | `STRUCT` | `Hash`, {Data} | |
435
+ #
436
+ # See [Data
437
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
438
+ #
439
+ # See [Data Types - Constructing a
440
+ # STRUCT](https://cloud.google.com/spanner/docs/data-types#constructing-a-struct).
441
+ # @param [Hash] types Types of the SQL parameters in `params`. It is not
442
+ # always possible for Cloud Spanner to infer the right SQL type from a
443
+ # value in `params`. In these cases, the `types` hash can be used to
444
+ # specify the exact SQL type for some or all of the SQL query
445
+ # parameters.
446
+ #
447
+ # The keys of the hash should be query string parameter placeholders,
448
+ # minus the "@". The values of the hash should be Cloud Spanner type
449
+ # codes from the following list:
450
+ #
451
+ # * `:BOOL`
452
+ # * `:BYTES`
453
+ # * `:DATE`
454
+ # * `:FLOAT64`
455
+ # * `:INT64`
456
+ # * `:STRING`
457
+ # * `:TIMESTAMP`
458
+ # * `Array` - Lists are specified by providing the type code in an
459
+ # array. For example, an array of integers are specified as
460
+ # `[:INT64]`.
461
+ # * {Fields} - Nested Structs are specified by providing a Fields
462
+ # object.
463
+ # @return [Integer] The lower bound number of rows that were modified.
464
+ #
465
+ # @example
466
+ # require "google/cloud/spanner"
467
+ #
468
+ # spanner = Google::Cloud::Spanner.new
469
+ # db = spanner.client "my-instance", "my-database"
470
+ #
471
+ # row_count = db.execute_partition_update \
472
+ # "UPDATE users SET friends = NULL WHERE active = false"
473
+ #
474
+ # @example Query using query parameters:
475
+ # require "google/cloud/spanner"
476
+ #
477
+ # spanner = Google::Cloud::Spanner.new
478
+ # db = spanner.client "my-instance", "my-database"
479
+ #
480
+ # row_count = db.execute_partition_update \
481
+ # "UPDATE users SET friends = NULL WHERE active = @active",
482
+ # params: { active: false }
483
+ #
484
+ def execute_partition_update sql, params: nil, types: nil
485
+ ensure_service!
486
+
487
+ params, types = Convert.to_input_params_and_types params, types
488
+
489
+ results = nil
490
+ @pool.with_session do |session|
491
+ results = session.execute_query \
492
+ sql, params: params, types: types,
493
+ transaction: pdml_transaction(session)
494
+ end
495
+ # Stream all PartialResultSet to get ResultSetStats
496
+ results.rows.to_a
497
+ # Raise an error if there is not a row count returned
498
+ if results.row_count.nil?
499
+ raise Google::Cloud::InvalidArgumentError,
500
+ "Partitioned DML statement is invalid."
501
+ end
502
+ results.row_count
503
+ end
504
+ alias execute_pdml execute_partition_update
316
505
 
317
506
  ##
318
507
  # Read rows from a database table, as a simple alternative to
319
- # {#execute}.
508
+ # {#execute_query}.
320
509
  #
321
510
  # @param [String] table The name of the table in the database to be
322
511
  # read.
@@ -764,7 +953,7 @@ module Google
764
953
  # db = spanner.client "my-instance", "my-database"
765
954
  #
766
955
  # db.transaction do |tx|
767
- # results = tx.execute "SELECT * FROM users"
956
+ # results = tx.execute_query "SELECT * FROM users"
768
957
  #
769
958
  # results.rows.each do |row|
770
959
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -883,7 +1072,7 @@ module Google
883
1072
  # db = spanner.client "my-instance", "my-database"
884
1073
  #
885
1074
  # db.snapshot do |snp|
886
- # results = snp.execute "SELECT * FROM users"
1075
+ # results = snp.execute_query "SELECT * FROM users"
887
1076
  #
888
1077
  # results.rows.each do |row|
889
1078
  # puts "User #{row[:id]} is #{row[:name]}"
@@ -1012,7 +1201,7 @@ module Google
1012
1201
  # types: users_types
1013
1202
  #
1014
1203
  def fields_for table
1015
- execute("SELECT * FROM #{table} WHERE 1 = 0").fields
1204
+ execute_query("SELECT * FROM #{table} WHERE 1 = 0").fields
1016
1205
  end
1017
1206
 
1018
1207
  ##
@@ -1159,6 +1348,11 @@ module Google
1159
1348
  }.delete_if { |_, v| v.nil? })))
1160
1349
  end
1161
1350
 
1351
+ def pdml_transaction session
1352
+ pdml_tx_grpc = @project.service.create_pdml session.path
1353
+ Google::Spanner::V1::TransactionSelector.new id: pdml_tx_grpc.id
1354
+ end
1355
+
1162
1356
  ##
1163
1357
  # Check for valid snapshot arguments
1164
1358
  def validate_snapshot_args! strong: nil,