activerecord-spanner-adapter 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/sync-repo-settings.yaml +16 -0
  4. data/.github/workflows/acceptance-tests-on-emulator.yaml +45 -0
  5. data/.github/workflows/acceptance-tests-on-production.yaml +36 -0
  6. data/.github/workflows/ci.yaml +33 -0
  7. data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +52 -0
  8. data/.github/workflows/nightly-acceptance-tests-on-production.yaml +35 -0
  9. data/.github/workflows/nightly-unit-tests.yaml +40 -0
  10. data/.github/workflows/release-please-label.yml +25 -0
  11. data/.github/workflows/release-please.yml +39 -0
  12. data/.github/workflows/rubocop.yaml +31 -0
  13. data/.gitignore +67 -5
  14. data/.kokoro/populate-secrets.sh +77 -0
  15. data/.kokoro/release.cfg +33 -0
  16. data/.kokoro/release.sh +15 -0
  17. data/.kokoro/trampoline_v2.sh +489 -0
  18. data/.rubocop.yml +46 -0
  19. data/.toys/release.rb +18 -0
  20. data/.trampolinerc +48 -0
  21. data/.yardopts +11 -0
  22. data/CHANGELOG.md +26 -0
  23. data/CODE_OF_CONDUCT.md +40 -0
  24. data/CONTRIBUTING.md +79 -0
  25. data/Gemfile +9 -4
  26. data/LICENSE +6 -6
  27. data/README.md +67 -30
  28. data/Rakefile +79 -3
  29. data/SECURITY.md +7 -0
  30. data/acceptance/cases/associations/has_many_associations_test.rb +119 -0
  31. data/acceptance/cases/associations/has_many_through_associations_test.rb +63 -0
  32. data/acceptance/cases/associations/has_one_associations_test.rb +79 -0
  33. data/acceptance/cases/associations/has_one_through_associations_test.rb +98 -0
  34. data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +211 -0
  35. data/acceptance/cases/migration/change_schema_test.rb +433 -0
  36. data/acceptance/cases/migration/change_table_test.rb +115 -0
  37. data/acceptance/cases/migration/column_attributes_test.rb +122 -0
  38. data/acceptance/cases/migration/column_positioning_test.rb +48 -0
  39. data/acceptance/cases/migration/columns_test.rb +201 -0
  40. data/acceptance/cases/migration/command_recorder_test.rb +406 -0
  41. data/acceptance/cases/migration/create_join_table_test.rb +216 -0
  42. data/acceptance/cases/migration/ddl_batching_test.rb +80 -0
  43. data/acceptance/cases/migration/foreign_key_test.rb +297 -0
  44. data/acceptance/cases/migration/index_test.rb +211 -0
  45. data/acceptance/cases/migration/references_foreign_key_test.rb +259 -0
  46. data/acceptance/cases/migration/references_index_test.rb +135 -0
  47. data/acceptance/cases/migration/references_statements_test.rb +166 -0
  48. data/acceptance/cases/migration/rename_column_test.rb +96 -0
  49. data/acceptance/cases/models/calculation_query_test.rb +128 -0
  50. data/acceptance/cases/models/generated_column_test.rb +126 -0
  51. data/acceptance/cases/models/mutation_test.rb +122 -0
  52. data/acceptance/cases/models/query_test.rb +147 -0
  53. data/acceptance/cases/sessions/session_not_found_test.rb +121 -0
  54. data/acceptance/cases/transactions/optimistic_locking_test.rb +141 -0
  55. data/acceptance/cases/transactions/read_only_transactions_test.rb +67 -0
  56. data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
  57. data/acceptance/cases/type/all_types_test.rb +152 -0
  58. data/acceptance/cases/type/binary_test.rb +59 -0
  59. data/acceptance/cases/type/boolean_test.rb +31 -0
  60. data/acceptance/cases/type/date_test.rb +32 -0
  61. data/acceptance/cases/type/date_time_test.rb +30 -0
  62. data/acceptance/cases/type/float_test.rb +27 -0
  63. data/acceptance/cases/type/integer_test.rb +44 -0
  64. data/acceptance/cases/type/numeric_test.rb +27 -0
  65. data/acceptance/cases/type/string_test.rb +79 -0
  66. data/acceptance/cases/type/text_test.rb +30 -0
  67. data/acceptance/cases/type/time_test.rb +87 -0
  68. data/acceptance/models/account.rb +13 -0
  69. data/acceptance/models/address.rb +9 -0
  70. data/acceptance/models/album.rb +12 -0
  71. data/acceptance/models/all_types.rb +8 -0
  72. data/acceptance/models/author.rb +11 -0
  73. data/acceptance/models/club.rb +12 -0
  74. data/acceptance/models/comment.rb +9 -0
  75. data/acceptance/models/customer.rb +9 -0
  76. data/acceptance/models/department.rb +9 -0
  77. data/acceptance/models/firm.rb +10 -0
  78. data/acceptance/models/member.rb +13 -0
  79. data/acceptance/models/member_type.rb +9 -0
  80. data/acceptance/models/membership.rb +10 -0
  81. data/acceptance/models/organization.rb +9 -0
  82. data/acceptance/models/post.rb +10 -0
  83. data/acceptance/models/singer.rb +10 -0
  84. data/acceptance/models/track.rb +20 -0
  85. data/acceptance/models/transaction.rb +9 -0
  86. data/acceptance/schema/schema.rb +143 -0
  87. data/acceptance/test_helper.rb +260 -0
  88. data/activerecord-spanner-adapter.gemspec +32 -17
  89. data/assets/solidus-db.png +0 -0
  90. data/benchmarks/README.md +17 -0
  91. data/benchmarks/Rakefile +14 -0
  92. data/benchmarks/application.rb +308 -0
  93. data/benchmarks/config/database.yml +8 -0
  94. data/benchmarks/config/environment.rb +12 -0
  95. data/benchmarks/db/migrate/01_create_tables.rb +25 -0
  96. data/benchmarks/db/schema.rb +29 -0
  97. data/benchmarks/models/album.rb +9 -0
  98. data/benchmarks/models/singer.rb +9 -0
  99. data/bin/console +6 -7
  100. data/examples/rails/README.md +262 -0
  101. data/examples/snippets/README.md +29 -0
  102. data/examples/snippets/Rakefile +57 -0
  103. data/examples/snippets/array-data-type/README.md +45 -0
  104. data/examples/snippets/array-data-type/Rakefile +13 -0
  105. data/examples/snippets/array-data-type/application.rb +45 -0
  106. data/examples/snippets/array-data-type/config/database.yml +8 -0
  107. data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
  108. data/examples/snippets/array-data-type/db/schema.rb +26 -0
  109. data/examples/snippets/array-data-type/db/seeds.rb +5 -0
  110. data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
  111. data/examples/snippets/bin/create_emulator_instance.rb +18 -0
  112. data/examples/snippets/bulk-insert/README.md +21 -0
  113. data/examples/snippets/bulk-insert/Rakefile +13 -0
  114. data/examples/snippets/bulk-insert/application.rb +64 -0
  115. data/examples/snippets/bulk-insert/config/database.yml +8 -0
  116. data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
  117. data/examples/snippets/bulk-insert/db/schema.rb +26 -0
  118. data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
  119. data/examples/snippets/bulk-insert/models/album.rb +9 -0
  120. data/examples/snippets/bulk-insert/models/singer.rb +9 -0
  121. data/examples/snippets/commit-timestamp/README.md +18 -0
  122. data/examples/snippets/commit-timestamp/Rakefile +13 -0
  123. data/examples/snippets/commit-timestamp/application.rb +53 -0
  124. data/examples/snippets/commit-timestamp/config/database.yml +8 -0
  125. data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
  126. data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
  127. data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
  128. data/examples/snippets/commit-timestamp/models/album.rb +9 -0
  129. data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
  130. data/examples/snippets/config/environment.rb +21 -0
  131. data/examples/snippets/create-records/README.md +12 -0
  132. data/examples/snippets/create-records/Rakefile +13 -0
  133. data/examples/snippets/create-records/application.rb +42 -0
  134. data/examples/snippets/create-records/config/database.yml +8 -0
  135. data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
  136. data/examples/snippets/create-records/db/schema.rb +26 -0
  137. data/examples/snippets/create-records/db/seeds.rb +5 -0
  138. data/examples/snippets/create-records/models/album.rb +9 -0
  139. data/examples/snippets/create-records/models/singer.rb +9 -0
  140. data/examples/snippets/date-data-type/README.md +19 -0
  141. data/examples/snippets/date-data-type/Rakefile +13 -0
  142. data/examples/snippets/date-data-type/application.rb +35 -0
  143. data/examples/snippets/date-data-type/config/database.yml +8 -0
  144. data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
  145. data/examples/snippets/date-data-type/db/schema.rb +21 -0
  146. data/examples/snippets/date-data-type/db/seeds.rb +16 -0
  147. data/examples/snippets/date-data-type/models/singer.rb +8 -0
  148. data/examples/snippets/generated-column/README.md +41 -0
  149. data/examples/snippets/generated-column/Rakefile +13 -0
  150. data/examples/snippets/generated-column/application.rb +37 -0
  151. data/examples/snippets/generated-column/config/database.yml +8 -0
  152. data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
  153. data/examples/snippets/generated-column/db/schema.rb +21 -0
  154. data/examples/snippets/generated-column/db/seeds.rb +18 -0
  155. data/examples/snippets/generated-column/models/singer.rb +8 -0
  156. data/examples/snippets/interleaved-tables/README.md +152 -0
  157. data/examples/snippets/interleaved-tables/Rakefile +13 -0
  158. data/examples/snippets/interleaved-tables/application.rb +109 -0
  159. data/examples/snippets/interleaved-tables/config/database.yml +8 -0
  160. data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
  161. data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
  162. data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
  163. data/examples/snippets/interleaved-tables/models/album.rb +15 -0
  164. data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
  165. data/examples/snippets/interleaved-tables/models/track.rb +25 -0
  166. data/examples/snippets/migrations/README.md +43 -0
  167. data/examples/snippets/migrations/Rakefile +13 -0
  168. data/examples/snippets/migrations/application.rb +26 -0
  169. data/examples/snippets/migrations/config/database.yml +8 -0
  170. data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
  171. data/examples/snippets/migrations/db/schema.rb +33 -0
  172. data/examples/snippets/migrations/db/seeds.rb +5 -0
  173. data/examples/snippets/migrations/models/album.rb +10 -0
  174. data/examples/snippets/migrations/models/singer.rb +10 -0
  175. data/examples/snippets/migrations/models/track.rb +9 -0
  176. data/examples/snippets/mutations/README.md +34 -0
  177. data/examples/snippets/mutations/Rakefile +13 -0
  178. data/examples/snippets/mutations/application.rb +47 -0
  179. data/examples/snippets/mutations/config/database.yml +8 -0
  180. data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
  181. data/examples/snippets/mutations/db/schema.rb +27 -0
  182. data/examples/snippets/mutations/db/seeds.rb +25 -0
  183. data/examples/snippets/mutations/models/album.rb +9 -0
  184. data/examples/snippets/mutations/models/singer.rb +9 -0
  185. data/examples/snippets/optimistic-locking/README.md +12 -0
  186. data/examples/snippets/optimistic-locking/Rakefile +13 -0
  187. data/examples/snippets/optimistic-locking/application.rb +48 -0
  188. data/examples/snippets/optimistic-locking/config/database.yml +8 -0
  189. data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
  190. data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
  191. data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
  192. data/examples/snippets/optimistic-locking/models/album.rb +9 -0
  193. data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
  194. data/examples/snippets/quickstart/README.md +26 -0
  195. data/examples/snippets/quickstart/Rakefile +13 -0
  196. data/examples/snippets/quickstart/application.rb +51 -0
  197. data/examples/snippets/quickstart/config/database.yml +8 -0
  198. data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
  199. data/examples/snippets/quickstart/db/schema.rb +26 -0
  200. data/examples/snippets/quickstart/db/seeds.rb +24 -0
  201. data/examples/snippets/quickstart/models/album.rb +9 -0
  202. data/examples/snippets/quickstart/models/singer.rb +9 -0
  203. data/examples/snippets/read-only-transactions/README.md +13 -0
  204. data/examples/snippets/read-only-transactions/Rakefile +13 -0
  205. data/examples/snippets/read-only-transactions/application.rb +49 -0
  206. data/examples/snippets/read-only-transactions/config/database.yml +8 -0
  207. data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
  208. data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
  209. data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
  210. data/examples/snippets/read-only-transactions/models/album.rb +9 -0
  211. data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
  212. data/examples/snippets/read-write-transactions/README.md +12 -0
  213. data/examples/snippets/read-write-transactions/Rakefile +13 -0
  214. data/examples/snippets/read-write-transactions/application.rb +39 -0
  215. data/examples/snippets/read-write-transactions/config/database.yml +8 -0
  216. data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
  217. data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
  218. data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
  219. data/examples/snippets/read-write-transactions/models/album.rb +9 -0
  220. data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
  221. data/examples/snippets/timestamp-data-type/README.md +17 -0
  222. data/examples/snippets/timestamp-data-type/Rakefile +13 -0
  223. data/examples/snippets/timestamp-data-type/application.rb +42 -0
  224. data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
  225. data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
  226. data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
  227. data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
  228. data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
  229. data/examples/solidus/README.md +172 -0
  230. data/lib/active_record/connection_adapters/spanner/database_statements.rb +224 -269
  231. data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
  232. data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
  233. data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
  234. data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
  235. data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
  236. data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
  237. data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
  238. data/lib/active_record/connection_adapters/spanner_adapter.rb +182 -78
  239. data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
  240. data/lib/active_record/type/spanner/array.rb +32 -0
  241. data/lib/active_record/type/spanner/bytes.rb +26 -0
  242. data/lib/active_record/type/spanner/spanner_active_record_converter.rb +32 -0
  243. data/lib/active_record/type/spanner/time.rb +37 -0
  244. data/lib/activerecord-spanner-adapter.rb +23 -0
  245. data/lib/activerecord_spanner_adapter/base.rb +217 -0
  246. data/lib/activerecord_spanner_adapter/connection.rb +324 -0
  247. data/lib/activerecord_spanner_adapter/errors.rb +13 -0
  248. data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
  249. data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
  250. data/lib/activerecord_spanner_adapter/index.rb +80 -0
  251. data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
  252. data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
  253. data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
  254. data/lib/activerecord_spanner_adapter/table.rb +61 -0
  255. data/lib/activerecord_spanner_adapter/transaction.rb +113 -0
  256. data/lib/activerecord_spanner_adapter/version.rb +9 -0
  257. data/lib/arel/visitors/spanner.rb +35 -0
  258. data/lib/spanner_client_ext.rb +82 -0
  259. data/renovate.json +5 -0
  260. metadata +387 -34
  261. data/.travis.yml +0 -5
  262. data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
  263. data/lib/active_record/connection_adapters/spanner.rb +0 -10
  264. data/lib/activerecord-spanner-adapter/version.rb +0 -3
@@ -0,0 +1,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,230 @@
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
+ }.freeze
27
70
 
28
- include Spanner::SchemaStatements
29
- include Spanner::DatabaseStatements
30
71
  include Spanner::Quoting
72
+ include Spanner::DatabaseStatements
73
+ include Spanner::SchemaStatements
31
74
 
32
- def initialize(connection, logger, config)
33
- conn_params = config.symbolize_keys.slice(*ADAPTER_OPTS)
34
- connect(conn_params)
35
- super(connection, logger, config)
75
+ def initialize connection, logger, connection_options, config
76
+ super connection, logger, config
77
+ @connection_options = connection_options
36
78
  end
37
79
 
38
- def schema_creation # :nodoc:
39
- Spanner::SchemaCreation.new self
80
+ def max_identifier_length
81
+ 128
40
82
  end
41
83
 
42
- def arel_visitor
43
- QueryVisitor.new(self)
84
+ def native_database_types
85
+ NATIVE_DATABASE_TYPES
44
86
  end
45
87
 
46
- def active?
47
- [
48
- ::GRPC::Core::ConnectivityStates::IDLE,
49
- ::GRPC::Core::ConnectivityStates::READY,
50
- ].include?(@conn.service.channel.connectivity_state)
88
+ # Database
89
+
90
+ def self.database_exists? config
91
+ connection = ActiveRecordSpannerAdapter::Connection.new config
92
+ connection.connect!
93
+ true
94
+ rescue ActiveRecord::NoDatabaseError
95
+ false
51
96
  end
52
97
 
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]
98
+ # Connection management
99
+
100
+ def active?
101
+ @connection.active?
58
102
  end
59
103
 
60
104
  def disconnect!
61
105
  super
62
- release_client!
106
+ @connection.disconnect!
107
+ end
108
+
109
+ def reset!
110
+ super
111
+ @connection.reset!
112
+ end
113
+ alias reconnect! reset!
114
+
115
+ # Spanner Connection API
116
+ delegate :ddl_batch, :ddl_batch?, :start_batch_ddl, :abort_batch, :run_batch, to: :@connection
117
+
118
+ def current_spanner_transaction
119
+ @connection.current_transaction
63
120
  end
64
121
 
65
- def prefetch_primary_key?(table_name = nil)
122
+ # Supported features
123
+
124
+ def supports_bulk_alter?
66
125
  true
67
126
  end
68
127
 
69
- def next_sequence_value(table_name = nil)
70
- require 'securerandom'
71
- SecureRandom.uuid
128
+ def supports_common_table_expressions?
129
+ true
72
130
  end
73
131
 
74
- private
75
- attr_reader :conn
132
+ def supports_explain?
133
+ false
134
+ end
76
135
 
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
136
+ def supports_foreign_keys?
137
+ true
138
+ end
139
+
140
+ def supports_index_sort_order?
141
+ true
142
+ end
143
+
144
+ def supports_insert_on_conflict?
145
+ true
146
+ end
147
+ alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
148
+ alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
149
+ alias supports_insert_conflict_target? supports_insert_on_conflict?
150
+
151
+ def supports_insert_returning?
152
+ true
153
+ end
154
+
155
+ def supports_multi_insert?
156
+ true
88
157
  end
89
158
 
90
- def instance
91
- @instance ||= conn.instance(@instance_id)
92
- raise ActiveRecord::NoDatabaseError unless @instance
159
+ def supports_optimizer_hints?
160
+ true
161
+ end
93
162
 
94
- @instance
163
+ def supports_primary_key?
164
+ true
95
165
  end
96
166
 
97
- def database
98
- return @db if @db
167
+ def prefetch_primary_key? _table_name = nil
168
+ true
169
+ end
99
170
 
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?
171
+ # Generate next sequence number for primary key
172
+ def next_sequence_value _sequence_name
173
+ SecureRandom.uuid.gsub("-", "").hex & 0x7FFFFFFFFFFFFFFF
174
+ end
104
175
 
105
- @db
176
+ def arel_visitor
177
+ Arel::Visitors::Spanner.new self
106
178
  end
107
179
 
108
- def raw_client
109
- @raw_client ||= conn.client(@instance_id, @database_id)
180
+ private
181
+
182
+ def initialize_type_map m = type_map
183
+ m.register_type "BOOL", Type::Boolean.new
184
+ register_class_with_limit(
185
+ m, %r{^BYTES}i, ActiveRecord::Type::Spanner::Bytes
186
+ )
187
+ m.register_type "DATE", Type::Date.new
188
+ m.register_type "FLOAT64", Type::Float.new
189
+ m.register_type "NUMERIC", Type::Decimal.new
190
+ m.register_type "INT64", Type::Integer.new(limit: 8)
191
+ register_class_with_limit m, %r{^STRING}i, Type::String
192
+ m.register_type "TIMESTAMP", ActiveRecord::Type::Spanner::Time.new
193
+
194
+ register_array_types m
110
195
  end
111
196
 
112
- def client
113
- @client ||= ConnectionAdapters::Spanner::InitialPhaseClient.new(raw_client)
197
+ def register_array_types m
198
+ m.register_type %r{^ARRAY<BOOL>}i, Type::Spanner::Array.new(Type::Boolean.new)
199
+ m.register_type %r{^ARRAY<BYTES\((MAX|d+)\)>}i, Type::Spanner::Array.new(Type::Binary.new)
200
+ m.register_type %r{^ARRAY<DATE>}i, Type::Spanner::Array.new(Type::Date.new)
201
+ m.register_type %r{^ARRAY<FLOAT64>}i, Type::Spanner::Array.new(Type::Float.new)
202
+ m.register_type %r{^ARRAY<NUMERIC>}i, Type::Spanner::Array.new(Type::Decimal.new)
203
+ m.register_type %r{^ARRAY<INT64>}i, Type::Spanner::Array.new(Type::Integer.new(limit: 8))
204
+ m.register_type %r{^ARRAY<STRING\((MAX|d+)\)>}i, Type::Spanner::Array.new(Type::String.new)
205
+ m.register_type %r{^ARRAY<TIMESTAMP>}i, Type::Spanner::Array.new(ActiveRecord::Type::Spanner::Time.new)
114
206
  end
115
207
 
116
- def release_client!
117
- @client&.close
118
- @client = nil
208
+ def extract_limit sql_type
209
+ value = /\((.*)\)/.match sql_type
210
+ return unless value
211
+
212
+ value[1] == "MAX" ? "MAX" : value[1].to_i
119
213
  end
120
214
 
121
- #def reset_transaction
122
- # # @transaction_manager = Spanner::TransactionManager.new(self, client)
123
- #end
215
+ def translate_exception exception, message:, sql:, binds:
216
+ if exception.is_a? Google::Cloud::FailedPreconditionError
217
+ case exception.message
218
+ when /.*does not specify a non-null value for these NOT NULL columns.*/,
219
+ /.*must not be NULL.*/
220
+ NotNullViolation.new message, sql: sql, binds: binds
221
+ else
222
+ super
223
+ end
224
+ else
225
+ super
226
+ end
227
+ end
124
228
  end
125
229
  end
126
230
  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::Tasks::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