activerecord-spanner-adapter 1.2.2 → 1.4.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/blunderbuss.yml +1 -1
  4. data/.github/workflows/acceptance-tests-on-emulator.yaml +8 -8
  5. data/.github/workflows/ci.yaml +6 -4
  6. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +14 -6
  7. data/.github/workflows/nightly-unit-tests.yaml +14 -6
  8. data/.release-please-manifest.json +1 -1
  9. data/CHANGELOG.md +28 -0
  10. data/Gemfile +1 -1
  11. data/README.md +4 -0
  12. data/acceptance/cases/migration/change_table_test.rb +23 -13
  13. data/acceptance/cases/migration/schema_dumper_test.rb +69 -0
  14. data/acceptance/cases/models/generated_column_test.rb +21 -7
  15. data/acceptance/cases/models/interleave_test.rb +36 -0
  16. data/acceptance/cases/models/logging_test.rb +57 -0
  17. data/acceptance/cases/models/query_test.rb +6 -1
  18. data/acceptance/cases/tasks/database_tasks_test.rb +407 -0
  19. data/acceptance/models/album_partial_disabled.rb +17 -0
  20. data/acceptance/schema/schema.rb +139 -134
  21. data/acceptance/test_helper.rb +2 -0
  22. data/examples/snippets/array-data-type/db/schema.rb +8 -3
  23. data/examples/snippets/bulk-insert/db/schema.rb +9 -4
  24. data/examples/snippets/commit-timestamp/db/schema.rb +11 -6
  25. data/examples/snippets/create-records/db/schema.rb +9 -4
  26. data/examples/snippets/date-data-type/db/schema.rb +8 -3
  27. data/examples/snippets/generated-column/db/schema.rb +6 -1
  28. data/examples/snippets/hints/db/schema.rb +6 -1
  29. data/examples/snippets/interleaved-tables/README.md +2 -2
  30. data/examples/snippets/interleaved-tables/db/schema.rb +5 -0
  31. data/examples/snippets/migrations/db/schema.rb +10 -5
  32. data/examples/snippets/mutations/db/schema.rb +9 -4
  33. data/examples/snippets/optimistic-locking/db/schema.rb +9 -4
  34. data/examples/snippets/partitioned-dml/db/schema.rb +5 -0
  35. data/examples/snippets/quickstart/db/schema.rb +9 -4
  36. data/examples/snippets/read-only-transactions/db/schema.rb +5 -0
  37. data/examples/snippets/read-write-transactions/db/schema.rb +9 -4
  38. data/examples/snippets/stale-reads/db/schema.rb +5 -0
  39. data/examples/snippets/timestamp-data-type/db/schema.rb +8 -3
  40. data/lib/active_record/connection_adapters/spanner/column.rb +23 -0
  41. data/lib/active_record/connection_adapters/spanner/database_statements.rb +7 -4
  42. data/lib/active_record/connection_adapters/spanner/quoting.rb +9 -0
  43. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +17 -4
  44. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +11 -2
  45. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +56 -0
  46. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +56 -9
  47. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +19 -4
  48. data/lib/active_record/connection_adapters/spanner_adapter.rb +11 -0
  49. data/lib/active_record/tasks/spanner_database_tasks.rb +18 -4
  50. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +10 -0
  51. data/lib/active_record/type/spanner/time.rb +10 -3
  52. data/lib/activerecord_spanner_adapter/base.rb +41 -27
  53. data/lib/activerecord_spanner_adapter/connection.rb +8 -3
  54. data/lib/activerecord_spanner_adapter/information_schema.rb +52 -3
  55. data/lib/activerecord_spanner_adapter/table/column.rb +7 -2
  56. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  57. data/lib/arel/visitors/spanner.rb +8 -2
  58. metadata +9 -3
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
@@ -11,19 +11,24 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
- create_table "albums", force: :cascade do |t|
16
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
17
18
  t.decimal "marketing_budget"
18
19
  t.integer "singer_id", limit: 8
19
20
  t.integer "lock_version", limit: 8
20
21
  end
21
22
 
22
- create_table "singers", force: :cascade do |t|
23
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
23
24
  t.string "first_name"
24
25
  t.string "last_name"
25
26
  t.integer "lock_version", limit: 8
26
27
  end
27
28
 
28
29
  add_foreign_key "albums", "singers"
30
+ connection.run_batch
31
+ rescue
32
+ abort_batch
33
+ raise
29
34
  end
@@ -11,6 +11,7 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
16
  create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
@@ -23,4 +24,8 @@ ActiveRecord::Schema.define(version: 1) do
23
24
  end
24
25
 
25
26
  add_foreign_key "albums", "singers"
27
+ connection.run_batch
28
+ rescue
29
+ abort_batch
30
+ raise
26
31
  end
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
@@ -11,16 +11,21 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
- create_table "albums", force: :cascade do |t|
16
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
17
18
  t.integer "singer_id", limit: 8
18
19
  end
19
20
 
20
- create_table "singers", force: :cascade do |t|
21
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
21
22
  t.string "first_name"
22
23
  t.string "last_name"
23
24
  end
24
25
 
25
26
  add_foreign_key "albums", "singers"
27
+ connection.run_batch
28
+ rescue
29
+ abort_batch
30
+ raise
26
31
  end
@@ -11,6 +11,7 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
16
  create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
@@ -23,4 +24,8 @@ ActiveRecord::Schema.define(version: 1) do
23
24
  end
24
25
 
25
26
  add_foreign_key "albums", "singers"
27
+ connection.run_batch
28
+ rescue
29
+ abort_batch
30
+ raise
26
31
  end
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
@@ -11,17 +11,22 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
- create_table "albums", force: :cascade do |t|
16
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
17
18
  t.decimal "marketing_budget"
18
19
  t.integer "singer_id", limit: 8
19
20
  end
20
21
 
21
- create_table "singers", force: :cascade do |t|
22
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
22
23
  t.string "first_name"
23
24
  t.string "last_name"
24
25
  end
25
26
 
26
27
  add_foreign_key "albums", "singers"
28
+ connection.run_batch
29
+ rescue
30
+ abort_batch
31
+ raise
27
32
  end
@@ -11,6 +11,7 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
16
  create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
@@ -23,4 +24,8 @@ ActiveRecord::Schema.define(version: 1) do
23
24
  end
24
25
 
25
26
  add_foreign_key "albums", "singers"
27
+ connection.run_batch
28
+ rescue
29
+ abort_batch
30
+ raise
26
31
  end
@@ -2,8 +2,8 @@
2
2
  # of editing this file, please use the migrations feature of Active Record to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
- # This file is the source Rails uses to define your schema when running `rails
6
- # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
7
  # be faster and is potentially less error prone than running all of your
8
8
  # migrations from scratch. Old migrations may fail to apply correctly if those
9
9
  # migrations use external dependencies or application code.
@@ -11,11 +11,16 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 1) do
14
+ connection.start_batch_ddl
14
15
 
15
- create_table "meetings", force: :cascade do |t|
16
+ create_table "meetings", id: { limit: 8 }, force: :cascade do |t|
16
17
  t.string "title"
17
18
  t.time "meeting_time"
18
19
  t.string "meeting_timezone"
19
20
  end
20
21
 
22
+ connection.run_batch
23
+ rescue
24
+ abort_batch
25
+ raise
21
26
  end
@@ -0,0 +1,23 @@
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ module ActiveRecord
10
+ module ConnectionAdapters
11
+ module Spanner
12
+ class Column < ConnectionAdapters::Column
13
+ def has_default? # rubocop:disable Naming/PredicateName
14
+ super && !virtual?
15
+ end
16
+
17
+ def virtual?
18
+ sql_type_metadata.generated
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -35,7 +35,10 @@ module ActiveRecord
35
35
  binds.delete staleness_hint
36
36
  end
37
37
 
38
- log sql, name do
38
+ log_args = [sql, name]
39
+ log_args.concat [binds, type_casted_binds(binds)] if log_statement_binds
40
+
41
+ log(*log_args) do
39
42
  types, params = to_types_and_params binds
40
43
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
41
44
  if transaction_required
@@ -219,9 +222,9 @@ module ActiveRecord
219
222
  end.to_h
220
223
  params = binds.enum_for(:each_with_index).map do |bind, i|
221
224
  type = bind.respond_to?(:type) ? bind.type : ActiveModel::Type::Integer
222
- value = bind
223
- value = type.serialize bind.value, :dml if type.respond_to?(:serialize) && type.method(:serialize).arity < 0
224
- value = type.serialize bind.value if type.respond_to?(:serialize) && type.method(:serialize).arity >= 0
225
+ bind_value = bind.respond_to?(:value) ? bind.value : bind
226
+ value = ActiveRecord::Type::Spanner::SpannerActiveRecordConverter
227
+ .serialize_with_transaction_isolation_level(type, bind_value, :dml)
225
228
 
226
229
  ["p#{i + 1}", value]
227
230
  end.to_h
@@ -54,6 +54,15 @@ module ActiveRecord
54
54
  def quoted_binary value
55
55
  "b'#{value}'"
56
56
  end
57
+
58
+ def _type_cast value
59
+ case value
60
+ when Array
61
+ ActiveSupport::JSON.encode value
62
+ else
63
+ super
64
+ end
65
+ end
57
66
  end
58
67
  end
59
68
  end
@@ -10,7 +10,8 @@ module ActiveRecord
10
10
  class SchemaCreation < SchemaCreation
11
11
  private
12
12
 
13
- # rubocop:disable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity
13
+ # rubocop:disable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
14
+ # rubocop:disable Metrics/MethodLength
14
15
 
15
16
  def visit_TableDefinition o
16
17
  create_sql = +"CREATE TABLE #{quote_table_name o.name} "
@@ -26,6 +27,14 @@ module ActiveRecord
26
27
  end
27
28
  end
28
29
 
30
+ if ActiveRecord::VERSION::MAJOR >= 7
31
+ statements.concat(o.check_constraints.map { |chk| accept chk })
32
+ elsif ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR >= 1
33
+ statements.concat(
34
+ o.check_constraints.map { |expression, options| check_constraint_in_create o.name, expression, options }
35
+ )
36
+ end
37
+
29
38
  create_sql << "(#{statements.join ', '}) " if statements.any?
30
39
 
31
40
  primary_keys = if o.primary_keys
@@ -63,7 +72,7 @@ module ActiveRecord
63
72
  def visit_ColumnDefinition o
64
73
  o.sql_type = type_to_sql o.type, **o.options
65
74
  column_sql = +"#{quote_column_name o.name} #{o.sql_type}"
66
- add_column_options! column_sql, column_options(o)
75
+ add_column_options! o, column_sql, column_options(o)
67
76
  column_sql
68
77
  end
69
78
 
@@ -112,12 +121,16 @@ module ActiveRecord
112
121
  sql
113
122
  end
114
123
 
115
- # rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity
124
+ # rubocop:enable Naming/MethodName, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
125
+ # rubocop:enable Metrics/MethodLength
116
126
 
117
- def add_column_options! sql, options
127
+ def add_column_options! column, sql, options
118
128
  if options[:null] == false || options[:primary_key] == true
119
129
  sql << " NOT NULL"
120
130
  end
131
+ if options.key? :default
132
+ sql << " DEFAULT #{quote_default_expression options[:default], column}"
133
+ end
121
134
 
122
135
  if !options[:allow_commit_timestamp].nil? &&
123
136
  options[:column].sql_type == "TIMESTAMP"
@@ -15,8 +15,8 @@ module ActiveRecord
15
15
  @on_delete = on_delete
16
16
  end
17
17
 
18
- def parent_key name
19
- column name, :parent_key, null: false
18
+ def parent_key name, type: nil
19
+ column name, :parent_key, null: false, passed_type: type
20
20
  end
21
21
 
22
22
  def interleave_in?
@@ -33,6 +33,15 @@ module ActiveRecord
33
33
  end
34
34
  end
35
35
  alias belongs_to references
36
+
37
+ def new_column_definition name, type, **options
38
+ case type
39
+ when :virtual
40
+ type = options[:type]
41
+ end
42
+
43
+ super
44
+ end
36
45
  end
37
46
 
38
47
  class Table < ActiveRecord::ConnectionAdapters::Table
@@ -13,6 +13,62 @@ module ActiveRecord
13
13
  def default_primary_key? column
14
14
  schema_type(column) == :integer
15
15
  end
16
+
17
+ def prepare_column_options column
18
+ spec = super
19
+
20
+ unless column.sql_type_metadata.allow_commit_timestamp.nil?
21
+ spec[:allow_commit_timestamp] = column.sql_type_metadata.allow_commit_timestamp
22
+ end
23
+
24
+ if column.virtual?
25
+ spec[:as] = extract_expression_for_virtual_column column
26
+ spec[:stored] = true
27
+ spec = { type: schema_type(column).inspect }.merge! spec
28
+ end
29
+
30
+ spec
31
+ end
32
+
33
+ def header stream
34
+ str = StringIO.new
35
+ super str
36
+ stream.puts <<~HEADER
37
+ #{str.string.rstrip}
38
+ connection.start_batch_ddl
39
+
40
+ HEADER
41
+ end
42
+
43
+ def trailer stream
44
+ stream.puts <<~TRAILER
45
+ connection.run_batch
46
+ rescue
47
+ abort_batch
48
+ raise
49
+ TRAILER
50
+ super
51
+ end
52
+
53
+ def index_parts index
54
+ index_parts = super
55
+ index_parts << "null_filtered: #{index.null_filtered.inspect}" if index.null_filtered
56
+ index_parts << "interleave_in: #{index.interleave_in.inspect}" if index.interleave_in
57
+ index_parts << "storing: #{format_index_parts index.storing}" if index.storing.present?
58
+ index_parts
59
+ end
60
+
61
+ private
62
+
63
+ def column_spec_for_primary_key column
64
+ spec = super
65
+ spec.except! :limit if default_primary_key? column
66
+ spec
67
+ end
68
+
69
+ def extract_expression_for_virtual_column column
70
+ column.default_function.inspect
71
+ end
16
72
  end
17
73
  end
18
74
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  require "active_record/connection_adapters/spanner/schema_creation"
10
10
  require "active_record/connection_adapters/spanner/schema_dumper"
11
+ require "active_record/connection_adapters/spanner/column"
11
12
 
12
13
  module ActiveRecord
13
14
  module ConnectionAdapters
@@ -39,18 +40,19 @@ module ActiveRecord
39
40
  end
40
41
  alias data_source_exists? table_exists?
41
42
 
42
- def create_table table_name, **options
43
+ def create_table table_name, id: :primary_key, **options
43
44
  td = create_table_definition table_name, options
44
45
 
45
- if options[:id] != false
46
+ if id
46
47
  pk = options.fetch :primary_key do
47
48
  Base.get_primary_key table_name.to_s.singularize
48
49
  end
50
+ id = id.fetch :type, :primary_key if id.is_a? Hash
49
51
 
50
52
  if pk.is_a? Array
51
53
  td.primary_keys pk
52
54
  else
53
- td.primary_key pk, options.fetch(:id, :primary_key), **{}
55
+ td.primary_key pk, id, **{}
54
56
  end
55
57
  end
56
58
 
@@ -108,16 +110,23 @@ module ActiveRecord
108
110
  end
109
111
 
110
112
  def new_column_from_field _table_name, field
111
- ConnectionAdapters::Column.new \
113
+ Spanner::Column.new \
112
114
  field.name,
113
115
  field.default,
114
- fetch_type_metadata(field.spanner_type, field.ordinal_position),
115
- field.nullable
116
+ fetch_type_metadata(field.spanner_type,
117
+ field.ordinal_position,
118
+ field.allow_commit_timestamp,
119
+ field.generated),
120
+ field.nullable,
121
+ field.default_function
116
122
  end
117
123
 
118
- def fetch_type_metadata sql_type, ordinal_position = nil
124
+ def fetch_type_metadata sql_type, ordinal_position = nil, allow_commit_timestamp = nil, generated = nil
119
125
  Spanner::TypeMetadata.new \
120
- super(sql_type), ordinal_position: ordinal_position
126
+ super(sql_type),
127
+ ordinal_position: ordinal_position,
128
+ allow_commit_timestamp: allow_commit_timestamp,
129
+ generated: generated
121
130
  end
122
131
 
123
132
  def add_column table_name, column_name, type, **options
@@ -376,6 +385,44 @@ module ActiveRecord
376
385
  end
377
386
  alias add_belongs_to add_reference
378
387
 
388
+ # Check Contraints
389
+
390
+ def check_constraints table_name
391
+ information_schema { |i| i.check_constraints table_name }
392
+ end
393
+
394
+ def assume_migrated_upto_version version
395
+ version = version.to_i
396
+ sm_table = quote_table_name schema_migration.table_name
397
+
398
+ migrated = migration_context.get_all_versions
399
+ versions = migration_context.migrations.map(&:version)
400
+
401
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote version.to_s})" unless migrated.include? version
402
+
403
+ inserting = (versions - migrated).select { |v| v < version }
404
+ return unless inserting.any?
405
+
406
+ if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
407
+ raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
408
+ end
409
+
410
+ execute insert_versions_sql(inserting)
411
+ end
412
+
413
+ def insert_versions_sql versions
414
+ sm_table = quote_table_name schema_migration.table_name
415
+
416
+ if versions.is_a? Array
417
+ sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
418
+ sql << versions.reverse.map { |v| "(#{quote v.to_s})" }.join(",\n")
419
+ sql << ";"
420
+ sql
421
+ else
422
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote versions.to_s});"
423
+ end
424
+ end
425
+
379
426
  def quoted_scope name = nil, type: nil
380
427
  scope = { schema: quote("") }
381
428
  scope[:name] = quote name if name
@@ -389,7 +436,7 @@ module ActiveRecord
389
436
 
390
437
  # rubocop:disable Lint/UnusedMethodArgument
391
438
  def type_to_sql type, limit: nil, precision: nil, scale: nil, **opts
392
- type = type.to_sym if type
439
+ type = opts[:passed_type] || type&.to_sym
393
440
  native = native_database_types[type]
394
441
 
395
442
  return type.to_s unless native
@@ -12,24 +12,39 @@ module ActiveRecord
12
12
  class TypeMetadata < DelegateClass(SqlTypeMetadata)
13
13
  undef to_yaml if method_defined? :to_yaml
14
14
 
15
- attr_reader :ordinal_position
15
+ include Deduplicable if defined?(Deduplicable)
16
16
 
17
- def initialize type_metadata, ordinal_position: nil
17
+ attr_reader :ordinal_position, :allow_commit_timestamp, :generated
18
+
19
+ def initialize type_metadata, ordinal_position: nil, allow_commit_timestamp: nil, generated: nil
18
20
  super type_metadata
19
21
  @ordinal_position = ordinal_position
22
+ @allow_commit_timestamp = allow_commit_timestamp
23
+ @generated = generated
20
24
  end
21
25
 
22
26
  def == other
23
27
  other.is_a?(TypeMetadata) &&
24
28
  __getobj__ == other.__getobj__ &&
25
- ordinal_position == other.ordinal_position
29
+ ordinal_position == other.ordinal_position &&
30
+ allow_commit_timestamp == other.allow_commit_timestamp &&
31
+ generated == other.generated
26
32
  end
27
33
  alias eql? ==
28
34
 
29
35
  def hash
30
36
  TypeMetadata.hash ^
31
37
  __getobj__.hash ^
32
- ordinal_position.hash
38
+ ordinal_position.hash ^
39
+ allow_commit_timestamp.hash ^
40
+ generated.hash
41
+ end
42
+
43
+ private
44
+
45
+ def deduplicated
46
+ __setobj__ __getobj__.deduplicate
47
+ super
33
48
  end
34
49
  end
35
50
  end
@@ -74,6 +74,9 @@ module ActiveRecord
74
74
  include Spanner::DatabaseStatements
75
75
  include Spanner::SchemaStatements
76
76
 
77
+ # Determines whether or not to log query binds when executing statements
78
+ class_attribute :log_statement_binds, instance_writer: false, default: false
79
+
77
80
  def initialize connection, logger, connection_options, config
78
81
  super connection, logger, config
79
82
  @connection_options = connection_options
@@ -170,6 +173,14 @@ module ActiveRecord
170
173
  true
171
174
  end
172
175
 
176
+ def supports_check_constraints?
177
+ true
178
+ end
179
+
180
+ def supports_virtual_columns?
181
+ true
182
+ end
183
+
173
184
  # Generate next sequence number for primary key
174
185
  def next_sequence_value _sequence_name
175
186
  SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF
@@ -29,7 +29,12 @@ module ActiveRecord
29
29
  end
30
30
 
31
31
  def purge
32
- drop
32
+ begin
33
+ drop
34
+ rescue ActiveRecord::NoDatabaseError # rubocop:disable Lint/HandleExceptions
35
+ # ignored; create the database
36
+ end
37
+
33
38
  create
34
39
  end
35
40
 
@@ -54,15 +59,24 @@ module ActiveRecord
54
59
  next if ignore_tables.any? &&
55
60
  (table_regx =~ statement || index_regx =~ statement)
56
61
  file.write statement
57
- file.write "\n"
62
+ file.write ";\n"
58
63
  end
59
64
  ensure
60
65
  file.close
61
66
  end
62
67
 
63
68
  def structure_load filename, _extra_flags
64
- statements = File.read(filename).split(/(?=^CREATE)/)
65
- @connection.execute_ddl statements
69
+ statements = File.read(filename).split(/;/).map(&:strip).reject(&:empty?)
70
+ ddls = statements.select { |s| s =~ /^(CREATE|ALTER|DROP|GRANT|REVOKE|ANALYZE)/ }
71
+ @connection.execute_ddl ddls
72
+
73
+ client = @connection.spanner.client @connection.instance_id,
74
+ @connection.database_id
75
+ dmls = statements.reject { |s| s =~ /^(CREATE|ALTER|DROP|GRANT|REVOKE|ANALYZE)/ }
76
+
77
+ client.transaction do |tx|
78
+ dmls.each { |dml| tx.execute_query dml }
79
+ end
66
80
  end
67
81
  end
68
82
 
@@ -10,6 +10,16 @@ module ActiveRecord
10
10
  module Type
11
11
  module Spanner
12
12
  class SpannerActiveRecordConverter
13
+ def self.serialize_with_transaction_isolation_level type, value, isolation_level
14
+ if type.respond_to? :serialize_with_isolation_level
15
+ type.serialize_with_isolation_level value, isolation_level
16
+ elsif type.respond_to? :serialize
17
+ type.serialize value
18
+ else
19
+ value
20
+ end
21
+ end
22
+
13
23
  ##
14
24
  # Converts an ActiveModel::Type to a Spanner type code.
15
25
  def self.convert_active_model_type_to_spanner type # rubocop:disable Metrics/CyclomaticComplexity
@@ -10,9 +10,16 @@ module ActiveRecord
10
10
  module Type
11
11
  module Spanner
12
12
  class Time < ActiveRecord::Type::Time
13
- def serialize value, *options
14
- return "PENDING_COMMIT_TIMESTAMP()" if value == :commit_timestamp && options.length && options[0] == :dml
15
- return "spanner.commit_timestamp()" if value == :commit_timestamp && options.length && options[0] == :mutation
13
+ def serialize_with_isolation_level value, isolation_level
14
+ if value == :commit_timestamp
15
+ return "PENDING_COMMIT_TIMESTAMP()" if isolation_level == :dml
16
+ return "spanner.commit_timestamp()" if isolation_level == :mutation
17
+ end
18
+
19
+ serialize value
20
+ end
21
+
22
+ def serialize value
16
23
  val = super value
17
24
  val.acts_like?(:time) ? val.utc.rfc3339(9) : val
18
25
  end