activerecord-spanner-adapter 0.3.0 → 1.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 (282) 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 +49 -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 +55 -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 +66 -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 +171 -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 +130 -0
  56. data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
  57. data/acceptance/cases/type/all_types_test.rb +172 -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/json_test.rb +34 -0
  65. data/acceptance/cases/type/numeric_test.rb +27 -0
  66. data/acceptance/cases/type/string_test.rb +79 -0
  67. data/acceptance/cases/type/text_test.rb +30 -0
  68. data/acceptance/cases/type/time_test.rb +87 -0
  69. data/acceptance/models/account.rb +13 -0
  70. data/acceptance/models/address.rb +9 -0
  71. data/acceptance/models/album.rb +12 -0
  72. data/acceptance/models/all_types.rb +8 -0
  73. data/acceptance/models/author.rb +11 -0
  74. data/acceptance/models/club.rb +12 -0
  75. data/acceptance/models/comment.rb +9 -0
  76. data/acceptance/models/customer.rb +9 -0
  77. data/acceptance/models/department.rb +9 -0
  78. data/acceptance/models/firm.rb +10 -0
  79. data/acceptance/models/member.rb +13 -0
  80. data/acceptance/models/member_type.rb +9 -0
  81. data/acceptance/models/membership.rb +10 -0
  82. data/acceptance/models/organization.rb +9 -0
  83. data/acceptance/models/post.rb +10 -0
  84. data/acceptance/models/singer.rb +10 -0
  85. data/acceptance/models/track.rb +20 -0
  86. data/acceptance/models/transaction.rb +9 -0
  87. data/acceptance/schema/schema.rb +147 -0
  88. data/acceptance/test_helper.rb +261 -0
  89. data/activerecord-spanner-adapter.gemspec +32 -17
  90. data/assets/solidus-db.png +0 -0
  91. data/benchmarks/README.md +17 -0
  92. data/benchmarks/Rakefile +14 -0
  93. data/benchmarks/application.rb +308 -0
  94. data/benchmarks/config/database.yml +8 -0
  95. data/benchmarks/config/environment.rb +12 -0
  96. data/benchmarks/db/migrate/01_create_tables.rb +25 -0
  97. data/benchmarks/db/schema.rb +29 -0
  98. data/benchmarks/models/album.rb +9 -0
  99. data/benchmarks/models/singer.rb +9 -0
  100. data/bin/console +6 -7
  101. data/examples/rails/README.md +262 -0
  102. data/examples/snippets/README.md +29 -0
  103. data/examples/snippets/Rakefile +57 -0
  104. data/examples/snippets/array-data-type/README.md +45 -0
  105. data/examples/snippets/array-data-type/Rakefile +13 -0
  106. data/examples/snippets/array-data-type/application.rb +45 -0
  107. data/examples/snippets/array-data-type/config/database.yml +8 -0
  108. data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
  109. data/examples/snippets/array-data-type/db/schema.rb +26 -0
  110. data/examples/snippets/array-data-type/db/seeds.rb +5 -0
  111. data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
  112. data/examples/snippets/bin/create_emulator_instance.rb +18 -0
  113. data/examples/snippets/bulk-insert/README.md +21 -0
  114. data/examples/snippets/bulk-insert/Rakefile +13 -0
  115. data/examples/snippets/bulk-insert/application.rb +64 -0
  116. data/examples/snippets/bulk-insert/config/database.yml +8 -0
  117. data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
  118. data/examples/snippets/bulk-insert/db/schema.rb +26 -0
  119. data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
  120. data/examples/snippets/bulk-insert/models/album.rb +9 -0
  121. data/examples/snippets/bulk-insert/models/singer.rb +9 -0
  122. data/examples/snippets/commit-timestamp/README.md +18 -0
  123. data/examples/snippets/commit-timestamp/Rakefile +13 -0
  124. data/examples/snippets/commit-timestamp/application.rb +53 -0
  125. data/examples/snippets/commit-timestamp/config/database.yml +8 -0
  126. data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
  127. data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
  128. data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
  129. data/examples/snippets/commit-timestamp/models/album.rb +9 -0
  130. data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
  131. data/examples/snippets/config/environment.rb +21 -0
  132. data/examples/snippets/create-records/README.md +12 -0
  133. data/examples/snippets/create-records/Rakefile +13 -0
  134. data/examples/snippets/create-records/application.rb +42 -0
  135. data/examples/snippets/create-records/config/database.yml +8 -0
  136. data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
  137. data/examples/snippets/create-records/db/schema.rb +26 -0
  138. data/examples/snippets/create-records/db/seeds.rb +5 -0
  139. data/examples/snippets/create-records/models/album.rb +9 -0
  140. data/examples/snippets/create-records/models/singer.rb +9 -0
  141. data/examples/snippets/date-data-type/README.md +19 -0
  142. data/examples/snippets/date-data-type/Rakefile +13 -0
  143. data/examples/snippets/date-data-type/application.rb +35 -0
  144. data/examples/snippets/date-data-type/config/database.yml +8 -0
  145. data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
  146. data/examples/snippets/date-data-type/db/schema.rb +21 -0
  147. data/examples/snippets/date-data-type/db/seeds.rb +16 -0
  148. data/examples/snippets/date-data-type/models/singer.rb +8 -0
  149. data/examples/snippets/generated-column/README.md +41 -0
  150. data/examples/snippets/generated-column/Rakefile +13 -0
  151. data/examples/snippets/generated-column/application.rb +37 -0
  152. data/examples/snippets/generated-column/config/database.yml +8 -0
  153. data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
  154. data/examples/snippets/generated-column/db/schema.rb +21 -0
  155. data/examples/snippets/generated-column/db/seeds.rb +18 -0
  156. data/examples/snippets/generated-column/models/singer.rb +8 -0
  157. data/examples/snippets/hints/README.md +19 -0
  158. data/examples/snippets/hints/Rakefile +13 -0
  159. data/examples/snippets/hints/application.rb +47 -0
  160. data/examples/snippets/hints/config/database.yml +8 -0
  161. data/examples/snippets/hints/db/migrate/01_create_tables.rb +23 -0
  162. data/examples/snippets/hints/db/schema.rb +28 -0
  163. data/examples/snippets/hints/db/seeds.rb +29 -0
  164. data/examples/snippets/hints/models/album.rb +9 -0
  165. data/examples/snippets/hints/models/singer.rb +9 -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/partitioned-dml/README.md +16 -0
  195. data/examples/snippets/partitioned-dml/Rakefile +13 -0
  196. data/examples/snippets/partitioned-dml/application.rb +48 -0
  197. data/examples/snippets/partitioned-dml/config/database.yml +8 -0
  198. data/examples/snippets/partitioned-dml/db/migrate/01_create_tables.rb +21 -0
  199. data/examples/snippets/partitioned-dml/db/schema.rb +26 -0
  200. data/examples/snippets/partitioned-dml/db/seeds.rb +29 -0
  201. data/examples/snippets/partitioned-dml/models/album.rb +9 -0
  202. data/examples/snippets/partitioned-dml/models/singer.rb +9 -0
  203. data/examples/snippets/quickstart/README.md +26 -0
  204. data/examples/snippets/quickstart/Rakefile +13 -0
  205. data/examples/snippets/quickstart/application.rb +51 -0
  206. data/examples/snippets/quickstart/config/database.yml +8 -0
  207. data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
  208. data/examples/snippets/quickstart/db/schema.rb +26 -0
  209. data/examples/snippets/quickstart/db/seeds.rb +24 -0
  210. data/examples/snippets/quickstart/models/album.rb +9 -0
  211. data/examples/snippets/quickstart/models/singer.rb +9 -0
  212. data/examples/snippets/read-only-transactions/README.md +13 -0
  213. data/examples/snippets/read-only-transactions/Rakefile +13 -0
  214. data/examples/snippets/read-only-transactions/application.rb +77 -0
  215. data/examples/snippets/read-only-transactions/config/database.yml +8 -0
  216. data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
  217. data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
  218. data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
  219. data/examples/snippets/read-only-transactions/models/album.rb +9 -0
  220. data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
  221. data/examples/snippets/read-write-transactions/README.md +12 -0
  222. data/examples/snippets/read-write-transactions/Rakefile +13 -0
  223. data/examples/snippets/read-write-transactions/application.rb +39 -0
  224. data/examples/snippets/read-write-transactions/config/database.yml +8 -0
  225. data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
  226. data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
  227. data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
  228. data/examples/snippets/read-write-transactions/models/album.rb +9 -0
  229. data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
  230. data/examples/snippets/stale-reads/README.md +27 -0
  231. data/examples/snippets/stale-reads/Rakefile +13 -0
  232. data/examples/snippets/stale-reads/application.rb +63 -0
  233. data/examples/snippets/stale-reads/config/database.yml +8 -0
  234. data/examples/snippets/stale-reads/db/migrate/01_create_tables.rb +21 -0
  235. data/examples/snippets/stale-reads/db/schema.rb +26 -0
  236. data/examples/snippets/stale-reads/db/seeds.rb +24 -0
  237. data/examples/snippets/stale-reads/models/album.rb +9 -0
  238. data/examples/snippets/stale-reads/models/singer.rb +9 -0
  239. data/examples/snippets/timestamp-data-type/README.md +17 -0
  240. data/examples/snippets/timestamp-data-type/Rakefile +13 -0
  241. data/examples/snippets/timestamp-data-type/application.rb +42 -0
  242. data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
  243. data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
  244. data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
  245. data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
  246. data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
  247. data/examples/solidus/README.md +172 -0
  248. data/lib/active_record/connection_adapters/spanner/database_statements.rb +244 -266
  249. data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
  250. data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
  251. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
  252. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
  253. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
  254. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
  255. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
  256. data/lib/active_record/connection_adapters/spanner_adapter.rb +185 -78
  257. data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
  258. data/lib/active_record/type/spanner/array.rb +32 -0
  259. data/lib/active_record/type/spanner/bytes.rb +26 -0
  260. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +33 -0
  261. data/lib/active_record/type/spanner/time.rb +37 -0
  262. data/lib/activerecord-spanner-adapter.rb +23 -0
  263. data/lib/activerecord_spanner_adapter/base.rb +238 -0
  264. data/lib/activerecord_spanner_adapter/connection.rb +350 -0
  265. data/lib/activerecord_spanner_adapter/errors.rb +13 -0
  266. data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
  267. data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
  268. data/lib/activerecord_spanner_adapter/index.rb +80 -0
  269. data/lib/activerecord_spanner_adapter/information_schema.rb +262 -0
  270. data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
  271. data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
  272. data/lib/activerecord_spanner_adapter/table.rb +61 -0
  273. data/lib/activerecord_spanner_adapter/transaction.rb +154 -0
  274. data/lib/activerecord_spanner_adapter/version.rb +9 -0
  275. data/lib/arel/visitors/spanner.rb +111 -0
  276. data/lib/spanner_client_ext.rb +107 -0
  277. data/renovate.json +5 -0
  278. metadata +405 -34
  279. data/.travis.yml +0 -5
  280. data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
  281. data/lib/active_record/connection_adapters/spanner.rb +0 -10
  282. data/lib/activerecord-spanner-adapter/version.rb +0 -3
@@ -0,0 +1,248 @@
1
+ # Copyright 2021 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 "models/author"
11
+ require "models/post"
12
+ require "models/comment"
13
+ require "models/organization"
14
+
15
+ module ActiveRecord
16
+ module Transactions
17
+ class ReadWriteTransactionsTest < SpannerAdapter::TestCase
18
+ include SpannerAdapter::Associations::TestHelper
19
+
20
+ attr_accessor :organization, :author, :post, :comment
21
+
22
+ def create_test_records
23
+ @organization = Organization.create name: "Organization 1"
24
+ @author = Author.create name: "David", organization: organization
25
+ @post = Post.create title: "Title - 1", author: author
26
+ @comment = Comment.create comment: "Comment - 1", post: post
27
+ end
28
+
29
+ def teardown
30
+ super
31
+
32
+ delete_test_records
33
+ end
34
+
35
+ def delete_test_records
36
+ Comment.destroy_all
37
+ Post.destroy_all
38
+ Author.destroy_all
39
+ Organization.destroy_all
40
+ end
41
+
42
+ # Runs the given block in a transaction with the given isolation level, or without a transaction if isolation is
43
+ # nil.
44
+ def run_in_transaction isolation
45
+ if isolation
46
+ Base.transaction isolation: isolation do
47
+ yield
48
+ end
49
+ else
50
+ yield
51
+ end
52
+ end
53
+
54
+ def test_create_multiple_records
55
+ [nil, :serializable, :buffered_mutations].each do |isolation|
56
+ initial_author_count = Author.count
57
+ initial_posts_count = Post.count
58
+ initial_comment_count = Comment.count
59
+
60
+ run_in_transaction isolation do
61
+ author = Author.create name: "Author 1", organization: organization
62
+ posts = Post.create [{title: "Post 1", author: author}, {title: "Post 2", author: author}]
63
+ Comment.create [
64
+ {comment: "Comment 1", post: posts[0]},
65
+ {comment: "Comment 2", post: posts[1]}
66
+ ]
67
+ end
68
+
69
+ # Verify that all the records were created.
70
+ assert_equal initial_author_count + 1, Author.count
71
+ assert_equal initial_posts_count + 2, Post.count
72
+ assert_equal initial_comment_count + 2, Comment.count
73
+ end
74
+ end
75
+
76
+ def test_update_multiple_records
77
+ [nil, :serializable, :buffered_mutations].each do |isolation|
78
+ create_test_records
79
+
80
+ run_in_transaction isolation do
81
+ organization.update name: "Updated name #{isolation}"
82
+ author.update name: "Updated name #{isolation}"
83
+ post.update title: "Updated title #{isolation}"
84
+ comment.update comment: "Updated comment #{isolation}"
85
+ end
86
+
87
+ assert_equal "Updated name #{isolation}", organization.reload.name
88
+ assert_equal "Updated name #{isolation}", author.reload.name
89
+ assert_equal "Updated title #{isolation}", post.reload.title
90
+ assert_equal "Updated comment #{isolation}", comment.reload.comment
91
+ end
92
+ end
93
+
94
+ def test_destroy_multiple_records
95
+ [nil, :serializable, :buffered_mutations].each do |isolation|
96
+ create_test_records
97
+
98
+ run_in_transaction isolation do
99
+ comment.destroy
100
+ post.destroy
101
+ author.destroy
102
+ organization.destroy
103
+ end
104
+
105
+ assert_equal 0, Organization.count
106
+ assert_equal 0, Author.count
107
+ assert_equal 0, Post.count
108
+ assert_equal 0, Comment.count
109
+ end
110
+ end
111
+
112
+ def test_delete_multiple_records
113
+ [nil, :serializable, :buffered_mutations].each do |isolation|
114
+ create_test_records
115
+
116
+ run_in_transaction isolation do
117
+ comment.delete
118
+ post.delete
119
+ author.delete
120
+ organization.delete
121
+ end
122
+
123
+ assert_equal 0, Organization.count
124
+ assert_equal 0, Author.count
125
+ assert_equal 0, Post.count
126
+ assert_equal 0, Comment.count
127
+ end
128
+ end
129
+
130
+ def test_destroy_parent_record
131
+ [nil, :serializable, :buffered_mutations].each do |isolation|
132
+ create_test_records
133
+
134
+ run_in_transaction isolation do
135
+ # Only destroy the top-level record. This should cascade to the author records, as those are
136
+ # marked with `dependent: destroy`. The dependants of Author are however not marked with
137
+ # `dependent: destroy`, which means that those will not be deleted, but the reference to Author will
138
+ # be set to nil.
139
+ organization.destroy
140
+ end
141
+
142
+ assert_equal 0, Organization.count
143
+ assert_equal 0, Author.count
144
+ assert_equal 1, Post.count # These are not marked with `dependent: destroy`
145
+ assert_nil Post.find(post.id).author # The author is set to NULL instead of deleting the posts.
146
+ assert_equal 1, Comment.count
147
+
148
+ # Delete all remaining test records to make sure the next iteration starts clean.
149
+ delete_test_records
150
+ end
151
+ end
152
+
153
+ def test_multiple_consecutive_transactions
154
+ isolation_levels = [nil, :serializable, :buffered_mutations]
155
+ isolation_levels.each do |isolation|
156
+
157
+ run_in_transaction isolation do
158
+ create_test_records
159
+ end
160
+
161
+ isolation_levels.each do |isolation|
162
+ run_in_transaction isolation do
163
+ create_test_records
164
+ end
165
+ end
166
+
167
+ transaction_count = isolation_levels.length + 1
168
+ assert_equal transaction_count, Organization.count
169
+ assert_equal transaction_count, Author.count
170
+ assert_equal transaction_count, Post.count
171
+ assert_equal transaction_count, Comment.count
172
+
173
+ delete_test_records
174
+ end
175
+ end
176
+
177
+ def test_read_your_writes
178
+ [nil, :serializable, :buffered_mutations].each do |isolation|
179
+ initial_author_count = Author.count
180
+ initial_posts_count = Post.count
181
+ initial_comment_count = Comment.count
182
+
183
+ run_in_transaction isolation do
184
+ author = Author.create name: "Author 1", organization: organization
185
+ posts = Post.create [{title: "Post 1", author: author}, {title: "Post 2", author: author}]
186
+ Comment.create [
187
+ {comment: "Comment 1", post: posts[0]},
188
+ {comment: "Comment 2", post: posts[1]}
189
+ ]
190
+
191
+ # Verify that the new records are visible, unless we are working with an actual transaction that
192
+ # uses buffered mutations. Implicit transactions (isolation = nil) will also use mutations, but each
193
+ # create call will automatically be committed, and the changes will be visible here.
194
+ unless isolation == :buffered_mutations
195
+ assert_equal initial_author_count + 1, Author.count
196
+ assert_equal initial_posts_count + 2, Post.count
197
+ assert_equal initial_comment_count + 2, Comment.count
198
+ else
199
+ assert_equal initial_author_count, Author.count
200
+ assert_equal initial_posts_count, Post.count
201
+ assert_equal initial_comment_count, Comment.count
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ def test_create_commit_timestamp
208
+ [nil, :serializable, :buffered_mutations].each do |isolation|
209
+ current_timestamp = Organization.connection.select_all("SELECT CURRENT_TIMESTAMP() AS t").to_a[0]["t"]
210
+ organization = nil
211
+ run_in_transaction isolation do
212
+ organization = Organization.create name: "Org with commit timestamp", last_updated: :commit_timestamp
213
+ end
214
+
215
+ organization.reload
216
+ assert organization.last_updated
217
+ assert organization.last_updated > current_timestamp
218
+ end
219
+ end
220
+
221
+ def test_update_commit_timestamp
222
+ [nil, :serializable, :buffered_mutations].each do |isolation|
223
+ organization = Organization.create name: "Org without commit timestamp"
224
+ current_timestamp = Organization.connection.select_all("SELECT CURRENT_TIMESTAMP() AS t").to_a[0]["t"]
225
+
226
+ run_in_transaction isolation do
227
+ organization.update last_updated: :commit_timestamp
228
+ end
229
+
230
+ organization.reload
231
+ assert organization.last_updated
232
+ assert organization.last_updated > current_timestamp
233
+ end
234
+ end
235
+
236
+ def test_pdml
237
+ create_test_records
238
+ assert Comment.count > 0
239
+
240
+ Comment.transaction isolation: :pdml do
241
+ Comment.delete_all
242
+ end
243
+
244
+ assert_equal 0, Comment.count
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,172 @@
1
+ # Copyright 2021 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 "models/all_types"
11
+
12
+ module ActiveRecord
13
+ module Type
14
+ class AllTypesTest < SpannerAdapter::TestCase
15
+ include SpannerAdapter::Associations::TestHelper
16
+
17
+ # Runs the given block in a transaction with the given isolation level, or without a transaction if isolation is
18
+ # nil.
19
+ def run_in_transaction isolation
20
+ if isolation
21
+ Base.transaction isolation: isolation do
22
+ yield
23
+ end
24
+ else
25
+ yield
26
+ end
27
+ end
28
+
29
+ def create_test_record
30
+ AllTypes.create col_string: "string", col_int64: 100, col_float64: 3.14, col_numeric: 6.626, col_bool: true,
31
+ col_bytes: StringIO.new("bytes"), col_date: ::Date.new(2021, 6, 23),
32
+ col_timestamp: ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"),
33
+ col_json: ENV["SPANNER_EMULATOR_HOST"] ? "" : { kind: "user_renamed", change: %w[jack john]},
34
+ col_array_string: ["string1", nil, "string2"],
35
+ col_array_int64: [100, nil, 200],
36
+ col_array_float64: [3.14, nil, 2.0/3.0],
37
+ col_array_numeric: [6.626, nil, 3.20],
38
+ col_array_bool: [true, nil, false],
39
+ col_array_bytes: [StringIO.new("bytes1"), nil, StringIO.new("bytes2")],
40
+ col_array_date: [::Date.new(2021, 6, 23), nil, ::Date.new(2021, 6, 24)],
41
+ col_array_timestamp: [::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), nil, \
42
+ ::Time.new(2021, 6, 24, 17, 8, 21, "+02:00")],
43
+ col_array_json: ENV["SPANNER_EMULATOR_HOST"] ? [""] : \
44
+ [{ kind: "user_renamed", change: %w[jack john]}, nil, \
45
+ { kind: "user_renamed", change: %w[alice meredith]}]
46
+ end
47
+
48
+ def test_create_record
49
+ [nil, :serializable, :buffered_mutations].each do |isolation|
50
+ initial_count = AllTypes.count
51
+ record = nil
52
+ run_in_transaction isolation do
53
+ record = create_test_record
54
+ end
55
+
56
+ # Verify that the record was created and that the data can be read back.
57
+ assert_equal initial_count + 1, AllTypes.count
58
+
59
+ record = AllTypes.find record.id
60
+ assert_equal "string", record.col_string
61
+ assert_equal 100, record.col_int64
62
+ assert_equal 3.14, record.col_float64
63
+ assert_equal 6.626, record.col_numeric
64
+ assert_equal true, record.col_bool
65
+ assert_equal StringIO.new("bytes").read, record.col_bytes.read
66
+ assert_equal ::Date.new(2021, 6, 23), record.col_date
67
+ assert_equal ::Time.new(2021, 6, 23, 17, 8, 21, "+02:00").utc, record.col_timestamp.utc
68
+ assert_equal ({"kind" => "user_renamed", "change" => %w[jack john]}),
69
+ record.col_json unless ENV["SPANNER_EMULATOR_HOST"]
70
+
71
+ assert_equal ["string1", nil, "string2"], record.col_array_string
72
+ assert_equal [100, nil, 200], record.col_array_int64
73
+ assert_equal [3.14, nil, 2.0/3.0], record.col_array_float64
74
+ assert_equal [6.626, nil, 3.20], record.col_array_numeric
75
+ assert_equal [true, nil, false], record.col_array_bool
76
+ assert_equal [StringIO.new("bytes1"), nil, StringIO.new("bytes2")].map { |bytes| bytes&.read },
77
+ record.col_array_bytes.map { |bytes| bytes&.read }
78
+ assert_equal [::Date.new(2021, 6, 23), nil, ::Date.new(2021, 6, 24)], record.col_array_date
79
+ assert_equal [::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), \
80
+ nil, \
81
+ ::Time.new(2021, 6, 24, 17, 8, 21, "+02:00")].map { |timestamp| timestamp&.utc },
82
+ record.col_array_timestamp.map { |timestamp| timestamp&.utc}
83
+ assert_equal [{"kind" => "user_renamed", "change" => %w[jack john]}, \
84
+ nil, \
85
+ {"kind" => "user_renamed", "change" => %w[alice meredith]}],
86
+ record.col_array_json unless ENV["SPANNER_EMULATOR_HOST"]
87
+ end
88
+ end
89
+
90
+ def test_update_record
91
+ [nil, :serializable, :buffered_mutations].each do |isolation|
92
+ # First create a test record outside a transaction.
93
+ record = create_test_record
94
+
95
+ run_in_transaction isolation do
96
+ # Update the record in a transaction using different isolation levels.
97
+ record.update col_string: "new string", col_int64: 200, col_float64: 6.28, col_numeric: 10.1,
98
+ col_bool: false, col_bytes: StringIO.new("new bytes"),
99
+ col_date: ::Date.new(2021, 6, 28),
100
+ col_timestamp: ::Time.new(2021, 6, 28, 11, 22, 21, "+02:00"),
101
+ col_json: ENV["SPANNER_EMULATOR_HOST"] ? "" : { kind: "user_created", change: %w[jack alice]},
102
+ col_array_string: ["new string 1", "new string 2"],
103
+ col_array_int64: [300, 200, 100],
104
+ col_array_float64: [1.1, 2.2, 3.3],
105
+ col_array_numeric: [3.3, 2.2, 1.1],
106
+ col_array_bool: [false, true, false],
107
+ col_array_bytes: [StringIO.new("new bytes 1"), StringIO.new("new bytes 2")],
108
+ col_array_date: [::Date.new(2021, 6, 28)],
109
+ col_array_timestamp: [::Time.utc(2020, 12, 31, 0, 0, 0)],
110
+ col_array_json: ENV["SPANNER_EMULATOR_HOST"] ?
111
+ [""] : \
112
+ [{ kind: "user_created", change: %w[jack alice]}]
113
+ end
114
+
115
+ # Verify that the record was updated.
116
+ record = AllTypes.find record.id
117
+ assert_equal "new string", record.col_string
118
+ assert_equal 200, record.col_int64
119
+ assert_equal 6.28, record.col_float64
120
+ assert_equal 10.1, record.col_numeric
121
+ assert_equal false, record.col_bool
122
+ assert_equal StringIO.new("new bytes").read, record.col_bytes.read
123
+ assert_equal ::Date.new(2021, 6, 28), record.col_date
124
+ assert_equal ::Time.new(2021, 6, 28, 11, 22, 21, "+02:00").utc, record.col_timestamp.utc
125
+ assert_equal ({"kind" => "user_created", "change" => %w[jack alice]}),
126
+ record.col_json unless ENV["SPANNER_EMULATOR_HOST"]
127
+
128
+ assert_equal ["new string 1", "new string 2"], record.col_array_string
129
+ assert_equal [300, 200, 100], record.col_array_int64
130
+ assert_equal [1.1, 2.2, 3.3], record.col_array_float64
131
+ assert_equal [3.3, 2.2, 1.1], record.col_array_numeric
132
+ assert_equal [false, true, false], record.col_array_bool
133
+ assert_equal [StringIO.new("new bytes 1"), StringIO.new("new bytes 2")].map(&:read),
134
+ record.col_array_bytes.map(&:read)
135
+ assert_equal [::Date.new(2021, 6, 28)], record.col_array_date
136
+ assert_equal [::Time.utc(2020, 12, 31, 0, 0, 0)], record.col_array_timestamp.map(&:utc)
137
+ assert_equal [{"kind" => "user_created", "change" => %w[jack alice]}],
138
+ record.col_array_json unless ENV["SPANNER_EMULATOR_HOST"]
139
+ end
140
+ end
141
+
142
+ def test_create_empty_arrays
143
+ [nil, :serializable, :buffered_mutations].each do |isolation|
144
+ record = nil
145
+ run_in_transaction isolation do
146
+ record = AllTypes.create \
147
+ col_array_string: [],
148
+ col_array_int64: [],
149
+ col_array_float64: [],
150
+ col_array_numeric: [],
151
+ col_array_bool: [],
152
+ col_array_bytes: [],
153
+ col_array_date: [],
154
+ col_array_timestamp: [],
155
+ col_array_json: []
156
+ end
157
+
158
+ record = AllTypes.find record.id
159
+ assert_equal [], record.col_array_string
160
+ assert_equal [], record.col_array_int64
161
+ assert_equal [], record.col_array_float64
162
+ assert_equal [], record.col_array_numeric
163
+ assert_equal [], record.col_array_bool
164
+ assert_equal [], record.col_array_bytes
165
+ assert_equal [], record.col_array_date
166
+ assert_equal [], record.col_array_timestamp
167
+ assert_equal [], record.col_array_json
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,59 @@
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
+ module Type
13
+ class BinaryTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "BYTES(MAX)", connection.type_to_sql(:binary)
18
+ assert_equal "BYTES(1024)", connection.type_to_sql(:binary, limit: 1024)
19
+ end
20
+
21
+ def test_set_binary_data_io_in_create
22
+ data = StringIO.new "hello"
23
+
24
+ record = TestTypeModel.create(data: data)
25
+ record.reload
26
+
27
+ assert_equal "hello", record.data.read
28
+ end
29
+
30
+ def test_set_binary_data_byte_string_in_create
31
+ data = StringIO.new "hello1"
32
+
33
+ record = TestTypeModel.create(data: data.read)
34
+ record.reload
35
+
36
+ assert_equal "hello1", record.data.read
37
+ end
38
+
39
+ def test_check_max_limit
40
+ str = "a" * 256
41
+
42
+ assert_raise(ActiveRecord::StatementInvalid) {
43
+ TestTypeModel.create(name: str)
44
+ }
45
+ end
46
+
47
+ def test_set_binary_data_from_file
48
+ Tempfile.create do |f|
49
+ f << "hello 123"
50
+
51
+ record = TestTypeModel.create(file: f)
52
+ record.reload
53
+
54
+ assert_equal "hello 123", record.file.read
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
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
+ module Type
13
+ class BooleanTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "BOOL", connection.type_to_sql(:boolean)
18
+ end
19
+
20
+ def test_set_boolean_value_in_create
21
+ record = TestTypeModel.create(active: true)
22
+ record.reload
23
+ assert_equal true, record.active
24
+
25
+ record = TestTypeModel.create(active: false)
26
+ record.reload
27
+ assert_equal false, record.active
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
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
+ module Type
13
+ class DateTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "DATE", connection.type_to_sql(:date)
18
+ end
19
+
20
+ def test_set_date
21
+ expected_date = ::Date.new 2020, 1, 31
22
+ record = TestTypeModel.new start_date: expected_date
23
+
24
+ assert_equal expected_date, record.start_date
25
+
26
+ record.save!
27
+ record.reload
28
+ assert_equal expected_date, record.start_date
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
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
+ module Type
13
+ class DateTimeTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "TIMESTAMP", connection.type_to_sql(:datetime)
18
+ assert_equal "TIMESTAMP", connection.type_to_sql(:datetime, limit: 128)
19
+ end
20
+
21
+ def test_datetime_seconds_precision_applied_to_timestamp
22
+ expected_time = ::Time.now
23
+ record = TestTypeModel.new start_datetime: expected_time
24
+
25
+ assert_equal expected_time, record.start_datetime
26
+ assert_equal expected_time.usec, record.start_datetime.usec
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
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
+ module Type
13
+ class FloatTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "FLOAT64", connection.type_to_sql(:float)
18
+ end
19
+
20
+ def test_set_float_value_in_create
21
+ record = TestTypeModel.create(weight: 123.32199)
22
+ record.reload
23
+ assert_equal 123.32199, record.weight
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
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
+ module Type
13
+ class IntegerTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "INT64", connection.type_to_sql(:integer)
18
+ assert_equal "INT64", connection.type_to_sql(:primary_key)
19
+ end
20
+
21
+ def test_set_integer_value_in_create
22
+ record = TestTypeModel.create(length: 123)
23
+
24
+ record.reload
25
+ assert_equal 123, record.length
26
+ end
27
+
28
+ def test_casting_models
29
+ type = Type::Integer.new
30
+
31
+ record = TestTypeModel.create(name: "Google")
32
+ assert_nil type.cast(record)
33
+ end
34
+
35
+ def test_values_out_of_range_can_re_assigned
36
+ model = TestTypeModel.new
37
+ model.length = 2147483648
38
+ model.length = 1
39
+
40
+ assert_equal 1, model.length
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright 2021 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
+ module Type
13
+ class DateTest < SpannerAdapter::TestCase
14
+ include SpannerAdapter::Types::TestHelper
15
+
16
+ def test_convert_to_sql_type
17
+ assert_equal "JSON", connection.type_to_sql(:json)
18
+ end
19
+
20
+ def test_set_json
21
+ return if ENV["SPANNER_EMULATOR_HOST"]
22
+
23
+ expected_hash = {"key"=>"value", "array_key"=>%w[value1 value2]}
24
+ record = TestTypeModel.new details: {key: "value", array_key: %w[value1 value2]}
25
+
26
+ assert_equal expected_hash, record.details
27
+
28
+ record.save!
29
+ record.reload
30
+ assert_equal expected_hash, record.details
31
+ end
32
+ end
33
+ end
34
+ end