activerecord-cockroachdb-adapter 8.0.2 → 8.1.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +10 -17
- data/Gemfile +2 -1
- data/Rakefile +0 -19
- data/lib/active_record/connection_adapters/cockroachdb/column.rb +66 -24
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +13 -3
- data/lib/active_record/connection_adapters/cockroachdb/database_tasks.rb +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/oid/spatial.rb +34 -19
- data/lib/active_record/connection_adapters/cockroachdb/quoting.rb +19 -3
- data/lib/active_record/connection_adapters/cockroachdb/referential_integrity.rb +87 -55
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +181 -102
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +42 -25
- data/lib/version.rb +1 -1
- data/test/cases/adapter_test.rb +98 -0
- data/test/cases/adapters/cockroachdb/referential_integrity_test.rb +51 -0
- data/test/cases/adapters/postgresql/active_schema_test.rb +71 -0
- data/test/cases/adapters/postgresql/change_schema_test.rb +75 -0
- data/test/cases/adapters/postgresql/connection_test.rb +46 -0
- data/test/cases/adapters/postgresql/ddl_test.rb +326 -0
- data/test/cases/adapters/postgresql/interval_test.rb +131 -0
- data/test/cases/adapters/postgresql/nested_class_test.rb +22 -0
- data/test/cases/adapters/postgresql/numeric_test.rb +28 -0
- data/test/cases/adapters/postgresql/postgis_test.rb +185 -0
- data/test/cases/adapters/postgresql/postgresql_adapter_test.rb +115 -0
- data/test/cases/adapters/postgresql/quoting_test.rb +30 -0
- data/test/cases/adapters/postgresql/schema_statements_test.rb +29 -0
- data/test/cases/adapters/postgresql/serial_test.rb +199 -0
- data/test/cases/adapters/postgresql/spatial_queries_test.rb +118 -0
- data/test/cases/adapters/postgresql/spatial_setup_test.rb +18 -0
- data/test/cases/adapters/postgresql/spatial_type_test.rb +41 -0
- data/test/cases/adapters/postgresql/timestamp_test.rb +58 -0
- data/test/cases/adapters/postgresql/virtual_column_test.rb +39 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +111 -0
- data/test/cases/associations/left_outer_join_association_test.rb +30 -0
- data/test/cases/associations_test.rb +108 -0
- data/test/cases/base_test.rb +31 -0
- data/test/cases/comment_test.rb +75 -0
- data/test/cases/connection_adapters/type_test.rb +28 -0
- data/test/cases/database_configurations/resolver_test.rb +24 -0
- data/test/cases/defaults_test.rb +45 -0
- data/test/cases/dirty_test.rb +24 -0
- data/test/cases/fixtures_test.rb +541 -0
- data/test/cases/helper_cockroachdb.rb +232 -0
- data/test/cases/inheritance_test.rb +42 -0
- data/test/cases/invertible_migration_test.rb +73 -0
- data/test/cases/marshal_serialization_test.rb +45 -0
- data/test/cases/migration/change_schema_test.rb +140 -0
- data/test/cases/migration/check_constraint_test.rb +125 -0
- data/test/cases/migration/columns_test.rb +50 -0
- data/test/cases/migration/create_join_table_test.rb +66 -0
- data/test/cases/migration/foreign_key_test.rb +390 -0
- data/test/cases/migration/hidden_column_test.rb +70 -0
- data/test/cases/migration/references_foreign_key_test.rb +124 -0
- data/test/cases/migration_test.rb +120 -0
- data/test/cases/persistence_test.rb +33 -0
- data/test/cases/primary_keys_test.rb +134 -0
- data/test/cases/relation/aost_test.rb +57 -0
- data/test/cases/relation/or_test.rb +26 -0
- data/test/cases/relation/table_hints_test.rb +124 -0
- data/test/cases/relation_test.rb +26 -0
- data/test/cases/relations_test.rb +29 -0
- data/test/cases/schema_dumper_test.rb +221 -0
- data/test/cases/show_create_test.rb +14 -0
- data/test/cases/strict_loading_test.rb +34 -0
- data/test/cases/tasks/cockroachdb_rake_test.rb +113 -0
- data/test/cases/test_fixtures_test.rb +57 -0
- data/test/cases/transactions_test.rb +51 -0
- data/test/cases/unsafe_raw_sql_test.rb +22 -0
- data/test/config.yml +23 -0
- data/test/excludes/ActiveRecord/AdapterTest.rb +3 -0
- data/test/excludes/ActiveRecord/AdapterTestWithoutTransaction.rb +25 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PoolConfig/ResolverTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/BindParameterTest.rb +15 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter/QuotingTest.rb +22 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesLegacyTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterPreventWritesTest.rb +1 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapterTest.rb +40 -0
- data/test/excludes/ActiveRecord/ConnectionAdapters/RegistrationIsolatedTest.rb +14 -0
- data/test/excludes/ActiveRecord/Encryption/EncryptionPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/EnvelopeEncryptionPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/ExtendedDeterministicQueriesPerformanceTest.rb +1 -0
- data/test/excludes/ActiveRecord/Encryption/StoragePerformanceTest.rb +2 -0
- data/test/excludes/ActiveRecord/InstrumentationTest.rb +16 -0
- data/test/excludes/ActiveRecord/InvertibleMigrationTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/ChangeSchemaTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/CheckConstraintTest.rb +6 -0
- data/test/excludes/ActiveRecord/Migration/ColumnsTest.rb +4 -0
- data/test/excludes/ActiveRecord/Migration/CompatibilityTest.rb +50 -0
- data/test/excludes/ActiveRecord/Migration/CompositeForeignKeyTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/CreateJoinTableTest.rb +2 -0
- data/test/excludes/ActiveRecord/Migration/ForeignKeyInCreateTest.rb +1 -0
- data/test/excludes/ActiveRecord/Migration/ForeignKeyTest.rb +35 -0
- data/test/excludes/ActiveRecord/Migration/InvalidOptionsTest.rb +14 -0
- data/test/excludes/ActiveRecord/Migration/PGChangeSchemaTest.rb +8 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesForeignKeyTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesIndexTest.rb +7 -0
- data/test/excludes/ActiveRecord/Migration/ReferencesStatementsTest.rb +3 -0
- data/test/excludes/ActiveRecord/MysqlDBCreateWithInvalidPermissionsTest.rb +1 -0
- data/test/excludes/ActiveRecord/OrTest.rb +1 -0
- data/test/excludes/ActiveRecord/PostgreSQLStructureDumpTest.rb +10 -0
- data/test/excludes/ActiveRecord/PostgresqlConnectionTest.rb +12 -0
- data/test/excludes/ActiveRecord/PostgresqlTransactionNestedTest.rb +14 -0
- data/test/excludes/ActiveRecord/PostgresqlTransactionTest.rb +4 -0
- data/test/excludes/ActiveRecord/RelationTest.rb +2 -0
- data/test/excludes/ActiveRecord/TooManyOrTest.rb +1 -0
- data/test/excludes/ActiveSupportSubclassWithFixturesTest.rb +1 -0
- data/test/excludes/AssociationDeprecationTest/NotifyModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/RaiseBacktraceModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/RaiseModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/WarnBacktraceModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/WarnModeTest.rb +2 -0
- data/test/excludes/AssociationDeprecationTest/fix_backtrace_cleaner.rb +10 -0
- data/test/excludes/BasicsTest.rb +1 -0
- data/test/excludes/BulkAlterTableMigrationsTest.rb +7 -0
- data/test/excludes/CalculationsTest.rb +4 -0
- data/test/excludes/CommentTest.rb +2 -0
- data/test/excludes/CoreTest.rb +1 -0
- data/test/excludes/CreateOrFindByWithinTransactions.rb +3 -0
- data/test/excludes/DefaultsUsingMultipleSchemasAndDomainTest.rb +7 -0
- data/test/excludes/DirtyTest.rb +3 -0
- data/test/excludes/EachTest.rb +8 -0
- data/test/excludes/EagerLoadPolyAssocsTest.rb +1 -0
- data/test/excludes/ExplicitlyNamedIndexMigrationTest.rb +1 -0
- data/test/excludes/FixturesResetPkSequenceTest.rb +3 -0
- data/test/excludes/FixturesTest.rb +21 -0
- data/test/excludes/FixturesWithForeignKeyViolationsTest.rb +2 -0
- data/test/excludes/ForeignTableTest.rb +10 -0
- data/test/excludes/InheritanceComputeTypeTest.rb +1 -0
- data/test/excludes/LeftOuterJoinAssociationTest.rb +2 -0
- data/test/excludes/LegacyPrimaryKeyTest/V4_2.rb +6 -0
- data/test/excludes/LegacyPrimaryKeyTest/V5_0.rb +6 -0
- data/test/excludes/MarshalSerializationTest.rb +4 -0
- data/test/excludes/MaterializedViewTest.rb +13 -0
- data/test/excludes/MigrationTest.rb +2 -0
- data/test/excludes/MultiDbMigratorTest.rb +2 -0
- data/test/excludes/NestedRelationScopingTest.rb +1 -0
- data/test/excludes/OrTest.rb +1 -0
- data/test/excludes/PersistenceTest.rb +3 -0
- data/test/excludes/PessimisticLockingTest.rb +1 -0
- data/test/excludes/PostgreSQLExplainTest.rb +7 -0
- data/test/excludes/PostgreSQLGeometricLineTest.rb +3 -0
- data/test/excludes/PostgreSQLGeometricTypesTest.rb +7 -0
- data/test/excludes/PostgreSQLPartitionsTest.rb +1 -0
- data/test/excludes/PostgreSQLReferentialIntegrityTest.rb +14 -0
- data/test/excludes/PostgresqlArrayTest.rb +21 -0
- data/test/excludes/PostgresqlBigSerialTest.rb +7 -0
- data/test/excludes/PostgresqlBitStringTest.rb +2 -0
- data/test/excludes/PostgresqlByteaTest.rb +1 -0
- data/test/excludes/PostgresqlCitextTest.rb +7 -0
- data/test/excludes/PostgresqlCollationTest.rb +5 -0
- data/test/excludes/PostgresqlCompositeTest.rb +2 -0
- data/test/excludes/PostgresqlCompositeWithCustomOIDTest.rb +2 -0
- data/test/excludes/PostgresqlDataTypeTest.rb +9 -0
- data/test/excludes/PostgresqlDefaultExpressionTest.rb +1 -0
- data/test/excludes/PostgresqlDeferredConstraintsTest.rb +3 -0
- data/test/excludes/PostgresqlDomainTest.rb +2 -0
- data/test/excludes/PostgresqlExtensionMigrationTest.rb +5 -0
- data/test/excludes/PostgresqlFullTextTest.rb +3 -0
- data/test/excludes/PostgresqlGeometricTest.rb +4 -0
- data/test/excludes/PostgresqlHstoreTest.rb +45 -0
- data/test/excludes/PostgresqlInfinityTest.rb +5 -0
- data/test/excludes/PostgresqlIntervalTest.rb +1 -0
- data/test/excludes/PostgresqlJSONBTest.rb +27 -0
- data/test/excludes/PostgresqlJSONTest.rb +27 -0
- data/test/excludes/PostgresqlLtreeTest.rb +4 -0
- data/test/excludes/PostgresqlMoneyTest.rb +12 -0
- data/test/excludes/PostgresqlNetworkTest.rb +69 -0
- data/test/excludes/PostgresqlNumberTest.rb +1 -0
- data/test/excludes/PostgresqlPointTest.rb +15 -0
- data/test/excludes/PostgresqlRangeTest.rb +3 -0
- data/test/excludes/PostgresqlRenameTableTest.rb +2 -0
- data/test/excludes/PostgresqlSerialTest.rb +7 -0
- data/test/excludes/PostgresqlTimestampFixtureTest.rb +2 -0
- data/test/excludes/PostgresqlTimestampMigrationTest.rb +3 -0
- data/test/excludes/PostgresqlTypeLookupTest.rb +2 -0
- data/test/excludes/PostgresqlUUIDGenerationTest.rb +7 -0
- data/test/excludes/PostgresqlUUIDTest.rb +1 -0
- data/test/excludes/PostgresqlVirtualColumnTest.rb +17 -0
- data/test/excludes/PostgresqlXMLTest.rb +5 -0
- data/test/excludes/PrimaryKeyIntegerNilDefaultTest.rb +1 -0
- data/test/excludes/PrimaryKeyIntegerTest.rb +3 -0
- data/test/excludes/PrimaryKeysTest.rb +2 -0
- data/test/excludes/RelationMergingTest.rb +15 -0
- data/test/excludes/RelationTest.rb +3 -0
- data/test/excludes/ReservedWordsMigrationTest.rb +1 -0
- data/test/excludes/SameNameDifferentDatabaseFixturesTest.rb +1 -0
- data/test/excludes/SanitizeTest.rb +13 -0
- data/test/excludes/SchemaAuthorizationTest.rb +8 -0
- data/test/excludes/SchemaCreateTableOptionsTest.rb +2 -0
- data/test/excludes/SchemaDumperDefaultsTest.rb +1 -0
- data/test/excludes/SchemaDumperTest.rb +11 -0
- data/test/excludes/SchemaForeignKeyTest.rb +1 -0
- data/test/excludes/SchemaIndexNullsOrderTest.rb +2 -0
- data/test/excludes/SchemaIndexOpclassTest.rb +3 -0
- data/test/excludes/SchemaTest.rb +49 -0
- data/test/excludes/SequenceNameDetectionTestCases/CollidedSequenceNameTest.rb +1 -0
- data/test/excludes/SequenceNameDetectionTestCases/LongerSequenceNameDetectionTest.rb +1 -0
- data/test/excludes/StrictLoadingFixturesTest.rb +1 -0
- data/test/excludes/TestFixturesTest.rb +1 -0
- data/test/excludes/TransactionInstrumentationTest.rb +1 -0
- data/test/excludes/TransactionIsolationTest.rb +1 -0
- data/test/excludes/TypeTest.rb +1 -0
- data/test/excludes/UniquenessValidationTest.rb +2 -0
- data/test/excludes/UnloggedTablesTest.rb +3 -0
- data/test/excludes/UnsafeRawSqlTest.rb +2 -0
- data/test/excludes/UpdateableViewTest.rb +5 -0
- data/test/excludes/WithAnnotationsTest.rb +6 -0
- data/test/models/building.rb +4 -0
- data/test/models/spatial_model.rb +5 -0
- data/test/schema/cockroachdb_specific_schema.rb +218 -0
- data/test/support/copy_cat.rb +83 -0
- data/test/support/exclude_from_transactional_tests.rb +33 -0
- data/test/support/paths_cockroachdb.rb +46 -0
- data/test/support/rake_helpers.rb +37 -0
- data/test/support/sql_logger.rb +55 -0
- data/test/support/template_creator.rb +114 -0
- metadata +422 -34
- data/.editorconfig +0 -7
- data/.github/issue_template.md +0 -46
- data/.github/reproduction_scripts/migrations.rb +0 -60
- data/.github/reproduction_scripts/models_and_database.rb +0 -49
- data/.github/workflows/ci.yml +0 -96
- data/.github/workflows/docker.yml +0 -57
- data/.gitignore +0 -18
- data/.gitmodules +0 -0
- data/activerecord-cockroachdb-adapter.gemspec +0 -38
- data/bin/console +0 -36
- data/bin/console_schemas/default.rb +0 -11
- data/bin/console_schemas/schemas.rb +0 -23
- data/bin/setup +0 -8
- data/bin/start-cockroachdb +0 -33
- data/build/Dockerfile +0 -14
- data/build/config.teamcity.yml +0 -31
- data/build/local-test.sh +0 -32
- data/build/teamcity-test.sh +0 -76
- data/docker.sh +0 -35
- data/lib/active_record/connection_adapters/cockroachdb/spatial_column_info.rb +0 -60
- data/setup.sql +0 -18
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper"
|
|
4
|
+
require "support/schema_dumping_helper"
|
|
5
|
+
|
|
6
|
+
module CockroachDB
|
|
7
|
+
class PostgresqlIntervalTest < ActiveRecord::PostgreSQLTestCase
|
|
8
|
+
include SchemaDumpingHelper
|
|
9
|
+
|
|
10
|
+
class IntervalDataType < ActiveRecord::Base
|
|
11
|
+
attribute :legacy_term, :string
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class DeprecatedIntervalDataType < ActiveRecord::Base; end
|
|
15
|
+
|
|
16
|
+
def setup
|
|
17
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
18
|
+
@connection.transaction do
|
|
19
|
+
@connection.create_table("interval_data_types") do |t|
|
|
20
|
+
t.interval "maximum_term"
|
|
21
|
+
t.interval "minimum_term", precision: 3
|
|
22
|
+
t.interval "default_term", default: "P3Y"
|
|
23
|
+
t.interval "all_terms", array: true
|
|
24
|
+
t.interval "legacy_term"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
@column_max = IntervalDataType.columns_hash["maximum_term"]
|
|
28
|
+
@column_min = IntervalDataType.columns_hash["minimum_term"]
|
|
29
|
+
assert(@column_max.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
|
|
30
|
+
assert(@column_min.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn))
|
|
31
|
+
assert_nil @column_max.precision
|
|
32
|
+
assert_equal 3, @column_min.precision
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
teardown do
|
|
36
|
+
@connection.execute "DROP TABLE IF EXISTS interval_data_types"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
IntervalTestCase = Struct.new(:input, :expected)
|
|
40
|
+
def test_postgresql_interval_parser
|
|
41
|
+
tests = [
|
|
42
|
+
IntervalTestCase.new('6 years', 6.year),
|
|
43
|
+
IntervalTestCase.new('6 years 5 mons', 6.year + 5.month),
|
|
44
|
+
IntervalTestCase.new('6 years 5 mons 4 days', 6.year + 5.month + 4.days),
|
|
45
|
+
IntervalTestCase.new('6 years 5 mons 4 days 03:00:00', 6.year + 5.month + 4.days + 3.hours),
|
|
46
|
+
IntervalTestCase.new('6 years 5 mons 4 days 03:02:00', 6.year + 5.month + 4.days + 3.hours + 2.minutes),
|
|
47
|
+
IntervalTestCase.new('6 years 5 mons 4 days 03:02:01',
|
|
48
|
+
6.year + 5.month + 4.days + 3.hours + 2.minutes + 1.second),
|
|
49
|
+
IntervalTestCase.new('6 years 5 mons 4 days 03:02:01.2',
|
|
50
|
+
6.year + 5.month + 4.days + 3.hours + 2.minutes + 1.2.seconds)
|
|
51
|
+
]
|
|
52
|
+
tests.each do |test_case|
|
|
53
|
+
result = ActiveRecord::ConnectionAdapters::CockroachDB::PostgresqlInterval::Parser.parse(test_case.input)
|
|
54
|
+
assert_equal test_case.expected, result
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_postgresql_interval_parser_error
|
|
59
|
+
assert_raises(ActiveRecord::ConnectionAdapters::CockroachDB::PostgresqlInterval::ParseError) do
|
|
60
|
+
test_str = 'This is an invalid interval'
|
|
61
|
+
ActiveRecord::ConnectionAdapters::CockroachDB::PostgresqlInterval::Parser.parse(test_str)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_column
|
|
66
|
+
assert_equal :interval, @column_max.type
|
|
67
|
+
assert_equal :interval, @column_min.type
|
|
68
|
+
assert_equal "interval", @column_max.sql_type
|
|
69
|
+
assert_equal "interval(3)", @column_min.sql_type
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_simple_interval
|
|
73
|
+
IntervalDataType.create!(
|
|
74
|
+
maximum_term: 6.year + 5.month + 4.days + 3.hours + 2.minutes + 1.seconds
|
|
75
|
+
)
|
|
76
|
+
i = IntervalDataType.last!
|
|
77
|
+
assert_equal "P6Y5M4DT3H2M1S", i.maximum_term.iso8601
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_interval_type
|
|
81
|
+
IntervalDataType.create!(
|
|
82
|
+
maximum_term: 6.year + 5.month + 4.days + 3.hours + 2.minutes + 1.seconds,
|
|
83
|
+
minimum_term: 1.year + 2.month + 3.days + 4.hours + 5.minutes + (6.234567).seconds,
|
|
84
|
+
all_terms: [1.month, 1.year, 1.hour],
|
|
85
|
+
legacy_term: "33 years",
|
|
86
|
+
)
|
|
87
|
+
i = IntervalDataType.last!
|
|
88
|
+
|
|
89
|
+
assert_equal "P6Y5M4DT3H2M1S", i.maximum_term.iso8601
|
|
90
|
+
assert_equal "P1Y2M3DT4H5M6.235S", i.minimum_term.iso8601
|
|
91
|
+
assert_equal "P3Y", i.default_term.iso8601
|
|
92
|
+
assert_equal %w[ P1M P1Y PT1H ], i.all_terms.map(&:iso8601)
|
|
93
|
+
|
|
94
|
+
assert_equal "P33Y", i.legacy_term
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_interval_type_cast_from_invalid_string
|
|
98
|
+
i = IntervalDataType.create!(maximum_term: "1 year 2 minutes")
|
|
99
|
+
i.reload
|
|
100
|
+
assert_nil i.maximum_term
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def test_interval_type_cast_from_numeric
|
|
104
|
+
i = IntervalDataType.create!(minimum_term: 36000)
|
|
105
|
+
i.reload
|
|
106
|
+
assert_equal "PT10H", i.minimum_term.iso8601
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_interval_type_cast_string_and_numeric_from_user
|
|
110
|
+
i = IntervalDataType.new(maximum_term: "P1YT2M", minimum_term: "PT10H", legacy_term: "P1DT1H")
|
|
111
|
+
assert i.maximum_term.is_a?(ActiveSupport::Duration)
|
|
112
|
+
assert i.legacy_term.is_a?(String)
|
|
113
|
+
assert_equal "P1YT2M", i.maximum_term.iso8601
|
|
114
|
+
assert_equal "PT10H", i.minimum_term.iso8601
|
|
115
|
+
assert_equal "P1DT1H", i.legacy_term
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def test_average_interval_type
|
|
119
|
+
IntervalDataType.create!([{ maximum_term: 6.years }, { maximum_term: 4.months }])
|
|
120
|
+
value = IntervalDataType.average(:maximum_term)
|
|
121
|
+
|
|
122
|
+
assert_equal 3.years + 2.months, value
|
|
123
|
+
assert_instance_of ActiveSupport::Duration, value
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_schema_dump_with_default_value
|
|
127
|
+
output = dump_table_schema "interval_data_types"
|
|
128
|
+
assert_match %r{t\.interval "default_term", default: "3 years 0 mons 0 days 0 hours 0 minutes 0 seconds"}, output
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cases/helper_cockroachdb'
|
|
4
|
+
|
|
5
|
+
class NestedClassTest < ActiveRecord::PostgreSQLTestCase
|
|
6
|
+
module Foo
|
|
7
|
+
def self.table_name_prefix
|
|
8
|
+
'foo_'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Bar < ActiveRecord::Base
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_nested_model
|
|
16
|
+
Foo::Bar.lease_connection.create_table(:foo_bars, force: true) do |t|
|
|
17
|
+
t.column 'latlon', :st_point, srid: 3785
|
|
18
|
+
end
|
|
19
|
+
assert_empty Foo::Bar.all
|
|
20
|
+
Foo::Bar.lease_connection.drop_table(:foo_bars)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "cases/helper_cockroachdb"
|
|
2
|
+
|
|
3
|
+
# Load dependencies from ActiveRecord test suite
|
|
4
|
+
require "support/schema_dumping_helper"
|
|
5
|
+
|
|
6
|
+
module CockroachDB
|
|
7
|
+
class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase
|
|
8
|
+
include SchemaDumpingHelper
|
|
9
|
+
|
|
10
|
+
class PostgresqlNumber < ActiveRecord::Base; end
|
|
11
|
+
|
|
12
|
+
setup do
|
|
13
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
14
|
+
@connection.create_table("postgresql_numbers", force: true) do |t|
|
|
15
|
+
t.decimal 'decimal_default'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
teardown do
|
|
20
|
+
@connection.drop_table "postgresql_decimals", if_exists: true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_decimal_values
|
|
24
|
+
record = PostgresqlNumber.new(decimal_default: 111.222)
|
|
25
|
+
assert_equal record.decimal_default, 111.222
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cases/helper_cockroachdb"
|
|
4
|
+
require "models/building"
|
|
5
|
+
|
|
6
|
+
class PostGISTest < ActiveRecord::PostgreSQLTestCase
|
|
7
|
+
def setup
|
|
8
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
spatial_factory_store.default = nil
|
|
13
|
+
spatial_factory_store.clear
|
|
14
|
+
reset_memoized_spatial_factories
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_postgis_available
|
|
18
|
+
assert_equal postgis_version, @connection.postgis_lib_version
|
|
19
|
+
valid_version = ["2.", "3."].any? { |major_ver| @connection.postgis_lib_version.start_with? major_ver }
|
|
20
|
+
assert valid_version
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_arel_visitor
|
|
24
|
+
visitor = Arel::Visitors::CockroachDB.new(@connection)
|
|
25
|
+
node = RGeo::ActiveRecord::SpatialConstantNode.new("POINT (1.0 2.0)")
|
|
26
|
+
collector = Arel::Collectors::PlainString.new
|
|
27
|
+
visitor.accept(node, collector)
|
|
28
|
+
assert_equal "ST_GeomFromText('POINT (1.0 2.0)')", collector.value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_arel_visitor_will_not_visit_string
|
|
32
|
+
visitor = Arel::Visitors::CockroachDB.new(@connection)
|
|
33
|
+
node = "POINT (1 2)"
|
|
34
|
+
collector = Arel::Collectors::PlainString.new
|
|
35
|
+
|
|
36
|
+
assert_raises(Arel::Visitors::UnsupportedVisitError) do
|
|
37
|
+
visitor.accept(node, collector)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_set_and_get_point
|
|
42
|
+
obj = klass.new
|
|
43
|
+
assert_nil obj.coordinates
|
|
44
|
+
obj.coordinates = factory.point(1.0, 2.0)
|
|
45
|
+
assert_equal factory.point(1.0, 2.0), obj.coordinates
|
|
46
|
+
assert_equal 3857, obj.coordinates.srid
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_set_and_get_point_from_wkt
|
|
50
|
+
obj = klass.new
|
|
51
|
+
assert_nil obj.coordinates
|
|
52
|
+
obj.coordinates = "POINT(1 2)"
|
|
53
|
+
assert_equal factory.point(1.0, 2.0), obj.coordinates
|
|
54
|
+
assert_equal 3857, obj.coordinates.srid
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_save_and_load_point
|
|
58
|
+
obj = klass.new
|
|
59
|
+
obj.coordinates = factory.point(1.0, 2.0)
|
|
60
|
+
obj.save!
|
|
61
|
+
id = obj.id
|
|
62
|
+
obj2 = klass.find(id)
|
|
63
|
+
assert_equal factory.point(1.0, 2.0), obj2.coordinates
|
|
64
|
+
assert_equal 3857, obj2.coordinates.srid
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_save_and_load_geographic_point
|
|
68
|
+
obj = klass.new
|
|
69
|
+
obj.latlon = geographic_factory.point(1.0, 2.0)
|
|
70
|
+
obj.save!
|
|
71
|
+
id = obj.id
|
|
72
|
+
obj2 = klass.find(id)
|
|
73
|
+
assert_equal geographic_factory.point(1.0, 2.0), obj2.latlon
|
|
74
|
+
assert_equal 4326, obj2.latlon.srid
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_save_and_load_point_from_wkt
|
|
78
|
+
obj = klass.new
|
|
79
|
+
obj.coordinates = "POINT(1 2)"
|
|
80
|
+
obj.save!
|
|
81
|
+
id = obj.id
|
|
82
|
+
obj2 = klass.find(id)
|
|
83
|
+
assert_equal factory.point(1.0, 2.0), obj2.coordinates
|
|
84
|
+
assert_equal 3857, obj2.coordinates.srid
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_set_point_bad_wkt
|
|
88
|
+
obj = klass.create(coordinates: "POINT (x)")
|
|
89
|
+
assert_nil obj.coordinates
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_set_point_wkt_wrong_type
|
|
93
|
+
assert_raises(ActiveRecord::StatementInvalid) do
|
|
94
|
+
klass.create(coordinates: "LINESTRING(1 2, 3 4, 5 6)")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_custom_factory
|
|
99
|
+
custom_factory = RGeo::Cartesian.preferred_factory(buffer_resolution: 8, srid: 3857)
|
|
100
|
+
spatial_factory_store.register(custom_factory, geo_type: "polygon", srid: 3857)
|
|
101
|
+
object = klass.new
|
|
102
|
+
boundary = custom_factory.point(1, 2).buffer(3)
|
|
103
|
+
object.boundary = boundary
|
|
104
|
+
object.save!
|
|
105
|
+
object.reload
|
|
106
|
+
assert_equal boundary.to_s, object.boundary.to_s
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_spatial_factory_attrs_parsing
|
|
110
|
+
factory = RGeo::Cartesian.preferred_factory(srid: 3857)
|
|
111
|
+
spatial_factory_store.register(factory, { srid: 3857,
|
|
112
|
+
sql_type: "geometry",
|
|
113
|
+
geo_type: "polygon",
|
|
114
|
+
has_z: false, has_m: false })
|
|
115
|
+
|
|
116
|
+
# wrong factory for default
|
|
117
|
+
spatial_factory_store.default = RGeo::Geographic.spherical_factory(srid: 4326)
|
|
118
|
+
|
|
119
|
+
object = klass.new
|
|
120
|
+
object.boundary = "POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))"
|
|
121
|
+
object.save!
|
|
122
|
+
object.reload
|
|
123
|
+
assert_equal(factory, object.boundary.factory)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_spatial_factory_retrieval
|
|
127
|
+
geo_factory = RGeo::Geographic.spherical_factory(srid: 4326)
|
|
128
|
+
spatial_factory_store.register(geo_factory, geo_type: "point", sql_type: "geography")
|
|
129
|
+
|
|
130
|
+
object = klass.new
|
|
131
|
+
object.latlon = "POINT(-122 47)"
|
|
132
|
+
point = object.latlon
|
|
133
|
+
assert_equal 47, point.latitude
|
|
134
|
+
object.shape = point
|
|
135
|
+
|
|
136
|
+
# test that shape column will not use geographic factory
|
|
137
|
+
object.save!
|
|
138
|
+
object.reload
|
|
139
|
+
refute_equal geo_factory, object.shape.factory
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def test_point_to_json
|
|
143
|
+
obj = klass.new
|
|
144
|
+
assert_match(/"latlon":null/, obj.to_json)
|
|
145
|
+
obj.latlon = factory.point(1.1, 2.0)
|
|
146
|
+
# NOTE: rgeo opiniated in trimming numbers (e.g 1.0 would be trimmed to 1).
|
|
147
|
+
# We follow this convention here.
|
|
148
|
+
assert_match(/"latlon":"POINT\s\(1.1\s2\)"/, obj.to_json)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def test_custom_column
|
|
152
|
+
rec = klass.new
|
|
153
|
+
rec.latlon = "POINT(0 0)"
|
|
154
|
+
rec.save
|
|
155
|
+
refute_nil klass.select("CURRENT_TIMESTAMP as ts").first.ts
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_multi_polygon_column
|
|
159
|
+
rec = klass.new
|
|
160
|
+
wkt = "MULTIPOLYGON (((-73.97210545302842 40.782991711401195, " \
|
|
161
|
+
"-73.97228912063449 40.78274091498208, " \
|
|
162
|
+
"-73.97235226842568 40.78276752827304, " \
|
|
163
|
+
"-73.97216860098405 40.783018324791776, " \
|
|
164
|
+
"-73.97210545302842 40.782991711401195)))"
|
|
165
|
+
rec.m_poly = wkt
|
|
166
|
+
assert rec.save
|
|
167
|
+
rec = klass.find(rec.id) # force reload
|
|
168
|
+
assert RGeo::Feature::MultiPolygon.check_type(rec.m_poly)
|
|
169
|
+
assert_equal wkt, rec.m_poly.to_s
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def klass
|
|
175
|
+
Building
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def reset_memoized_spatial_factories
|
|
179
|
+
# necessary to reset the @spatial_factory variable on spatial
|
|
180
|
+
# OIDs, otherwise the results of early tests will be memoized
|
|
181
|
+
# since the table is not dropped and recreated between test cases.
|
|
182
|
+
klass.lease_connection.send(:reload_type_map)
|
|
183
|
+
klass.reset_column_information
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
require "cases/helper_cockroachdb"
|
|
2
|
+
require "cases/helper"
|
|
3
|
+
require "support/ddl_helper"
|
|
4
|
+
require "support/connection_helper"
|
|
5
|
+
require "support/copy_cat"
|
|
6
|
+
|
|
7
|
+
module CockroachDB
|
|
8
|
+
module ConnectionAdapters
|
|
9
|
+
class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase
|
|
10
|
+
self.use_transactional_tests = false
|
|
11
|
+
include DdlHelper
|
|
12
|
+
include ConnectionHelper
|
|
13
|
+
|
|
14
|
+
def setup
|
|
15
|
+
@connection = ActiveRecord::Base.lease_connection
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def teardown
|
|
19
|
+
# use connection without follower_reads and telemetry
|
|
20
|
+
database_config = { "adapter" => "cockroachdb", "database" => "activerecord_unittest" }
|
|
21
|
+
ar_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
22
|
+
database_config.update(ar_config.configuration_hash)
|
|
23
|
+
|
|
24
|
+
ActiveRecord::Base.establish_connection(database_config)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_database_exists_returns_false_when_the_database_does_not_exist
|
|
28
|
+
config_base = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary").configuration_hash
|
|
29
|
+
[ config_base.merge(database: "non_extant_database", adapter: "cockroachdb"),
|
|
30
|
+
config_base.merge(database: "non_extant_database", adapter: "postgresql")
|
|
31
|
+
].each do |config|
|
|
32
|
+
assert_not ActiveRecord::ConnectionAdapters::CockroachDBAdapter.database_exists?(config),
|
|
33
|
+
"expected database #{config[:database]} to not exist"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def test_database_exists_returns_true_when_the_database_exists
|
|
38
|
+
db_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
39
|
+
assert ActiveRecord::ConnectionAdapters::CockroachDBAdapter.database_exists?(db_config.configuration_hash),
|
|
40
|
+
"expected database #{db_config.database} to exist"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_using_telemetry_builtin_connects_properly
|
|
44
|
+
database_config = { "adapter" => "cockroachdb", "database" => "activerecord_unittest" }
|
|
45
|
+
ar_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
46
|
+
database_config.update(ar_config.configuration_hash)
|
|
47
|
+
database_config[:disable_cockroachdb_telemetry] = false
|
|
48
|
+
|
|
49
|
+
ActiveRecord::Base.establish_connection(database_config)
|
|
50
|
+
conn = ActiveRecord::Base.lease_connection
|
|
51
|
+
conn_config = conn.instance_variable_get("@config")
|
|
52
|
+
|
|
53
|
+
assert_equal(false, conn_config[:disable_cockroachdb_telemetry])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_using_follower_reads_connects_properly
|
|
57
|
+
database_config = { "use_follower_reads_for_type_introspection": true, "adapter" => "cockroachdb", "database" => "activerecord_unittest" }
|
|
58
|
+
ar_config = ActiveRecord::Base.configurations.configs_for(env_name: "arunit", name: "primary")
|
|
59
|
+
database_config.update(ar_config.configuration_hash)
|
|
60
|
+
|
|
61
|
+
ActiveRecord::Base.establish_connection(database_config)
|
|
62
|
+
conn = ActiveRecord::Base.lease_connection
|
|
63
|
+
conn_config = conn.instance_variable_get("@config")
|
|
64
|
+
|
|
65
|
+
assert conn_config[:use_follower_reads_for_type_introspection]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# OVERRIDE: CockroachDB adds parentheses around the WHERE clause's content.
|
|
69
|
+
def test_partial_index_on_column_named_like_keyword
|
|
70
|
+
with_example_table('id serial primary key, number integer, "primary" boolean') do
|
|
71
|
+
@connection.add_index "ex", "id", name: "partial", where: "primary" # "primary" is a keyword
|
|
72
|
+
index = @connection.indexes("ex").find { |idx| idx.name == "partial" }
|
|
73
|
+
assert_equal '("primary")', index.where
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# OVERRIDE: Different behaviour between PostgreSQL and CockroachDB.
|
|
78
|
+
def test_invalid_index
|
|
79
|
+
with_example_table do
|
|
80
|
+
@connection.exec_query("INSERT INTO ex (number) VALUES (1), (1)")
|
|
81
|
+
error = assert_raises(ActiveRecord::RecordNotUnique) do
|
|
82
|
+
@connection.add_index(:ex, :number, unique: true, algorithm: :concurrently, name: :invalid_index)
|
|
83
|
+
end
|
|
84
|
+
assert_match(/duplicate key value violates unique constraint/, error.message)
|
|
85
|
+
assert_equal @connection.pool, error.connection_pool
|
|
86
|
+
|
|
87
|
+
# In CRDB this tests won't create the index at all.
|
|
88
|
+
assert_not @connection.index_exists?(:ex, :number, name: :invalid_index)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# OVERRIDE: the `default_sequence_name` is `nil`, let's `to_s` it
|
|
93
|
+
# for a fair comparison.
|
|
94
|
+
CopyCat.copy_methods(self, ActiveRecord::ConnectionAdapters::PostgreSQLAdapterTest,
|
|
95
|
+
:test_pk_and_sequence_for,
|
|
96
|
+
:test_pk_and_sequence_for_with_non_standard_primary_key
|
|
97
|
+
) do
|
|
98
|
+
attr_accessor :already_updated
|
|
99
|
+
def on_send(node)
|
|
100
|
+
return super unless node in [:send, _, :default_sequence_name, [:str, "ex"], [:str, "id"|"code"]]
|
|
101
|
+
|
|
102
|
+
raise "The source code must have changed" if already_updated
|
|
103
|
+
already_updated ||= :yes
|
|
104
|
+
insert_after(node.loc.expression, ".to_s")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
CopyCat.copy_methods(self, ActiveRecord::ConnectionAdapters::PostgreSQLAdapterTest,
|
|
111
|
+
:with_example_table
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "cases/helper_cockroachdb"
|
|
2
|
+
|
|
3
|
+
# Load dependencies from ActiveRecord test suite
|
|
4
|
+
require "support/schema_dumping_helper"
|
|
5
|
+
|
|
6
|
+
module CockroachDB
|
|
7
|
+
class PostgresqlQuotingTest < ActiveRecord::PostgreSQLTestCase
|
|
8
|
+
def setup
|
|
9
|
+
@conn = ActiveRecord::Base.lease_connection
|
|
10
|
+
@raise_int_wider_than_64bit = ActiveRecord.raise_int_wider_than_64bit
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Replace the original test since numbers are quoted.
|
|
14
|
+
def test_do_not_raise_when_int_is_not_wider_than_64bit
|
|
15
|
+
value = 9223372036854775807
|
|
16
|
+
assert_equal "'9223372036854775807'", @conn.quote(value)
|
|
17
|
+
|
|
18
|
+
value = -9223372036854775808
|
|
19
|
+
assert_equal "'-9223372036854775808'", @conn.quote(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Replace the original test since numbers are quoted.
|
|
23
|
+
def test_do_not_raise_when_raise_int_wider_than_64bit_is_false
|
|
24
|
+
ActiveRecord.raise_int_wider_than_64bit = false
|
|
25
|
+
value = 9223372036854775807 + 1
|
|
26
|
+
assert_equal "'9223372036854775808'", @conn.quote(value)
|
|
27
|
+
ActiveRecord.raise_int_wider_than_64bit = @raise_int_wider_than_64bit
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cases/helper_cockroachdb'
|
|
4
|
+
require 'models/building'
|
|
5
|
+
|
|
6
|
+
class SchemaStatementsTest < ActiveRecord::PostgreSQLTestCase
|
|
7
|
+
def test_no_crdb_internal_in_schema_names
|
|
8
|
+
assert_not_includes Building.lease_connection.schema_names, "crdb_internal"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_initialize_type_map
|
|
12
|
+
initialized_types = Building.lease_connection.send(:type_map).keys
|
|
13
|
+
|
|
14
|
+
# PostGIS types must be initialized first, so
|
|
15
|
+
# ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#load_additional_types can use them.
|
|
16
|
+
# https://github.com/rails/rails/blob/8d57cb39a88787bb6cfb7e1c481556ef6d8ede7a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L593
|
|
17
|
+
assert_equal initialized_types.first(9), %w[
|
|
18
|
+
geography
|
|
19
|
+
geometry
|
|
20
|
+
geometry_collection
|
|
21
|
+
line_string
|
|
22
|
+
multi_line_string
|
|
23
|
+
multi_point
|
|
24
|
+
multi_polygon
|
|
25
|
+
st_point
|
|
26
|
+
st_polygon
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
end
|