activerecord-spanner-adapter 1.6.3 → 2.0.0

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