activerecord-spanner-adapter 1.2.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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