neo4j 5.2.15 → 6.0.0.alpha.1
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 +17 -19
- data/Gemfile +1 -1
- data/lib/neo4j.rb +4 -1
- data/lib/neo4j/active_node.rb +6 -15
- data/lib/neo4j/active_node/has_n.rb +52 -21
- data/lib/neo4j/active_node/has_n/association.rb +1 -1
- data/lib/neo4j/active_node/id_property.rb +1 -1
- data/lib/neo4j/active_node/id_property/accessor.rb +1 -1
- data/lib/neo4j/active_node/labels.rb +7 -101
- data/lib/neo4j/active_node/labels/index.rb +87 -0
- data/lib/neo4j/active_node/persistence.rb +3 -2
- data/lib/neo4j/active_node/query.rb +1 -1
- data/lib/neo4j/active_node/query/query_proxy.rb +7 -9
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +0 -1
- data/lib/neo4j/active_node/query/query_proxy_link.rb +12 -4
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -6
- data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +4 -8
- data/lib/neo4j/active_node/unpersisted.rb +12 -10
- data/lib/neo4j/active_node/validations.rb +2 -2
- data/lib/neo4j/active_rel.rb +7 -4
- data/lib/neo4j/active_rel/persistence.rb +13 -4
- data/lib/neo4j/active_rel/query.rb +8 -0
- data/lib/neo4j/active_rel/related_node.rb +1 -27
- data/lib/neo4j/errors.rb +2 -0
- data/lib/neo4j/schema/operation.rb +91 -0
- data/lib/neo4j/shared.rb +3 -3
- data/lib/neo4j/shared/callbacks.rb +2 -7
- data/lib/neo4j/shared/{declared_property_manager.rb → declared_properties.rb} +34 -2
- data/lib/neo4j/shared/declared_property.rb +19 -0
- data/lib/neo4j/shared/declared_property/index.rb +37 -0
- data/lib/neo4j/shared/initialize.rb +2 -2
- data/lib/neo4j/shared/persistence.rb +3 -25
- data/lib/neo4j/shared/property.rb +24 -10
- data/lib/neo4j/shared/type_converters.rb +131 -6
- data/lib/neo4j/tasks/migration.rake +3 -3
- data/lib/neo4j/type_converters.rb +1 -1
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +2 -2
- metadata +13 -10
@@ -0,0 +1,87 @@
|
|
1
|
+
module Neo4j::ActiveNode::Labels
|
2
|
+
module Index
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :declared_properties, :indexed_properties
|
9
|
+
|
10
|
+
# Creates a Neo4j index on given property
|
11
|
+
#
|
12
|
+
# This can also be done on the property directly, see Neo4j::ActiveNode::Property::ClassMethods#property.
|
13
|
+
#
|
14
|
+
# @param [Symbol] property the property we want a Neo4j index on
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class Person
|
18
|
+
# include Neo4j::ActiveNode
|
19
|
+
# property :name
|
20
|
+
# index :name
|
21
|
+
# end
|
22
|
+
def index(property)
|
23
|
+
Neo4j::Session.on_next_session_available do |_|
|
24
|
+
declared_properties.index_or_fail!(property, id_property_name)
|
25
|
+
schema_create_operation(:index, property)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a neo4j constraint on this class for given property
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# Person.constraint :name, type: :unique
|
33
|
+
def constraint(property, constraints = {type: :unique})
|
34
|
+
Neo4j::Session.on_next_session_available do
|
35
|
+
declared_properties.constraint_or_fail!(property, id_property_name)
|
36
|
+
schema_create_operation(:constraint, property, constraints)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [Symbol] property The name of the property index to be dropped
|
41
|
+
def drop_index(property, options = {})
|
42
|
+
Neo4j::Session.on_next_session_available do
|
43
|
+
declared_properties[property].unindex! if declared_properties[property]
|
44
|
+
schema_drop_operation(:index, property, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param [Symbol] property The name of the property constraint to be dropped
|
49
|
+
# @param [Hash] constraint The constraint type to be dropped.
|
50
|
+
def drop_constraint(property, constraint = {type: :unique})
|
51
|
+
Neo4j::Session.on_next_session_available do
|
52
|
+
declared_properties[property].unconstraint! if declared_properties[property]
|
53
|
+
schema_drop_operation(:constraint, property, constraint)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def index?(property)
|
58
|
+
mapped_label.indexes[:property_keys].include?([property])
|
59
|
+
end
|
60
|
+
|
61
|
+
def constraint?(property)
|
62
|
+
mapped_label.unique_constraints[:property_keys].include?([property])
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def schema_create_operation(type, property, options = {})
|
68
|
+
new_schema_class(type, property, options).create!
|
69
|
+
end
|
70
|
+
|
71
|
+
def schema_drop_operation(type, property, options = {})
|
72
|
+
new_schema_class(type, property, options).drop!
|
73
|
+
end
|
74
|
+
|
75
|
+
def new_schema_class(type, property, options)
|
76
|
+
case type
|
77
|
+
when :index
|
78
|
+
Neo4j::Schema::ExactIndexOperation
|
79
|
+
when :constraint
|
80
|
+
Neo4j::Schema::UniqueConstraintOperation
|
81
|
+
else
|
82
|
+
fail "Unknown Schema Operation class #{type}"
|
83
|
+
end.new(mapped_label_name, property, options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -40,7 +40,7 @@ module Neo4j::ActiveNode
|
|
40
40
|
# @see Neo4j::Rails::Validations Neo4j::Rails::Validations - for the :validate parameter
|
41
41
|
# @see Neo4j::Rails::Callbacks Neo4j::Rails::Callbacks - for callbacks
|
42
42
|
def save!(*args)
|
43
|
-
fail
|
43
|
+
save(*args) or fail(RecordInvalidError, self) # rubocop:disable Style/AndOr
|
44
44
|
end
|
45
45
|
|
46
46
|
# Creates a model with values matching those of the instance attributes and returns its id.
|
@@ -130,8 +130,9 @@ module Neo4j::ActiveNode
|
|
130
130
|
|
131
131
|
def find_or_create(find_attributes, set_attributes = {})
|
132
132
|
on_create_attributes = set_attributes.merge(on_create_props(find_attributes))
|
133
|
+
on_match_attributes = set_attributes.merge(on_match_props)
|
133
134
|
neo4j_session.query.merge(n: {self.mapped_label_names => find_attributes})
|
134
|
-
.on_create_set(n: on_create_attributes)
|
135
|
+
.on_create_set(n: on_create_attributes).on_match_set(n: on_match_attributes)
|
135
136
|
.pluck(:n).first
|
136
137
|
end
|
137
138
|
|
@@ -68,7 +68,7 @@ module Neo4j
|
|
68
68
|
# @param [String, Symbol] node_var A string or symbol to use as the starting identifier.
|
69
69
|
# @return [Neo4j::ActiveNode::Query::QueryProxy]
|
70
70
|
def as(node_var)
|
71
|
-
query_proxy(node: node_var)
|
71
|
+
query_proxy(node: node_var, context: self.name)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
end
|
@@ -149,13 +149,9 @@ module Neo4j
|
|
149
149
|
alias_method :order_by, :order
|
150
150
|
|
151
151
|
# Cypher string for the QueryProxy's query. This will not include params. For the full output, see <tt>to_cypher_with_params</tt>.
|
152
|
-
|
153
|
-
query.to_cypher(*args)
|
154
|
-
end
|
152
|
+
delegate :to_cypher, to: :query
|
155
153
|
|
156
|
-
|
157
|
-
query.print_cypher
|
158
|
-
end
|
154
|
+
delegate :print_cypher, to: :query
|
159
155
|
|
160
156
|
# Returns a string of the cypher query with return objects and params
|
161
157
|
# @param [Array] columns array containing symbols of identifiers used in the query
|
@@ -167,7 +163,7 @@ module Neo4j
|
|
167
163
|
|
168
164
|
# To add a relationship for the node for the association on this QueryProxy
|
169
165
|
def <<(other_node)
|
170
|
-
@start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node
|
166
|
+
@start_object._persisted_obj ? create(other_node, {}) : defer_create(other_node)
|
171
167
|
self
|
172
168
|
end
|
173
169
|
|
@@ -288,7 +284,9 @@ module Neo4j
|
|
288
284
|
end
|
289
285
|
|
290
286
|
def _session
|
291
|
-
@session || (@model && @model.neo4j_session)
|
287
|
+
(@session || (@model && @model.neo4j_session)).tap do |session|
|
288
|
+
fail 'No session found!' if session.nil?
|
289
|
+
end
|
292
290
|
end
|
293
291
|
|
294
292
|
def _association_arrow(properties = {}, create = false)
|
@@ -337,7 +335,7 @@ module Neo4j
|
|
337
335
|
|
338
336
|
def build_deeper_query_proxy(method, args)
|
339
337
|
new_link.tap do |new_query_proxy|
|
340
|
-
Link.for_args(@model, method, args).each { |link| new_query_proxy._add_links(link) }
|
338
|
+
Link.for_args(@model, method, args, association).each { |link| new_query_proxy._add_links(link) }
|
341
339
|
end
|
342
340
|
end
|
343
341
|
end
|
@@ -55,7 +55,7 @@ module Neo4j
|
|
55
55
|
elsif key == model.id_property_name && value.is_a?(Neo4j::ActiveNode)
|
56
56
|
value.id
|
57
57
|
else
|
58
|
-
model
|
58
|
+
converted_value(model, key, value)
|
59
59
|
end
|
60
60
|
|
61
61
|
new(:where, ->(v, _) { {v => {key => val}} })
|
@@ -72,9 +72,11 @@ module Neo4j
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# We don't accept strings here. If you want to use a string, just use where.
|
75
|
-
def for_rel_where_clause(arg, _)
|
75
|
+
def for_rel_where_clause(arg, _, association)
|
76
76
|
arg.each_with_object([]) do |(key, value), result|
|
77
|
-
|
77
|
+
rel_class = association.relationship_class if association.relationship_class
|
78
|
+
val = rel_class ? converted_value(rel_class, key, value) : value
|
79
|
+
result << new(:where, ->(_, rel_var) { {rel_var => {key => val}} })
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
@@ -82,9 +84,11 @@ module Neo4j
|
|
82
84
|
[new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => arg} })]
|
83
85
|
end
|
84
86
|
|
85
|
-
def for_args(model, clause, args)
|
87
|
+
def for_args(model, clause, args, association = nil)
|
86
88
|
if [:where, :where_not].include?(clause) && args[0].is_a?(String) # Better way?
|
87
89
|
[for_arg(model, clause, args[0], *args[1..-1])]
|
90
|
+
elsif clause == :rel_where
|
91
|
+
args.map { |arg| for_arg(model, clause, arg, association) }
|
88
92
|
else
|
89
93
|
args.map { |arg| for_arg(model, clause, arg) }
|
90
94
|
end
|
@@ -97,6 +101,10 @@ module Neo4j
|
|
97
101
|
rescue NoMethodError
|
98
102
|
default
|
99
103
|
end
|
104
|
+
|
105
|
+
def converted_value(model, key, value)
|
106
|
+
model.declared_properties.value_for_where(key, value)
|
107
|
+
end
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
@@ -32,13 +32,11 @@ module Neo4j
|
|
32
32
|
|
33
33
|
def first_and_last(func, target)
|
34
34
|
new_query, pluck_proc = if self.query.clause?(:order)
|
35
|
-
|
36
|
-
|
37
|
-
[new_query, pluck_proc]
|
35
|
+
[self.query.with(identity),
|
36
|
+
proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
|
38
37
|
else
|
39
|
-
|
40
|
-
|
41
|
-
[new_query, pluck_proc]
|
38
|
+
[self.order(order_property).limit(1),
|
39
|
+
proc { |var| var }]
|
42
40
|
end
|
43
41
|
result = query_with_target(target) do |var|
|
44
42
|
final_pluck = pluck_proc.call(var)
|
@@ -2,14 +2,10 @@ module Neo4j
|
|
2
2
|
module ActiveNode
|
3
3
|
module Query
|
4
4
|
module QueryProxyUnpersisted
|
5
|
-
def defer_create(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@start_object.association_proxy_cache[key] << other_nodes
|
10
|
-
else
|
11
|
-
@start_object.association_proxy_cache[key] = [other_nodes]
|
12
|
-
end
|
5
|
+
def defer_create(other_node)
|
6
|
+
@start_object.pending_associations << @association.name
|
7
|
+
|
8
|
+
@start_object.association_proxy(@association.name).add_to_cache(other_node)
|
13
9
|
end
|
14
10
|
end
|
15
11
|
end
|
@@ -2,7 +2,7 @@ module Neo4j
|
|
2
2
|
module ActiveNode
|
3
3
|
module Unpersisted
|
4
4
|
def pending_associations
|
5
|
-
@pending_associations ||=
|
5
|
+
@pending_associations ||= []
|
6
6
|
end
|
7
7
|
|
8
8
|
def pending_associations?
|
@@ -17,34 +17,36 @@ module Neo4j
|
|
17
17
|
def pending_associations_with_nodes
|
18
18
|
return unless pending_associations?
|
19
19
|
{}.tap do |deferred_nodes|
|
20
|
-
pending_associations.
|
21
|
-
nodes_for_creation =
|
22
|
-
|
20
|
+
pending_associations.uniq.each do |association_name|
|
21
|
+
nodes_for_creation = association_proxy(association_name)
|
22
|
+
nodes_for_creation = nodes_for_creation.reject(&:persisted?) if self.persisted?
|
23
|
+
|
24
|
+
deferred_nodes[association_name] = nodes_for_creation
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
29
|
# @param [Hash] deferred_nodes A hash created by :pending_associations_with_nodes
|
28
30
|
def process_unpersisted_nodes!(deferred_nodes)
|
29
|
-
deferred_nodes.each_pair do |k,
|
30
|
-
save_and_associate_queue(k, v
|
31
|
+
deferred_nodes.each_pair do |k, v|
|
32
|
+
save_and_associate_queue(k, v)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
36
|
|
35
|
-
def save_and_associate_queue(association_name, node_queue
|
36
|
-
association_proc = proc { |node| save_and_associate_node(association_name, node
|
37
|
+
def save_and_associate_queue(association_name, node_queue)
|
38
|
+
association_proc = proc { |node| save_and_associate_node(association_name, node) }
|
37
39
|
node_queue.each do |element|
|
38
40
|
element.is_a?(Array) ? element.each { |node| association_proc.call(node) } : association_proc.call(element)
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
def save_and_associate_node(association_name, node
|
44
|
+
def save_and_associate_node(association_name, node)
|
43
45
|
if node.respond_to?(:changed?)
|
44
46
|
node.save if node.changed? || !node.persisted?
|
45
47
|
fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted?
|
46
48
|
end
|
47
|
-
|
49
|
+
association_proxy(association_name) << node
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
@@ -37,10 +37,10 @@ module Neo4j
|
|
37
37
|
# TODO: Added as find(:name => nil) throws error
|
38
38
|
value = '' if value.nil?
|
39
39
|
|
40
|
-
conditions[attribute] = options[:case_sensitive] ? value :
|
40
|
+
conditions[attribute] = options[:case_sensitive] ? value : /#{Regexp.escape(value.to_s)}/i
|
41
41
|
|
42
42
|
found = record.class.as(:result).where(conditions)
|
43
|
-
found = found.
|
43
|
+
found = found.where_not(neo_id: record.neo_id) if record._persisted_obj
|
44
44
|
found
|
45
45
|
end
|
46
46
|
|
data/lib/neo4j/active_rel.rb
CHANGED
@@ -27,13 +27,16 @@ module Neo4j
|
|
27
27
|
attribute_descriptions = attribute_pairs.join(', ')
|
28
28
|
separator = ' ' unless attribute_descriptions.empty?
|
29
29
|
|
30
|
-
cypher_representation = "#{node_cypher_representation(
|
30
|
+
cypher_representation = "#{node_cypher_representation(from_node)}-[:#{type}]->#{node_cypher_representation(to_node)}"
|
31
31
|
"#<#{self.class.name} #{cypher_representation}#{separator}#{attribute_descriptions}>"
|
32
32
|
end
|
33
33
|
|
34
|
-
def node_cypher_representation(
|
35
|
-
|
36
|
-
|
34
|
+
def node_cypher_representation(node)
|
35
|
+
node_class = node.class
|
36
|
+
id_name = node_class.id_property_name
|
37
|
+
labels = ':' + node_class.mapped_label_names.join(':')
|
38
|
+
|
39
|
+
"(#{labels} {#{id_name}: #{node.id.inspect}})"
|
37
40
|
end
|
38
41
|
|
39
42
|
def neo4j_obj
|
@@ -12,7 +12,7 @@ module Neo4j::ActiveRel
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def save!(*args)
|
15
|
-
fail
|
15
|
+
save(*args) or fail(RelInvalidError, self) # rubocop:disable Style/AndOr
|
16
16
|
end
|
17
17
|
|
18
18
|
def create_model
|
@@ -60,14 +60,23 @@ module Neo4j::ActiveRel
|
|
60
60
|
type = from_node == node ? :_from_class : :_to_class
|
61
61
|
type_class = self.class.send(type)
|
62
62
|
|
63
|
-
|
64
|
-
|
65
|
-
unless node.class.mapped_label_names.include?(type_class.to_s.constantize.mapped_label_name)
|
63
|
+
unless valid_type?(type_class, node)
|
66
64
|
fail ModelClassInvalidError, type_validation_error_message(node, type_class)
|
67
65
|
end
|
68
66
|
end
|
69
67
|
end
|
70
68
|
|
69
|
+
def valid_type?(type_object, node)
|
70
|
+
case type_object
|
71
|
+
when false, :any
|
72
|
+
true
|
73
|
+
when Array
|
74
|
+
type_object.any? { |c| valid_type?(c, node) }
|
75
|
+
else
|
76
|
+
node.class.mapped_label_names.include?(type_object.to_s.constantize.mapped_label_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
71
80
|
def type_validation_error_message(node, type_class)
|
72
81
|
"Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
|
73
82
|
end
|
@@ -41,11 +41,17 @@ module Neo4j::ActiveRel
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
+
def deprecation_warning!
|
45
|
+
ActiveSupport::Deprecation.warn 'The Neo4j::ActiveRel::Query module has been deprecated and will be removed in a future version of the gem.', caller
|
46
|
+
end
|
47
|
+
|
44
48
|
def where_query
|
49
|
+
deprecation_warning!
|
45
50
|
Neo4j::Session.query.match("#{cypher_string(:outbound)}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
|
46
51
|
end
|
47
52
|
|
48
53
|
def all_query
|
54
|
+
deprecation_warning!
|
49
55
|
Neo4j::Session.query.match("#{cypher_string}-[r1:`#{self._type}`]->#{cypher_string(:inbound)}")
|
50
56
|
end
|
51
57
|
|
@@ -71,6 +77,8 @@ module Neo4j::ActiveRel
|
|
71
77
|
given_class.constantize
|
72
78
|
when Symbol
|
73
79
|
given_class.to_s.constantize
|
80
|
+
when Array
|
81
|
+
fail "ActiveRel query methods are being deprecated and do not support Array (from|to)_class options. Current value: #{given_class}"
|
74
82
|
else
|
75
83
|
given_class
|
76
84
|
end
|
@@ -4,7 +4,6 @@ module Neo4j::ActiveRel
|
|
4
4
|
# will result in a query to load the node if the node is not already loaded.
|
5
5
|
class RelatedNode
|
6
6
|
class InvalidParameterError < StandardError; end
|
7
|
-
class UnsetRelatedNodeError < StandardError; end
|
8
7
|
|
9
8
|
# ActiveRel's related nodes can be initialized with nothing, an integer, or a fully wrapped node.
|
10
9
|
#
|
@@ -31,41 +30,20 @@ module Neo4j::ActiveRel
|
|
31
30
|
|
32
31
|
# Loads a node from the database or returns the node if already laoded
|
33
32
|
def loaded
|
34
|
-
fail NilRelatedNodeError, 'Node not set, cannot load' if @node.nil?
|
35
33
|
@node = @node.respond_to?(:neo_id) ? @node : Neo4j::Node.load(@node)
|
36
34
|
end
|
37
35
|
|
38
|
-
# @param [String, Symbol, Array] clazz An alternate label to use in the event the node is not present or loaded
|
39
|
-
def cypher_representation(clazz)
|
40
|
-
case
|
41
|
-
when !set?
|
42
|
-
"(#{formatted_label_list(clazz)})"
|
43
|
-
when set? && !loaded?
|
44
|
-
"(Node with neo_id #{@node})"
|
45
|
-
else
|
46
|
-
node_class = self.class
|
47
|
-
id_name = node_class.id_property_name
|
48
|
-
labels = ':' + node_class.mapped_label_names.join(':')
|
49
|
-
|
50
|
-
"(#{labels} {#{id_name}: #{@node.id.inspect}})"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
36
|
# @return [Boolean] indicates whether a node has or has not been fully loaded from the database
|
55
37
|
def loaded?
|
56
38
|
@node.respond_to?(:neo_id)
|
57
39
|
end
|
58
40
|
|
59
|
-
def set?
|
60
|
-
!@node.nil?
|
61
|
-
end
|
62
|
-
|
63
41
|
def method_missing(*args, &block)
|
64
42
|
loaded.send(*args, &block)
|
65
43
|
end
|
66
44
|
|
67
45
|
def respond_to_missing?(method_name, include_private = false)
|
68
|
-
loaded if @node.is_a?(
|
46
|
+
loaded if @node.is_a?(Integer)
|
69
47
|
@node.respond_to?(method_name) ? true : super
|
70
48
|
end
|
71
49
|
|
@@ -75,10 +53,6 @@ module Neo4j::ActiveRel
|
|
75
53
|
|
76
54
|
private
|
77
55
|
|
78
|
-
def formatted_label_list(list)
|
79
|
-
list.is_a?(Array) ? list.join(' || ') : list
|
80
|
-
end
|
81
|
-
|
82
56
|
def valid_node_param?(node)
|
83
57
|
node.nil? || node.is_a?(Integer) || node.respond_to?(:neo_id)
|
84
58
|
end
|