activerecord-spanner-adapter 1.6.3 → 2.0.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 (141) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/acceptance-tests-on-emulator.yaml +3 -7
  3. data/.github/workflows/acceptance-tests-on-production.yaml +1 -1
  4. data/.github/workflows/ci.yaml +3 -7
  5. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +4 -33
  6. data/.github/workflows/nightly-acceptance-tests-on-production.yaml +1 -1
  7. data/.github/workflows/nightly-unit-tests.yaml +5 -33
  8. data/.github/workflows/rubocop.yaml +1 -1
  9. data/.github/workflows/samples.yaml +30 -0
  10. data/.kokoro/populate-secrets.sh +5 -1
  11. data/.kokoro/release.cfg +22 -12
  12. data/.kokoro/release.sh +1 -3
  13. data/.kokoro/trampoline_v2.sh +19 -11
  14. data/.release-please-manifest.json +1 -1
  15. data/.rubocop.yml +2 -2
  16. data/.trampolinerc +6 -1
  17. data/CHANGELOG.md +37 -0
  18. data/Gemfile +7 -5
  19. data/README.md +11 -9
  20. data/Rakefile +2 -2
  21. data/acceptance/cases/migration/command_recorder_test.rb +7 -38
  22. data/acceptance/cases/migration/references_index_test.rb +2 -11
  23. data/acceptance/cases/migration/schema_dumper_test.rb +21 -9
  24. data/acceptance/cases/models/binary_identifiers.rb +97 -0
  25. data/acceptance/cases/models/insert_all_test.rb +22 -7
  26. data/acceptance/cases/sessions/session_not_found_test.rb +2 -0
  27. data/acceptance/cases/tasks/database_tasks_test.rb +1 -0
  28. data/acceptance/models/binary_project.rb +20 -0
  29. data/acceptance/models/string_io.rb +28 -0
  30. data/acceptance/models/user.rb +20 -0
  31. data/acceptance/test_helper.rb +6 -1
  32. data/activerecord-spanner-adapter.gemspec +3 -3
  33. data/benchmarks/application.rb +3 -7
  34. data/examples/snippets/Rakefile +27 -5
  35. data/examples/snippets/array-data-type/application.rb +1 -5
  36. data/examples/snippets/array-data-type/config/database.yml +1 -0
  37. data/examples/snippets/bit-reversed-sequence/application.rb +0 -4
  38. data/examples/snippets/bit-reversed-sequence/config/database.yml +1 -0
  39. data/examples/snippets/bit-reversed-sequence/db/seeds.rb +2 -2
  40. data/examples/snippets/bulk-insert/application.rb +1 -5
  41. data/examples/snippets/bulk-insert/config/database.yml +1 -0
  42. data/examples/snippets/commit-timestamp/application.rb +0 -4
  43. data/examples/snippets/commit-timestamp/config/database.yml +1 -0
  44. data/examples/snippets/config/environment.rb +5 -0
  45. data/examples/snippets/create-records/application.rb +1 -5
  46. data/examples/snippets/create-records/config/database.yml +1 -0
  47. data/examples/snippets/date-data-type/application.rb +1 -5
  48. data/examples/snippets/date-data-type/config/database.yml +1 -0
  49. data/examples/snippets/date-data-type/db/seeds.rb +1 -1
  50. data/examples/snippets/generated-column/application.rb +0 -4
  51. data/examples/snippets/generated-column/config/database.yml +1 -0
  52. data/examples/snippets/generated-column/db/seeds.rb +1 -1
  53. data/examples/snippets/hints/application.rb +0 -4
  54. data/examples/snippets/hints/config/database.yml +1 -0
  55. data/examples/snippets/hints/db/seeds.rb +1 -1
  56. data/examples/snippets/interleaved-tables/application.rb +1 -5
  57. data/examples/snippets/interleaved-tables/config/database.yml +1 -0
  58. data/examples/snippets/interleaved-tables/db/seeds.rb +1 -1
  59. data/examples/snippets/interleaved-tables/models/album.rb +6 -2
  60. data/examples/snippets/interleaved-tables/models/track.rb +5 -1
  61. data/examples/snippets/interleaved-tables-before-7.1/application.rb +1 -5
  62. data/examples/snippets/interleaved-tables-before-7.1/config/database.yml +1 -0
  63. data/examples/snippets/interleaved-tables-before-7.1/db/seeds.rb +1 -1
  64. data/examples/snippets/migrations/application.rb +0 -4
  65. data/examples/snippets/migrations/config/database.yml +1 -0
  66. data/examples/snippets/mutations/application.rb +1 -5
  67. data/examples/snippets/mutations/config/database.yml +1 -0
  68. data/examples/snippets/mutations/db/seeds.rb +1 -1
  69. data/examples/snippets/optimistic-locking/application.rb +0 -4
  70. data/examples/snippets/optimistic-locking/config/database.yml +1 -0
  71. data/examples/snippets/optimistic-locking/db/seeds.rb +1 -1
  72. data/examples/snippets/partitioned-dml/application.rb +0 -4
  73. data/examples/snippets/partitioned-dml/config/database.yml +1 -0
  74. data/examples/snippets/partitioned-dml/db/seeds.rb +1 -1
  75. data/examples/snippets/query-logs/application.rb +15 -13
  76. data/examples/snippets/query-logs/config/database.yml +1 -0
  77. data/examples/snippets/query-logs/db/seeds.rb +1 -1
  78. data/examples/snippets/quickstart/application.rb +0 -4
  79. data/examples/snippets/quickstart/config/database.yml +1 -0
  80. data/examples/snippets/quickstart/db/seeds.rb +1 -1
  81. data/examples/snippets/read-only-transactions/application.rb +0 -4
  82. data/examples/snippets/read-only-transactions/config/database.yml +1 -0
  83. data/examples/snippets/read-only-transactions/db/seeds.rb +1 -1
  84. data/examples/snippets/read-write-transactions/application.rb +2 -6
  85. data/examples/snippets/read-write-transactions/config/database.yml +1 -0
  86. data/examples/snippets/read-write-transactions/db/seeds.rb +1 -1
  87. data/examples/snippets/stale-reads/application.rb +0 -4
  88. data/examples/snippets/stale-reads/config/database.yml +1 -0
  89. data/examples/snippets/stale-reads/db/seeds.rb +1 -1
  90. data/examples/snippets/tags/application.rb +0 -4
  91. data/examples/snippets/tags/config/database.yml +1 -0
  92. data/examples/snippets/tags/db/seeds.rb +1 -1
  93. data/examples/snippets/timestamp-data-type/application.rb +0 -4
  94. data/examples/snippets/timestamp-data-type/config/database.yml +1 -0
  95. data/lib/active_record/connection_adapters/spanner/column.rb +3 -3
  96. data/lib/active_record/connection_adapters/spanner/database_statements.rb +37 -23
  97. data/lib/active_record/connection_adapters/spanner/quoting.rb +19 -6
  98. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +7 -9
  99. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +12 -2
  100. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +28 -46
  101. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +4 -6
  102. data/lib/active_record/connection_adapters/spanner_adapter.rb +54 -27
  103. data/lib/active_record/tasks/spanner_database_tasks.rb +4 -4
  104. data/lib/active_record/type/spanner/array.rb +4 -0
  105. data/lib/active_record/type/spanner/bytes.rb +10 -0
  106. data/lib/activerecord-spanner-adapter.rb +5 -1
  107. data/lib/activerecord_spanner_adapter/base.rb +58 -30
  108. data/lib/activerecord_spanner_adapter/connection.rb +9 -5
  109. data/lib/activerecord_spanner_adapter/foreign_key.rb +9 -2
  110. data/lib/activerecord_spanner_adapter/index/column.rb +6 -1
  111. data/lib/activerecord_spanner_adapter/index.rb +10 -2
  112. data/lib/activerecord_spanner_adapter/information_schema.rb +1 -1
  113. data/lib/activerecord_spanner_adapter/primary_key.rb +2 -2
  114. data/lib/activerecord_spanner_adapter/table/column.rb +12 -3
  115. data/lib/activerecord_spanner_adapter/table.rb +8 -2
  116. data/lib/activerecord_spanner_adapter/transaction.rb +1 -1
  117. data/lib/activerecord_spanner_adapter/version.rb +1 -1
  118. data/lib/arel/visitors/spanner.rb +16 -11
  119. data/lib/spanner_client_ext.rb +4 -3
  120. metadata +15 -34
  121. data/examples/snippets/array-data-type/db/schema.rb +0 -31
  122. data/examples/snippets/bit-reversed-sequence/db/schema.rb +0 -31
  123. data/examples/snippets/bulk-insert/db/schema.rb +0 -31
  124. data/examples/snippets/commit-timestamp/db/schema.rb +0 -34
  125. data/examples/snippets/create-records/db/schema.rb +0 -31
  126. data/examples/snippets/date-data-type/db/schema.rb +0 -26
  127. data/examples/snippets/generated-column/db/schema.rb +0 -26
  128. data/examples/snippets/hints/db/schema.rb +0 -33
  129. data/examples/snippets/interleaved-tables/db/schema.rb +0 -39
  130. data/examples/snippets/interleaved-tables-before-7.1/db/schema.rb +0 -37
  131. data/examples/snippets/migrations/db/schema.rb +0 -38
  132. data/examples/snippets/mutations/db/schema.rb +0 -32
  133. data/examples/snippets/optimistic-locking/db/schema.rb +0 -34
  134. data/examples/snippets/partitioned-dml/db/schema.rb +0 -31
  135. data/examples/snippets/query-logs/db/schema.rb +0 -31
  136. data/examples/snippets/quickstart/db/schema.rb +0 -31
  137. data/examples/snippets/read-only-transactions/db/schema.rb +0 -31
  138. data/examples/snippets/read-write-transactions/db/schema.rb +0 -32
  139. data/examples/snippets/stale-reads/db/schema.rb +0 -31
  140. data/examples/snippets/tags/db/schema.rb +0 -31
  141. data/examples/snippets/timestamp-data-type/db/schema.rb +0 -26
@@ -17,15 +17,27 @@ module ActiveRecord
17
17
  ActiveRecord::gem_version >= Gem::Version.create('7.1.0')
18
18
  end
19
19
 
20
+ def is_7_2_or_higher?
21
+ ActiveRecord::gem_version >= Gem::Version.create('7.2.0')
22
+ end
23
+
24
+ def pool_or_connection
25
+ if is_7_2_or_higher?
26
+ ActiveRecord::Base.connection_pool
27
+ else
28
+ ActiveRecord::Base.connection
29
+ end
30
+ end
31
+
20
32
  def test_dump_schema_contains_start_batch_ddl
21
- connection = ActiveRecord::Base.connection
33
+ connection = pool_or_connection
22
34
  schema = StringIO.new
23
35
  ActiveRecord::SchemaDumper.dump connection, schema
24
36
  assert schema.string.include?("connection.start_batch_ddl")
25
37
  end
26
38
 
27
39
  def test_dump_schema_contains_run_batch
28
- connection = ActiveRecord::Base.connection
40
+ connection = pool_or_connection
29
41
  schema = StringIO.new
30
42
  ActiveRecord::SchemaDumper.dump connection, schema
31
43
  assert schema.string.include?(" connection.run_batch\n"\
@@ -35,7 +47,7 @@ module ActiveRecord
35
47
  end
36
48
 
37
49
  def test_dump_schema_contains_albums_table
38
- connection = ActiveRecord::Base.connection
50
+ connection = pool_or_connection
39
51
  schema = StringIO.new
40
52
  ActiveRecord::SchemaDumper.dump connection, schema
41
53
  sql = schema.string
@@ -47,42 +59,42 @@ module ActiveRecord
47
59
  end
48
60
 
49
61
  def test_dump_schema_contains_interleaved_index
50
- connection = ActiveRecord::Base.connection
62
+ connection = pool_or_connection
51
63
  schema = StringIO.new
52
64
  ActiveRecord::SchemaDumper.dump connection, schema
53
65
  assert schema.string.include?("t.index [\"singerid\", \"albumid\", \"title\"], name: \"index_tracks_on_singerid_and_albumid_and_title\", order: { singerid: :asc, albumid: :asc, title: :asc }, null_filtered: true, interleave_in: \"albums\""), schema.string
54
66
  end
55
67
 
56
68
  def test_dump_schema_should_not_contain_id_limit
57
- connection = ActiveRecord::Base.connection
69
+ connection = pool_or_connection
58
70
  schema = StringIO.new
59
71
  ActiveRecord::SchemaDumper.dump connection, schema
60
72
  assert !schema.string.include?("id: { limit: 8 }")
61
73
  end
62
74
 
63
75
  def test_dump_schema_contains_commit_timestamp
64
- connection = ActiveRecord::Base.connection
76
+ connection = pool_or_connection
65
77
  schema = StringIO.new
66
78
  ActiveRecord::SchemaDumper.dump connection, schema
67
79
  assert schema.string.include?("t.time \"last_updated\", allow_commit_timestamp: true"), schema.string
68
80
  end
69
81
 
70
82
  def test_dump_schema_contains_virtual_column
71
- connection = ActiveRecord::Base.connection
83
+ connection = pool_or_connection
72
84
  schema = StringIO.new
73
85
  ActiveRecord::SchemaDumper.dump connection, schema
74
86
  assert schema.string.include?("t.virtual \"full_name\", type: :string, as: \"COALESCE(first_name || ' ', '') || last_name\", stored: true"), schema.string
75
87
  end
76
88
 
77
89
  def test_dump_schema_contains_string_array
78
- connection = ActiveRecord::Base.connection
90
+ connection = pool_or_connection
79
91
  schema = StringIO.new
80
92
  ActiveRecord::SchemaDumper.dump connection, schema
81
93
  assert schema.string.include?("t.string \"col_array_string\", array: true"), schema.string
82
94
  end
83
95
 
84
96
  def test_dump_schema_index_storing
85
- connection = ActiveRecord::Base.connection
97
+ connection = pool_or_connection
86
98
  schema = StringIO.new
87
99
  ActiveRecord::SchemaDumper.dump connection, schema
88
100
  assert schema.string.include?("t.index [\"last_name\"], name: \"index_singers_on_last_name\", order: { last_name: :asc }, storing: [\"first_name\", \"tracks_count\"]"), schema.string
@@ -0,0 +1,97 @@
1
+ # Copyright 2025 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
+ require "test_helper"
10
+ require "test_helpers/with_separate_database"
11
+ require_relative "../../models/user"
12
+ require_relative "../../models/binary_project"
13
+
14
+ module Models
15
+ class DefaultValueTest < SpannerAdapter::TestCase
16
+ include TestHelpers::WithSeparateDatabase
17
+
18
+ def setup
19
+ super
20
+
21
+ connection.create_table :users, id: :binary do |t|
22
+ t.string :email, null: false
23
+ t.string :full_name, null: false
24
+ end
25
+ connection.create_table :binary_projects, id: :binary do |t|
26
+ t.string :name, null: false
27
+ t.string :description, null: false
28
+ t.binary :owner_id, null: false
29
+ t.foreign_key :users, column: :owner_id
30
+ end
31
+ end
32
+
33
+ def test_includes_works
34
+ user = User.create!(
35
+ email: "test@example.com",
36
+ full_name: "Test User"
37
+ )
38
+ 3.times do |i|
39
+ Project.create!(
40
+ name: "Project #{i}",
41
+ description: "Description #{i}",
42
+ owner: user
43
+ )
44
+ end
45
+
46
+ # First verify the association works without includes
47
+ projects = Project.all
48
+ assert_equal 3, projects.count
49
+
50
+ # Compare the base64 content instead of the StringIO objects
51
+ first_project = projects.first
52
+ assert_equal to_base64(user.id), to_base64(first_project.owner_id)
53
+
54
+ # Now verify includes is working
55
+ query_count = count_queries do
56
+ loaded_projects = Project.includes(:owner).to_a
57
+ loaded_projects.each do |project|
58
+ # Access the owner to ensure it's preloaded
59
+ assert_equal user.full_name, project.owner.full_name
60
+ end
61
+ assert_equal 3, loaded_projects.count
62
+ end
63
+
64
+ # Spanner should execute 2 queries: one for projects and one for users
65
+ assert_equal 2, query_count
66
+
67
+ user = User.all.includes(:projects).first
68
+ assert user
69
+ project_count = 0
70
+ user.projects.each do |_|
71
+ project_count += 1
72
+ end
73
+ assert_equal 3, project_count
74
+ assert_equal 3, user.projects.count
75
+ end
76
+
77
+ private
78
+
79
+ def to_base64 buffer
80
+ buffer.rewind
81
+ value = buffer.read
82
+ Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
83
+ end
84
+
85
+ def count_queries(&block)
86
+ count = 0
87
+ counter_fn = ->(name, started, finished, unique_id, payload) {
88
+ unless %w[CACHE SCHEMA].include?(payload[:name])
89
+ count += 1
90
+ end
91
+ }
92
+
93
+ ActiveSupport::Notifications.subscribed(counter_fn, "sql.active_record", &block)
94
+ count
95
+ end
96
+ end
97
+ end
@@ -30,13 +30,24 @@ module ActiveRecord
30
30
  { id: Author.next_sequence_value, name: "Carol" },
31
31
  ]
32
32
 
33
- assert_raise(NotImplementedError) { Author.insert_all(values) }
33
+ Author.insert_all(values)
34
+
35
+ authors = Author.all.order(:name)
36
+
37
+ assert_equal "Alice", authors[0].name
38
+ assert_equal "Bob", authors[1].name
39
+ assert_equal "Carol", authors[2].name
34
40
  end
35
41
 
36
42
  def test_insert
37
43
  value = { id: Author.next_sequence_value, name: "Alice" }
38
44
 
39
- assert_raise(NotImplementedError) { Author.insert(value) }
45
+ Author.insert(value)
46
+
47
+ authors = Author.all.order(:name)
48
+
49
+ assert_equal 1, authors.length
50
+ assert_equal "Alice", authors[0].name
40
51
  end
41
52
 
42
53
  def test_insert_all!
@@ -136,12 +147,16 @@ module ActiveRecord
136
147
  { id: Author.next_sequence_value, name: "Carol" },
137
148
  ]
138
149
 
139
- err = assert_raise(NotImplementedError) do
140
- ActiveRecord::Base.transaction do
141
- Author.upsert_all(values)
142
- end
150
+ ActiveRecord::Base.transaction do
151
+ Author.upsert_all(values)
143
152
  end
144
- assert_match "Use upsert outside a transaction block", err.message
153
+
154
+ authors = Author.all.order(:name)
155
+
156
+ assert_equal 3, authors.length
157
+ assert_equal "Alice", authors[0].name
158
+ assert_equal "Bob", authors[1].name
159
+ assert_equal "Carol", authors[2].name
145
160
  end
146
161
 
147
162
  def test_upsert_all_with_buffered_mutation_transaction
@@ -51,6 +51,8 @@ module ActiveRecord
51
51
  sessions.each do |session|
52
52
  client.delete_session Google::Cloud::Spanner::V1::DeleteSessionRequest.new name: session.name
53
53
  end
54
+ # Wait a bit to ensure that the sessions are really deleted.
55
+ sleep 5 unless ENV["SPANNER_EMULATOR_HOST"]
54
56
  end
55
57
 
56
58
  def test_single_read
@@ -48,6 +48,7 @@ module ActiveRecord
48
48
  end
49
49
 
50
50
  def teardown
51
+ drop_database
51
52
  ActiveRecord::Base.connection_pool.disconnect!
52
53
  FileUtils.rm_rf ActiveRecord::Tasks::DatabaseTasks.db_dir
53
54
  ActiveRecord::Tasks::DatabaseTasks.db_dir = @original_db_dir
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 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
+ require_relative 'string_io'
10
+
11
+ class BinaryProject < ActiveRecord::Base
12
+ belongs_to :owner, class_name: 'User'
13
+
14
+ before_create :set_uuid
15
+ private
16
+
17
+ def set_uuid
18
+ self.id ||= StringIO.new(SecureRandom.random_bytes(16))
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright 2025 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
+ # Add equality and hash functions to StringIO to use it for sets.
10
+ class StringIO
11
+ def ==(o)
12
+ o.class == self.class && self.to_base64 == o.to_base64
13
+ end
14
+
15
+ def eql?(o)
16
+ self == o
17
+ end
18
+
19
+ def hash
20
+ to_base64.hash
21
+ end
22
+
23
+ def to_base64
24
+ self.rewind
25
+ value = self.read
26
+ Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 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
+ require_relative 'string_io'
10
+
11
+ class User < ActiveRecord::Base
12
+ has_many :binary_projects, foreign_key: :owner_id
13
+
14
+ before_create :set_uuid
15
+ private
16
+
17
+ def set_uuid
18
+ self.id ||= StringIO.new(SecureRandom.random_bytes(16))
19
+ end
20
+ end
@@ -5,9 +5,11 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  gem "minitest"
8
+ require "logger" # https://github.com/rails/rails/issues/54260
8
9
  require "minitest/autorun"
9
10
  require "minitest/focus"
10
11
  require "minitest/rg"
12
+ require "ostruct"
11
13
  require "active_support"
12
14
  require "google/cloud/spanner"
13
15
  require "active_record"
@@ -18,7 +20,10 @@ require "securerandom"
18
20
  require "composite_primary_keys" if ActiveRecord::gem_version < Gem::Version.create('7.1.0')
19
21
 
20
22
  # rubocop:disable Style/GlobalVars
21
-
23
+ #
24
+ if ActiveRecord.gem_version >= Gem::Version.create("7.2.0")
25
+ ActiveRecord::ConnectionAdapters.register("spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter")
26
+ end
22
27
  $spanner_test_database = "ar-test-#{SecureRandom.hex 4}"
23
28
 
24
29
  def connector_config
@@ -22,16 +22,16 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename f }
23
23
  spec.require_paths = ["lib"]
24
24
 
25
- spec.required_ruby_version = ">= 2.7"
25
+ spec.required_ruby_version = ">= 3.1"
26
26
 
27
27
  spec.add_dependency "google-cloud-spanner", "~> 2.18"
28
28
  # Pin gRPC to 1.64.3, as 1.65 and 1.66 cause test runs to hang randomly.
29
29
  spec.add_dependency "grpc", "1.64.3"
30
- spec.add_runtime_dependency "activerecord", [">= 6.0.0", "< 7.2"]
30
+ spec.add_runtime_dependency "activerecord", [">= 6.1", "< 9"]
31
31
 
32
32
  spec.add_development_dependency "autotest-suffix", "~> 1.1"
33
33
  spec.add_development_dependency "bundler", "~> 2.0"
34
- spec.add_development_dependency "google-style", "~> 1.24.0"
34
+ spec.add_development_dependency "google-style", "~> 1.30.1"
35
35
  spec.add_development_dependency "minitest", "~> 5.10"
36
36
  spec.add_development_dependency "minitest-autotest", "~> 1.0"
37
37
  spec.add_development_dependency "minitest-focus", "~> 1.1"
@@ -63,7 +63,7 @@ class Application
63
63
 
64
64
  puts ""
65
65
  puts "Press any key to end the application"
66
- STDIN.getch
66
+ $stdin.getch
67
67
  end
68
68
 
69
69
  def self.execute_individual_benchmarks singer, client
@@ -120,9 +120,7 @@ class Application
120
120
  sql = "SELECT * FROM Singers WHERE id=@id"
121
121
  params = { id: singer[:id] }
122
122
  param_types = { id: :INT64 }
123
- client.execute(sql, params: params, types: param_types).rows.each do |row|
124
- return row
125
- end
123
+ client.execute(sql, params: params, types: param_types).rows.first(1)
126
124
  else
127
125
  Singer.find singer.id
128
126
  end
@@ -134,9 +132,7 @@ class Application
134
132
  sql = "SELECT * FROM Singers WHERE id=@id"
135
133
  params = { id: singer[:id] }
136
134
  param_types = { id: :INT64 }
137
- client.execute(sql, params: params, types: param_types).rows.each do |row|
138
- return row
139
- end
135
+ client.execute(sql, params: params, types: param_types).rows.first(1)
140
136
  else
141
137
  singer.reload
142
138
  end
@@ -7,19 +7,37 @@
7
7
  require_relative "config/environment"
8
8
  require "docker"
9
9
 
10
- desc "Lists all available samples."
11
- task :list do
12
- samples = Dir.entries(".").select do |entry|
10
+ def fetch_samples
11
+ Dir.entries(".").select do |entry|
13
12
  File.directory?(File.join(".", entry)) \
14
- && !%w[. ..].include?(entry) \
15
- && File.exist?(File.join(".", entry, "application.rb"))
13
+ && !%w[. ..].include?(entry) \
14
+ && File.exist?(File.join(".", entry, "application.rb"))
16
15
  end
16
+ end
17
+
18
+ desc "Lists all available samples."
19
+ task :list do
20
+ samples = fetch_samples
17
21
  puts "Available samples: "
18
22
  samples.sort.each { |dir| puts " #{dir}" }
19
23
  puts ""
20
24
  puts "Run a sample with the command `bundle exec rake run\\[<sample-name>\\]`"
21
25
  end
22
26
 
27
+ desc "Runs all samples."
28
+ task :all do
29
+ samples = fetch_samples
30
+ ar_version = ENV.fetch "AR_VERSION", "~> 7.1.0"
31
+ less_than_7_1 = ar_version.dup.to_s.sub("~>", "").strip < "7.1.0"
32
+ samples.delete "interleaved-tables" if less_than_7_1
33
+ samples.delete "interleaved-tables-before-7.1" unless less_than_7_1
34
+ samples.delete "bit-reversed-sequence" if less_than_7_1
35
+ samples.delete "query-logs" if less_than_7_1
36
+ samples.each do |sample|
37
+ run_sample sample
38
+ end
39
+ end
40
+
23
41
  desc "Runs a simple ActiveRecord tutorial on a Spanner emulator."
24
42
  task :run, [:sample] do |_t, args|
25
43
  sample = args[:sample]
@@ -28,7 +46,11 @@ task :run, [:sample] do |_t, args|
28
46
  puts ""
29
47
  sample = "quickstart"
30
48
  end
49
+ run_sample sample
50
+ end
31
51
 
52
+ def run_sample sample
53
+ puts "Running #{sample}"
32
54
  puts "Downloading Spanner emulator image..."
33
55
  Docker::Image.create "fromImage" => "gcr.io/cloud-spanner-emulator/emulator:latest"
34
56
  puts "Creating Spanner emulator container..."
@@ -9,7 +9,7 @@ require_relative "../config/environment"
9
9
  require_relative "models/entity_with_array_types"
10
10
 
11
11
  class Application
12
- def self.run # rubocop:disable Metrics/AbcSize
12
+ def self.run
13
13
  # Create a record with all array types.
14
14
  record = EntityWithArrayTypes.create \
15
15
  col_array_string: ["value1", "value2", "value3"],
@@ -35,10 +35,6 @@ class Application
35
35
  puts "Bytes array: #{record.col_array_bytes.map(&:read)}"
36
36
  puts "Date array: #{record.col_array_date}"
37
37
  puts "Timestamp array: #{record.col_array_timestamp}"
38
-
39
- puts ""
40
- puts "Press any key to end the application"
41
- STDIN.getch
42
38
  end
43
39
  end
44
40
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -22,10 +22,6 @@ class Application
22
22
 
23
23
  # List all singers, albums and tracks.
24
24
  list_singers_albums
25
-
26
- puts ""
27
- puts "Press any key to end the application"
28
- STDIN.getch
29
25
  end
30
26
 
31
27
  def self.find_album singerid, albumid
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -4,7 +4,7 @@
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
- require_relative "../../config/environment.rb"
7
+ require_relative "../../config/environment"
8
8
  require_relative "../models/singer"
9
9
  require_relative "../models/album"
10
10
 
@@ -14,7 +14,7 @@ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Te
14
14
  adjectives = %w[daily happy blue generous cooked bad open]
15
15
  nouns = %w[windows potatoes bank street tree glass bottle]
16
16
 
17
- # Note: We do not use mutations to insert these rows, because letting the database generate the primary key means that
17
+ # NOTE: We do not use mutations to insert these rows, because letting the database generate the primary key means that
18
18
  # we rely on a `THEN RETURN id` clause in the insert statement. This is only supported for DML statements, and not for
19
19
  # mutations.
20
20
  ActiveRecord::Base.transaction do
@@ -46,7 +46,7 @@ class Application
46
46
  ]
47
47
  end
48
48
  puts ""
49
- puts "Created a batch of #{singers.length} singers and #{albums.length} "\
49
+ puts "Created a batch of #{singers.length} singers and #{albums.length} " \
50
50
  "albums using a transaction with buffered mutations:"
51
51
  singers.each do |s|
52
52
  puts " Created singer #{s.first_name} #{s.last_name} with id #{s.id}"
@@ -54,10 +54,6 @@ class Application
54
54
  puts " with album #{a.title} with id #{a.id}"
55
55
  end
56
56
  end
57
-
58
- puts ""
59
- puts "Press any key to end the application"
60
- STDIN.getch
61
57
  end
62
58
  end
63
59
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -43,10 +43,6 @@ class Application
43
43
  puts "Singer and album updated:"
44
44
  puts "#{singer.first_name} #{singer.last_name} (Last updated: #{singer.last_updated.strftime format})"
45
45
  puts " #{album.title} (Last updated: #{album.last_updated.strftime format})"
46
-
47
- puts ""
48
- puts "Press any key to end the application"
49
- STDIN.getch
50
46
  end
51
47
  end
52
48
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -4,11 +4,16 @@
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
+ require "logger" # https://github.com/rails/rails/issues/54260
7
8
  require "active_record"
8
9
  require "bundler"
9
10
 
10
11
  Dir["../../lib/*.rb"].each { |file| require file }
11
12
 
13
+ if ActiveRecord.version >= Gem::Version.create("7.2.0")
14
+ ActiveRecord::ConnectionAdapters.register "spanner", "ActiveRecord::ConnectionAdapters::SpannerAdapter"
15
+ end
16
+
12
17
  Bundler.require
13
18
 
14
19
  ActiveRecord::Base.establish_connection(
@@ -13,7 +13,7 @@ class Application
13
13
  def self.run
14
14
  # Creating a single record without an explicit transaction will automatically save it to the database.
15
15
  # It is not recommended to call Entity.create repeatedly to insert multiple records, as each call will
16
- # use a separate Spanner transaction. Instead multiple records should be created by passing an array of
16
+ # use a separate Spanner transaction. Instead, multiple records should be created by passing an array of
17
17
  # entities to the Entity.create method.
18
18
  singer = Singer.create first_name: "Dave", last_name: "Allison"
19
19
  puts ""
@@ -32,10 +32,6 @@ class Application
32
32
  singers.each do |s|
33
33
  puts " Created singer #{s.first_name} #{s.last_name} with id #{s.id}"
34
34
  end
35
-
36
- puts ""
37
- puts "Press any key to end the application"
38
- STDIN.getch
39
35
  end
40
36
  end
41
37
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -17,7 +17,7 @@ class Application
17
17
  puts "#{"#{singer.first_name} #{singer.last_name}".ljust 30}#{singer.birth_date}"
18
18
  end
19
19
 
20
- # Update the birth date of a random singer using the current system time. Any time and timezone information will be
20
+ # Update the birthdate of a random singer using the current system time. Any time and timezone information will be
21
21
  # lost after saving the record as a DATE only contains the year, month and day-of-month information.
22
22
  singer = Singer.all.sample
23
23
  singer.update birth_date: Time.now
@@ -25,10 +25,6 @@ class Application
25
25
  puts ""
26
26
  puts "Updated birth date to current system time:"
27
27
  puts "#{"#{singer.first_name} #{singer.last_name}".ljust 30}#{singer.birth_date}"
28
-
29
- puts ""
30
- puts "Press any key to end the application"
31
- STDIN.getch
32
28
  end
33
29
  end
34
30
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false
@@ -4,7 +4,7 @@
4
4
  # license that can be found in the LICENSE file or at
5
5
  # https://opensource.org/licenses/MIT.
6
6
  #
7
- require_relative "../../config/environment.rb"
7
+ require_relative "../../config/environment"
8
8
  require_relative "../models/singer"
9
9
 
10
10
  first_names = %w[Nelson Todd William Alex Dominique Adenoid Steve Nathan Beverly Annie Amy Norma Diana Regan Phyllis]
@@ -27,10 +27,6 @@ class Application
27
27
  singer.reload
28
28
  puts ""
29
29
  puts "Singer updated: #{singer.full_name}"
30
-
31
- puts ""
32
- puts "Press any key to end the application"
33
- STDIN.getch
34
30
  end
35
31
  end
36
32
 
@@ -6,3 +6,4 @@ development:
6
6
  database: testdb
7
7
  pool: 5
8
8
  timeout: 5000
9
+ schema_dump: false