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.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +7 -0
- data/.github/sync-repo-settings.yaml +16 -0
- data/.github/workflows/acceptance-tests-on-emulator.yaml +45 -0
- data/.github/workflows/acceptance-tests-on-production.yaml +36 -0
- data/.github/workflows/ci.yaml +33 -0
- data/.github/workflows/nightly-acceptance-tests-on-emulator.yaml +52 -0
- data/.github/workflows/nightly-acceptance-tests-on-production.yaml +35 -0
- data/.github/workflows/nightly-unit-tests.yaml +40 -0
- data/.github/workflows/release-please-label.yml +25 -0
- data/.github/workflows/release-please.yml +39 -0
- data/.github/workflows/rubocop.yaml +31 -0
- data/.gitignore +67 -5
- data/.kokoro/populate-secrets.sh +77 -0
- data/.kokoro/release.cfg +33 -0
- data/.kokoro/release.sh +15 -0
- data/.kokoro/trampoline_v2.sh +489 -0
- data/.rubocop.yml +46 -0
- data/.toys/release.rb +18 -0
- data/.trampolinerc +48 -0
- data/.yardopts +11 -0
- data/CHANGELOG.md +26 -0
- data/CODE_OF_CONDUCT.md +40 -0
- data/CONTRIBUTING.md +79 -0
- data/Gemfile +9 -4
- data/LICENSE +6 -6
- data/README.md +67 -30
- data/Rakefile +79 -3
- data/SECURITY.md +7 -0
- data/acceptance/cases/associations/has_many_associations_test.rb +119 -0
- data/acceptance/cases/associations/has_many_through_associations_test.rb +63 -0
- data/acceptance/cases/associations/has_one_associations_test.rb +79 -0
- data/acceptance/cases/associations/has_one_through_associations_test.rb +98 -0
- data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb +211 -0
- data/acceptance/cases/migration/change_schema_test.rb +433 -0
- data/acceptance/cases/migration/change_table_test.rb +115 -0
- data/acceptance/cases/migration/column_attributes_test.rb +122 -0
- data/acceptance/cases/migration/column_positioning_test.rb +48 -0
- data/acceptance/cases/migration/columns_test.rb +201 -0
- data/acceptance/cases/migration/command_recorder_test.rb +406 -0
- data/acceptance/cases/migration/create_join_table_test.rb +216 -0
- data/acceptance/cases/migration/ddl_batching_test.rb +80 -0
- data/acceptance/cases/migration/foreign_key_test.rb +297 -0
- data/acceptance/cases/migration/index_test.rb +211 -0
- data/acceptance/cases/migration/references_foreign_key_test.rb +259 -0
- data/acceptance/cases/migration/references_index_test.rb +135 -0
- data/acceptance/cases/migration/references_statements_test.rb +166 -0
- data/acceptance/cases/migration/rename_column_test.rb +96 -0
- data/acceptance/cases/models/calculation_query_test.rb +128 -0
- data/acceptance/cases/models/generated_column_test.rb +126 -0
- data/acceptance/cases/models/mutation_test.rb +122 -0
- data/acceptance/cases/models/query_test.rb +147 -0
- data/acceptance/cases/sessions/session_not_found_test.rb +121 -0
- data/acceptance/cases/transactions/optimistic_locking_test.rb +141 -0
- data/acceptance/cases/transactions/read_only_transactions_test.rb +67 -0
- data/acceptance/cases/transactions/read_write_transactions_test.rb +248 -0
- data/acceptance/cases/type/all_types_test.rb +152 -0
- data/acceptance/cases/type/binary_test.rb +59 -0
- data/acceptance/cases/type/boolean_test.rb +31 -0
- data/acceptance/cases/type/date_test.rb +32 -0
- data/acceptance/cases/type/date_time_test.rb +30 -0
- data/acceptance/cases/type/float_test.rb +27 -0
- data/acceptance/cases/type/integer_test.rb +44 -0
- data/acceptance/cases/type/numeric_test.rb +27 -0
- data/acceptance/cases/type/string_test.rb +79 -0
- data/acceptance/cases/type/text_test.rb +30 -0
- data/acceptance/cases/type/time_test.rb +87 -0
- data/acceptance/models/account.rb +13 -0
- data/acceptance/models/address.rb +9 -0
- data/acceptance/models/album.rb +12 -0
- data/acceptance/models/all_types.rb +8 -0
- data/acceptance/models/author.rb +11 -0
- data/acceptance/models/club.rb +12 -0
- data/acceptance/models/comment.rb +9 -0
- data/acceptance/models/customer.rb +9 -0
- data/acceptance/models/department.rb +9 -0
- data/acceptance/models/firm.rb +10 -0
- data/acceptance/models/member.rb +13 -0
- data/acceptance/models/member_type.rb +9 -0
- data/acceptance/models/membership.rb +10 -0
- data/acceptance/models/organization.rb +9 -0
- data/acceptance/models/post.rb +10 -0
- data/acceptance/models/singer.rb +10 -0
- data/acceptance/models/track.rb +20 -0
- data/acceptance/models/transaction.rb +9 -0
- data/acceptance/schema/schema.rb +143 -0
- data/acceptance/test_helper.rb +260 -0
- data/activerecord-spanner-adapter.gemspec +32 -17
- data/assets/solidus-db.png +0 -0
- data/benchmarks/README.md +17 -0
- data/benchmarks/Rakefile +14 -0
- data/benchmarks/application.rb +308 -0
- data/benchmarks/config/database.yml +8 -0
- data/benchmarks/config/environment.rb +12 -0
- data/benchmarks/db/migrate/01_create_tables.rb +25 -0
- data/benchmarks/db/schema.rb +29 -0
- data/benchmarks/models/album.rb +9 -0
- data/benchmarks/models/singer.rb +9 -0
- data/bin/console +6 -7
- data/examples/rails/README.md +262 -0
- data/examples/snippets/README.md +29 -0
- data/examples/snippets/Rakefile +57 -0
- data/examples/snippets/array-data-type/README.md +45 -0
- data/examples/snippets/array-data-type/Rakefile +13 -0
- data/examples/snippets/array-data-type/application.rb +45 -0
- data/examples/snippets/array-data-type/config/database.yml +8 -0
- data/examples/snippets/array-data-type/db/migrate/01_create_tables.rb +24 -0
- data/examples/snippets/array-data-type/db/schema.rb +26 -0
- data/examples/snippets/array-data-type/db/seeds.rb +5 -0
- data/examples/snippets/array-data-type/models/entity_with_array_types.rb +18 -0
- data/examples/snippets/bin/create_emulator_instance.rb +18 -0
- data/examples/snippets/bulk-insert/README.md +21 -0
- data/examples/snippets/bulk-insert/Rakefile +13 -0
- data/examples/snippets/bulk-insert/application.rb +64 -0
- data/examples/snippets/bulk-insert/config/database.yml +8 -0
- data/examples/snippets/bulk-insert/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/bulk-insert/db/schema.rb +26 -0
- data/examples/snippets/bulk-insert/db/seeds.rb +5 -0
- data/examples/snippets/bulk-insert/models/album.rb +9 -0
- data/examples/snippets/bulk-insert/models/singer.rb +9 -0
- data/examples/snippets/commit-timestamp/README.md +18 -0
- data/examples/snippets/commit-timestamp/Rakefile +13 -0
- data/examples/snippets/commit-timestamp/application.rb +53 -0
- data/examples/snippets/commit-timestamp/config/database.yml +8 -0
- data/examples/snippets/commit-timestamp/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/commit-timestamp/db/schema.rb +29 -0
- data/examples/snippets/commit-timestamp/db/seeds.rb +5 -0
- data/examples/snippets/commit-timestamp/models/album.rb +9 -0
- data/examples/snippets/commit-timestamp/models/singer.rb +9 -0
- data/examples/snippets/config/environment.rb +21 -0
- data/examples/snippets/create-records/README.md +12 -0
- data/examples/snippets/create-records/Rakefile +13 -0
- data/examples/snippets/create-records/application.rb +42 -0
- data/examples/snippets/create-records/config/database.yml +8 -0
- data/examples/snippets/create-records/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/create-records/db/schema.rb +26 -0
- data/examples/snippets/create-records/db/seeds.rb +5 -0
- data/examples/snippets/create-records/models/album.rb +9 -0
- data/examples/snippets/create-records/models/singer.rb +9 -0
- data/examples/snippets/date-data-type/README.md +19 -0
- data/examples/snippets/date-data-type/Rakefile +13 -0
- data/examples/snippets/date-data-type/application.rb +35 -0
- data/examples/snippets/date-data-type/config/database.yml +8 -0
- data/examples/snippets/date-data-type/db/migrate/01_create_tables.rb +20 -0
- data/examples/snippets/date-data-type/db/schema.rb +21 -0
- data/examples/snippets/date-data-type/db/seeds.rb +16 -0
- data/examples/snippets/date-data-type/models/singer.rb +8 -0
- data/examples/snippets/generated-column/README.md +41 -0
- data/examples/snippets/generated-column/Rakefile +13 -0
- data/examples/snippets/generated-column/application.rb +37 -0
- data/examples/snippets/generated-column/config/database.yml +8 -0
- data/examples/snippets/generated-column/db/migrate/01_create_tables.rb +23 -0
- data/examples/snippets/generated-column/db/schema.rb +21 -0
- data/examples/snippets/generated-column/db/seeds.rb +18 -0
- data/examples/snippets/generated-column/models/singer.rb +8 -0
- data/examples/snippets/interleaved-tables/README.md +152 -0
- data/examples/snippets/interleaved-tables/Rakefile +13 -0
- data/examples/snippets/interleaved-tables/application.rb +109 -0
- data/examples/snippets/interleaved-tables/config/database.yml +8 -0
- data/examples/snippets/interleaved-tables/db/migrate/01_create_tables.rb +44 -0
- data/examples/snippets/interleaved-tables/db/schema.rb +32 -0
- data/examples/snippets/interleaved-tables/db/seeds.rb +40 -0
- data/examples/snippets/interleaved-tables/models/album.rb +15 -0
- data/examples/snippets/interleaved-tables/models/singer.rb +20 -0
- data/examples/snippets/interleaved-tables/models/track.rb +25 -0
- data/examples/snippets/migrations/README.md +43 -0
- data/examples/snippets/migrations/Rakefile +13 -0
- data/examples/snippets/migrations/application.rb +26 -0
- data/examples/snippets/migrations/config/database.yml +8 -0
- data/examples/snippets/migrations/db/migrate/01_create_tables.rb +28 -0
- data/examples/snippets/migrations/db/schema.rb +33 -0
- data/examples/snippets/migrations/db/seeds.rb +5 -0
- data/examples/snippets/migrations/models/album.rb +10 -0
- data/examples/snippets/migrations/models/singer.rb +10 -0
- data/examples/snippets/migrations/models/track.rb +9 -0
- data/examples/snippets/mutations/README.md +34 -0
- data/examples/snippets/mutations/Rakefile +13 -0
- data/examples/snippets/mutations/application.rb +47 -0
- data/examples/snippets/mutations/config/database.yml +8 -0
- data/examples/snippets/mutations/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/mutations/db/schema.rb +27 -0
- data/examples/snippets/mutations/db/seeds.rb +25 -0
- data/examples/snippets/mutations/models/album.rb +9 -0
- data/examples/snippets/mutations/models/singer.rb +9 -0
- data/examples/snippets/optimistic-locking/README.md +12 -0
- data/examples/snippets/optimistic-locking/Rakefile +13 -0
- data/examples/snippets/optimistic-locking/application.rb +48 -0
- data/examples/snippets/optimistic-locking/config/database.yml +8 -0
- data/examples/snippets/optimistic-locking/db/migrate/01_create_tables.rb +26 -0
- data/examples/snippets/optimistic-locking/db/schema.rb +29 -0
- data/examples/snippets/optimistic-locking/db/seeds.rb +25 -0
- data/examples/snippets/optimistic-locking/models/album.rb +9 -0
- data/examples/snippets/optimistic-locking/models/singer.rb +9 -0
- data/examples/snippets/quickstart/README.md +26 -0
- data/examples/snippets/quickstart/Rakefile +13 -0
- data/examples/snippets/quickstart/application.rb +51 -0
- data/examples/snippets/quickstart/config/database.yml +8 -0
- data/examples/snippets/quickstart/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/quickstart/db/schema.rb +26 -0
- data/examples/snippets/quickstart/db/seeds.rb +24 -0
- data/examples/snippets/quickstart/models/album.rb +9 -0
- data/examples/snippets/quickstart/models/singer.rb +9 -0
- data/examples/snippets/read-only-transactions/README.md +13 -0
- data/examples/snippets/read-only-transactions/Rakefile +13 -0
- data/examples/snippets/read-only-transactions/application.rb +49 -0
- data/examples/snippets/read-only-transactions/config/database.yml +8 -0
- data/examples/snippets/read-only-transactions/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/read-only-transactions/db/schema.rb +26 -0
- data/examples/snippets/read-only-transactions/db/seeds.rb +24 -0
- data/examples/snippets/read-only-transactions/models/album.rb +9 -0
- data/examples/snippets/read-only-transactions/models/singer.rb +9 -0
- data/examples/snippets/read-write-transactions/README.md +12 -0
- data/examples/snippets/read-write-transactions/Rakefile +13 -0
- data/examples/snippets/read-write-transactions/application.rb +39 -0
- data/examples/snippets/read-write-transactions/config/database.yml +8 -0
- data/examples/snippets/read-write-transactions/db/migrate/01_create_tables.rb +22 -0
- data/examples/snippets/read-write-transactions/db/schema.rb +27 -0
- data/examples/snippets/read-write-transactions/db/seeds.rb +25 -0
- data/examples/snippets/read-write-transactions/models/album.rb +9 -0
- data/examples/snippets/read-write-transactions/models/singer.rb +9 -0
- data/examples/snippets/timestamp-data-type/README.md +17 -0
- data/examples/snippets/timestamp-data-type/Rakefile +13 -0
- data/examples/snippets/timestamp-data-type/application.rb +42 -0
- data/examples/snippets/timestamp-data-type/config/database.yml +8 -0
- data/examples/snippets/timestamp-data-type/db/migrate/01_create_tables.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/schema.rb +21 -0
- data/examples/snippets/timestamp-data-type/db/seeds.rb +6 -0
- data/examples/snippets/timestamp-data-type/models/meeting.rb +19 -0
- data/examples/solidus/README.md +172 -0
- data/lib/active_record/connection_adapters/spanner/database_statements.rb +224 -269
- data/lib/active_record/connection_adapters/spanner/quoting.rb +42 -50
- data/lib/active_record/connection_adapters/spanner/schema_cache.rb +43 -0
- data/lib/active_record/connection_adapters/spanner/schema_creation.rb +125 -9
- data/lib/active_record/connection_adapters/spanner/schema_definitions.rb +122 -0
- data/lib/active_record/connection_adapters/spanner/schema_dumper.rb +19 -0
- data/lib/active_record/connection_adapters/spanner/schema_statements.rb +553 -139
- data/lib/active_record/connection_adapters/spanner/type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/spanner_adapter.rb +182 -78
- data/lib/active_record/tasks/spanner_database_tasks.rb +74 -0
- data/lib/active_record/type/spanner/array.rb +32 -0
- data/lib/active_record/type/spanner/bytes.rb +26 -0
- data/lib/active_record/type/spanner/spanner_active_record_converter.rb +32 -0
- data/lib/active_record/type/spanner/time.rb +37 -0
- data/lib/activerecord-spanner-adapter.rb +23 -0
- data/lib/activerecord_spanner_adapter/base.rb +217 -0
- data/lib/activerecord_spanner_adapter/connection.rb +324 -0
- data/lib/activerecord_spanner_adapter/errors.rb +13 -0
- data/lib/activerecord_spanner_adapter/foreign_key.rb +29 -0
- data/lib/activerecord_spanner_adapter/index/column.rb +38 -0
- data/lib/activerecord_spanner_adapter/index.rb +80 -0
- data/lib/activerecord_spanner_adapter/information_schema.rb +261 -0
- data/lib/activerecord_spanner_adapter/primary_key.rb +31 -0
- data/lib/activerecord_spanner_adapter/table/column.rb +59 -0
- data/lib/activerecord_spanner_adapter/table.rb +61 -0
- data/lib/activerecord_spanner_adapter/transaction.rb +113 -0
- data/lib/activerecord_spanner_adapter/version.rb +9 -0
- data/lib/arel/visitors/spanner.rb +35 -0
- data/lib/spanner_client_ext.rb +82 -0
- data/renovate.json +5 -0
- metadata +387 -34
- data/.travis.yml +0 -5
- data/lib/active_record/connection_adapters/spanner/client.rb +0 -190
- data/lib/active_record/connection_adapters/spanner.rb +0 -10
- data/lib/activerecord-spanner-adapter/version.rb +0 -3
data/acceptance/cases/interleaved_associations/has_many_associations_using_interleaved_test.rb
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Copyright 2021 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Use of this source code is governed by an MIT-style
|
|
4
|
+
# license that can be found in the LICENSE file or at
|
|
5
|
+
# https://opensource.org/licenses/MIT.
|
|
6
|
+
|
|
7
|
+
# frozen_string_literal: true
|
|
8
|
+
|
|
9
|
+
require "test_helper"
|
|
10
|
+
require "models/singer"
|
|
11
|
+
require "models/album"
|
|
12
|
+
require "models/track"
|
|
13
|
+
|
|
14
|
+
module ActiveRecord
|
|
15
|
+
module Associations
|
|
16
|
+
class HasManyUsingInterleavedTest < SpannerAdapter::TestCase
|
|
17
|
+
include SpannerAdapter::Associations::TestHelper
|
|
18
|
+
|
|
19
|
+
attr_accessor :singer, :album1, :album2
|
|
20
|
+
|
|
21
|
+
def setup
|
|
22
|
+
super
|
|
23
|
+
|
|
24
|
+
@singer = Singer.create first_name: "FirstName1", last_name: "LastName1"
|
|
25
|
+
|
|
26
|
+
@album2 = Album.create title: "Title2", singer: singer
|
|
27
|
+
@album1 = Album.create title: "Title1", singer: singer
|
|
28
|
+
|
|
29
|
+
@track2_1 = Track.create title: "Title2_1", album: album2, duration: 3.6
|
|
30
|
+
@track2_2 = Track.create title: "Title2_2", album: album2, duration: 3.3
|
|
31
|
+
@track1_1 = Track.create title: "Title1_1", album: album1, duration: 4.5
|
|
32
|
+
@track1_2 = Track.create title: "Title1_2", album: album1
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def teardown
|
|
36
|
+
Album.destroy_all
|
|
37
|
+
Singer.destroy_all
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_has_many
|
|
41
|
+
assert_equal 2, singer.albums.count
|
|
42
|
+
assert_equal singer.albums.pluck(:title).sort, %w[Title1 Title2]
|
|
43
|
+
|
|
44
|
+
assert_equal 4, singer.tracks.count
|
|
45
|
+
assert_equal singer.tracks.pluck(:title).sort, %w[Title1_1 Title1_2 Title2_1 Title2_2]
|
|
46
|
+
|
|
47
|
+
assert_equal 2, album1.tracks.count
|
|
48
|
+
assert_equal album1.tracks.pluck(:title).sort, %w[Title1_1 Title1_2]
|
|
49
|
+
assert_equal 2, album2.tracks.count
|
|
50
|
+
assert_equal album2.tracks.pluck(:title).sort, %w[Title2_1 Title2_2]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_finding_using_associated_fields
|
|
54
|
+
assert_equal Album.where(singerid: singer.id).to_a, singer.albums.to_a
|
|
55
|
+
assert_equal Track.where(singerid: singer.id).to_a, singer.tracks.to_a
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_successful_build_association
|
|
59
|
+
album = singer.albums.build title: "New Title"
|
|
60
|
+
assert album.save
|
|
61
|
+
|
|
62
|
+
singer.reload
|
|
63
|
+
assert_equal album, singer.albums.find(album.id)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_successful_build_nested_association
|
|
67
|
+
track = album1.tracks.build title: "New Title", duration: 4.45
|
|
68
|
+
assert track.save
|
|
69
|
+
|
|
70
|
+
album1.reload
|
|
71
|
+
assert_equal track, album1.tracks.find(track.id)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_create_and_destroy_associated_records
|
|
75
|
+
singer2 = Singer.new first_name: "First", last_name: "Last"
|
|
76
|
+
singer2.albums.build title: "New Title 1"
|
|
77
|
+
singer2.albums.build title: "New Title 2"
|
|
78
|
+
singer2.save!
|
|
79
|
+
|
|
80
|
+
singer2.reload
|
|
81
|
+
|
|
82
|
+
assert_equal 2, singer2.albums.count
|
|
83
|
+
assert_equal 4, Album.count
|
|
84
|
+
|
|
85
|
+
singer2.albums.destroy_all
|
|
86
|
+
singer2.reload
|
|
87
|
+
|
|
88
|
+
assert_equal 0, singer2.albums.count
|
|
89
|
+
assert_equal 2, Album.count
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_create_and_destroy_nested_associated_records
|
|
93
|
+
album3 = Album.new singer: singer, title: "Title 3"
|
|
94
|
+
album3.tracks.build title: "Title3_1", duration: 2.5, singer: singer
|
|
95
|
+
album3.tracks.build title: "Title3_2", singer: singer
|
|
96
|
+
album3.save!
|
|
97
|
+
|
|
98
|
+
album3.reload
|
|
99
|
+
|
|
100
|
+
assert_equal 2, album3.tracks.count
|
|
101
|
+
assert_equal 6, singer.tracks.count
|
|
102
|
+
assert_equal 6, Track.count
|
|
103
|
+
|
|
104
|
+
album3.tracks.destroy_all
|
|
105
|
+
album3.reload
|
|
106
|
+
|
|
107
|
+
assert_equal 0, album3.tracks.count
|
|
108
|
+
assert_equal 4, Track.count
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def test_create_and_delete_associated_records
|
|
112
|
+
singer2 = Singer.new first_name: "First", last_name: "Last"
|
|
113
|
+
singer2.albums.build title: "Album - 11"
|
|
114
|
+
singer2.albums.build title: "Album - 12"
|
|
115
|
+
singer2.save!
|
|
116
|
+
|
|
117
|
+
singer2.reload
|
|
118
|
+
|
|
119
|
+
assert_equal 2, singer2.albums.count
|
|
120
|
+
assert_equal 4, Album.count
|
|
121
|
+
|
|
122
|
+
assert_equal 2, singer2.albums.delete_all
|
|
123
|
+
singer2.reload
|
|
124
|
+
|
|
125
|
+
assert_equal 0, singer2.albums.count
|
|
126
|
+
assert_equal 2, Album.count
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_create_and_delete_nested_associated_records
|
|
130
|
+
album3 = Album.new title: "Album 3", singer: singer
|
|
131
|
+
album3.tracks.build title: "Track - 31", singer: singer
|
|
132
|
+
album3.tracks.build title: "Track - 32", singer: singer
|
|
133
|
+
album3.save!
|
|
134
|
+
|
|
135
|
+
album3.reload
|
|
136
|
+
|
|
137
|
+
assert_equal 2, album3.tracks.count
|
|
138
|
+
assert_equal 6, Track.count
|
|
139
|
+
|
|
140
|
+
assert_equal 2, album3.tracks.delete_all
|
|
141
|
+
album3.reload
|
|
142
|
+
|
|
143
|
+
assert_equal 0, album3.tracks.count
|
|
144
|
+
assert_equal 4, Track.count
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_update_associated_records
|
|
148
|
+
count = singer.albums.update_all title: "Title - Update"
|
|
149
|
+
assert_equal singer.albums.count, count
|
|
150
|
+
|
|
151
|
+
singer.reload
|
|
152
|
+
singer.albums.each do |album|
|
|
153
|
+
assert_equal "Title - Update", album.title
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_update_nested_associated_records
|
|
158
|
+
count = album1.tracks.update_all title: "Title - Update", duration: 6.626
|
|
159
|
+
assert_equal album1.tracks.count, count
|
|
160
|
+
|
|
161
|
+
album1.reload
|
|
162
|
+
album1.tracks.each do |track|
|
|
163
|
+
assert_equal "Title - Update", track.title
|
|
164
|
+
assert_equal 6.626, track.duration
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def test_fetch_associated_record_with_order
|
|
169
|
+
albums = singer.albums.order title: :desc
|
|
170
|
+
assert_equal %w[Title2 Title1], albums.pluck(:title)
|
|
171
|
+
|
|
172
|
+
albums = singer.albums.order title: :asc
|
|
173
|
+
assert_equal %w[Title1 Title2], albums.pluck(:title)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def test_fetch_nested_associated_record_with_order
|
|
177
|
+
tracks = album1.tracks.order duration: :desc
|
|
178
|
+
assert_equal [4.5, nil], tracks.pluck(:duration)
|
|
179
|
+
|
|
180
|
+
tracks = album1.tracks.order duration: :asc
|
|
181
|
+
assert_equal [nil, 4.5], tracks.pluck(:duration)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_set_counter_cache
|
|
185
|
+
singer.tracks.create! title: "New Title 1", album: album1
|
|
186
|
+
singer.tracks.create! title: "New Title 2", album: album2
|
|
187
|
+
|
|
188
|
+
singer.reload
|
|
189
|
+
assert_equal 6, singer.tracks_count
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_cascade_destroy
|
|
193
|
+
assert_equal 4, singer.tracks.count
|
|
194
|
+
|
|
195
|
+
assert album1.destroy
|
|
196
|
+
|
|
197
|
+
singer.reload
|
|
198
|
+
assert_equal 2, singer.tracks.count
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def test_cascade_delete
|
|
202
|
+
assert_equal 4, singer.tracks.count
|
|
203
|
+
|
|
204
|
+
assert album1.delete
|
|
205
|
+
|
|
206
|
+
singer.reload
|
|
207
|
+
assert_equal 2, singer.tracks.count
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,433 @@
|
|
|
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
|
+
require "test_helper"
|
|
10
|
+
|
|
11
|
+
module ActiveRecord
|
|
12
|
+
class Migration
|
|
13
|
+
class ChangeSchemaTest < SpannerAdapter::TestCase
|
|
14
|
+
include SpannerAdapter::Migration::TestHelper
|
|
15
|
+
|
|
16
|
+
attr_reader :connection, :table_name
|
|
17
|
+
|
|
18
|
+
def setup
|
|
19
|
+
skip_test_table_create!
|
|
20
|
+
super
|
|
21
|
+
@table_name = :testings
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def teardown
|
|
25
|
+
connection.ddl_batch do
|
|
26
|
+
connection.drop_table :testings rescue nil
|
|
27
|
+
end
|
|
28
|
+
ActiveRecord::Base.primary_key_prefix_type = nil
|
|
29
|
+
ActiveRecord::Base.clear_cache!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_add_column
|
|
33
|
+
testing_table_with_only_foo_attribute do
|
|
34
|
+
assert_equal connection.columns(:testings).size, 2
|
|
35
|
+
connection.add_column :testings, :name, :string
|
|
36
|
+
assert_equal connection.columns(:testings).size, 3
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_create_table_adds_id
|
|
41
|
+
connection.ddl_batch do
|
|
42
|
+
connection.create_table :testings do |t|
|
|
43
|
+
t.column :foo, :string
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
assert_equal %w[id foo].sort, connection.columns(:testings).map(&:name).sort
|
|
48
|
+
assert_equal "id", connection.primary_key(:testings)
|
|
49
|
+
|
|
50
|
+
column = connection.columns(:testings).find { |c| c.name == "id" }
|
|
51
|
+
|
|
52
|
+
assert_equal "id", column.name
|
|
53
|
+
assert_equal :integer, column.type
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_create_table_with_custom_primary_key
|
|
57
|
+
connection.ddl_batch do
|
|
58
|
+
connection.create_table :testings, force: true, primary_key: :email do |t|
|
|
59
|
+
t.string :name
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
assert_equal "email", connection.primary_key(:testings)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_create_table_with_custom_primary_key_using_options
|
|
67
|
+
connection.ddl_batch do
|
|
68
|
+
connection.create_table :testings, force: true, id: false do |t|
|
|
69
|
+
t.string :phone, primary_key: true
|
|
70
|
+
t.string :name
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
assert_equal "phone", connection.primary_key(:testings)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_create_table_with_custom_primary_key_using_type
|
|
78
|
+
connection.ddl_batch do
|
|
79
|
+
connection.create_table :testings, force: true, id: false do |t|
|
|
80
|
+
t.primary_key :username, limit: 255
|
|
81
|
+
t.string :name
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
assert_equal "username", connection.primary_key(:testings)
|
|
86
|
+
|
|
87
|
+
column = connection.columns(:testings).find { |c| c.name == "username" }
|
|
88
|
+
|
|
89
|
+
assert_equal "username", column.name
|
|
90
|
+
assert_equal :integer, column.type
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_create_table_with_custom_primary_key_type
|
|
94
|
+
connection.ddl_batch do
|
|
95
|
+
connection.create_table :testings, force: true, id: false do |t|
|
|
96
|
+
t.primary_key :user_id, :integer
|
|
97
|
+
t.string :name
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
assert_equal "user_id", connection.primary_key(:testings)
|
|
102
|
+
|
|
103
|
+
column = connection.columns(:testings).find { |c| c.name == "user_id" }
|
|
104
|
+
|
|
105
|
+
assert_equal "user_id", column.name
|
|
106
|
+
assert_equal :integer, column.type
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_create_table_with_not_null_column
|
|
110
|
+
connection.ddl_batch do
|
|
111
|
+
connection.create_table :testings do |t|
|
|
112
|
+
t.column :foo, :string, null: false
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
assert_raises ActiveRecord::StatementInvalid do
|
|
117
|
+
connection.transaction {
|
|
118
|
+
connection.execute "insert into testings (id, foo) values (#{generate_id}, NULL)"
|
|
119
|
+
}
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def test_create_table_with_limits
|
|
124
|
+
connection.ddl_batch do
|
|
125
|
+
connection.create_table :testings do |t|
|
|
126
|
+
t.column :foo, :string, limit: 255
|
|
127
|
+
t.column :default_int, :integer
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
columns = connection.columns :testings
|
|
132
|
+
foo = columns.detect { |c| c.name == "foo" }
|
|
133
|
+
assert_equal "STRING(255)", foo.sql_type
|
|
134
|
+
assert_equal 255, foo.limit
|
|
135
|
+
|
|
136
|
+
default_int = columns.detect { |c| c.name == "default_int" }
|
|
137
|
+
assert_equal "INT64", default_int.sql_type
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
|
|
141
|
+
ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
|
|
142
|
+
|
|
143
|
+
connection.ddl_batch do
|
|
144
|
+
connection.create_table :testings do |t|
|
|
145
|
+
t.column :foo, :string
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
assert_equal "testing_id", connection.primary_key(:testings)
|
|
150
|
+
|
|
151
|
+
columns = connection.columns(:testings)
|
|
152
|
+
assert_equal %w[testing_id foo].sort, columns.map(&:name).sort
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_create_table_with_primary_key_prefix_as_table_name
|
|
156
|
+
ActiveRecord::Base.primary_key_prefix_type = :table_name
|
|
157
|
+
|
|
158
|
+
connection.ddl_batch do
|
|
159
|
+
connection.create_table :testings do |t|
|
|
160
|
+
t.column :foo, :string
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
assert_equal "testingid", connection.primary_key(:testings)
|
|
165
|
+
|
|
166
|
+
columns = connection.columns(:testings)
|
|
167
|
+
assert_equal %w[testingid foo].sort, columns.map(&:name).sort
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def test_create_table_raises_when_redefining_primary_key_column
|
|
171
|
+
error = assert_raise ArgumentError do
|
|
172
|
+
connection.ddl_batch do
|
|
173
|
+
connection.create_table :testings do |t|
|
|
174
|
+
t.column :id, :string
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
assert_equal "you can't redefine the primary key column 'id'. To define a custom primary key, pass { id: false } to create_table.", error.message
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def test_create_table_raises_when_redefining_custom_primary_key_column
|
|
183
|
+
error = assert_raise ArgumentError do
|
|
184
|
+
connection.ddl_batch do
|
|
185
|
+
connection.create_table :testings, primary_key: :testing_id do |t|
|
|
186
|
+
t.column :testing_id, :string
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
assert_equal "you can't redefine the primary key column 'testing_id'. To define a custom primary key, pass { id: false } to create_table.", error.message
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def test_create_table_raises_when_defining_existing_column
|
|
195
|
+
error = assert_raise ArgumentError do
|
|
196
|
+
connection.ddl_batch do
|
|
197
|
+
connection.create_table :testings do |t|
|
|
198
|
+
t.column :testing_column, :string
|
|
199
|
+
t.column :testing_column, :integer
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
assert_equal "you can't define an already defined column 'testing_column'.", error.message
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def test_create_table_with_timestamps_should_create_datetime_columns
|
|
208
|
+
connection.ddl_batch do
|
|
209
|
+
connection.create_table table_name do |t|
|
|
210
|
+
t.timestamps
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
created_columns = connection.columns table_name
|
|
214
|
+
|
|
215
|
+
created_at_column = created_columns.detect { |c| c.name == "created_at" }
|
|
216
|
+
updated_at_column = created_columns.detect { |c| c.name == "updated_at" }
|
|
217
|
+
|
|
218
|
+
assert_not created_at_column.null
|
|
219
|
+
assert_not updated_at_column.null
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def test_create_table_with_timestamps_should_create_datetime_columns_with_options
|
|
223
|
+
connection.ddl_batch do
|
|
224
|
+
connection.create_table table_name do |t|
|
|
225
|
+
t.timestamps null: true
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
created_columns = connection.columns table_name
|
|
229
|
+
|
|
230
|
+
created_at_column = created_columns.detect { |c| c.name == "created_at" }
|
|
231
|
+
updated_at_column = created_columns.detect { |c| c.name == "updated_at" }
|
|
232
|
+
|
|
233
|
+
assert created_at_column.null
|
|
234
|
+
assert updated_at_column.null
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def test_create_table_without_a_block
|
|
238
|
+
connection.create_table table_name
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def test_add_column_not_null_without_default
|
|
242
|
+
skip "Unimplemented error: Cannot add NOT NULL column to existing table"
|
|
243
|
+
connection.ddl_batch do
|
|
244
|
+
connection.create_table :testings do |t|
|
|
245
|
+
t.column :foo, :string
|
|
246
|
+
end
|
|
247
|
+
connection.add_column :testings, :bar, :string, null: false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
assert_raise ActiveRecord::NotNullViolation do
|
|
251
|
+
connection.transaction {
|
|
252
|
+
connection.execute "insert into testings (id, foo, bar) values (#{generate_id}, 'hello', NULL)"
|
|
253
|
+
}
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def test_add_column_with_timestamp_type
|
|
258
|
+
connection.ddl_batch do
|
|
259
|
+
connection.create_table :testings do |t|
|
|
260
|
+
t.column :foo, :timestamp
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
column = connection.columns(:testings).find { |c| c.name == "foo" }
|
|
265
|
+
|
|
266
|
+
assert_equal :time, column.type
|
|
267
|
+
assert_equal "TIMESTAMP", column.sql_type
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def test_change_column_quotes_column_names
|
|
271
|
+
connection.ddl_batch do
|
|
272
|
+
connection.create_table :testings do |t|
|
|
273
|
+
t.column :select, :string
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
# These two changes cannot be in one batch, as the change_column operation will check whether the column
|
|
277
|
+
# actually exists before executing any DDL statements.
|
|
278
|
+
connection.ddl_batch do
|
|
279
|
+
connection.change_column :testings, :select, :string, limit: 10
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
connection.transaction {
|
|
283
|
+
connection.execute "insert into testings (#{connection.quote_column_name 'id'},"\
|
|
284
|
+
"#{connection.quote_column_name 'select'}) values (#{generate_id}, '7 chars')"
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def test_keeping_notnull_constraints_on_change
|
|
289
|
+
person_klass = Class.new ActiveRecord::Base
|
|
290
|
+
connection.ddl_batch do
|
|
291
|
+
connection.create_table :testings do |t|
|
|
292
|
+
t.column :title, :string
|
|
293
|
+
end
|
|
294
|
+
person_klass.table_name = "testings"
|
|
295
|
+
person_klass.connection.add_column "testings", "wealth", :integer, null: false
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
person_klass.reset_column_information
|
|
299
|
+
assert_equal false, person_klass.columns_hash["wealth"].null
|
|
300
|
+
|
|
301
|
+
assert_nothing_raised {
|
|
302
|
+
person_klass.connection.transaction {
|
|
303
|
+
person_klass.connection.execute("insert into testings (id, title, wealth) values (#{generate_id}, 'tester', 100000)")
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# change column, make it nullable
|
|
308
|
+
connection.ddl_batch do
|
|
309
|
+
person_klass.connection.change_column "testings", "wealth", :integer, null: true
|
|
310
|
+
end
|
|
311
|
+
person_klass.reset_column_information
|
|
312
|
+
assert_nil person_klass.columns_hash["wealth"].default
|
|
313
|
+
assert_equal true, person_klass.columns_hash["wealth"].null
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def test_change_column_null
|
|
317
|
+
testing_table_with_only_foo_attribute do
|
|
318
|
+
notnull_migration = Class.new(ActiveRecord::Migration::Current) do
|
|
319
|
+
def change
|
|
320
|
+
change_column_null :testings, :foo, false
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
notnull_migration.new.suppress_messages do
|
|
324
|
+
notnull_migration.migrate(:up)
|
|
325
|
+
assert_equal false, connection.columns(:testings).find { |c| c.name == "foo" }.null
|
|
326
|
+
notnull_migration.migrate(:down)
|
|
327
|
+
assert connection.columns(:testings).find { |c| c.name == "foo" }.null
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def test_column_exists
|
|
333
|
+
connection.ddl_batch do
|
|
334
|
+
connection.create_table :testings do |t|
|
|
335
|
+
t.column :foo, :string
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
assert connection.column_exists?(:testings, :foo)
|
|
340
|
+
assert_not connection.column_exists?(:testings, :bar)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def test_column_exists_with_type
|
|
344
|
+
connection.ddl_batch do
|
|
345
|
+
connection.create_table :testings do |t|
|
|
346
|
+
t.column :foo, :string
|
|
347
|
+
t.column :bar, :float
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
assert connection.column_exists?(:testings, :foo, :string)
|
|
352
|
+
assert_not connection.column_exists?(:testings, :foo, :integer)
|
|
353
|
+
|
|
354
|
+
assert connection.column_exists?(:testings, :bar, :float)
|
|
355
|
+
assert_not connection.column_exists?(:testings, :bar, :integer)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def test_column_exists_with_definition
|
|
359
|
+
connection.ddl_batch do
|
|
360
|
+
connection.create_table :testings do |t|
|
|
361
|
+
t.column :foo, :string, limit: 100
|
|
362
|
+
t.column :bar, :float
|
|
363
|
+
t.column :taggable_id, :integer, null: false
|
|
364
|
+
t.column :taggable_type, :string
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
assert connection.column_exists?(:testings, :foo, :string, limit: 100)
|
|
369
|
+
assert_not connection.column_exists?(:testings, :foo, :string, limit: nil)
|
|
370
|
+
assert connection.column_exists?(:testings, :bar, :float)
|
|
371
|
+
assert connection.column_exists?(:testings, :taggable_id, :integer, null: false)
|
|
372
|
+
assert_not connection.column_exists?(:testings, :taggable_id, :integer, null: true)
|
|
373
|
+
assert connection.column_exists?(:testings, :taggable_type, :string, default: nil)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def test_column_exists_on_table_with_no_options_parameter_supplied
|
|
377
|
+
connection.ddl_batch do
|
|
378
|
+
connection.create_table :testings do |t|
|
|
379
|
+
t.string :foo
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
connection.change_table :testings do |t|
|
|
383
|
+
assert t.column_exists?(:foo)
|
|
384
|
+
assert_not (t.column_exists?(:bar))
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def test_drop_table_if_exists
|
|
389
|
+
connection.ddl_batch do
|
|
390
|
+
connection.create_table(:testings)
|
|
391
|
+
end
|
|
392
|
+
assert connection.table_exists?(:testings)
|
|
393
|
+
connection.ddl_batch do
|
|
394
|
+
connection.drop_table(:testings, if_exists: true)
|
|
395
|
+
end
|
|
396
|
+
assert_not connection.table_exists?(:testings)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def test_drop_table_if_exists_nothing_raised
|
|
400
|
+
assert_nothing_raised { connection.drop_table(:nonexistent, if_exists: true) }
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def test_drop_and_create_table_with_indexes
|
|
404
|
+
connection.ddl_batch do
|
|
405
|
+
connection.create_table :testings, force: true do |t|
|
|
406
|
+
t.string :name, index: true
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
assert_nothing_raised {
|
|
411
|
+
connection.create_table :testings, force: true do |t|
|
|
412
|
+
t.string :name, index: true
|
|
413
|
+
end
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
assert_equal true, connection.table_exists?(:testings)
|
|
417
|
+
assert_equal true, connection.index_exists?(:testings, :name)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
private
|
|
421
|
+
|
|
422
|
+
def testing_table_with_only_foo_attribute
|
|
423
|
+
connection.ddl_batch do
|
|
424
|
+
connection.create_table :testings do |t|
|
|
425
|
+
t.column :foo, :string
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
yield
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|