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,21 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `rails
6
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 1) do
14
+
15
+ create_table "singers", force: :cascade do |t|
16
+ t.string "first_name"
17
+ t.string "last_name"
18
+ t.date "birth_date"
19
+ end
20
+
21
+ end
@@ -0,0 +1,16 @@
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
+ require_relative "../../config/environment.rb"
8
+ require_relative "../models/singer"
9
+
10
+ first_names = %w[Nelson Todd William Alex Dominique Adenoid Steve Nathan Beverly Annie Amy Norma Diana Regan Phyllis]
11
+ last_names = %w[Thornton Morgan Lawson Collins Frost Maxwell Sanders Fleming Jones Webb Walker French Montgomery Quinn]
12
+
13
+ 30.times do
14
+ Singer.create first_name: first_names.sample, last_name: last_names.sample,
15
+ birth_date: Date.new(rand(1920...2010), rand(1...12), rand(1...28))
16
+ end
@@ -0,0 +1,8 @@
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
+ class Singer < ActiveRecord::Base
8
+ end
@@ -0,0 +1,41 @@
1
+ # Sample - Generated Columns
2
+
3
+ This example shows how to use generated columns with the Spanner ActiveRecord adapter.
4
+
5
+ See https://cloud.google.com/spanner/docs/generated-column/how-to for more information on generated columns.
6
+
7
+ This example uses the following table schema:
8
+
9
+ ```sql
10
+ CREATE TABLE singers (
11
+ id INT64 NOT NULL,
12
+ first_name STRING(100),
13
+ last_name STRING(200) NOT NULL,
14
+ full_name STRING(300) NOT NULL AS (COALESCE(first_name || ' ', '') || last_name) STORED,
15
+ ) PRIMARY KEY (id);
16
+ ```
17
+
18
+ This schema can be created in ActiveRecord as follows:
19
+
20
+ ```ruby
21
+ create_table :singers do |t|
22
+ t.string :first_name, limit: 100
23
+ t.string :last_name, limit: 200, null: false
24
+ t.string :full_name, limit: 300, null: false, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
25
+ end
26
+ ```
27
+
28
+ The `full_name` attribute will automatically be set by Cloud Spanner, and it is not allowed to set a value for the
29
+ attribute when creating a record in ActiveRecord, or to update the value of an existing record. Instead, only the
30
+ `first_name` and `last_name` attributes should be set.
31
+
32
+ ## Running the Sample
33
+
34
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
35
+ against that emulator. The emulator will automatically be stopped when the application finishes.
36
+
37
+ Run the application with the command
38
+
39
+ ```bash
40
+ bundle exec rake run
41
+ ```
@@ -0,0 +1,13 @@
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
+ require_relative "../config/environment"
8
+ require "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to work with generated columns in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[generated-column]" }
13
+ end
@@ -0,0 +1,37 @@
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
+ require "io/console"
8
+ require_relative "../config/environment"
9
+ require_relative "models/singer"
10
+
11
+ class Application
12
+ def self.run
13
+ puts ""
14
+ puts "Listing all singers:"
15
+ Singer.all.order("last_name, first_name").each do |singer|
16
+ puts singer.full_name
17
+ end
18
+
19
+ # Create a new singer and print out the full name.
20
+ singer = Singer.create first_name: "Alice", last_name: "Rees"
21
+ singer.reload
22
+ puts ""
23
+ puts "Singer created: #{singer.full_name}"
24
+
25
+ # Update the last name of the singer and print out the full name.
26
+ singer.update last_name: "Rees-Goodwin"
27
+ singer.reload
28
+ puts ""
29
+ puts "Singer updated: #{singer.full_name}"
30
+
31
+ puts ""
32
+ puts "Press any key to end the application"
33
+ STDIN.getch
34
+ end
35
+ end
36
+
37
+ Application.run
@@ -0,0 +1,8 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ pool: 5
8
+ timeout: 5000
@@ -0,0 +1,23 @@
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
+ class CreateTables < ActiveRecord::Migration[6.0]
8
+ def change
9
+ connection.ddl_batch do
10
+ create_table :singers do |t|
11
+ t.string :first_name, limit: 100
12
+ t.string :last_name, limit: 200, null: false
13
+
14
+ # Create a generated column that contains the full name of the singer. This will be the concatenated first name
15
+ # and last name of the singer, or only the last name if the first name is null. The `as` keyword is what
16
+ # instructs the Spanner ActiveRecord adapter to create a generated column. Note the `stored` option that is set
17
+ # to true. This is required, as Cloud Spanner (currently) does not support non-stored generated columns.
18
+ # See also https://cloud.google.com/spanner/docs/generated-column/how-to for more information.
19
+ t.string :full_name, limit: 300, null: false, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 1) do
14
+
15
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
16
+ t.string "first_name", limit: 100
17
+ t.string "last_name", limit: 200, null: false
18
+ t.string "full_name", limit: 300, null: false
19
+ end
20
+
21
+ end
@@ -0,0 +1,18 @@
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
+ require_relative "../../config/environment.rb"
8
+ require_relative "../models/singer"
9
+
10
+ first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
11
+ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
12
+
13
+ # This ensures all the records are inserted using one read/write transaction that will use mutations instead of DML.
14
+ ActiveRecord::Base.transaction isolation: :buffered_mutations do
15
+ 10.times do
16
+ Singer.create first_name: first_names.sample, last_name: last_names.sample
17
+ end
18
+ end
@@ -0,0 +1,8 @@
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
+ class Singer < ActiveRecord::Base
8
+ end
@@ -0,0 +1,19 @@
1
+ # Sample - Query Hints
2
+
3
+ This example shows how to use query hints Spanner ActiveRecord adapter. Statement hints and
4
+ table hints can be specified using the optimizer_hints method. Join hints must be specified
5
+ in a join string.
6
+
7
+ See https://cloud.google.com/spanner/docs/query-syntax#sql_syntax for more information on
8
+ the supported query hints.
9
+
10
+ ## Running the Sample
11
+
12
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
13
+ against that emulator. The emulator will automatically be stopped when the application finishes.
14
+
15
+ Run the application with the command
16
+
17
+ ```bash
18
+ bundle exec rake run
19
+ ```
@@ -0,0 +1,13 @@
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
+ require_relative "../config/environment"
8
+ require "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to work with generated columns in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[hints]" }
13
+ end
@@ -0,0 +1,47 @@
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
+ require "io/console"
8
+ require_relative "../config/environment"
9
+ require_relative "models/singer"
10
+ require_relative "models/album"
11
+
12
+ class Application
13
+ def self.run
14
+ puts ""
15
+ puts "Listing all singers using additional parallelism:"
16
+ # A statement hint must be prefixed with 'statement_hint:'
17
+ Singer.optimizer_hints("statement_hint: @{USE_ADDITIONAL_PARALLELISM=TRUE}")
18
+ .order("last_name, first_name").each do |singer|
19
+ puts singer.full_name
20
+ end
21
+
22
+ puts ""
23
+ puts "Listing all singers using the index on full_name:"
24
+ # All table hints must be prefixed with 'table_hint:'.
25
+ # Queries may contain multiple table hints.
26
+ Singer.optimizer_hints("table_hint: singers@{FORCE_INDEX=index_singers_on_full_name}")
27
+ .order("full_name").each do |singer|
28
+ puts singer.full_name
29
+ end
30
+
31
+ puts ""
32
+ puts "Listing all singers with at least one album that starts with 'blue':"
33
+ # Join hints cannot be specified using an optimizer_hint. Instead, the join can
34
+ # be specified using a string that includes the join hint.
35
+ Singer.joins("INNER JOIN @{JOIN_METHOD=HASH_JOIN} albums " \
36
+ "on singers.id=albums.singer_id AND albums.title LIKE 'blue%'")
37
+ .distinct.order("last_name, first_name").each do |singer|
38
+ puts singer.full_name
39
+ end
40
+
41
+ puts ""
42
+ puts "Press any key to end the application"
43
+ STDIN.getch
44
+ end
45
+ end
46
+
47
+ Application.run
@@ -0,0 +1,8 @@
1
+ development:
2
+ adapter: spanner
3
+ emulator_host: localhost:9010
4
+ project: test-project
5
+ instance: test-instance
6
+ database: testdb
7
+ pool: 5
8
+ timeout: 5000
@@ -0,0 +1,23 @@
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
+ class CreateTables < ActiveRecord::Migration[6.0]
8
+ def change
9
+ connection.ddl_batch do
10
+ create_table :singers do |t|
11
+ t.string :first_name, limit: 100
12
+ t.string :last_name, limit: 200, null: false
13
+ t.string :full_name, limit: 300, null: false, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
14
+ t.index [:full_name], name: "index_singers_on_full_name"
15
+ end
16
+
17
+ create_table :albums do |t|
18
+ t.string :title
19
+ t.references :singer, index: false, foreign_key: true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # This file is auto-generated from the current state of the database. Instead
2
+ # of editing this file, please use the migrations feature of Active Record to
3
+ # incrementally modify your database, and then regenerate this schema definition.
4
+ #
5
+ # This file is the source Rails uses to define your schema when running `bin/rails
6
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7
+ # be faster and is potentially less error prone than running all of your
8
+ # migrations from scratch. Old migrations may fail to apply correctly if those
9
+ # migrations use external dependencies or application code.
10
+ #
11
+ # It's strongly recommended that you check this file into your version control system.
12
+
13
+ ActiveRecord::Schema.define(version: 1) do
14
+
15
+ create_table "albums", id: { limit: 8 }, force: :cascade do |t|
16
+ t.string "title"
17
+ t.integer "singer_id", limit: 8
18
+ end
19
+
20
+ create_table "singers", id: { limit: 8 }, force: :cascade do |t|
21
+ t.string "first_name", limit: 100
22
+ t.string "last_name", limit: 200, null: false
23
+ t.string "full_name", limit: 300, null: false
24
+ t.index ["full_name"], name: "index_singers_on_full_name", order: { full_name: :asc }
25
+ end
26
+
27
+ add_foreign_key "albums", "singers"
28
+ end
@@ -0,0 +1,29 @@
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
+ require_relative "../../config/environment.rb"
8
+ require_relative "../models/singer"
9
+ require_relative "../models/album"
10
+
11
+ first_names = %w[Pete Alice John Ethel Trudy Naomi Wendy Ruben Thomas Elly]
12
+ last_names = %w[Wendelson Allison Peterson Johnson Henderson Ericsson Aronson Tennet Courtou]
13
+
14
+ adjectives = %w[daily happy blue generous cooked bad open]
15
+ nouns = %w[windows potatoes bank street tree glass bottle]
16
+
17
+ # This ensures all the records are inserted using one read/write transaction that will use mutations instead of DML.
18
+ ActiveRecord::Base.transaction isolation: :buffered_mutations do
19
+ singers = []
20
+ 5.times do
21
+ singers << Singer.create(first_name: first_names.sample, last_name: last_names.sample)
22
+ end
23
+
24
+ albums = []
25
+ 20.times do
26
+ singer = singers.sample
27
+ albums << Album.create(title: "#{adjectives.sample} #{nouns.sample}", singer: singer)
28
+ end
29
+ end
@@ -0,0 +1,9 @@
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
+ class Album < ActiveRecord::Base
8
+ belongs_to :singer
9
+ end
@@ -0,0 +1,9 @@
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
+ class Singer < ActiveRecord::Base
8
+ has_many :albums
9
+ end
@@ -0,0 +1,152 @@
1
+ # Sample - Interleaved Tables
2
+
3
+ This example shows how to use interleaved tables with the Spanner ActiveRecord adapter.
4
+
5
+ See https://cloud.google.com/spanner/docs/schema-and-data-model#creating-interleaved-tables for more information
6
+ on interleaved tables if you are not familiar with this concept.
7
+
8
+ ## Creating Interleaved Tables in ActiveRecord
9
+ You can create interleaved tables using migrations in ActiveRecord by using the following Spanner ActiveRecord specific
10
+ methods that are defined on `TableDefinition`:
11
+ * `interleave_in`: Specifies which parent table a child table should be interleaved in and optionally whether
12
+ deletes of a parent record should automatically cascade delete all child records.
13
+ * `parent_key`: Creates a column that is a reference to (a part of) the primary key of the parent table. Each child
14
+ table must include all the primary key columns of the parent table as a `parent_key`.
15
+
16
+ Cloud Spanner requires a child table to include the exact same primary key columns as the parent table in addition to
17
+ the primary key column(s) of the child table. This means that the default `id` primary key column of ActiveRecord is
18
+ not usable in combination with interleaved tables. Instead each primary key column should be prefixed with the table
19
+ name of the table that it references, or use some other unique name.
20
+
21
+ This example uses the following table schema:
22
+
23
+ ```sql
24
+ CREATE TABLE singers (
25
+ singerid INT64 NOT NULL,
26
+ first_name STRING(MAX),
27
+ last_name STRING(MAX)
28
+ ) PRIMARY KEY (singerid);
29
+
30
+ CREATE TABLE albums (
31
+ albumid INT64 NOT NULL,
32
+ singerid INT64 NOT NULL,
33
+ title STRING(MAX)
34
+ ) PRIMARY KEY (singerid, albumid), INTERLEAVE IN PARENT singers;
35
+
36
+ CREATE TABLE tracks (
37
+ trackid INT64 NOT NULL,
38
+ singerid INT64 NOT NULL,
39
+ albumid INT64 NOT NULL,
40
+ title STRING(MAX),
41
+ duration NUMERIC
42
+ ) PRIMARY KEY (singerid, albumid, trackid), INTERLEAVE IN PARENT albums ON DELETE CASCADE;
43
+ ```
44
+
45
+ This schema can be created in ActiveRecord as follows:
46
+
47
+ ```ruby
48
+ create_table :singers, id: false do |t|
49
+ # Explicitly define the primary key with a custom name to prevent all primary key columns from being named `id`.
50
+ t.primary_key :singerid
51
+ t.string :first_name
52
+ t.string :last_name
53
+ end
54
+
55
+ create_table :albums, id: false do |t|
56
+ # Interleave the `albums` table in the parent table `singers`.
57
+ t.interleave_in :singers
58
+ t.primary_key :albumid
59
+ # `singerid` is defined as a `parent_key` which makes it a part of the primary key in the table definition, but
60
+ # it is not presented to ActiveRecord as part of the primary key, to prevent ActiveRecord from considering this
61
+ # to be an entity with a composite primary key (which is not supported by ActiveRecord).
62
+ t.parent_key :singerid
63
+ t.string :title
64
+ end
65
+
66
+ create_table :tracks, id: false do |t|
67
+ # Interleave the `tracks` table in the parent table `albums` and cascade delete all tracks that belong to an
68
+ # album when an album is deleted.
69
+ t.interleave_in :albums, :cascade
70
+ # `trackid` is considered the only primary key column by ActiveRecord.
71
+ t.primary_key :trackid
72
+ # `singerid` and `albumid` form the parent key of `tracks`. These are part of the primary key definition in the
73
+ # database, but are presented as parent keys to ActiveRecord.
74
+ t.parent_key :singerid
75
+ t.parent_key :albumid
76
+ t.string :title
77
+ t.numeric :duration
78
+ end
79
+ ```
80
+
81
+ ## Models for Interleaved Tables
82
+ An interleaved table parent/child relationship must be modelled as a `belongs_to`/`has_many` association in
83
+ ActiveRecord. As the columns that are used to reference a parent record use a custom column name, it is required to also
84
+ include the custom column name in the `belongs_to` and `has_many` definitions.
85
+
86
+ Instances of these models can be used in the same way as any other association in ActiveRecord, but with a couple of
87
+ inherent limitations:
88
+ * It is not possible to change the parent record of a child record. For instance, changing the singer of an album in the
89
+ above example is impossible, as Cloud Spanner does not allow such an update.
90
+ * It is not possible to de-reference a parent record by setting it to null.
91
+ * It is only possible to delete a parent record with existing child records, if the child records are also deleted. This
92
+ can be done by enabling ON DELETE CASCADE in Cloud Spanner, or by deleting the child records using ActiveRecord.
93
+
94
+ ### Example Models
95
+
96
+ ```ruby
97
+ class Singer < ActiveRecord::Base
98
+ # `albums` is defined as INTERLEAVE IN PARENT `singers`. The primary key of `albums` is (`singerid`, `albumid`), but
99
+ # only `albumid` is used by ActiveRecord as the primary key. The `singerid` column is defined as a `parent_key` of
100
+ # `albums` (see also the `db/migrate/01_create_tables.rb` file).
101
+ has_many :albums, foreign_key: "singerid"
102
+
103
+ # `tracks` is defined as INTERLEAVE IN PARENT `albums`. The primary key of `tracks` is
104
+ # (`singerid`, `albumid`, `trackid`), but only `trackid` is used by ActiveRecord as the primary key. The `singerid`
105
+ # and `albumid` columns are defined as `parent_key` of `tracks` (see also the `db/migrate/01_create_tables.rb` file).
106
+ # The `singerid` column can therefore be used to associate tracks with a singer without the need to go through albums.
107
+ # Note also that the inclusion of `singerid` as a column in `tracks` is required in order to make `tracks` a child
108
+ # table of `albums` which has primary key (`singerid`, `albumid`).
109
+ has_many :tracks, foreign_key: "singerid"
110
+ end
111
+
112
+ class Album < ActiveRecord::Base
113
+ # `albums` is defined as INTERLEAVE IN PARENT `singers`. The primary key of `singers` is `singerid`.
114
+ belongs_to :singer, foreign_key: "singerid"
115
+
116
+ # `tracks` is defined as INTERLEAVE IN PARENT `albums`. The primary key of `albums` is (`singerid`, `albumid`), but
117
+ # only `albumid` is used by ActiveRecord as the primary key. The `singerid` column is defined as a `parent_key` of
118
+ # `albums` (see also the `db/migrate/01_create_tables.rb` file).
119
+ has_many :tracks, foreign_key: "albumid"
120
+ end
121
+
122
+ class Track < ActiveRecord::Base
123
+ # `tracks` is defined as INTERLEAVE IN PARENT `albums`. The primary key of `albums` is ()`singerid`, `albumid`).
124
+ belongs_to :album, foreign_key: "albumid"
125
+
126
+ # `tracks` also has a `singerid` column should be used to associate a Track with a Singer.
127
+ belongs_to :singer, foreign_key: "singerid"
128
+
129
+ # Override the default initialize method to automatically set the singer attribute when an album is given.
130
+ def initialize attributes = nil
131
+ super
132
+ self.singer ||= album&.singer
133
+ end
134
+
135
+ def album=value
136
+ super
137
+ # Ensure the singer of this track is equal to the singer of the album that is set.
138
+ self.singer = value&.singer
139
+ end
140
+ end
141
+ ```
142
+
143
+ ## Running the Sample
144
+
145
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
146
+ against that emulator. The emulator will automatically be stopped when the application finishes.
147
+
148
+ Run the application with the command
149
+
150
+ ```bash
151
+ bundle exec rake run
152
+ ```
@@ -0,0 +1,13 @@
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
+ require_relative "../config/environment"
8
+ require "sinatra/activerecord/rake"
9
+
10
+ desc "Sample showing how to work with interleaved tables in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[interleaved-tables]" }
13
+ end