activerecord-spanner-adapter 0.3.0 → 0.5.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 (264) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/sync-repo-settings.yaml +16 -0
  4. data/.github/workflows/acceptance-tests-on-emulator.yaml +45 -0
  5. data/.github/workflows/acceptance-tests-on-production.yaml +36 -0
  6. data/.github/workflows/ci.yaml +33 -0
  7. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +52 -0
  8. data/.github/workflows/nightly-acceptance-tests-on-production.yaml +35 -0
  9. data/.github/workflows/nightly-unit-tests.yaml +40 -0
  10. data/.github/workflows/release-please-label.yml +25 -0
  11. data/.github/workflows/release-please.yml +39 -0
  12. data/.github/workflows/rubocop.yaml +31 -0
  13. data/.gitignore +67 -5
  14. data/.kokoro/populate-secrets.sh +77 -0
  15. data/.kokoro/release.cfg +33 -0
  16. data/.kokoro/release.sh +15 -0
  17. data/.kokoro/trampoline_v2.sh +489 -0
  18. data/.rubocop.yml +46 -0
  19. data/.toys/release.rb +18 -0
  20. data/.trampolinerc +48 -0
  21. data/.yardopts +11 -0
  22. data/CHANGELOG.md +26 -0
  23. data/CODE_OF_CONDUCT.md +40 -0
  24. data/CONTRIBUTING.md +79 -0
  25. data/Gemfile +9 -4
  26. data/LICENSE +6 -6
  27. data/README.md +67 -30
  28. data/Rakefile +79 -3
  29. data/SECURITY.md +7 -0
  30. data/acceptance/cases/associations/has_many_associations_test.rb +119 -0
  31. data/acceptance/cases/associations/has_many_through_associations_test.rb +63 -0
  32. data/acceptance/cases/associations/has_one_associations_test.rb +79 -0
  33. data/acceptance/cases/associations/has_one_through_associations_test.rb +98 -0
  34. data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +211 -0
  35. data/acceptance/cases/migration/change_schema_test.rb +433 -0
  36. data/acceptance/cases/migration/change_table_test.rb +115 -0
  37. data/acceptance/cases/migration/column_attributes_test.rb +122 -0
  38. data/acceptance/cases/migration/column_positioning_test.rb +48 -0
  39. data/acceptance/cases/migration/columns_test.rb +201 -0
  40. data/acceptance/cases/migration/command_recorder_test.rb +406 -0
  41. data/acceptance/cases/migration/create_join_table_test.rb +216 -0
  42. data/acceptance/cases/migration/ddl_batching_test.rb +80 -0
  43. data/acceptance/cases/migration/foreign_key_test.rb +297 -0
  44. data/acceptance/cases/migration/index_test.rb +211 -0
  45. data/acceptance/cases/migration/references_foreign_key_test.rb +259 -0
  46. data/acceptance/cases/migration/references_index_test.rb +135 -0
  47. data/acceptance/cases/migration/references_statements_test.rb +166 -0
  48. data/acceptance/cases/migration/rename_column_test.rb +96 -0
  49. data/acceptance/cases/models/calculation_query_test.rb +128 -0
  50. data/acceptance/cases/models/generated_column_test.rb +126 -0
  51. data/acceptance/cases/models/mutation_test.rb +122 -0
  52. data/acceptance/cases/models/query_test.rb +147 -0
  53. data/acceptance/cases/sessions/session_not_found_test.rb +121 -0
  54. data/acceptance/cases/transactions/optimistic_locking_test.rb +141 -0
  55. data/acceptance/cases/transactions/read_only_transactions_test.rb +67 -0
  56. data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
  57. data/acceptance/cases/type/all_types_test.rb +152 -0
  58. data/acceptance/cases/type/binary_test.rb +59 -0
  59. data/acceptance/cases/type/boolean_test.rb +31 -0
  60. data/acceptance/cases/type/date_test.rb +32 -0
  61. data/acceptance/cases/type/date_time_test.rb +30 -0
  62. data/acceptance/cases/type/float_test.rb +27 -0
  63. data/acceptance/cases/type/integer_test.rb +44 -0
  64. data/acceptance/cases/type/numeric_test.rb +27 -0
  65. data/acceptance/cases/type/string_test.rb +79 -0
  66. data/acceptance/cases/type/text_test.rb +30 -0
  67. data/acceptance/cases/type/time_test.rb +87 -0
  68. data/acceptance/models/account.rb +13 -0
  69. data/acceptance/models/address.rb +9 -0
  70. data/acceptance/models/album.rb +12 -0
  71. data/acceptance/models/all_types.rb +8 -0
  72. data/acceptance/models/author.rb +11 -0
  73. data/acceptance/models/club.rb +12 -0
  74. data/acceptance/models/comment.rb +9 -0
  75. data/acceptance/models/customer.rb +9 -0
  76. data/acceptance/models/department.rb +9 -0
  77. data/acceptance/models/firm.rb +10 -0
  78. data/acceptance/models/member.rb +13 -0
  79. data/acceptance/models/member_type.rb +9 -0
  80. data/acceptance/models/membership.rb +10 -0
  81. data/acceptance/models/organization.rb +9 -0
  82. data/acceptance/models/post.rb +10 -0
  83. data/acceptance/models/singer.rb +10 -0
  84. data/acceptance/models/track.rb +20 -0
  85. data/acceptance/models/transaction.rb +9 -0
  86. data/acceptance/schema/schema.rb +143 -0
  87. data/acceptance/test_helper.rb +260 -0
  88. data/activerecord-spanner-adapter.gemspec +32 -17
  89. data/assets/solidus-db.png +0 -0
  90. data/benchmarks/README.md +17 -0
  91. data/benchmarks/Rakefile +14 -0
  92. data/benchmarks/application.rb +308 -0
  93. data/benchmarks/config/database.yml +8 -0
  94. data/benchmarks/config/environment.rb +12 -0
  95. data/benchmarks/db/migrate/01_create_tables.rb +25 -0
  96. data/benchmarks/db/schema.rb +29 -0
  97. data/benchmarks/models/album.rb +9 -0
  98. data/benchmarks/models/singer.rb +9 -0
  99. data/bin/console +6 -7
  100. data/examples/rails/README.md +262 -0
  101. data/examples/snippets/README.md +29 -0
  102. data/examples/snippets/Rakefile +57 -0
  103. data/examples/snippets/array-data-type/README.md +45 -0
  104. data/examples/snippets/array-data-type/Rakefile +13 -0
  105. data/examples/snippets/array-data-type/application.rb +45 -0
  106. data/examples/snippets/array-data-type/config/database.yml +8 -0
  107. data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
  108. data/examples/snippets/array-data-type/db/schema.rb +26 -0
  109. data/examples/snippets/array-data-type/db/seeds.rb +5 -0
  110. data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
  111. data/examples/snippets/bin/create_emulator_instance.rb +18 -0
  112. data/examples/snippets/bulk-insert/README.md +21 -0
  113. data/examples/snippets/bulk-insert/Rakefile +13 -0
  114. data/examples/snippets/bulk-insert/application.rb +64 -0
  115. data/examples/snippets/bulk-insert/config/database.yml +8 -0
  116. data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
  117. data/examples/snippets/bulk-insert/db/schema.rb +26 -0
  118. data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
  119. data/examples/snippets/bulk-insert/models/album.rb +9 -0
  120. data/examples/snippets/bulk-insert/models/singer.rb +9 -0
  121. data/examples/snippets/commit-timestamp/README.md +18 -0
  122. data/examples/snippets/commit-timestamp/Rakefile +13 -0
  123. data/examples/snippets/commit-timestamp/application.rb +53 -0
  124. data/examples/snippets/commit-timestamp/config/database.yml +8 -0
  125. data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
  126. data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
  127. data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
  128. data/examples/snippets/commit-timestamp/models/album.rb +9 -0
  129. data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
  130. data/examples/snippets/config/environment.rb +21 -0
  131. data/examples/snippets/create-records/README.md +12 -0
  132. data/examples/snippets/create-records/Rakefile +13 -0
  133. data/examples/snippets/create-records/application.rb +42 -0
  134. data/examples/snippets/create-records/config/database.yml +8 -0
  135. data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
  136. data/examples/snippets/create-records/db/schema.rb +26 -0
  137. data/examples/snippets/create-records/db/seeds.rb +5 -0
  138. data/examples/snippets/create-records/models/album.rb +9 -0
  139. data/examples/snippets/create-records/models/singer.rb +9 -0
  140. data/examples/snippets/date-data-type/README.md +19 -0
  141. data/examples/snippets/date-data-type/Rakefile +13 -0
  142. data/examples/snippets/date-data-type/application.rb +35 -0
  143. data/examples/snippets/date-data-type/config/database.yml +8 -0
  144. data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
  145. data/examples/snippets/date-data-type/db/schema.rb +21 -0
  146. data/examples/snippets/date-data-type/db/seeds.rb +16 -0
  147. data/examples/snippets/date-data-type/models/singer.rb +8 -0
  148. data/examples/snippets/generated-column/README.md +41 -0
  149. data/examples/snippets/generated-column/Rakefile +13 -0
  150. data/examples/snippets/generated-column/application.rb +37 -0
  151. data/examples/snippets/generated-column/config/database.yml +8 -0
  152. data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
  153. data/examples/snippets/generated-column/db/schema.rb +21 -0
  154. data/examples/snippets/generated-column/db/seeds.rb +18 -0
  155. data/examples/snippets/generated-column/models/singer.rb +8 -0
  156. data/examples/snippets/interleaved-tables/README.md +152 -0
  157. data/examples/snippets/interleaved-tables/Rakefile +13 -0
  158. data/examples/snippets/interleaved-tables/application.rb +109 -0
  159. data/examples/snippets/interleaved-tables/config/database.yml +8 -0
  160. data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
  161. data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
  162. data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
  163. data/examples/snippets/interleaved-tables/models/album.rb +15 -0
  164. data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
  165. data/examples/snippets/interleaved-tables/models/track.rb +25 -0
  166. data/examples/snippets/migrations/README.md +43 -0
  167. data/examples/snippets/migrations/Rakefile +13 -0
  168. data/examples/snippets/migrations/application.rb +26 -0
  169. data/examples/snippets/migrations/config/database.yml +8 -0
  170. data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
  171. data/examples/snippets/migrations/db/schema.rb +33 -0
  172. data/examples/snippets/migrations/db/seeds.rb +5 -0
  173. data/examples/snippets/migrations/models/album.rb +10 -0
  174. data/examples/snippets/migrations/models/singer.rb +10 -0
  175. data/examples/snippets/migrations/models/track.rb +9 -0
  176. data/examples/snippets/mutations/README.md +34 -0
  177. data/examples/snippets/mutations/Rakefile +13 -0
  178. data/examples/snippets/mutations/application.rb +47 -0
  179. data/examples/snippets/mutations/config/database.yml +8 -0
  180. data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
  181. data/examples/snippets/mutations/db/schema.rb +27 -0
  182. data/examples/snippets/mutations/db/seeds.rb +25 -0
  183. data/examples/snippets/mutations/models/album.rb +9 -0
  184. data/examples/snippets/mutations/models/singer.rb +9 -0
  185. data/examples/snippets/optimistic-locking/README.md +12 -0
  186. data/examples/snippets/optimistic-locking/Rakefile +13 -0
  187. data/examples/snippets/optimistic-locking/application.rb +48 -0
  188. data/examples/snippets/optimistic-locking/config/database.yml +8 -0
  189. data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
  190. data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
  191. data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
  192. data/examples/snippets/optimistic-locking/models/album.rb +9 -0
  193. data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
  194. data/examples/snippets/quickstart/README.md +26 -0
  195. data/examples/snippets/quickstart/Rakefile +13 -0
  196. data/examples/snippets/quickstart/application.rb +51 -0
  197. data/examples/snippets/quickstart/config/database.yml +8 -0
  198. data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
  199. data/examples/snippets/quickstart/db/schema.rb +26 -0
  200. data/examples/snippets/quickstart/db/seeds.rb +24 -0
  201. data/examples/snippets/quickstart/models/album.rb +9 -0
  202. data/examples/snippets/quickstart/models/singer.rb +9 -0
  203. data/examples/snippets/read-only-transactions/README.md +13 -0
  204. data/examples/snippets/read-only-transactions/Rakefile +13 -0
  205. data/examples/snippets/read-only-transactions/application.rb +49 -0
  206. data/examples/snippets/read-only-transactions/config/database.yml +8 -0
  207. data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
  208. data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
  209. data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
  210. data/examples/snippets/read-only-transactions/models/album.rb +9 -0
  211. data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
  212. data/examples/snippets/read-write-transactions/README.md +12 -0
  213. data/examples/snippets/read-write-transactions/Rakefile +13 -0
  214. data/examples/snippets/read-write-transactions/application.rb +39 -0
  215. data/examples/snippets/read-write-transactions/config/database.yml +8 -0
  216. data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
  217. data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
  218. data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
  219. data/examples/snippets/read-write-transactions/models/album.rb +9 -0
  220. data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
  221. data/examples/snippets/timestamp-data-type/README.md +17 -0
  222. data/examples/snippets/timestamp-data-type/Rakefile +13 -0
  223. data/examples/snippets/timestamp-data-type/application.rb +42 -0
  224. data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
  225. data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
  226. data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
  227. data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
  228. data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
  229. data/examples/solidus/README.md +172 -0
  230. data/lib/active_record/connection_adapters/spanner/database_statements.rb +224 -269
  231. data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
  232. data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
  233. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
  234. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
  235. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
  236. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
  237. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
  238. data/lib/active_record/connection_adapters/spanner_adapter.rb +182 -78
  239. data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
  240. data/lib/active_record/type/spanner/array.rb +32 -0
  241. data/lib/active_record/type/spanner/bytes.rb +26 -0
  242. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +32 -0
  243. data/lib/active_record/type/spanner/time.rb +37 -0
  244. data/lib/activerecord-spanner-adapter.rb +23 -0
  245. data/lib/activerecord_spanner_adapter/base.rb +217 -0
  246. data/lib/activerecord_spanner_adapter/connection.rb +324 -0
  247. data/lib/activerecord_spanner_adapter/errors.rb +13 -0
  248. data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
  249. data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
  250. data/lib/activerecord_spanner_adapter/index.rb +80 -0
  251. data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
  252. data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
  253. data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
  254. data/lib/activerecord_spanner_adapter/table.rb +61 -0
  255. data/lib/activerecord_spanner_adapter/transaction.rb +113 -0
  256. data/lib/activerecord_spanner_adapter/version.rb +9 -0
  257. data/lib/arel/visitors/spanner.rb +35 -0
  258. data/lib/spanner_client_ext.rb +82 -0
  259. data/renovate.json +5 -0
  260. metadata +387 -34
  261. data/.travis.yml +0 -5
  262. data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
  263. data/lib/active_record/connection_adapters/spanner.rb +0 -10
  264. data/lib/activerecord-spanner-adapter/version.rb +0 -3
@@ -0,0 +1,80 @@
1
+ # Copyright 2020 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
+
11
+ module ActiveRecord
12
+ class Migration
13
+ class DDLBatchingTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Migration::TestHelper
15
+ include ActiveSupport::Testing::Stream
16
+
17
+ class Box < ActiveRecord::Base
18
+ end
19
+
20
+ class CreateBoxMigration < ActiveRecord::Migration::Current
21
+ def change
22
+ connection.ddl_batch do
23
+ create_table("boxes") do |t|
24
+ t.string :name
25
+ end
26
+
27
+ add_column :boxes, :length, :integer
28
+ end
29
+
30
+ Box.create(name: "Box1", length: 10)
31
+ end
32
+ end
33
+
34
+ def setup
35
+ skip_test_table_create!
36
+
37
+ super
38
+ end
39
+
40
+ def teardown
41
+ super
42
+
43
+ connection.ddl_batch do
44
+ [:boxes, :ddl_batch_test].each do |name|
45
+ if connection.table_exists?(name)
46
+ connection.drop_table name
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ def test_ddl_batching
53
+ information_schema = connection.send :information_schema
54
+
55
+ connection.ddl_batch do
56
+ connection.create_table("ddl_batch_test") do |t|
57
+ t.string :name
58
+ end
59
+ connection.add_column :ddl_batch_test, :created_at, :time
60
+
61
+ assert_not information_schema.table(:ddl_batch_test)
62
+ end
63
+
64
+ assert information_schema.table(:ddl_batch_test)
65
+ end
66
+
67
+ def test_ddl_batching_with_dml_statement
68
+ migration = CreateBoxMigration.new
69
+ silence_stream($stdout) { migration.migrate(:up) }
70
+
71
+ assert connection.table_exists?(:boxes)
72
+ assert connection.column_exists?(:boxes, :length, :integer)
73
+
74
+ assert_equal 1, Box.count
75
+ box = Box.first
76
+ assert_equal 10, box.length
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,297 @@
1
+ # Copyright 2020 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
+
11
+ module ActiveRecord
12
+ class Migration
13
+ class ForeignKeyTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Migration::TestHelper
15
+ include ActiveSupport::Testing::Stream
16
+
17
+ class Rocket < ActiveRecord::Base
18
+ end
19
+
20
+ class Astronaut < ActiveRecord::Base
21
+ end
22
+
23
+ def setup
24
+ skip_test_table_create!
25
+ super
26
+
27
+ connection.ddl_batch do
28
+ connection.create_table "rockets", force: true do |t|
29
+ t.string :name
30
+ end
31
+
32
+ connection.create_table "astronauts", force: true do |t|
33
+ t.string :name
34
+ t.references :rocket
35
+ end
36
+ end
37
+ end
38
+
39
+ def teardown
40
+ connection.ddl_batch do
41
+ connection.drop_table "astronauts", if_exists: true
42
+ connection.drop_table "rockets", if_exists: true
43
+ connection.drop_table "fk_test_has_fk", if_exists: true
44
+ connection.drop_table "fk_test_has_pk", if_exists: true
45
+ end
46
+ end
47
+
48
+ def test_foreign_keys
49
+ connection.ddl_batch do
50
+ connection.create_table :fk_test_has_pk, primary_key: "pk_id", force: :cascade do |t|
51
+ end
52
+
53
+ connection.create_table :fk_test_has_fk, force: true do |t|
54
+ t.references :fk, null: false
55
+ t.foreign_key :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
56
+ end
57
+ end
58
+
59
+ foreign_keys = connection.foreign_keys("fk_test_has_fk")
60
+ assert_equal 1, foreign_keys.size
61
+
62
+ fk = foreign_keys.first
63
+ assert_equal "fk_test_has_fk", fk.from_table
64
+ assert_equal "fk_test_has_pk", fk.to_table
65
+ assert_equal "fk_id", fk.column
66
+ assert_equal "pk_id", fk.primary_key
67
+ end
68
+
69
+ def test_add_foreign_key_inferes_column
70
+ connection.ddl_batch do
71
+ connection.add_foreign_key :astronauts, :rockets
72
+ end
73
+
74
+ foreign_keys = connection.foreign_keys("astronauts")
75
+ assert_equal 1, foreign_keys.size
76
+
77
+ fk = foreign_keys.first
78
+ assert_equal "astronauts", fk.from_table
79
+ assert_equal "rockets", fk.to_table
80
+ assert_equal "rocket_id", fk.column
81
+ assert_equal "id", fk.primary_key
82
+ end
83
+
84
+ def test_add_foreign_key_with_column
85
+ connection.ddl_batch do
86
+ connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
87
+ end
88
+
89
+ foreign_keys = connection.foreign_keys("astronauts")
90
+ assert_equal 1, foreign_keys.size
91
+
92
+ fk = foreign_keys.first
93
+ assert_equal "astronauts", fk.from_table
94
+ assert_equal "rockets", fk.to_table
95
+ assert_equal "rocket_id", fk.column
96
+ assert_equal "id", fk.primary_key
97
+ end
98
+
99
+ def test_add_foreign_key_with_non_standard_primary_key
100
+ connection.ddl_batch do
101
+ connection.create_table :space_shuttles, id: false, force: true do |t|
102
+ t.integer :pk, primary_key: true
103
+ end
104
+
105
+ connection.add_foreign_key(:astronauts, :space_shuttles,
106
+ column: "rocket_id", primary_key: "pk", name: "custom_pk")
107
+ end
108
+
109
+ foreign_keys = connection.foreign_keys("astronauts")
110
+ assert_equal 1, foreign_keys.size
111
+
112
+ fk = foreign_keys.first
113
+ assert_equal "astronauts", fk.from_table
114
+ assert_equal "space_shuttles", fk.to_table
115
+ assert_equal "pk", fk.primary_key
116
+ ensure
117
+ connection.ddl_batch do
118
+ connection.remove_foreign_key :astronauts, name: "custom_pk", to_table: "space_shuttles"
119
+ connection.drop_table :space_shuttles
120
+ end
121
+ end
122
+
123
+ def test_foreign_key_exists
124
+ connection.ddl_batch do
125
+ connection.add_foreign_key :astronauts, :rockets
126
+ end
127
+
128
+ assert connection.foreign_key_exists?(:astronauts, :rockets)
129
+ assert_not connection.foreign_key_exists?(:astronauts, :stars)
130
+ end
131
+
132
+ def test_foreign_key_exists_by_column
133
+ connection.ddl_batch do
134
+ connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
135
+ end
136
+
137
+ assert connection.foreign_key_exists?(:astronauts, column: "rocket_id")
138
+ assert_not connection.foreign_key_exists?(:astronauts, column: "star_id")
139
+ end
140
+
141
+ def test_foreign_key_exists_by_name
142
+ connection.ddl_batch do
143
+ connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
144
+ end
145
+
146
+ assert connection.foreign_key_exists?(:astronauts, name: "fancy_named_fk")
147
+ assert_not connection.foreign_key_exists?(:astronauts, name: "other_fancy_named_fk")
148
+ end
149
+
150
+ def test_foreign_key_exists_in_change_table
151
+ connection.change_table(:astronauts) do |t|
152
+ t.foreign_key :rockets, column: "rocket_id", name: "fancy_named_fk"
153
+
154
+ assert t.foreign_key_exists?(column: "rocket_id")
155
+ assert_not t.foreign_key_exists?(column: "star_id")
156
+ end
157
+ end
158
+
159
+ def test_remove_foreign_key_inferes_column
160
+ connection.ddl_batch do
161
+ connection.add_foreign_key :astronauts, :rockets
162
+ end
163
+
164
+ assert_equal 1, connection.foreign_keys("astronauts").size
165
+ connection.ddl_batch do
166
+ @connection.remove_foreign_key :astronauts, :rockets
167
+ end
168
+ assert_equal [], connection.foreign_keys("astronauts")
169
+ end
170
+
171
+ def test_remove_foreign_key_by_column
172
+ connection.ddl_batch do
173
+ connection.add_foreign_key :astronauts, :rockets, column: "rocket_id"
174
+ end
175
+
176
+ assert_equal 1, connection.foreign_keys("astronauts").size
177
+ connection.ddl_batch do
178
+ @connection.remove_foreign_key :astronauts, column: "rocket_id"
179
+ end
180
+ assert_equal [], connection.foreign_keys("astronauts")
181
+ end
182
+
183
+ def test_remove_foreign_key_by_symbol_column
184
+ connection.ddl_batch do
185
+ connection.add_foreign_key :astronauts, :rockets, column: :rocket_id
186
+ end
187
+
188
+ assert_equal 1, connection.foreign_keys("astronauts").size
189
+ connection.ddl_batch do
190
+ connection.remove_foreign_key :astronauts, column: :rocket_id
191
+ end
192
+ assert_equal [], connection.foreign_keys("astronauts")
193
+ end
194
+
195
+ def test_remove_foreign_key_by_name
196
+ connection.ddl_batch do
197
+ connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk"
198
+ end
199
+
200
+ assert_equal 1, connection.foreign_keys("astronauts").size
201
+ connection.ddl_batch do
202
+ connection.remove_foreign_key :astronauts, name: "fancy_named_fk"
203
+ end
204
+ assert_equal [], connection.foreign_keys("astronauts")
205
+ end
206
+
207
+ def test_remove_foreign_non_existing_foreign_key_raises
208
+ e = assert_raises ArgumentError do
209
+ connection.remove_foreign_key :astronauts, :rockets
210
+ end
211
+ assert_equal "Table 'astronauts' has no foreign key for rockets", e.message
212
+ end
213
+
214
+ def test_remove_foreign_key_by_the_select_one_on_the_same_table
215
+ connection.ddl_batch do
216
+ connection.add_foreign_key :astronauts, :rockets
217
+ connection.add_reference :astronauts, :myrocket, foreign_key: { to_table: :rockets }
218
+ end
219
+
220
+ assert_equal 2, connection.foreign_keys("astronauts").size
221
+
222
+ connection.ddl_batch do
223
+ connection.remove_foreign_key :astronauts, :rockets, column: "myrocket_id"
224
+ end
225
+
226
+ assert_equal [["astronauts", "rockets", "rocket_id"]],
227
+ connection.foreign_keys("astronauts").map { |fk| [fk.from_table, fk.to_table, fk.column] }
228
+ end
229
+
230
+ class CreateCitiesAndHousesMigration < ActiveRecord::Migration::Current
231
+ def change
232
+ connection.ddl_batch do
233
+ create_table("cities") { |t| }
234
+
235
+ create_table("houses") do |t|
236
+ t.references :city
237
+ end
238
+ add_foreign_key :houses, :cities, column: "city_id"
239
+ end
240
+
241
+ # remove and re-add to test that schema is updated and not accidentally cached
242
+ remove_foreign_key :houses, :cities
243
+ add_foreign_key :houses, :cities, column: "city_id"
244
+ end
245
+ end
246
+
247
+ def test_add_foreign_key_is_reversible
248
+ migration = CreateCitiesAndHousesMigration.new
249
+ silence_stream($stdout) { migration.migrate(:up) }
250
+ assert_equal 1, connection.foreign_keys("houses").size
251
+ ensure
252
+ silence_stream($stdout) { migration.migrate(:down) }
253
+ end
254
+
255
+ class CreateSchoolsAndClassesMigration < ActiveRecord::Migration::Current
256
+ def up
257
+ connection.ddl_batch do
258
+ create_table(:schools)
259
+
260
+ create_table(:classes) do |t|
261
+ t.references :school
262
+ end
263
+ add_foreign_key :classes, :schools
264
+ end
265
+ end
266
+
267
+ def down
268
+ connection.ddl_batch do
269
+ drop_table :classes, if_exists: true
270
+ drop_table :schools, if_exists: true
271
+ end
272
+ end
273
+ end
274
+
275
+ def test_add_foreign_key_with_prefix
276
+ ActiveRecord::Base.table_name_prefix = "p_"
277
+ migration = CreateSchoolsAndClassesMigration.new
278
+ silence_stream($stdout) { migration.migrate(:up) }
279
+ assert_equal 1, connection.foreign_keys("p_classes").size
280
+ ensure
281
+ silence_stream($stdout) { migration.migrate(:down) }
282
+ ActiveRecord::Base.table_name_prefix = nil
283
+ end
284
+
285
+ def test_add_foreign_key_with_suffix
286
+ ActiveRecord::Base.table_name_suffix = "_s"
287
+ migration = CreateSchoolsAndClassesMigration.new
288
+ silence_stream($stdout) { migration.migrate(:up) }
289
+ assert_equal 1, connection.foreign_keys("classes_s").size
290
+ ensure
291
+ silence_stream($stdout) { migration.migrate(:down) }
292
+ ActiveRecord::Base.table_name_suffix = nil
293
+ end
294
+ end
295
+ end
296
+ end
297
+
@@ -0,0 +1,211 @@
1
+ # Copyright 2020 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
+
11
+ module ActiveRecord
12
+ class Migration
13
+ class IndexTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Migration::TestHelper
15
+
16
+ attr_reader :table_name
17
+
18
+ def setup
19
+ skip_test_table_create!
20
+ super
21
+
22
+ @table_name = :testings
23
+
24
+ connection.ddl_batch do
25
+ connection.create_table table_name do |t|
26
+ t.column :foo, :string, limit: 100
27
+ t.column :bar, :string, limit: 100
28
+
29
+ t.string :first_name
30
+ t.string :last_name, limit: 100
31
+ t.string :key, limit: 100
32
+ t.boolean :administrator
33
+ end
34
+ end
35
+ end
36
+
37
+ def teardown
38
+ connection.ddl_batch do
39
+ connection.drop_table :testings
40
+ end rescue nil
41
+ ActiveRecord::Base.primary_key_prefix_type = nil
42
+ end
43
+
44
+ def test_rename_index
45
+ connection.add_index(table_name, [:foo], name: "old_idx")
46
+ connection.ddl_batch do
47
+ connection.rename_index(table_name, "old_idx", "new_idx")
48
+ end
49
+
50
+ assert_not connection.index_name_exists?(table_name, "old_idx")
51
+ assert connection.index_name_exists?(table_name, "new_idx")
52
+ end
53
+
54
+ def test_rename_index_too_long
55
+ too_long_index_name = good_index_name + "x"
56
+ connection.add_index(table_name, [:foo], name: "old_idx")
57
+ e = assert_raises(ArgumentError) {
58
+ connection.rename_index(table_name, "old_idx", too_long_index_name)
59
+ }
60
+ assert_match(/too long; the limit is #{connection.index_name_length} characters/, e.message)
61
+
62
+ assert connection.index_name_exists?(table_name, "old_idx")
63
+ end
64
+
65
+ def test_remove_nonexistent_index
66
+ assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
67
+ end
68
+
69
+ def test_add_index_works_with_long_index_names
70
+ connection.add_index(table_name, "foo", name: good_index_name)
71
+
72
+ assert connection.index_name_exists?(table_name, good_index_name)
73
+ connection.remove_index(table_name, name: good_index_name)
74
+ end
75
+
76
+ def test_add_index_does_not_accept_too_long_index_names
77
+ too_long_index_name = good_index_name + "x"
78
+
79
+ e = assert_raises(ArgumentError) {
80
+ connection.add_index(table_name, "foo", name: too_long_index_name)
81
+ }
82
+ assert_match(/too long; the limit is #{connection.index_name_length} characters/, e.message)
83
+
84
+ assert_not connection.index_name_exists?(table_name, too_long_index_name)
85
+ connection.add_index(table_name, "foo", name: good_index_name)
86
+ end
87
+
88
+ def test_internal_index_with_name_matching_database_limit
89
+ good_index_name = "x" * connection.index_name_length
90
+ connection.add_index(table_name, "foo", name: good_index_name)
91
+
92
+ assert connection.index_name_exists?(table_name, good_index_name)
93
+ connection.remove_index(table_name, name: good_index_name)
94
+ end
95
+
96
+ def test_index_symbol_names
97
+ connection.add_index table_name, :foo, name: :symbol_index_name
98
+ assert connection.index_exists?(table_name, :foo, name: :symbol_index_name)
99
+
100
+ connection.remove_index table_name, name: :symbol_index_name
101
+ assert_not connection.index_exists?(table_name, :foo, name: :symbol_index_name)
102
+ end
103
+
104
+ def test_index_exists
105
+ connection.add_index :testings, :foo
106
+
107
+ assert connection.index_exists?(:testings, :foo)
108
+ assert_not connection.index_exists?(:testings, :bar)
109
+ end
110
+
111
+ def test_index_exists_on_multiple_columns
112
+ connection.add_index :testings, [:foo, :bar]
113
+
114
+ assert connection.index_exists?(:testings, [:foo, :bar])
115
+ end
116
+
117
+ def test_index_exists_with_custom_name_checks_columns
118
+ connection.add_index :testings, [:foo, :bar], name: "my_index"
119
+ assert connection.index_exists?(:testings, [:foo, :bar], name: "my_index")
120
+ assert_not connection.index_exists?(:testings, [:foo], name: "my_index")
121
+ end
122
+
123
+ def test_valid_index_options
124
+ assert_raise ArgumentError do
125
+ connection.add_index :testings, :foo, unqiue: true
126
+ end
127
+ end
128
+
129
+ def test_unique_index_exists
130
+ connection.add_index :testings, :foo, unique: true
131
+
132
+ assert connection.index_exists?(:testings, :foo, unique: true)
133
+ end
134
+
135
+ def test_order_index_exists
136
+ connection.add_index :testings, :foo, order: { foo: :desc }
137
+
138
+ assert connection.index_exists?(:testings, :foo)
139
+
140
+ index = connection.indexes(:testings).first
141
+ assert_equal({ foo: :desc }, index.orders)
142
+ end
143
+
144
+ def test_named_index_exists
145
+ connection.add_index :testings, :foo, name: "custom_index_name"
146
+
147
+ assert connection.index_exists?(:testings, :foo)
148
+ assert connection.index_exists?(:testings, :foo, name: "custom_index_name")
149
+ assert_not connection.index_exists?(:testings, :foo, name: "other_index_name")
150
+ end
151
+
152
+ def test_remove_named_index
153
+ connection.add_index :testings, :foo, name: "index_testings_on_custom_index_name"
154
+
155
+ assert connection.index_exists?(:testings, :foo)
156
+
157
+ assert_raise(ArgumentError) { connection.remove_index(:testings, "custom_index_name") }
158
+
159
+ connection.remove_index :testings, :foo
160
+ assert_not connection.index_exists?(:testings, :foo)
161
+ end
162
+
163
+ def test_add_index
164
+ connection.add_index("testings", "last_name")
165
+ connection.remove_index("testings", "last_name")
166
+
167
+ connection.add_index("testings", ["last_name", "first_name"])
168
+ connection.remove_index("testings", column: ["last_name", "first_name"])
169
+
170
+ connection.add_index("testings", ["last_name", "first_name"])
171
+ connection.remove_index("testings", name: :index_testings_on_last_name_and_first_name)
172
+ connection.add_index("testings", ["last_name", "first_name"])
173
+ connection.remove_index("testings", "last_name_and_first_name")
174
+
175
+ connection.add_index("testings", ["last_name", "first_name"])
176
+ connection.remove_index("testings", ["last_name", "first_name"])
177
+
178
+ connection.add_index("testings", "key", unique: true)
179
+ connection.remove_index("testings", "key")
180
+
181
+ connection.add_index("testings", ["key"], name: "key_idx", unique: true)
182
+ connection.remove_index("testings", name: "key_idx")
183
+
184
+ connection.add_index("testings", %w(last_name first_name administrator), name: "named_admin")
185
+ connection.remove_index("testings", name: "named_admin")
186
+
187
+ connection.add_index("testings", ["last_name"], order: { last_name: :desc })
188
+ connection.remove_index("testings", ["last_name"])
189
+ connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc })
190
+ connection.remove_index("testings", ["last_name", "first_name"])
191
+ connection.add_index("testings", ["last_name", "first_name"], order: { last_name: :desc, first_name: :asc })
192
+ connection.remove_index("testings", ["last_name", "first_name"])
193
+ connection.add_index("testings", ["last_name", "first_name"], order: :desc)
194
+ connection.remove_index("testings", ["last_name", "first_name"])
195
+ end
196
+
197
+ def test_add_interleaved_index
198
+ connection.add_index "albums", ["singerid", "title"], interleave_in: :singers
199
+ index = connection.indexes(:albums).first
200
+
201
+ assert index, "Could not find interleaved index"
202
+ assert_equal "singers", index.interleave_in
203
+ end
204
+
205
+ private
206
+ def good_index_name
207
+ "x" * connection.index_name_length
208
+ end
209
+ end
210
+ end
211
+ end