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,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,126 +1,233 @@
1
- require 'google/cloud/spanner'
2
-
3
- require 'active_record/connection_adapters/abstract_adapter'
4
- require 'active_record/connection_adapters/spanner/client'
5
- require 'active_record/connection_adapters/spanner/database_statements'
6
- require 'active_record/connection_adapters/spanner/schema_creation'
7
- require 'active_record/connection_adapters/spanner/schema_statements'
8
- require 'active_record/connection_adapters/spanner/quoting'
9
- require 'grpc'
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"
10
28
 
11
29
  module ActiveRecord
12
- module ConnectionHandling
13
- def spanner_connection(config)
14
- 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
15
40
  end
16
41
  end
17
42
 
18
43
  module ConnectionAdapters
19
- # A Google Cloud Spanner adapter
20
- #
21
- # Options:
22
- # - 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
+
23
52
  class SpannerAdapter < AbstractAdapter
24
- ADAPTER_NAME = 'Spanner'.freeze
25
- CLIENT_PARAMS = [:project, :keyfile, :scope, :timeout, :client_config].freeze
26
- 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
27
71
 
28
- include Spanner::SchemaStatements
29
- include Spanner::DatabaseStatements
30
72
  include Spanner::Quoting
73
+ include Spanner::DatabaseStatements
74
+ include Spanner::SchemaStatements
31
75
 
32
- def initialize(connection, logger, config)
33
- conn_params = config.symbolize_keys.slice(*ADAPTER_OPTS)
34
- connect(conn_params)
35
- super(connection, logger, config)
76
+ def initialize connection, logger, connection_options, config
77
+ super connection, logger, config
78
+ @connection_options = connection_options
36
79
  end
37
80
 
38
- def schema_creation # :nodoc:
39
- Spanner::SchemaCreation.new self
81
+ def max_identifier_length
82
+ 128
40
83
  end
41
84
 
42
- def arel_visitor
43
- QueryVisitor.new(self)
85
+ def native_database_types
86
+ NATIVE_DATABASE_TYPES
44
87
  end
45
88
 
46
- def active?
47
- [
48
- ::GRPC::Core::ConnectivityStates::IDLE,
49
- ::GRPC::Core::ConnectivityStates::READY,
50
- ].include?(@conn.service.channel.connectivity_state)
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
51
97
  end
52
98
 
53
- def connect(params)
54
- client_params = params.slice(*CLIENT_PARAMS)
55
- @conn = Google::Cloud::Spanner.new(**client_params)
56
- @instance_id = params[:instance]
57
- @database_id = params[:database]
99
+ # Connection management
100
+
101
+ def active?
102
+ @connection.active?
58
103
  end
59
104
 
60
105
  def disconnect!
61
106
  super
62
- release_client!
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
63
121
  end
64
122
 
65
- def prefetch_primary_key?(table_name = nil)
123
+ # Supported features
124
+
125
+ def supports_bulk_alter?
66
126
  true
67
127
  end
68
128
 
69
- def next_sequence_value(table_name = nil)
70
- require 'securerandom'
71
- SecureRandom.uuid
129
+ def supports_common_table_expressions?
130
+ true
72
131
  end
73
132
 
74
- private
75
- attr_reader :conn
133
+ def supports_explain?
134
+ false
135
+ end
76
136
 
77
- def initialize_type_map(m) # :nodoc:
78
- register_class_with_limit m, %r(STRING)i, Type::String
79
- register_class_with_limit m, %r(BYTES)i, Type::Binary
80
- m.register_type %r[STRING(MAX)]i, Type::Text.new(limit: 10 * 1024**2)
81
- m.register_type %r[BYTES(MAX)]i, Type::Binary.new(limit: 10 * 1024**2)
82
- m.register_type %r[BOOL]i, Type::Boolean.new
83
- m.register_type %r[INT64]i, Type::Integer.new(limit: 8)
84
- m.register_type %r[FLOAT64]i, Type::Float.new(limit: 53)
85
- m.register_type %r[DATE]i, Type::Date.new
86
- m.register_type %r[TIMESTAMP]i, Type::DateTime.new
87
- # TODO(yugui) Support array and struct
137
+ def supports_foreign_keys?
138
+ true
139
+ end
140
+
141
+ def supports_index_sort_order?
142
+ true
143
+ end
144
+
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?
151
+
152
+ def supports_insert_returning?
153
+ true
154
+ end
155
+
156
+ def supports_multi_insert?
157
+ true
88
158
  end
89
159
 
90
- def instance
91
- @instance ||= conn.instance(@instance_id)
92
- raise ActiveRecord::NoDatabaseError unless @instance
160
+ def supports_optimizer_hints?
161
+ true
162
+ end
93
163
 
94
- @instance
164
+ def supports_primary_key?
165
+ true
95
166
  end
96
167
 
97
- def database
98
- return @db if @db
168
+ def prefetch_primary_key? _table_name = nil
169
+ true
170
+ end
99
171
 
100
- @db = instance.database(@database_id)
101
- raise ActiveRecord::NoDatabaseError unless @db
102
- raise ActiveRecord::ConnectionNotEstablished,
103
- "database #{@db.database_path} is not ready" unless @db.ready?
172
+ # Generate next sequence number for primary key
173
+ def next_sequence_value _sequence_name
174
+ SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF
175
+ end
104
176
 
105
- @db
177
+ def arel_visitor
178
+ Arel::Visitors::Spanner.new self
106
179
  end
107
180
 
108
- def raw_client
109
- @raw_client ||= conn.client(@instance_id, @database_id)
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
110
197
  end
111
198
 
112
- def client
113
- @client ||= ConnectionAdapters::Spanner::InitialPhaseClient.new(raw_client)
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)
114
209
  end
115
210
 
116
- def release_client!
117
- @client&.close
118
- @client = nil
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
119
216
  end
120
217
 
121
- #def reset_transaction
122
- # # @transaction_manager = Spanner::TransactionManager.new(self, client)
123
- #end
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
230
+ end
124
231
  end
125
232
  end
126
233
  end
@@ -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