cassanity 0.5.1 → 0.6.0.beta1
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 +7 -0
- data/Gemfile +1 -1
- data/README.md +3 -16
- data/cassanity.gemspec +3 -1
- data/examples/keyspaces.rb +2 -2
- data/lib/cassanity/argument_generators/column_families.rb +1 -1
- data/lib/cassanity/argument_generators/columns.rb +2 -2
- data/lib/cassanity/argument_generators/keyspace_create.rb +4 -21
- data/lib/cassanity/argument_generators/with_clause.rb +2 -9
- data/lib/cassanity/client.rb +16 -13
- data/lib/cassanity/executors/{cassandra_cql.rb → cql_rb.rb} +28 -11
- data/lib/cassanity/keyspace.rb +7 -22
- data/lib/cassanity/migrator.rb +2 -2
- data/lib/cassanity/result_transformers/column_families.rb +1 -1
- data/lib/cassanity/result_transformers/columns.rb +2 -2
- data/lib/cassanity/result_transformers/keyspaces.rb +1 -1
- data/lib/cassanity/result_transformers/result_to_array.rb +1 -5
- data/lib/cassanity/retry_strategies/retry_strategy.rb +3 -3
- data/lib/cassanity/statement.rb +58 -0
- data/lib/cassanity/version.rb +1 -1
- data/spec/helper.rb +2 -4
- data/spec/integration/cassanity/column_family_spec.rb +62 -64
- data/spec/integration/cassanity/connection_spec.rb +2 -8
- data/spec/integration/cassanity/instrumentation/log_subscriber_spec.rb +4 -1
- data/spec/integration/cassanity/instrumentation/metriks_subscriber_spec.rb +2 -1
- data/spec/integration/cassanity/instrumentation/statsd_subscriber_spec.rb +2 -1
- data/spec/integration/cassanity/keyspace_spec.rb +1 -1
- data/spec/integration/cassanity/migration_spec.rb +5 -5
- data/spec/integration/cassanity/migrator_spec.rb +4 -4
- data/spec/support/cassanity_helpers.rb +7 -5
- data/spec/unit/cassanity/argument_generators/column_families_spec.rb +1 -1
- data/spec/unit/cassanity/argument_generators/columns_spec.rb +3 -3
- data/spec/unit/cassanity/argument_generators/keyspace_create_spec.rb +12 -16
- data/spec/unit/cassanity/argument_generators/with_clause_spec.rb +5 -6
- data/spec/unit/cassanity/client_spec.rb +15 -53
- data/spec/unit/cassanity/connection_spec.rb +6 -6
- data/spec/unit/cassanity/keyspace_spec.rb +12 -14
- data/spec/unit/cassanity/result_transformers/result_to_array_spec.rb +3 -16
- data/spec/unit/cassanity/statement_spec.rb +100 -0
- metadata +35 -24
- data/spec/unit/cassanity/executors/cassandra_cql_spec.rb +0 -286
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7892f054f8690a99ae75b121cc43d873c917566a
|
4
|
+
data.tar.gz: 19c266c25056ef75b019463cca2d9bb49133ea72
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 76cbbe3e3afdf0e741325d8e48fd11b51ac08718ab60f423a6ebf6f31c067a48382598605652ab3cedc3e30cdc401f3f0c7860981f82d49749e6fe9d50755a2c
|
7
|
+
data.tar.gz: 909558dee131c53a6c648f2f3148ac44d00f2065bbd33049a46923f793dcac6b36f1b3cd45385b1be96bafa452a927ec7c7467629b4a300b1427649bd83b8431
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,13 @@
|
|
1
|
-
# Cassanity
|
1
|
+
# Cassanity [](http://travis-ci.org/jnunemaker/cassanity)
|
2
2
|
|
3
3
|
Layer of goodness on top of cassandra-cql so you do not have to write CQL strings all over the place.
|
4
4
|
|
5
|
-
## Note about Cassandra 1.2
|
6
|
-
|
7
|
-
At this time, cassandra 1.2 is not supported. Under the hood, cassanity uses [cassandra-cql](https://github.com/kreynolds/cassandra-cql), which does not currently support 1.2. It needs the thrift bindings ([initial pull request](https://github.com/kreynolds/cassandra-cql/pull/39)) updated for 1.2 or to wrap the new binary protocol ([initial pull request](https://github.com/kreynolds/cassandra-cql/pull/40)). I'm hoping to work on this in February or March, but if you want to take a stab, that would be awesome.
|
8
|
-
|
9
5
|
## Installation
|
10
6
|
|
11
7
|
Add this line to your application's Gemfile:
|
12
8
|
|
13
9
|
gem 'cassanity'
|
14
10
|
|
15
|
-
And then execute:
|
16
|
-
|
17
|
-
$ bundle
|
18
|
-
|
19
|
-
Or install it yourself as:
|
20
|
-
|
21
|
-
$ gem install cassanity
|
22
|
-
|
23
11
|
## Usage
|
24
12
|
|
25
13
|
```ruby
|
@@ -110,9 +98,8 @@ You can also do a lot more. Here are a few more [examples](https://github.com/jn
|
|
110
98
|
|
111
99
|
## Compatibility
|
112
100
|
|
113
|
-
* Ruby 1.9.3
|
114
|
-
* Cassandra CQL 3.
|
115
|
-
* Any version of cassandra that works with cassandra-cql and supports CQL 3.
|
101
|
+
* Ruby >= 1.9.3
|
102
|
+
* Cassandra >= 1.2 with CQL >= 3.1.0
|
116
103
|
|
117
104
|
## Contributing
|
118
105
|
|
data/cassanity.gemspec
CHANGED
@@ -17,5 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_dependency '
|
20
|
+
gem.add_dependency 'cql-rb', '~>1.1', '>=1.1.1'
|
21
|
+
|
22
|
+
gem.add_development_dependency 'simple_uuid', '~>0.4'
|
21
23
|
end
|
data/examples/keyspaces.rb
CHANGED
@@ -15,11 +15,11 @@ module Cassanity
|
|
15
15
|
cql = 'SELECT * FROM system.schema_columns'
|
16
16
|
|
17
17
|
if (keyspace_name = args[:keyspace_name])
|
18
|
-
where[:
|
18
|
+
where[:keyspace_name] = keyspace_name
|
19
19
|
end
|
20
20
|
|
21
21
|
if (column_family_name = args[:column_family_name])
|
22
|
-
where[:
|
22
|
+
where[:columnfamily_name] = column_family_name
|
23
23
|
end
|
24
24
|
|
25
25
|
where_cql, *where_variables = @where_clause.call(where: where)
|
@@ -14,22 +14,9 @@ module Cassanity
|
|
14
14
|
name = args.fetch(:keyspace_name)
|
15
15
|
cql = "CREATE KEYSPACE #{name}"
|
16
16
|
|
17
|
-
|
18
|
-
strategy_class: default_strategy_class,
|
19
|
-
strategy_options: default_strategy_options,
|
20
|
-
}
|
21
|
-
|
22
|
-
if args[:strategy_class]
|
23
|
-
with[:strategy_class] = args[:strategy_class]
|
24
|
-
end
|
25
|
-
|
26
|
-
if args[:strategy_options]
|
27
|
-
args[:strategy_options].each do |key, value|
|
28
|
-
with[:strategy_options][key] = value
|
29
|
-
end
|
30
|
-
end
|
17
|
+
replication = default_replication_options.merge(args[:replication] || {})
|
31
18
|
|
32
|
-
with_cql, *with_variables = @with_clause.call(with:
|
19
|
+
with_cql, *with_variables = @with_clause.call(with: { replication: replication })
|
33
20
|
cql << with_cql
|
34
21
|
variables.concat(with_variables)
|
35
22
|
|
@@ -37,13 +24,9 @@ module Cassanity
|
|
37
24
|
end
|
38
25
|
|
39
26
|
# Private
|
40
|
-
def
|
41
|
-
'SimpleStrategy'
|
42
|
-
end
|
43
|
-
|
44
|
-
# Private
|
45
|
-
def default_strategy_options
|
27
|
+
def default_replication_options
|
46
28
|
{
|
29
|
+
class: 'SimpleStrategy',
|
47
30
|
replication_factor: 1,
|
48
31
|
}
|
49
32
|
end
|
@@ -17,15 +17,8 @@ module Cassanity
|
|
17
17
|
withs << "COMPACT STORAGE"
|
18
18
|
end
|
19
19
|
else
|
20
|
-
|
21
|
-
|
22
|
-
withs << "#{key}:#{sub_key} = ?"
|
23
|
-
variables << sub_value
|
24
|
-
end
|
25
|
-
else
|
26
|
-
withs << "#{key} = ?"
|
27
|
-
variables << value
|
28
|
-
end
|
20
|
+
withs << "#{key} = ?"
|
21
|
+
variables << value
|
29
22
|
end
|
30
23
|
end
|
31
24
|
|
data/lib/cassanity/client.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require '
|
3
|
-
require 'cassanity/executors/
|
2
|
+
require 'cql'
|
3
|
+
require 'cassanity/executors/cql_rb'
|
4
4
|
require 'cassanity/connection'
|
5
5
|
|
6
6
|
module Cassanity
|
7
7
|
class Client
|
8
8
|
extend Forwardable
|
9
9
|
|
10
|
-
# Public: The instance of the
|
10
|
+
# Public: The instance of the Cql::Client being used.
|
11
11
|
attr_reader :driver
|
12
12
|
|
13
|
-
# Public: The instance of the Cassanity::Executors::
|
13
|
+
# Public: The instance of the Cassanity::Executors::CqlRb that will
|
14
14
|
# execute all queries.
|
15
15
|
attr_reader :executor
|
16
16
|
|
@@ -22,18 +22,21 @@ module Cassanity
|
|
22
22
|
#
|
23
23
|
# servers - The String or Array of Strings representing the servers to
|
24
24
|
# connect to.
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
# port - The Integer representing the port to connect to Cassandra with.
|
26
|
+
# This must be the same for all hosts.
|
27
|
+
# options - The Hash of Cql::Client options.
|
28
|
+
# :default_consistency - default consistency for the connection
|
29
|
+
# (if not specified, set to :quorum)
|
30
|
+
def initialize(servers = nil, port = nil, options = {})
|
31
|
+
servers ||= ['127.0.0.1']
|
32
|
+
port ||= 9042
|
33
|
+
|
34
|
+
@options = options.merge(hosts: servers, port: port)
|
31
35
|
@instrumenter = @options.delete(:instrumenter)
|
32
36
|
@retry_strategy = @options.delete(:retry_strategy)
|
33
37
|
|
34
|
-
@driver =
|
35
|
-
|
36
|
-
@executor = Cassanity::Executors::CassandraCql.new({
|
38
|
+
@driver = Cql::Client.connect(@options)
|
39
|
+
@executor = Cassanity::Executors::CqlRb.new({
|
37
40
|
driver: @driver,
|
38
41
|
instrumenter: @instrumenter,
|
39
42
|
retry_strategy: @retry_strategy,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'forwardable'
|
2
|
-
require '
|
2
|
+
require 'cql'
|
3
3
|
require 'cassanity/error'
|
4
|
+
require 'cassanity/statement'
|
4
5
|
require 'cassanity/instrumenters/noop'
|
5
6
|
require 'cassanity/argument_generators/keyspaces'
|
6
7
|
require 'cassanity/argument_generators/keyspace_create'
|
@@ -29,7 +30,7 @@ require 'cassanity/retry_strategies/exponential_backoff'
|
|
29
30
|
|
30
31
|
module Cassanity
|
31
32
|
module Executors
|
32
|
-
class
|
33
|
+
class CqlRb
|
33
34
|
extend Forwardable
|
34
35
|
|
35
36
|
# Private: Hash of commands to related argument generators.
|
@@ -88,7 +89,7 @@ module Cassanity
|
|
88
89
|
# Internal: Initializes a cassandra-cql based CQL executor.
|
89
90
|
#
|
90
91
|
# args - The Hash of arguments.
|
91
|
-
# :driver - The
|
92
|
+
# :driver - The Cql::Client connection instance.
|
92
93
|
# :instrumenter - What should be used to instrument all the things
|
93
94
|
# (default: Cassanity::Instrumenters::Noop).
|
94
95
|
# :argument_generators - A Hash where each key is a command name
|
@@ -100,13 +101,13 @@ module Cassanity
|
|
100
101
|
# transformer that responds to `call`
|
101
102
|
# (optional).
|
102
103
|
# :retry_strategy - What retry strategy to use on failed
|
103
|
-
#
|
104
|
+
# Cql::Client calls
|
104
105
|
# (default: Cassanity::Instrumenters::RetryNTimes)
|
105
106
|
#
|
106
107
|
# Examples
|
107
108
|
#
|
108
|
-
# driver =
|
109
|
-
# Cassanity::Executors::
|
109
|
+
# driver = Cql::Client.connect(hosts: ['cassandra.example.com'])
|
110
|
+
# Cassanity::Executors::CqlRb.new(driver: driver)
|
110
111
|
#
|
111
112
|
def initialize(args = {})
|
112
113
|
@driver = args.fetch(:driver)
|
@@ -148,20 +149,36 @@ module Cassanity
|
|
148
149
|
arguments = args[:arguments]
|
149
150
|
|
150
151
|
if arguments
|
152
|
+
# TODO: As a temporary measure, we remove this deprecated option
|
153
|
+
# while we have time to update each gem (e.g., adapter-cassanity)
|
154
|
+
# that sets it. Consistency should be specified at the connection
|
155
|
+
# level for now.
|
156
|
+
if arguments[:using]
|
157
|
+
arguments[:using].delete(:consistency)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Instrumentation parameters
|
151
161
|
if (keyspace_name = arguments[:keyspace_name])
|
152
162
|
payload[:keyspace_name] = keyspace_name
|
153
163
|
end
|
154
|
-
|
155
164
|
if (column_family_name = arguments[:column_family_name])
|
156
165
|
payload[:column_family_name] = column_family_name
|
157
166
|
end
|
167
|
+
|
168
|
+
# Select keyspace before query runs
|
169
|
+
if command != :keyspace_create && (keyspace_name = arguments[:keyspace_name])
|
170
|
+
@driver.use(keyspace_name)
|
171
|
+
end
|
158
172
|
end
|
159
173
|
|
160
174
|
begin
|
161
|
-
|
162
|
-
payload[:cql] =
|
163
|
-
payload[:cql_variables] =
|
164
|
-
|
175
|
+
cql, *variables = generator.call(arguments)
|
176
|
+
payload[:cql] = cql
|
177
|
+
payload[:cql_variables] = variables
|
178
|
+
|
179
|
+
statement = Cassanity::Statement.new(cql)
|
180
|
+
result = @retry_strategy.execute(payload) { @driver.execute(statement.interpolate(variables)) }
|
181
|
+
|
165
182
|
transformer = @result_transformers.fetch(command, Mirror)
|
166
183
|
transformed_result = transformer.call(result, args[:transformer_arguments])
|
167
184
|
payload[:result] = transformed_result
|
data/lib/cassanity/keyspace.rb
CHANGED
@@ -9,26 +9,20 @@ module Cassanity
|
|
9
9
|
attr_reader :executor
|
10
10
|
|
11
11
|
# Internal
|
12
|
-
attr_reader :
|
13
|
-
|
14
|
-
# Internal
|
15
|
-
attr_reader :strategy_options
|
12
|
+
attr_reader :replication
|
16
13
|
|
17
14
|
# Public: Initializes a Keyspace.
|
18
15
|
#
|
19
16
|
# args - The Hash of arguments (default: {}).
|
20
17
|
# :name - The String name of the keyspace.
|
21
18
|
# :executor - What will execute the queries. Must respond to `call`.
|
22
|
-
# :
|
23
|
-
#
|
24
|
-
# :strategy_options - The Hash of strategy options to use when
|
25
|
-
# creating keyspace.
|
19
|
+
# :replication - Hash of replication options (e.g., :class,
|
20
|
+
# :replication_factor)
|
26
21
|
#
|
27
22
|
def initialize(args = {})
|
28
23
|
@name = args.fetch(:name).to_sym
|
29
24
|
@executor = args.fetch(:executor)
|
30
|
-
@
|
31
|
-
@strategy_options = args[:strategy_options]
|
25
|
+
@replication = args.fetch(:replication, {})
|
32
26
|
end
|
33
27
|
|
34
28
|
# Public: Returns true or false depending on if keyspace exists in the
|
@@ -57,8 +51,8 @@ module Cassanity
|
|
57
51
|
#
|
58
52
|
# # override options from initialization
|
59
53
|
# create({
|
60
|
-
#
|
61
|
-
#
|
54
|
+
# replication: {
|
55
|
+
# class: 'NetworkTopologyStrategy',
|
62
56
|
# dc1: 1,
|
63
57
|
# dc2: 3,
|
64
58
|
# }
|
@@ -67,16 +61,7 @@ module Cassanity
|
|
67
61
|
# Returns whatever is returned by executor.
|
68
62
|
def create(args = {})
|
69
63
|
create_arguments = {}.merge(args)
|
70
|
-
|
71
|
-
if @strategy_class
|
72
|
-
create_arguments[:strategy_class] = @strategy_class
|
73
|
-
end
|
74
|
-
|
75
|
-
if @strategy_options
|
76
|
-
strategy_options = args[:strategy_options] || {}
|
77
|
-
create_arguments[:strategy_options] = strategy_options.merge(@strategy_options)
|
78
|
-
end
|
79
|
-
|
64
|
+
create_arguments[:replication] = @replication.merge(create_arguments[:replication] || {})
|
80
65
|
create_arguments[:keyspace_name] = @name
|
81
66
|
|
82
67
|
@executor.call({
|
data/lib/cassanity/migrator.rb
CHANGED
@@ -52,7 +52,7 @@ module Cassanity
|
|
52
52
|
def migrated_up(migration)
|
53
53
|
column_family.insert({
|
54
54
|
data: {
|
55
|
-
version: migration.version,
|
55
|
+
version: migration.version.to_s,
|
56
56
|
name: migration.name,
|
57
57
|
migrated_at: Time.now.utc,
|
58
58
|
},
|
@@ -63,7 +63,7 @@ module Cassanity
|
|
63
63
|
def migrated_down(migration)
|
64
64
|
column_family.delete({
|
65
65
|
where: {
|
66
|
-
version: migration.version,
|
66
|
+
version: migration.version.to_s,
|
67
67
|
name: migration.name,
|
68
68
|
},
|
69
69
|
})
|
@@ -7,7 +7,7 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of column families.
|
8
8
|
def call(result, args = {})
|
9
9
|
column_families = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
column_families << ColumnFamily.new({
|
12
12
|
name: row['columnfamily'] || row['columnfamily_name'],
|
13
13
|
keyspace: args[:keyspace],
|
@@ -7,9 +7,9 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of column families.
|
8
8
|
def call(result, args = {})
|
9
9
|
columns = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
columns << Column.new({
|
12
|
-
name: row['
|
12
|
+
name: row['column_name'],
|
13
13
|
type: row['validator'],
|
14
14
|
column_family: args[:column_family],
|
15
15
|
})
|
@@ -7,7 +7,7 @@ module Cassanity
|
|
7
7
|
# Internal: Turns result into Array of keyspaces.
|
8
8
|
def call(result, args = {})
|
9
9
|
keyspaces = []
|
10
|
-
result.
|
10
|
+
result.each do |row|
|
11
11
|
name = row['name'] || row['keyspace'] || row['keyspace_name']
|
12
12
|
keyspaces << Keyspace.new({
|
13
13
|
name: name,
|
@@ -10,7 +10,7 @@ module Cassanity
|
|
10
10
|
end
|
11
11
|
|
12
12
|
# Public: execute the given block, and handle errors raised
|
13
|
-
# by the
|
13
|
+
# by the Cql::Client driver. Call the retry method (overridden in your
|
14
14
|
# subclass) on each failed attempt with a current retry count and
|
15
15
|
# the error raised by the block.
|
16
16
|
#
|
@@ -25,11 +25,11 @@ module Cassanity
|
|
25
25
|
begin
|
26
26
|
payload[:attempts] = attempt unless payload.nil?
|
27
27
|
return yield
|
28
|
-
rescue
|
28
|
+
rescue Cql::CqlError => e
|
29
29
|
fail(attempt, e)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
|
-
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Cassanity
|
6
|
+
class Statement
|
7
|
+
def initialize(cql, options = {})
|
8
|
+
@cql = cql
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
@cql_version = @options.fetch(:cql_version, '3.0.0')
|
12
|
+
end
|
13
|
+
|
14
|
+
def interpolate(variables)
|
15
|
+
e = variables.to_enum
|
16
|
+
@cql.gsub(/\?/) { quote(e.next) }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def quote(var)
|
21
|
+
if Array === var
|
22
|
+
var.map { |v| quote(v) }.join(',')
|
23
|
+
elsif Hash === var
|
24
|
+
%({#{var.map { |k, v| "#{quote(k)}:#{quote(v)}" }.join(',')}})
|
25
|
+
elsif String === var
|
26
|
+
%('#{escape_string(var)}')
|
27
|
+
elsif BigDecimal === var && cql2?
|
28
|
+
%('#{var.to_s}')
|
29
|
+
elsif Numeric === var
|
30
|
+
var.to_s
|
31
|
+
elsif Date === var
|
32
|
+
%('#{var.strftime('%Y-%m-%d')}')
|
33
|
+
elsif Time === var
|
34
|
+
(var.to_f * 1000).to_i
|
35
|
+
elsif TrueClass === var || FalseClass === var
|
36
|
+
if cql2?
|
37
|
+
%('#{var.to_s}')
|
38
|
+
else
|
39
|
+
var.to_s
|
40
|
+
end
|
41
|
+
elsif var.respond_to?(:to_guid)
|
42
|
+
var.to_guid
|
43
|
+
elsif var.respond_to?(:to_s)
|
44
|
+
%('#{var.to_s}')
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Unable to escape #{var} (of type #{var.class})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def escape_string(str)
|
51
|
+
str.gsub("'", "''")
|
52
|
+
end
|
53
|
+
|
54
|
+
def cql2?
|
55
|
+
@cql_version.start_with?('2')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/cassanity/version.rb
CHANGED
data/spec/helper.rb
CHANGED
@@ -29,7 +29,5 @@ RSpec.configure do |config|
|
|
29
29
|
config.include CassanityHelpers
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
CassanityServers = "#{host}:#{port}"
|
32
|
+
CassanityHost = Array(ENV.fetch('CASSANITY_HOST', '127.0.0.1'))
|
33
|
+
CassanityPort = ENV.fetch('CASSANITY_PORT', '9042').to_i
|