activerecord-cockroachdb-adapter 0.3.0.beta1 → 6.0.0beta2
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/.gitignore +1 -0
- data/CONTRIBUTING.md +15 -3
- data/README.md +1 -1
- data/activerecord-cockroachdb-adapter.gemspec +2 -2
- data/build/teamcity-test.sh +7 -4
- data/docker.sh +1 -1
- data/lib/active_record/connection_adapters/cockroachdb/database_statements.rb +81 -0
- data/lib/active_record/connection_adapters/cockroachdb/schema_statements.rb +48 -0
- data/lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb +10 -4
- data/lib/active_record/connection_adapters/cockroachdb_adapter.rb +54 -14
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2d3e8ca47a966406dd46a2516ee8e28d4634109974b012604efa5eeef41661c
|
4
|
+
data.tar.gz: 78f2720ce377db8b5d61c830c2f8819f4a928bceee3121668348293944b459ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7928d308947df985eac1ecf4ac2338d8ed5e079c2436562a822b149699b0bc42e38411e90a73a5623c376613ca610060d68f9040d823b9fa65d2869baa98a6fd
|
7
|
+
data.tar.gz: 5421bb06b3185d331a0a43e700e2475f6ce28ee38d433ba28e3e82c4a0e361a94518dd46fd41b54d1a83f1a217d19e098eef0a251ad7e79803263c08bfd7e3dc
|
data/.gitignore
CHANGED
data/CONTRIBUTING.md
CHANGED
@@ -46,19 +46,31 @@ Using [bundler](http://bundler.io/), install the dependancies of Rails.
|
|
46
46
|
bundle install
|
47
47
|
```
|
48
48
|
|
49
|
-
Then, to run the full
|
49
|
+
Then, to run the full test suite with an active CockroachDB instance:
|
50
50
|
|
51
51
|
```bash
|
52
52
|
bundle exec rake test
|
53
53
|
```
|
54
54
|
|
55
|
-
To run specific tests, set environemnt variable `TEST_FILES_AR`. For example, to run ActiveRecord tests `test/cases/associations_test.rb` and `test/cases/ar_schema_test.rb.rb`
|
55
|
+
To run specific ActiveRecord tests, set environemnt variable `TEST_FILES_AR`. For example, to run ActiveRecord tests `test/cases/associations_test.rb` and `test/cases/ar_schema_test.rb.rb`
|
56
56
|
|
57
57
|
```bash
|
58
58
|
TEST_FILES_AR="test/cases/associations_test.rb,test/cases/ar_schema_test.rb" bundle exec rake test
|
59
59
|
```
|
60
60
|
|
61
|
-
|
61
|
+
To run specific CockroachDB Adapter tests, set environemnt variable `TEST_FILES`. For example, to run CockroachDB Adpater tests `test/cases/adapter_test.rb` and `test/cases/associations/left_outer_join_association_test.rb`
|
62
|
+
|
63
|
+
```bash
|
64
|
+
TEST_FILES="test/cases/adapter_test.rb,test/cases/associations/left_outer_join_association_test.rb" bundle exec rake test
|
65
|
+
```
|
66
|
+
|
67
|
+
To run a specific test case, use minitest's `-n` option to run tests that match a given pattern. All minitest options are set via the `TESTOPTS` environemnt variable. For example, to run `test_indexes` from CockroachDB's `test/cases/adapter_test.rb` file
|
68
|
+
|
69
|
+
```bash
|
70
|
+
TEST_FILES="test/cases/adapter_test.rb" TESTOPTS=`-n=/test_indexes/` bundle exec rake test
|
71
|
+
```
|
72
|
+
|
73
|
+
By default, tests will be run from the bundled version of Rails. To run against a local copy, set environemnt variable `RAILS_SOURCE`. Running against a local copy of Rails can be helpful when try to debug issues.
|
62
74
|
|
63
75
|
```bash
|
64
76
|
RAILS_SOURCE="path/to/local_copy" bundle exec rake test
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ CockroachDB adapter for ActiveRecord 4 and 5. This is a lightweight extension of
|
|
7
7
|
Add this line to your project's Gemfile:
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
gem 'activerecord-cockroachdb-adapter', '~>
|
10
|
+
gem 'activerecord-cockroachdb-adapter', '~> 5.2.0'
|
11
11
|
```
|
12
12
|
|
13
13
|
If you're using Rails 4.x, use the `0.1.x` versions of this gem.
|
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "activerecord-cockroachdb-adapter"
|
7
|
-
spec.version = "
|
7
|
+
spec.version = "6.0.0beta2"
|
8
8
|
spec.licenses = ["Apache-2.0"]
|
9
9
|
spec.authors = ["Cockroach Labs"]
|
10
10
|
spec.email = ["cockroach-db@googlegroups.com"]
|
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.description = "Allows the use of CockroachDB as a backend for ActiveRecord and Rails apps."
|
14
14
|
spec.homepage = "https://github.com/cockroachdb/activerecord-cockroachdb-adapter"
|
15
15
|
|
16
|
-
spec.add_dependency "activerecord", "~>
|
16
|
+
spec.add_dependency "activerecord", "~> 6.0.3"
|
17
17
|
spec.add_dependency "pg", ">= 0.20"
|
18
18
|
|
19
19
|
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
data/build/teamcity-test.sh
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
2
|
|
3
|
-
set -
|
3
|
+
set -euox pipefail
|
4
4
|
|
5
5
|
# Download CockroachDB
|
6
|
-
VERSION=v20.
|
6
|
+
VERSION=v20.2.3
|
7
7
|
wget -qO- https://binaries.cockroachdb.com/cockroach-$VERSION.linux-amd64.tgz | tar xvz
|
8
8
|
readonly COCKROACH=./cockroach-$VERSION.linux-amd64/cockroach
|
9
9
|
|
@@ -21,7 +21,7 @@ run_cockroach() {
|
|
21
21
|
cockroach quit --insecure || true
|
22
22
|
rm -rf cockroach-data
|
23
23
|
# Start CockroachDB.
|
24
|
-
cockroach start --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
|
24
|
+
cockroach start-single-node --max-sql-memory=25% --cache=25% --insecure --host=localhost --listening-url-file="$urlfile" >/dev/null 2>&1 &
|
25
25
|
# Ensure CockroachDB is stopped on script exit.
|
26
26
|
trap "echo 'Exit routine: Killing CockroachDB.' && kill -9 $! &> /dev/null" EXIT
|
27
27
|
# Wait until CockroachDB has started.
|
@@ -33,6 +33,9 @@ run_cockroach() {
|
|
33
33
|
done
|
34
34
|
cockroach sql --insecure -e 'CREATE DATABASE activerecord_unittest;'
|
35
35
|
cockroach sql --insecure -e 'CREATE DATABASE activerecord_unittest2;'
|
36
|
+
cockroach sql --insecure -e 'SET CLUSTER SETTING sql.stats.automatic_collection.enabled = false;'
|
37
|
+
cockroach sql --insecure -e 'SET CLUSTER SETTING sql.stats.histogram_collection.enabled = false;'
|
38
|
+
cockroach sql --insecure -e "SET CLUSTER SETTING jobs.retention_time = '180s';"
|
36
39
|
}
|
37
40
|
|
38
41
|
# Install ruby dependencies.
|
@@ -41,7 +44,7 @@ bundle install
|
|
41
44
|
|
42
45
|
run_cockroach
|
43
46
|
|
44
|
-
if ! (bundle exec rake test); then
|
47
|
+
if ! (RUBYOPT="-W0" TESTOPTS="-v" bundle exec rake test); then
|
45
48
|
echo "Tests failed"
|
46
49
|
HAS_FAILED=1
|
47
50
|
else
|
data/docker.sh
CHANGED
@@ -15,6 +15,87 @@ module ActiveRecord
|
|
15
15
|
serializable: "SERIALIZABLE"
|
16
16
|
}
|
17
17
|
end
|
18
|
+
|
19
|
+
# Overridden to avoid using transactions for schema creation.
|
20
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
21
|
+
fixture_inserts = build_fixture_statements(fixture_set)
|
22
|
+
table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name(table)}" }
|
23
|
+
statements = table_deletes + fixture_inserts
|
24
|
+
|
25
|
+
with_multi_statements do
|
26
|
+
disable_referential_integrity do
|
27
|
+
execute_batch(statements, "Fixtures Load")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def execute_batch(statements, name = nil)
|
34
|
+
statements.each do |statement|
|
35
|
+
execute(statement, name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
DEFAULT_INSERT_VALUE = Arel.sql("DEFAULT").freeze
|
40
|
+
private_constant :DEFAULT_INSERT_VALUE
|
41
|
+
|
42
|
+
def default_insert_value(column)
|
43
|
+
DEFAULT_INSERT_VALUE
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_fixture_sql(fixtures, table_name)
|
47
|
+
columns = schema_cache.columns_hash(table_name)
|
48
|
+
|
49
|
+
values_list = fixtures.map do |fixture|
|
50
|
+
fixture = fixture.stringify_keys
|
51
|
+
|
52
|
+
unknown_columns = fixture.keys - columns.keys
|
53
|
+
if unknown_columns.any?
|
54
|
+
raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
|
55
|
+
end
|
56
|
+
|
57
|
+
columns.map do |name, column|
|
58
|
+
if fixture.key?(name)
|
59
|
+
type = lookup_cast_type_from_column(column)
|
60
|
+
with_yaml_fallback(type.serialize(fixture[name]))
|
61
|
+
else
|
62
|
+
default_insert_value(column)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
table = Arel::Table.new(table_name)
|
68
|
+
manager = Arel::InsertManager.new
|
69
|
+
manager.into(table)
|
70
|
+
|
71
|
+
if values_list.size == 1
|
72
|
+
values = values_list.shift
|
73
|
+
new_values = []
|
74
|
+
columns.each_key.with_index { |column, i|
|
75
|
+
unless values[i].equal?(DEFAULT_INSERT_VALUE)
|
76
|
+
new_values << values[i]
|
77
|
+
manager.columns << table[column]
|
78
|
+
end
|
79
|
+
}
|
80
|
+
values_list << new_values
|
81
|
+
else
|
82
|
+
columns.each_key { |column| manager.columns << table[column] }
|
83
|
+
end
|
84
|
+
|
85
|
+
manager.values = manager.create_values_list(values_list)
|
86
|
+
manager.to_sql
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_fixture_statements(fixture_set)
|
90
|
+
fixture_set.map do |table_name, fixtures|
|
91
|
+
next if fixtures.empty?
|
92
|
+
build_fixture_sql(fixtures, table_name)
|
93
|
+
end.compact
|
94
|
+
end
|
95
|
+
|
96
|
+
def with_multi_statements
|
97
|
+
yield
|
98
|
+
end
|
18
99
|
end
|
19
100
|
end
|
20
101
|
end
|
@@ -38,6 +38,54 @@ module ActiveRecord
|
|
38
38
|
def default_sequence_name(table_name, pk = "id")
|
39
39
|
nil
|
40
40
|
end
|
41
|
+
|
42
|
+
# CockroachDB will use INT8 if the SQL type is INTEGER, so we make it use
|
43
|
+
# INT4 explicitly when needed.
|
44
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
|
45
|
+
sql = \
|
46
|
+
case type.to_s
|
47
|
+
when "integer"
|
48
|
+
case limit
|
49
|
+
when nil; "int"
|
50
|
+
when 1, 2; "int2"
|
51
|
+
when 3, 4; "int4"
|
52
|
+
when 5..8; "int8"
|
53
|
+
else super
|
54
|
+
end
|
55
|
+
else
|
56
|
+
super
|
57
|
+
end
|
58
|
+
# The call to super might have appeneded [] already.
|
59
|
+
if array && type != :primary_key && !sql.end_with?("[]")
|
60
|
+
sql = "#{sql}[]"
|
61
|
+
end
|
62
|
+
sql
|
63
|
+
end
|
64
|
+
|
65
|
+
# This overrides the method from PostegreSQL adapter
|
66
|
+
# Resets the sequence of a table's primary key to the maximum value.
|
67
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
68
|
+
unless pk && sequence
|
69
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
70
|
+
|
71
|
+
pk ||= default_pk
|
72
|
+
sequence ||= default_sequence
|
73
|
+
end
|
74
|
+
|
75
|
+
if @logger && pk && !sequence
|
76
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence."
|
77
|
+
end
|
78
|
+
|
79
|
+
if pk && sequence
|
80
|
+
quoted_sequence = quote_table_name(sequence)
|
81
|
+
max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA")
|
82
|
+
if max_pk.nil?
|
83
|
+
minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA")
|
84
|
+
end
|
85
|
+
|
86
|
+
query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA")
|
87
|
+
end
|
88
|
+
end
|
41
89
|
end
|
42
90
|
end
|
43
91
|
end
|
@@ -8,16 +8,22 @@ module ActiveRecord
|
|
8
8
|
# transactions that fail due to serialization errors. Failed
|
9
9
|
# transactions will be retried until they pass or the max retry limit is
|
10
10
|
# exceeded.
|
11
|
-
def within_new_transaction(
|
12
|
-
attempts = options.fetch(:attempts, 0)
|
11
|
+
def within_new_transaction(isolation: nil, joinable: true, attempts: 0)
|
13
12
|
super
|
14
|
-
rescue ActiveRecord::
|
13
|
+
rescue ActiveRecord::StatementInvalid => error
|
14
|
+
raise unless retryable? error
|
15
15
|
raise if attempts >= @connection.max_transaction_retries
|
16
16
|
|
17
17
|
attempts += 1
|
18
18
|
sleep_seconds = (2 ** attempts + rand) / 10
|
19
19
|
sleep(sleep_seconds)
|
20
|
-
within_new_transaction(
|
20
|
+
within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts) { yield }
|
21
|
+
end
|
22
|
+
|
23
|
+
def retryable?(error)
|
24
|
+
return true if error.is_a? ActiveRecord::SerializationFailure
|
25
|
+
return retryable? error.cause if error.cause
|
26
|
+
false
|
21
27
|
end
|
22
28
|
end
|
23
29
|
end
|
@@ -24,10 +24,14 @@ module ActiveRecord
|
|
24
24
|
valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:sslmode, :application_name]
|
25
25
|
conn_params.slice!(*valid_conn_param_keys)
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
conn = PG.connect(conn_params)
|
28
|
+
ConnectionAdapters::CockroachDBAdapter.new(conn, logger, conn_params, config)
|
29
|
+
rescue ::PG::Error, ActiveRecord::ActiveRecordError => error
|
30
|
+
if error.message.include?("does not exist")
|
31
|
+
raise ActiveRecord::NoDatabaseError
|
32
|
+
else
|
33
|
+
raise
|
34
|
+
end
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -56,6 +60,10 @@ module ActiveRecord
|
|
56
60
|
100000
|
57
61
|
end
|
58
62
|
|
63
|
+
def supports_bulk_alter?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
59
67
|
def supports_json?
|
60
68
|
# FIXME(joey): Add a version check.
|
61
69
|
true
|
@@ -69,18 +77,12 @@ module ActiveRecord
|
|
69
77
|
false
|
70
78
|
end
|
71
79
|
|
72
|
-
def supports_ranges?
|
73
|
-
# See cockroachdb/cockroach#17022
|
74
|
-
false
|
75
|
-
end
|
76
|
-
|
77
80
|
def supports_materialized_views?
|
78
81
|
false
|
79
82
|
end
|
80
83
|
|
81
84
|
def supports_partial_index?
|
82
|
-
|
83
|
-
false
|
85
|
+
@crdb_version >= 202
|
84
86
|
end
|
85
87
|
|
86
88
|
def supports_expression_index?
|
@@ -103,8 +105,7 @@ module ActiveRecord
|
|
103
105
|
end
|
104
106
|
|
105
107
|
def supports_advisory_locks?
|
106
|
-
|
107
|
-
true
|
108
|
+
false
|
108
109
|
end
|
109
110
|
|
110
111
|
def supports_virtual_columns?
|
@@ -112,6 +113,10 @@ module ActiveRecord
|
|
112
113
|
false
|
113
114
|
end
|
114
115
|
|
116
|
+
def supports_string_to_array_coercion?
|
117
|
+
@crdb_version >= 202
|
118
|
+
end
|
119
|
+
|
115
120
|
# This is hardcoded to 63 (as previously was in ActiveRecord 5.0) to aid in
|
116
121
|
# migration from PostgreSQL to CockroachDB. In practice, this limitation
|
117
122
|
# is arbitrary since CockroachDB supports index name lengths and table alias
|
@@ -127,6 +132,31 @@ module ActiveRecord
|
|
127
132
|
alias index_name_length max_identifier_length
|
128
133
|
alias table_alias_length max_identifier_length
|
129
134
|
|
135
|
+
def initialize(connection, logger, conn_params, config)
|
136
|
+
super(connection, logger, conn_params, config)
|
137
|
+
crdb_version_string = query_value("SHOW crdb_version")
|
138
|
+
if crdb_version_string.include? "v1."
|
139
|
+
version_num = 1
|
140
|
+
elsif crdb_version_string.include? "v2."
|
141
|
+
version_num 2
|
142
|
+
elsif crdb_version_string.include? "v19.1."
|
143
|
+
version_num = 191
|
144
|
+
elsif crdb_version_string.include? "v19.2."
|
145
|
+
version_num = 192
|
146
|
+
elsif crdb_version_string.include? "v20.1."
|
147
|
+
version_num = 201
|
148
|
+
else
|
149
|
+
version_num = 202
|
150
|
+
end
|
151
|
+
@crdb_version = version_num
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.database_exists?(config)
|
155
|
+
!!ActiveRecord::Base.cockroachdb_connection(config)
|
156
|
+
rescue ActiveRecord::NoDatabaseError
|
157
|
+
false
|
158
|
+
end
|
159
|
+
|
130
160
|
private
|
131
161
|
|
132
162
|
def initialize_type_map(m = type_map)
|
@@ -199,7 +229,8 @@ module ActiveRecord
|
|
199
229
|
def extract_value_from_default(default)
|
200
230
|
super ||
|
201
231
|
extract_escaped_string_from_default(default) ||
|
202
|
-
extract_time_from_default(default)
|
232
|
+
extract_time_from_default(default) ||
|
233
|
+
extract_empty_array_from_default(default)
|
203
234
|
end
|
204
235
|
|
205
236
|
# Both PostgreSQL and CockroachDB use C-style string escapes under the
|
@@ -241,6 +272,15 @@ module ActiveRecord
|
|
241
272
|
nil
|
242
273
|
end
|
243
274
|
|
275
|
+
# CockroachDB stores default values for arrays in the `ARRAY[...]` format.
|
276
|
+
# In general, it is hard to parse that, but it is easy to handle the common
|
277
|
+
# case of an empty array.
|
278
|
+
def extract_empty_array_from_default(default)
|
279
|
+
return unless supports_string_to_array_coercion?
|
280
|
+
return unless default =~ /\AARRAY\[\]\z/
|
281
|
+
return "{}"
|
282
|
+
end
|
283
|
+
|
244
284
|
# end private
|
245
285
|
end
|
246
286
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-cockroachdb-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cockroach Labs
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.0.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 6.0.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pg
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,7 +77,7 @@ licenses:
|
|
77
77
|
- Apache-2.0
|
78
78
|
metadata:
|
79
79
|
allowed_push_host: https://rubygems.org
|
80
|
-
post_install_message:
|
80
|
+
post_install_message:
|
81
81
|
rdoc_options: []
|
82
82
|
require_paths:
|
83
83
|
- lib
|
@@ -92,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: 1.3.1
|
94
94
|
requirements: []
|
95
|
-
rubygems_version: 3.1.
|
96
|
-
signing_key:
|
95
|
+
rubygems_version: 3.1.4
|
96
|
+
signing_key:
|
97
97
|
specification_version: 4
|
98
98
|
summary: CockroachDB adapter for ActiveRecord.
|
99
99
|
test_files: []
|