activerecord-spanner-adapter 0.1.0 → 0.7.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 (292) 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 +42 -0
  23. data/CODE_OF_CONDUCT.md +40 -0
  24. data/CONTRIBUTING.md +79 -0
  25. data/Gemfile +9 -5
  26. data/LICENSE +6 -6
  27. data/README.md +67 -30
  28. data/Rakefile +74 -2
  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/interleaved-tables/README.md +152 -0
  167. data/examples/snippets/interleaved-tables/Rakefile +13 -0
  168. data/examples/snippets/interleaved-tables/application.rb +109 -0
  169. data/examples/snippets/interleaved-tables/config/database.yml +8 -0
  170. data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
  171. data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
  172. data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
  173. data/examples/snippets/interleaved-tables/models/album.rb +15 -0
  174. data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
  175. data/examples/snippets/interleaved-tables/models/track.rb +25 -0
  176. data/examples/snippets/migrations/README.md +43 -0
  177. data/examples/snippets/migrations/Rakefile +13 -0
  178. data/examples/snippets/migrations/application.rb +26 -0
  179. data/examples/snippets/migrations/config/database.yml +8 -0
  180. data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
  181. data/examples/snippets/migrations/db/schema.rb +33 -0
  182. data/examples/snippets/migrations/db/seeds.rb +5 -0
  183. data/examples/snippets/migrations/models/album.rb +10 -0
  184. data/examples/snippets/migrations/models/singer.rb +10 -0
  185. data/examples/snippets/migrations/models/track.rb +9 -0
  186. data/examples/snippets/mutations/README.md +34 -0
  187. data/examples/snippets/mutations/Rakefile +13 -0
  188. data/examples/snippets/mutations/application.rb +47 -0
  189. data/examples/snippets/mutations/config/database.yml +8 -0
  190. data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
  191. data/examples/snippets/mutations/db/schema.rb +27 -0
  192. data/examples/snippets/mutations/db/seeds.rb +25 -0
  193. data/examples/snippets/mutations/models/album.rb +9 -0
  194. data/examples/snippets/mutations/models/singer.rb +9 -0
  195. data/examples/snippets/optimistic-locking/README.md +12 -0
  196. data/examples/snippets/optimistic-locking/Rakefile +13 -0
  197. data/examples/snippets/optimistic-locking/application.rb +48 -0
  198. data/examples/snippets/optimistic-locking/config/database.yml +8 -0
  199. data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
  200. data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
  201. data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
  202. data/examples/snippets/optimistic-locking/models/album.rb +9 -0
  203. data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
  204. data/examples/snippets/partitioned-dml/README.md +16 -0
  205. data/examples/snippets/partitioned-dml/Rakefile +13 -0
  206. data/examples/snippets/partitioned-dml/application.rb +48 -0
  207. data/examples/snippets/partitioned-dml/config/database.yml +8 -0
  208. data/examples/snippets/partitioned-dml/db/migrate/01_create_tables.rb +21 -0
  209. data/examples/snippets/partitioned-dml/db/schema.rb +26 -0
  210. data/examples/snippets/partitioned-dml/db/seeds.rb +29 -0
  211. data/examples/snippets/partitioned-dml/models/album.rb +9 -0
  212. data/examples/snippets/partitioned-dml/models/singer.rb +9 -0
  213. data/examples/snippets/quickstart/README.md +26 -0
  214. data/examples/snippets/quickstart/Rakefile +13 -0
  215. data/examples/snippets/quickstart/application.rb +51 -0
  216. data/examples/snippets/quickstart/config/database.yml +8 -0
  217. data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
  218. data/examples/snippets/quickstart/db/schema.rb +26 -0
  219. data/examples/snippets/quickstart/db/seeds.rb +24 -0
  220. data/examples/snippets/quickstart/models/album.rb +9 -0
  221. data/examples/snippets/quickstart/models/singer.rb +9 -0
  222. data/examples/snippets/read-only-transactions/README.md +13 -0
  223. data/examples/snippets/read-only-transactions/Rakefile +13 -0
  224. data/examples/snippets/read-only-transactions/application.rb +77 -0
  225. data/examples/snippets/read-only-transactions/config/database.yml +8 -0
  226. data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
  227. data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
  228. data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
  229. data/examples/snippets/read-only-transactions/models/album.rb +9 -0
  230. data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
  231. data/examples/snippets/read-write-transactions/README.md +12 -0
  232. data/examples/snippets/read-write-transactions/Rakefile +13 -0
  233. data/examples/snippets/read-write-transactions/application.rb +39 -0
  234. data/examples/snippets/read-write-transactions/config/database.yml +8 -0
  235. data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
  236. data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
  237. data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
  238. data/examples/snippets/read-write-transactions/models/album.rb +9 -0
  239. data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
  240. data/examples/snippets/stale-reads/README.md +27 -0
  241. data/examples/snippets/stale-reads/Rakefile +13 -0
  242. data/examples/snippets/stale-reads/application.rb +63 -0
  243. data/examples/snippets/stale-reads/config/database.yml +8 -0
  244. data/examples/snippets/stale-reads/db/migrate/01_create_tables.rb +21 -0
  245. data/examples/snippets/stale-reads/db/schema.rb +26 -0
  246. data/examples/snippets/stale-reads/db/seeds.rb +24 -0
  247. data/examples/snippets/stale-reads/models/album.rb +9 -0
  248. data/examples/snippets/stale-reads/models/singer.rb +9 -0
  249. data/examples/snippets/timestamp-data-type/README.md +17 -0
  250. data/examples/snippets/timestamp-data-type/Rakefile +13 -0
  251. data/examples/snippets/timestamp-data-type/application.rb +42 -0
  252. data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
  253. data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
  254. data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
  255. data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
  256. data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
  257. data/examples/solidus/README.md +172 -0
  258. data/lib/active_record/connection_adapters/spanner/database_statements.rb +244 -251
  259. data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
  260. data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
  261. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +129 -7
  262. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
  263. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
  264. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -141
  265. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
  266. data/lib/active_record/connection_adapters/spanner_adapter.rb +188 -70
  267. data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
  268. data/lib/active_record/type/spanner/array.rb +32 -0
  269. data/lib/active_record/type/spanner/bytes.rb +26 -0
  270. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +33 -0
  271. data/lib/active_record/type/spanner/time.rb +37 -0
  272. data/lib/activerecord-spanner-adapter.rb +23 -0
  273. data/lib/activerecord_spanner_adapter/base.rb +238 -0
  274. data/lib/activerecord_spanner_adapter/connection.rb +324 -0
  275. data/lib/activerecord_spanner_adapter/errors.rb +13 -0
  276. data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
  277. data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
  278. data/lib/activerecord_spanner_adapter/index.rb +80 -0
  279. data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
  280. data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
  281. data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
  282. data/lib/activerecord_spanner_adapter/table.rb +61 -0
  283. data/lib/activerecord_spanner_adapter/transaction.rb +123 -0
  284. data/lib/activerecord_spanner_adapter/version.rb +9 -0
  285. data/lib/arel/visitors/spanner.rb +111 -0
  286. data/lib/spanner_client_ext.rb +103 -0
  287. data/renovate.json +5 -0
  288. metadata +417 -36
  289. data/.gitmodules +0 -3
  290. data/.travis.yml +0 -5
  291. data/lib/active_record/connection_adapters/spanner.rb +0 -10
  292. data/lib/activerecord-spanner-adapter/version.rb +0 -3
@@ -0,0 +1,37 @@
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
+ module ActiveRecord
10
+ module ConnectionAdapters
11
+ module Spanner
12
+ class TypeMetadata < DelegateClass(SqlTypeMetadata)
13
+ undef to_yaml if method_defined? :to_yaml
14
+
15
+ attr_reader :ordinal_position
16
+
17
+ def initialize type_metadata, ordinal_position: nil
18
+ super type_metadata
19
+ @ordinal_position = ordinal_position
20
+ end
21
+
22
+ def == other
23
+ other.is_a?(TypeMetadata) &&
24
+ __getobj__ == other.__getobj__ &&
25
+ ordinal_position == other.ordinal_position
26
+ end
27
+ alias eql? ==
28
+
29
+ def hash
30
+ TypeMetadata.hash ^
31
+ __getobj__.hash ^
32
+ ordinal_position.hash
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,115 +1,233 @@
1
- require 'google/cloud/spanner'
2
-
3
- require 'active_record/connection_adapters/abstract_adapter'
4
- require 'active_record/connection_adapters/spanner/database_statements'
5
- require 'active_record/connection_adapters/spanner/schema_creation'
6
- require 'active_record/connection_adapters/spanner/schema_statements'
7
- require 'active_record/connection_adapters/spanner/quoting'
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
+ require "securerandom"
8
+ require "google/cloud/spanner"
9
+ require "spanner_client_ext"
10
+ require "active_record/connection_adapters/abstract_adapter"
11
+ require "active_record/connection_adapters/spanner/database_statements"
12
+ require "active_record/connection_adapters/spanner/schema_statements"
13
+ require "active_record/connection_adapters/spanner/schema_cache"
14
+ require "active_record/connection_adapters/spanner/schema_definitions"
15
+ require "active_record/connection_adapters/spanner/type_metadata"
16
+ require "active_record/connection_adapters/spanner/quoting"
17
+ require "active_record/type/spanner/array"
18
+ require "active_record/type/spanner/bytes"
19
+ require "active_record/type/spanner/spanner_active_record_converter"
20
+ require "active_record/type/spanner/time"
21
+ require "arel/visitors/spanner"
22
+ require "activerecord_spanner_adapter/base"
23
+ require "activerecord_spanner_adapter/connection"
24
+ require "activerecord_spanner_adapter/errors"
25
+ require "activerecord_spanner_adapter/information_schema"
26
+ require "activerecord_spanner_adapter/primary_key"
27
+ require "activerecord_spanner_adapter/transaction"
8
28
 
9
29
  module ActiveRecord
10
- module ConnectionHandling
11
- def spanner_connection(config)
12
- ConnectionAdapters::SpannerAdapter.new(nil, logger, config)
30
+ module ConnectionHandling # :nodoc:
31
+ def spanner_connection config
32
+ connection = ActiveRecordSpannerAdapter::Connection.new config
33
+ connection.connect!
34
+ ConnectionAdapters::SpannerAdapter.new connection, logger, nil, config
35
+ rescue Google::Cloud::Error => error
36
+ if error.instance_of? Google::Cloud::NotFoundError
37
+ raise ActiveRecord::NoDatabaseError
38
+ end
39
+ raise error
13
40
  end
14
41
  end
15
42
 
16
43
  module ConnectionAdapters
17
- # A Google Cloud Spanner adapter
18
- #
19
- # Options:
20
- # - project
44
+ module AbstractPool
45
+ def get_schema_cache connection
46
+ @schema_cache ||= SpannerSchemaCache.new connection
47
+ @schema_cache.connection = connection
48
+ @schema_cache
49
+ end
50
+ end
51
+
21
52
  class SpannerAdapter < AbstractAdapter
22
- ADAPTER_NAME = 'Spanner'.freeze
23
- CLIENT_PARAMS = [:project, :keyfile, :scope, :timeout, :client_config].freeze
24
- ADAPTER_OPTS = (CLIENT_PARAMS + [:instance, :database]).freeze
53
+ ADAPTER_NAME = "spanner".freeze
54
+ NATIVE_DATABASE_TYPES = {
55
+ primary_key: "INT64",
56
+ parent_key: "INT64",
57
+ string: { name: "STRING", limit: "MAX" },
58
+ text: { name: "STRING", limit: "MAX" },
59
+ integer: { name: "INT64" },
60
+ bigint: { name: "INT64" },
61
+ float: { name: "FLOAT64" },
62
+ decimal: { name: "NUMERIC" },
63
+ numeric: { name: "NUMERIC" },
64
+ datetime: { name: "TIMESTAMP" },
65
+ time: { name: "TIMESTAMP" },
66
+ date: { name: "DATE" },
67
+ binary: { name: "BYTES", limit: "MAX" },
68
+ boolean: { name: "BOOL" },
69
+ json: { name: "JSON" }
70
+ }.freeze
25
71
 
26
- include Spanner::SchemaStatements
27
- include Spanner::DatabaseStatements
28
72
  include Spanner::Quoting
73
+ include Spanner::DatabaseStatements
74
+ include Spanner::SchemaStatements
29
75
 
30
- def initialize(connection, logger, config)
31
- super(connection, logger, config)
32
- conn_params = config.symbolize_keys.slice(*ADAPTER_OPTS)
33
- connect(conn_params)
76
+ def initialize connection, logger, connection_options, config
77
+ super connection, logger, config
78
+ @connection_options = connection_options
34
79
  end
35
80
 
36
- def schema_creation # :nodoc:
37
- Spanner::SchemaCreation.new self
81
+ def max_identifier_length
82
+ 128
38
83
  end
39
84
 
40
- def arel_visitor
41
- QueryVisitor.new(self)
85
+ def native_database_types
86
+ NATIVE_DATABASE_TYPES
42
87
  end
43
88
 
44
- def active?
45
- !!@client
46
- # TODO(yugui) Check db.service.channel.connectivity_state once it is fixed?
89
+ # Database
90
+
91
+ def self.database_exists? config
92
+ connection = ActiveRecordSpannerAdapter::Connection.new config
93
+ connection.connect!
94
+ true
95
+ rescue ActiveRecord::NoDatabaseError
96
+ false
47
97
  end
48
98
 
49
- def connect(params)
50
- client_params = params.slice(*CLIENT_PARAMS)
51
- @client = Google::Cloud::Spanner.new(**client_params)
52
- @instance_id = params[:instance]
53
- @database_id = params[:database]
99
+ # Connection management
100
+
101
+ def active?
102
+ @connection.active?
54
103
  end
55
104
 
56
105
  def disconnect!
57
- invalidate_session
106
+ super
107
+ @connection.disconnect!
108
+ end
109
+
110
+ def reset!
111
+ super
112
+ @connection.reset!
113
+ end
114
+ alias reconnect! reset!
115
+
116
+ # Spanner Connection API
117
+ delegate :ddl_batch, :ddl_batch?, :start_batch_ddl, :abort_batch, :run_batch, to: :@connection
118
+
119
+ def current_spanner_transaction
120
+ @connection.current_transaction
58
121
  end
59
122
 
60
- def prefetch_primary_key?(table_name = nil)
123
+ # Supported features
124
+
125
+ def supports_bulk_alter?
61
126
  true
62
127
  end
63
128
 
64
- def next_sequence_value(table_name = nil)
65
- require 'securerandom'
66
- SecureRandom.uuid
129
+ def supports_common_table_expressions?
130
+ true
67
131
  end
68
132
 
69
- private
70
- attr_reader :client
133
+ def supports_explain?
134
+ false
135
+ end
71
136
 
72
- def initialize_type_map(m) # :nodoc:
73
- register_class_with_limit m, %r(STRING)i, Type::String
74
- register_class_with_limit m, %r(BYTES)i, Type::Binary
75
- m.register_type %r[STRING(MAX)]i, Type::Text.new(limit: 10 * 1024**2)
76
- m.register_type %r[BYTES(MAX)]i, Type::Binary.new(limit: 10 * 1024**2)
77
- m.register_type %r[BOOL]i, Type::Boolean.new
78
- m.register_type %r[INT64]i, Type::Integer.new(limit: 8)
79
- m.register_type %r[FLOAT64]i, Type::Float.new(limit: 53)
80
- m.register_type %r[DATE]i, Type::Date.new
81
- m.register_type %r[TIMESTAMP]i, Type::DateTime.new
82
- # TODO(yugui) Support array and struct
137
+ def supports_foreign_keys?
138
+ true
83
139
  end
84
140
 
141
+ def supports_index_sort_order?
142
+ true
143
+ end
85
144
 
86
- def instance
87
- @instance ||= client.instance(@instance_id)
88
- raise ActiveRecord::NoDatabaseError unless @instance
145
+ def supports_insert_on_conflict?
146
+ true
147
+ end
148
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
149
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
150
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
89
151
 
90
- @instance
152
+ def supports_insert_returning?
153
+ true
91
154
  end
92
155
 
93
- def database
94
- return @db if @db
156
+ def supports_multi_insert?
157
+ true
158
+ end
95
159
 
96
- @db = instance.database(@database_id)
97
- raise ActiveRecord::NoDatabaseError unless @db
98
- raise ActiveRecord::ConnectionNotEstablished,
99
- "database #{@db.database_path} is not ready" unless @db.ready?
160
+ def supports_optimizer_hints?
161
+ true
162
+ end
163
+
164
+ def supports_primary_key?
165
+ true
166
+ end
100
167
 
101
- @db
168
+ def prefetch_primary_key? _table_name = nil
169
+ true
102
170
  end
103
171
 
104
- def session
105
- @session ||= database.session
172
+ # Generate next sequence number for primary key
173
+ def next_sequence_value _sequence_name
174
+ SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF
106
175
  end
107
176
 
108
- def invalidate_session
109
- @session&.delete_session
110
- @session = nil
177
+ def arel_visitor
178
+ Arel::Visitors::Spanner.new self
179
+ end
180
+
181
+ private
182
+
183
+ def initialize_type_map m = type_map
184
+ m.register_type "BOOL", Type::Boolean.new
185
+ register_class_with_limit(
186
+ m, %r{^BYTES}i, ActiveRecord::Type::Spanner::Bytes
187
+ )
188
+ m.register_type "DATE", Type::Date.new
189
+ m.register_type "FLOAT64", Type::Float.new
190
+ m.register_type "NUMERIC", Type::Decimal.new
191
+ m.register_type "INT64", Type::Integer.new(limit: 8)
192
+ register_class_with_limit m, %r{^STRING}i, Type::String
193
+ m.register_type "TIMESTAMP", ActiveRecord::Type::Spanner::Time.new
194
+ m.register_type "JSON", ActiveRecord::Type::Json.new
195
+
196
+ register_array_types m
197
+ end
198
+
199
+ def register_array_types m
200
+ m.register_type %r{^ARRAY<BOOL>}i, Type::Spanner::Array.new(Type::Boolean.new)
201
+ m.register_type %r{^ARRAY<BYTES\((MAX|d+)\)>}i, Type::Spanner::Array.new(Type::Binary.new)
202
+ m.register_type %r{^ARRAY<DATE>}i, Type::Spanner::Array.new(Type::Date.new)
203
+ m.register_type %r{^ARRAY<FLOAT64>}i, Type::Spanner::Array.new(Type::Float.new)
204
+ m.register_type %r{^ARRAY<NUMERIC>}i, Type::Spanner::Array.new(Type::Decimal.new)
205
+ m.register_type %r{^ARRAY<INT64>}i, Type::Spanner::Array.new(Type::Integer.new(limit: 8))
206
+ m.register_type %r{^ARRAY<STRING\((MAX|d+)\)>}i, Type::Spanner::Array.new(Type::String.new)
207
+ m.register_type %r{^ARRAY<TIMESTAMP>}i, Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Time.new)
208
+ m.register_type %r{^ARRAY<JSON>}i, Type::Spanner::Array.new(ActiveRecord::Type::Json.new)
209
+ end
210
+
211
+ def extract_limit sql_type
212
+ value = /\((.*)\)/.match sql_type
213
+ return unless value
214
+
215
+ value[1] == "MAX" ? "MAX" : value[1].to_i
216
+ end
217
+
218
+ def translate_exception exception, message:, sql:, binds:
219
+ if exception.is_a? Google::Cloud::FailedPreconditionError
220
+ case exception.message
221
+ when /.*does not specify a non-null value for these NOT NULL columns.*/,
222
+ /.*must not be NULL.*/
223
+ NotNullViolation.new message, sql: sql, binds: binds
224
+ else
225
+ super
226
+ end
227
+ else
228
+ super
229
+ end
111
230
  end
112
231
  end
113
232
  end
114
233
  end
115
-
@@ -0,0 +1,74 @@
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
+ require "active_record/connection_adapters/spanner_adapter"
8
+
9
+ module ActiveRecord
10
+ module Tasks
11
+ class SpannerDatabaseTasks
12
+ def initialize config
13
+ config = config.symbolize_keys
14
+ @connection = ActiveRecordSpannerAdapter::Connection.new config
15
+ end
16
+
17
+ def create
18
+ @connection.create_database
19
+ rescue Google::Cloud::Error => error
20
+ if error.instance_of? Google::Cloud::AlreadyExistsError
21
+ raise ActiveRecord::DatabaseAlreadyExists
22
+ end
23
+
24
+ raise error
25
+ end
26
+
27
+ def drop
28
+ @connection.database.drop
29
+ end
30
+
31
+ def purge
32
+ drop
33
+ create
34
+ end
35
+
36
+ def charset
37
+ nil
38
+ end
39
+
40
+ def collation
41
+ nil
42
+ end
43
+
44
+ def structure_dump filename, _extra_flags
45
+ file = File.open filename, "w"
46
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
47
+
48
+ if ignore_tables.any?
49
+ index_regx = /^CREATE(.*)INDEX(.*)ON (#{ignore_tables.join "|"})\(/
50
+ table_regx = /^CREATE TABLE (#{ignore_tables.join "|"})/
51
+ end
52
+
53
+ @connection.database.ddl(force: true).each do |statement|
54
+ next if ignore_tables.any? &&
55
+ (table_regx =~ statement || index_regx =~ statement)
56
+ file.write statement
57
+ file.write "\n"
58
+ end
59
+ ensure
60
+ file.close
61
+ end
62
+
63
+ def structure_load filename, _extra_flags
64
+ statements = File.read(filename).split(/(?=^CREATE)/)
65
+ @connection.execute_ddl statements
66
+ end
67
+ end
68
+
69
+ DatabaseTasks.register_task(
70
+ /spanner/,
71
+ "ActiveRecord::Tasks::SpannerDatabaseTasks"
72
+ )
73
+ end
74
+ end
@@ -0,0 +1,32 @@
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
+ module ActiveRecord
8
+ module Type
9
+ module Spanner
10
+ class Array < Type::Value
11
+ attr_reader :element_type
12
+ delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :element_type
13
+
14
+ def initialize element_type
15
+ @element_type = element_type
16
+ end
17
+
18
+ def serialize value
19
+ return super if value.nil?
20
+ return super unless @element_type.is_a? Type::Decimal
21
+ return super unless value.respond_to? :map
22
+
23
+ # Convert a decimal (NUMERIC) array to a String array to prevent it from being encoded as a FLOAT64 array.
24
+ value.map do |v|
25
+ next if v.nil?
26
+ v.to_s
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
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
+ module ActiveRecord
10
+ module Type
11
+ module Spanner
12
+ class Bytes < ActiveRecord::Type::Binary
13
+ def serialize value
14
+ return super value if value.nil?
15
+
16
+ if value.respond_to?(:read) && value.respond_to?(:rewind)
17
+ value.rewind
18
+ value = value.read
19
+ end
20
+
21
+ Base64.strict_encode64 value.force_encoding("ASCII-8BIT")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
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
+ module ActiveRecord
10
+ module Type
11
+ module Spanner
12
+ class SpannerActiveRecordConverter
13
+ ##
14
+ # Converts an ActiveModel::Type to a Spanner type code.
15
+ def self.convert_active_model_type_to_spanner type # rubocop:disable Metrics/CyclomaticComplexity
16
+ case type
17
+ when NilClass then nil
18
+ when ActiveModel::Type::Integer, ActiveModel::Type::BigInteger then :INT64
19
+ when ActiveModel::Type::Boolean then :BOOL
20
+ when ActiveModel::Type::String, ActiveModel::Type::ImmutableString then :STRING
21
+ when ActiveModel::Type::Binary, ActiveRecord::Type::Spanner::Bytes then :BYTES
22
+ when ActiveModel::Type::Float then :FLOAT64
23
+ when ActiveModel::Type::Decimal then :NUMERIC
24
+ when ActiveModel::Type::DateTime, ActiveModel::Type::Time, ActiveRecord::Type::Spanner::Time then :TIMESTAMP
25
+ when ActiveModel::Type::Date then :DATE
26
+ when ActiveRecord::Type::Json then :JSON
27
+ when ActiveRecord::Type::Spanner::Array then [convert_active_model_type_to_spanner(type.element_type)]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
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
+ module ActiveRecord
10
+ module Type
11
+ module Spanner
12
+ class Time < ActiveRecord::Type::Time
13
+ def serialize value, *options
14
+ return "PENDING_COMMIT_TIMESTAMP()" if value == :commit_timestamp && options.length && options[0] == :dml
15
+ return "spanner.commit_timestamp()" if value == :commit_timestamp && options.length && options[0] == :mutation
16
+ val = super value
17
+ val.acts_like?(:time) ? val.utc.rfc3339(9) : val
18
+ end
19
+
20
+ def user_input_in_time_zone value
21
+ return value.in_time_zone if value.is_a? ::Time
22
+ super value
23
+ end
24
+
25
+ private
26
+
27
+ def cast_value value
28
+ if value.is_a? ::String
29
+ value = value.empty? ? nil : ::Time.parse(value)
30
+ end
31
+
32
+ value
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
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
+ require "activerecord_spanner_adapter/version"
8
+
9
+ if defined?(Rails)
10
+ module ActiveRecord
11
+ module ConnectionAdapters
12
+ class SpannerRailtie < ::Rails::Railtie
13
+ rake_tasks do
14
+ require "active_record/tasks/spanner_database_tasks"
15
+ end
16
+
17
+ ActiveSupport.on_load :active_record do
18
+ require "active_record/connection_adapters/spanner_adapter"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end