activerecord-spanner-adapter 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,25 @@
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, null: false, limit: 200
13
+ t.string :full_name, null: false, limit: 300, as: "COALESCE(first_name || ' ', '') || last_name", stored: true
14
+ t.date :birth_date
15
+ t.binary :picture
16
+ end
17
+
18
+ create_table :albums do |t|
19
+ t.string :title
20
+ t.date :release_date
21
+ t.references :singer, index: false
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
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 "albums", force: :cascade do |t|
16
+ t.string "title"
17
+ t.date "release_date"
18
+ t.integer "singer_id", limit: 8
19
+ end
20
+
21
+ create_table "singers", force: :cascade 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
25
+ t.date "birth_date"
26
+ t.binary "picture"
27
+ end
28
+
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
data/bin/console CHANGED
@@ -1,14 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "activerecord/spanner/adapter"
4
+ require "activerecord-spanner-adapter"
5
+
6
+ require "active_record"
5
7
 
6
8
  # You can add fixtures and/or initialization code here to make experimenting
7
9
  # with your gem easier. You can also use a different console, if you like.
8
10
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
11
+ require "pry"
12
+ require "pry-byebug" # For easy debugging
13
+ Pry.start
@@ -0,0 +1,262 @@
1
+ ## activerecord-spanner-adapter for Rails tutorial
2
+
3
+ This example shows how to use activerecord-spanner-adapter for Cloud Spanner as a backend database for [Rails's tutorials](https://guides.rubyonrails.org/getting_started.html).
4
+
5
+ ### Create a Spanner instance
6
+ This step will create a Cloud Spanner instance. Cloud Spanner is a billable component of the Google Cloud. For information on the cost of using Cloud Spanner, see [Pricing](https://cloud.google.com/spanner/pricing).
7
+
8
+ __Note__: If you want to try the tutorial without Cloud Spanner cost, you can use the emulator. Read the [doc](https://cloud.google.com/spanner/docs/emulator) for more details.
9
+
10
+ 1. Set the project environment variable:
11
+ ```shell
12
+ export PROJECT_ID=[your-cloud-project-id]
13
+ ```
14
+ 1. Set the default project:
15
+ ```shell
16
+ gcloud config set project $PROJECT_ID
17
+ ```
18
+ 1. If you haven't enabled the Cloud Spanner service, enable it:
19
+ ```shell
20
+ gcloud services enable spanner.googleapis.com
21
+ ```
22
+ 1. Create an instance. This tutorial will use a one-node instance in `regional-us-central1`.
23
+ You can choose a region close to you:
24
+ ```shell
25
+ gcloud spanner instances create test-instance --config=regional-us-central1 \
26
+ --description="Rails Demo Instance" --nodes=1
27
+ ```
28
+ 1. Verify the instance has been created:
29
+ ```shell
30
+ gcloud spanner instances list
31
+ ```
32
+ You should see output like the following:
33
+ ```
34
+ NAME DISPLAY_NAME CONFIG NODE_COUNT STATE
35
+ test-instance Rails Demo Instance regional-us-central1 1 READY
36
+ ```
37
+ 1. Set the default instance:
38
+ ```shell
39
+ gcloud config set spanner/instance test-instance
40
+ ```
41
+
42
+ Read the [Cloud Spanner setup guide](https://cloud.google.com/spanner/docs/getting-started/set-up) for more details.
43
+
44
+ ### Create a Rails project
45
+
46
+ If you don't have Ruby and Rails installed, please follow the steps in the following links to install them:
47
+
48
+ - [Installing Ruby](https://www.ruby-lang.org/en/documentation/installation/)
49
+ - [Installing Rails](https://guides.rubyonrails.org/getting_started.html#creating-a-new-rails-project-installing-rails)
50
+
51
+ If you are not familiar with Active Record, you can read more about it on [Ruby on Rails Guides](https://guides.rubyonrails.org/)
52
+
53
+ 1. Verify the Ruby and Rails versions:
54
+ ```shell
55
+ ruby --version
56
+ rails --version
57
+ ```
58
+ The versions should be Ruby 2.6 or higher and Rails 6.0 or higher.
59
+ 1. Create a new Rails project:
60
+
61
+ ```shell
62
+ rails new blog
63
+ ```
64
+ 1. The `blog` directory will have a number of generated files and folders that make up the structure of a Rails application. You can list them:
65
+ ```shell
66
+ cd blog
67
+ ls -l
68
+ ```
69
+ 1. Starting up the web server and make sure the sample application works:
70
+ ```shell
71
+ bin/rails server
72
+ ```
73
+ After the server starts, open your browser and navigate to [http://localhost:3000](http://localhost:3000). You should see the default Rails page with the sentence: __Yay! You're on Rails!__
74
+
75
+ ### Create a service account
76
+
77
+ 1. Create a service account:
78
+ ```shell
79
+ gcloud iam service-accounts create activerecord-spanner
80
+ ```
81
+ 1. Grant an IAM role to access Cloud Spanner:
82
+ ```shell
83
+ gcloud projects add-iam-policy-binding ${PROJECT_ID} \
84
+ --member="serviceAccount:activerecord-spanner@${PROJECT_ID}.iam.gserviceaccount.com" \
85
+ --role="roles/spanner.databaseAdmin"
86
+ ```
87
+ Here the role `roles/spanner.databaseAdmin` is granted to the service account. If you want to restrict the permissions further, you can choose to create a custom role with proper permissions.
88
+ 1. Create a key file and download it:
89
+ ```shell
90
+ gcloud iam service-accounts keys create activerecord-spanner-key.json \
91
+ --iam-account=activerecord-spanner@${PROJECT_ID}.iam.gserviceaccount.com
92
+ ```
93
+ This tutorial uses the key file to access Cloud Spanner. If you are using services such as Compute Engine, Cloud Run, or Cloud Functions, you can associate the service account to the instance and avoid using the key file.
94
+ 1. From the previous step, a key file should be created and downloaded. You can run the following command to view its content:
95
+ ```shell
96
+ cat activerecord-spanner-key.json
97
+ ```
98
+
99
+ ### Use Cloud Spanner adapter in Gemfile
100
+ 1. Edit the Gemfile file of the `blog` app and add the `activerecord-spanner-adapter` gem:
101
+ ```ruby
102
+ gem 'activerecord-spanner-adapter'
103
+ ```
104
+ 1. Install gems:
105
+
106
+ ```shell
107
+ bundle install
108
+ ```
109
+
110
+ ### Update database.yml to use Cloud Spanner.
111
+ After the Cloud Spanner instance is running, you'll need a few variables:
112
+ * Cloud project id
113
+ * Cloud Spanner instance id, such as `test-instance`
114
+ * Database name, such as `blog_dev`
115
+ * Credential: Credential key file path or export `GOOGLE_CLOUD_KEYFILE`environment variable.
116
+
117
+ Edit the file `config/database.yml` and make the section `DATABASES` into the following:
118
+
119
+ ```yml
120
+ default: &default
121
+ adapter: "spanner"
122
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 10 } %>
123
+ project: [PROJECT_ID]
124
+ instance: test-instance
125
+ credentials: activerecord-spanner-key.json
126
+
127
+ development:
128
+ <<: *default
129
+ database: blog_dev
130
+
131
+ test:
132
+ <<: *default
133
+ database: blog_test
134
+
135
+ production:
136
+ <<: *default
137
+ database: blog
138
+ ```
139
+
140
+ Replace `[PROJECT_ID]` with the project id you are currently using.
141
+
142
+ ### Create database
143
+
144
+ You now can run the following command to create the database:
145
+ ```shell
146
+ ./bin/rails db:create
147
+ ```
148
+ You should see output like the following:
149
+ ```
150
+ Created database 'blog_dev'
151
+ ```
152
+ ### Generate a Model and apply the migration
153
+ 1. Use the model generato to define a model:
154
+ ```shell
155
+ bin/rails generate model Article title:string body:text
156
+ ```
157
+ 1. Apply the migration:
158
+ ```shell
159
+ ./bin/rails db:migrate
160
+ ```
161
+ The command takes a while to complete. When it's done, you will have an output like the following:
162
+ ```
163
+ $ ./bin/rails db:migrate
164
+ == 20210803025742 CreateArticles: migrating ===================================
165
+ -- create_table(:articles)
166
+ -> 23.5728s
167
+ == 20210803025742 CreateArticles: migrated (23.5729s) =========================
168
+ ```
169
+
170
+ ### Use the CLI to interact with the database
171
+ 1. Run the following command to start `irb`:
172
+ ```shell
173
+ bin/rails console
174
+ ```
175
+ 1. At the prompt, initialize a new `Article` object:
176
+ ```ruby
177
+ article = Article.new(title: "Hello Rails", body: "I am on Rails!")
178
+ ```
179
+ 1. Run the following command to save the object to the database:
180
+ ```ruby
181
+ article.save
182
+ ```
183
+ 1. Review the object and you can see the field `id`, `created_at`, and `updated_at` have been set:
184
+ ```ruby
185
+ article
186
+ ```
187
+ Sample output:
188
+ ```
189
+ => #<Article id: 4170057092403543076, title: "Hello Rails", body: "I am on Rails!", created_at: "2021-08-03 03:06:26.096275000 +0000", updated_at: "2021-08-03 03:06:26.096275000 +0000">
190
+ ```
191
+ 1. You can find `Article.find(id)` or `Article.all` to fetch data from the database. For example:
192
+ ```ruby
193
+ irb(main):007:0> Article.find(4170057092403543076)
194
+ Article Load (49.2ms) SELECT `articles`.* FROM `articles` WHERE `articles`.`id` = @p1 LIMIT @p2
195
+ => #<Article id: 4170057092403543076, title: "Hello Rails", body: "I am on Rails!", created_at: "2021-08-03 03:06:26.096275000 +0000", updated_at: "2021-08-03 03:06:26.096275000 +0000">
196
+
197
+ irb(main):008:0> Article.all
198
+ Article Load (73.9ms) SELECT `articles`.* FROM `articles` /* loading for inspect */ LIMIT @p1
199
+ => #<ActiveRecord::Relation [#<Article id: 4170057092403543076, title: "Hello Rails", body: "I am on Rails!", created_at: "2021-08-03 03:06:26.096275000 +0000", updated_at: "2021-08-03 03:06:26.096275000 +0000">]>
200
+ ```
201
+
202
+ ### Update the app to show a list of records
203
+ 1. Use the controller generator to create a controller:
204
+ ```shell
205
+ bin/rails generate controller Articles index
206
+ ```
207
+ 1. Open the file `app/controllers/articles_controller.rb`, and change the `index` action to fetch all articles from the database:
208
+ ```ruby
209
+ class ArticlesController < ApplicationController
210
+ def index
211
+ @articles = Article.all
212
+ end
213
+ end
214
+ ```
215
+ 1. Open `app/views/articles/index.html.erb`, and update the file as the following:
216
+ ```html
217
+ <h1>Articles</h1>
218
+ <ul>
219
+ <% @articles.each do |article| %>
220
+ <li>
221
+ <%= article.title %>
222
+ </li>
223
+ <% end %>
224
+ </ul>
225
+ ```
226
+ 1. Run your server again
227
+ ```shell
228
+ ./bin/rails s
229
+ ```
230
+ 1. In your browser, navigate to the URL [http://localhost:3000/articles/index](http://localhost:3000/articles/index). And you will see the `Hello Rails` record you entered previously.
231
+ 1. [Optional] you can follow the rest of the steps in the [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html) guide.
232
+
233
+ ### Clean up
234
+
235
+ To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, you can delete the resources you created. You can either
236
+ delete the entire project or delete individual resources.
237
+
238
+ Deleting a project has the following effects:
239
+
240
+ * Everything in the project is deleted. If you used an existing project for this tutorial, when you delete it, you also delete any other work you've done in the
241
+ project.
242
+ * Custom project IDs are lost. When you created this project, you might have created a custom project ID that you want to use in the future. To preserve the URLs
243
+ that use the project ID, delete selected resources inside the project instead of deleting the whole project.
244
+
245
+ If you plan to explore multiple tutorials, reusing projects can help you to avoid exceeding project quota limits.
246
+
247
+ #### Delete the project
248
+
249
+ The easiest way to eliminate billing is to delete the project you created for the tutorial.
250
+
251
+ 1. In the Cloud Console, go to the [**Manage resources** page](https://console.cloud.google.com/iam-admin/projects).
252
+ 1. In the project list, select the project that you want to delete and then click **Delete**.
253
+ 1. In the dialog, type the project ID and then click **Shut down** to delete the project.
254
+
255
+ #### Delete the resources
256
+
257
+ If you don't want to delete the project, you can delete the provisioned resources:
258
+
259
+ gcloud iam service-accounts delete \
260
+ activerecord-spanner@${PROJECT_ID}.iam.gserviceaccount.com
261
+
262
+ gcloud spanner instances delete test-instance
@@ -0,0 +1,29 @@
1
+ # Sample snippets
2
+
3
+ This directory contains a number of simple standalone samples that show how to use ActiveRecord with Cloud Spanner.
4
+
5
+ ### Running from this directory
6
+ The samples can be executed using the following command in this directory:
7
+
8
+ ```bash
9
+ bundle exec rake run\[<sample_name>\]
10
+ ```
11
+
12
+ Example:
13
+
14
+ ```bash
15
+ bundle exec rake run\[quickstart\]
16
+ ```
17
+
18
+ The available samples can be listed using the command
19
+
20
+ ```bash
21
+ bundle exec rake list
22
+ ```
23
+
24
+ ### Running from sample directory
25
+ You can also run a sample by calling the following command __in the directory of the sample__:
26
+
27
+ ```bash
28
+ bundle exec rake run
29
+ ```
@@ -0,0 +1,57 @@
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 "docker"
9
+
10
+ desc "Lists all available samples."
11
+ task :list do
12
+ samples = Dir.entries(".").select do |entry|
13
+ File.directory?(File.join(".", entry)) \
14
+ && !%w[. ..].include?(entry) \
15
+ && File.exist?(File.join(".", entry, "application.rb"))
16
+ end
17
+ puts "Available samples: "
18
+ samples.sort.each { |dir| puts " #{dir}" }
19
+ puts ""
20
+ puts "Run a sample with the command `bundle exec rake run\\[<sample-name>\\]`"
21
+ end
22
+
23
+ desc "Runs a simple ActiveRecord tutorial on a Spanner emulator."
24
+ task :run, [:sample] do |_t, args|
25
+ sample = args[:sample]
26
+ unless sample
27
+ puts "Missing argument :sample. Running quickstart sample"
28
+ puts ""
29
+ sample = "quickstart"
30
+ end
31
+
32
+ puts "Downloading Spanner emulator image..."
33
+ Docker::Image.create "fromImage" => "gcr.io/cloud-spanner-emulator/emulator:latest"
34
+ puts "Creating Spanner emulator container..."
35
+ container = Docker::Container.create(
36
+ "Image" => "gcr.io/cloud-spanner-emulator/emulator:latest",
37
+ "ExposedPorts" => { "9010/tcp" => {} },
38
+ "HostConfig" => {
39
+ "PortBindings" => {
40
+ "9010/tcp" => [{ "HostPort" => "9010" }]
41
+ }
42
+ }
43
+ )
44
+
45
+ begin
46
+ puts "Starting Spanner emulator..."
47
+ container.start!
48
+ Dir.chdir sample do
49
+ sh "ruby ../bin/create_emulator_instance.rb"
50
+ sh "rake db:migrate"
51
+ sh "rake db:seed"
52
+ sh "ruby application.rb"
53
+ end
54
+ ensure
55
+ container.stop!
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ # Sample - Array Data Type
2
+
3
+ This example shows how to use the `ARRAY` data type with the Spanner ActiveRecord adapter. The sample uses a single
4
+ table that has one column for each possible `ARRAY` data type:
5
+
6
+ ```sql
7
+ CREATE TABLE entity_with_array_types (
8
+ id INT64 NOT NULL,
9
+ col_array_string ARRAY<STRING(MAX)>,
10
+ col_array_int64 ARRAY<INT64>,
11
+ col_array_float64 ARRAY<FLOAT64>,
12
+ col_array_numeric ARRAY<NUMERIC>,
13
+ col_array_bool ARRAY<BOOL>,
14
+ col_array_bytes ARRAY<BYTES(MAX)>,
15
+ col_array_date ARRAY<DATE>,
16
+ col_array_timestamp ARRAY<TIMESTAMP>,
17
+ ) PRIMARY KEY (id);
18
+ ```
19
+
20
+ This schema is created in ActiveRecord as follows:
21
+
22
+ ```ruby
23
+ create_table :entity_with_array_types do |t|
24
+ # Create a table with a column with each possible array type.
25
+ t.column :col_array_string, :string, array: true
26
+ t.column :col_array_int64, :bigint, array: true
27
+ t.column :col_array_float64, :float, array: true
28
+ t.column :col_array_numeric, :numeric, array: true
29
+ t.column :col_array_bool, :boolean, array: true
30
+ t.column :col_array_bytes, :binary, array: true
31
+ t.column :col_array_date, :date, array: true
32
+ t.column :col_array_timestamp, :datetime, array: true
33
+ end
34
+ ```
35
+
36
+ ## Running the Sample
37
+
38
+ The sample will automatically start a Spanner Emulator in a docker container and execute the sample
39
+ against that emulator. The emulator will automatically be stopped when the application finishes.
40
+
41
+ Run the application with the command
42
+
43
+ ```bash
44
+ bundle exec rake run
45
+ ```
@@ -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 array data types in ActiveRecord."
11
+ task :run do
12
+ Dir.chdir("..") { sh "bundle exec rake run[array-data-type]" }
13
+ end
@@ -0,0 +1,45 @@
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/entity_with_array_types"
10
+
11
+ class Application
12
+ def self.run # rubocop:disable Metrics/AbcSize
13
+ # Create a record with all array types.
14
+ record = EntityWithArrayTypes.create \
15
+ col_array_string: ["value1", "value2", "value3"],
16
+ col_array_int64: [100, 200, 300],
17
+ col_array_float64: [3.14, 2.0 / 3.0],
18
+ col_array_numeric: [6.626, 3.20],
19
+ # All arrays can contain null elements.
20
+ col_array_bool: [true, false, nil, true],
21
+ col_array_bytes: [StringIO.new("value1"), StringIO.new("value2")],
22
+ col_array_date: [::Date.new(2021, 6, 23), ::Date.new(2021, 6, 28)],
23
+ # Timestamps can be specified in any timezone, but Cloud Spanner will always convert and store them in UTC.
24
+ col_array_timestamp: [::Time.new(2021, 6, 23, 17, 8, 21, "+02:00"), ::Time.utc(2021, 6, 23, 17, 8, 21)]
25
+
26
+ # Reload the record from Cloud Spanner and print out the values.
27
+ record = record.reload
28
+ puts ""
29
+ puts "Saved record #{record.id} with array values: "
30
+ puts "String array: #{record.col_array_string}"
31
+ puts "Int64 array: #{record.col_array_int64}"
32
+ puts "Float64 array: #{record.col_array_float64}"
33
+ puts "Numeric array: #{record.col_array_numeric}"
34
+ puts "Bool array: #{record.col_array_bool}"
35
+ puts "Bytes array: #{record.col_array_bytes.map(&:read)}"
36
+ puts "Date array: #{record.col_array_date}"
37
+ puts "Timestamp array: #{record.col_array_timestamp}"
38
+
39
+ puts ""
40
+ puts "Press any key to end the application"
41
+ STDIN.getch
42
+ end
43
+ end
44
+
45
+ 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,24 @@
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
+ # Execute the entire migration as one DDL batch.
10
+ connection.ddl_batch do
11
+ create_table :entity_with_array_types do |t|
12
+ # Create a table with a column with each possible array type.
13
+ t.column :col_array_string, :string, array: true
14
+ t.column :col_array_int64, :bigint, array: true
15
+ t.column :col_array_float64, :float, array: true
16
+ t.column :col_array_numeric, :numeric, array: true
17
+ t.column :col_array_bool, :boolean, array: true
18
+ t.column :col_array_bytes, :binary, array: true
19
+ t.column :col_array_date, :date, array: true
20
+ t.column :col_array_timestamp, :datetime, array: true
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
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 "entity_with_array_types", force: :cascade do |t|
16
+ t.string "col_array_string"
17
+ t.integer "col_array_int64", limit: 8
18
+ t.float "col_array_float64"
19
+ t.decimal "col_array_numeric"
20
+ t.boolean "col_array_bool"
21
+ t.binary "col_array_bytes"
22
+ t.date "col_array_date"
23
+ t.time "col_array_timestamp"
24
+ end
25
+
26
+ end
@@ -0,0 +1,5 @@
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.
@@ -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
+ class EntityWithArrayTypes < ActiveRecord::Base
8
+ # This entity has one attribute for each possible ARRAY data type in Cloud Spanner.
9
+ # col_array_string ARRAY<STRING(MAX)>
10
+ # col_array_int64 ARRAY<INT64>
11
+ # col_array_float64 ARRAY<FLOAT64>
12
+ # col_array_numeric ARRAY<NUMERIC>
13
+ # col_array_bool ARRAY<BOOL>
14
+ # col_array_bytes ARRAY<BYTES(MAX)>
15
+ # col_array_date ARRAY<DATE>
16
+ # col_array_timestamp ARRAY<TIMESTAMP>
17
+ #
18
+ 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 "google/cloud/spanner"
8
+
9
+ spanner = Google::Cloud::Spanner.new project: "test-project", emulator_host: "localhost:9010"
10
+ job = spanner.create_instance "test-instance",
11
+ name: "Test Instance",
12
+ config: "emulator-config",
13
+ nodes: 1
14
+ job.wait_until_done!
15
+
16
+ instance = spanner.instance "test-instance"
17
+ job = instance.create_database "testdb", statements: []
18
+ job.wait_until_done!