google-cloud-spanner 1.6.4 → 1.7.1

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: 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,