arel_toolkit 0.3.0 → 0.4.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/.codeclimate.yml +3 -0
- data/.gitignore +4 -1
- data/.rubocop.yml +13 -5
- data/.travis.yml +7 -2
- data/Appraisals +9 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +22 -5
- data/README.md +59 -18
- data/arel_toolkit.gemspec +5 -1
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/arel_gems.gemfile +10 -0
- data/gemfiles/arel_gems.gemfile.lock +274 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/default.gemfile.lock +198 -0
- data/lib/arel/enhance.rb +16 -0
- data/lib/arel/enhance/context_enhancer/arel_table.rb +75 -0
- data/lib/arel/enhance/node.rb +189 -0
- data/lib/arel/enhance/path.rb +38 -0
- data/lib/arel/enhance/path_node.rb +26 -0
- data/lib/arel/enhance/query.rb +36 -0
- data/lib/arel/enhance/visitor.rb +81 -0
- data/lib/arel/extensions.rb +24 -4
- data/lib/arel/extensions/active_record_type_caster_map.rb +7 -0
- data/lib/arel/extensions/array.rb +2 -9
- data/lib/arel/extensions/at_time_zone.rb +10 -3
- data/lib/arel/extensions/binary.rb +7 -0
- data/lib/arel/extensions/bit_string.rb +2 -9
- data/lib/arel/extensions/case.rb +17 -0
- data/lib/arel/extensions/conflict.rb +9 -0
- data/lib/arel/extensions/contains.rb +27 -5
- data/lib/arel/extensions/current_catalog.rb +4 -0
- data/lib/arel/extensions/current_date.rb +4 -0
- data/lib/arel/extensions/current_of_expression.rb +2 -9
- data/lib/arel/extensions/current_role.rb +4 -0
- data/lib/arel/extensions/current_row.rb +7 -0
- data/lib/arel/extensions/current_schema.rb +4 -0
- data/lib/arel/extensions/current_user.rb +4 -0
- data/lib/arel/extensions/dealocate.rb +31 -0
- data/lib/arel/extensions/default_values.rb +4 -0
- data/lib/arel/extensions/delete_manager.rb +22 -6
- data/lib/arel/extensions/delete_statement.rb +26 -9
- data/lib/arel/extensions/dot.rb +11 -0
- data/lib/arel/extensions/extract_from.rb +3 -10
- data/lib/arel/extensions/factorial.rb +10 -2
- data/lib/arel/extensions/false.rb +7 -0
- data/lib/arel/extensions/function.rb +42 -13
- data/lib/arel/extensions/indirection.rb +3 -12
- data/lib/arel/extensions/infer.rb +6 -6
- data/lib/arel/extensions/infix_operation.rb +17 -0
- data/lib/arel/extensions/insert_manager.rb +19 -3
- data/lib/arel/extensions/insert_statement.rb +30 -11
- data/lib/arel/extensions/into.rb +21 -0
- data/lib/arel/extensions/named_argument.rb +3 -8
- data/lib/arel/extensions/named_function.rb +7 -0
- data/lib/arel/extensions/ordering.rb +21 -6
- data/lib/arel/extensions/overlaps.rb +9 -0
- data/lib/arel/extensions/overlay.rb +9 -0
- data/lib/arel/extensions/position.rb +3 -8
- data/lib/arel/extensions/prepare.rb +39 -0
- data/lib/arel/extensions/row.rb +3 -8
- data/lib/arel/extensions/select_core.rb +58 -0
- data/lib/arel/extensions/select_manager.rb +22 -6
- data/lib/arel/extensions/select_statement.rb +31 -9
- data/lib/arel/extensions/session_user.rb +4 -0
- data/lib/arel/extensions/set_to_default.rb +4 -0
- data/lib/arel/extensions/substring.rb +8 -0
- data/lib/arel/extensions/table.rb +43 -10
- data/lib/arel/extensions/time_with_precision.rb +6 -0
- data/lib/arel/extensions/to_sql.rb +27 -0
- data/lib/arel/extensions/transaction.rb +3 -8
- data/lib/arel/extensions/tree_manager.rb +10 -0
- data/lib/arel/extensions/trim.rb +8 -0
- data/lib/arel/extensions/true.rb +7 -0
- data/lib/arel/extensions/type_cast.rb +7 -0
- data/lib/arel/extensions/unary.rb +7 -0
- data/lib/arel/extensions/unary_operation.rb +16 -0
- data/lib/arel/extensions/unknown.rb +4 -0
- data/lib/arel/extensions/update_manager.rb +22 -6
- data/lib/arel/extensions/update_statement.rb +27 -10
- data/lib/arel/extensions/user.rb +4 -0
- data/lib/arel/extensions/values_list.rb +15 -0
- data/lib/arel/extensions/variable_set.rb +9 -0
- data/lib/arel/extensions/variable_show.rb +3 -8
- data/lib/arel/middleware/chain.rb +1 -5
- data/lib/arel/middleware/railtie.rb +10 -0
- data/lib/arel/sql_to_arel.rb +6 -3
- data/lib/arel/sql_to_arel/pg_query_visitor.rb +43 -15
- data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +1 -1
- data/lib/arel/sql_to_arel/result.rb +0 -4
- data/lib/arel/transformer.rb +7 -0
- data/lib/arel/transformer/add_schema_to_table.rb +26 -0
- data/lib/arel/transformer/remove_active_record_info.rb +42 -0
- data/lib/arel_toolkit.rb +8 -1
- data/lib/arel_toolkit/version.rb +1 -1
- metadata +81 -8
- data/lib/arel/extensions/unbound_column_reference.rb +0 -5
- data/lib/arel/sql_formatter.rb +0 -59
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Enhance
|
|
3
|
+
class Path
|
|
4
|
+
attr_reader :nodes
|
|
5
|
+
|
|
6
|
+
def initialize(nodes = [])
|
|
7
|
+
@nodes = nodes
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def append(path_node)
|
|
11
|
+
Path.new(nodes + [path_node])
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def dig_send(object)
|
|
15
|
+
selected_object = object
|
|
16
|
+
nodes.each do |path_node|
|
|
17
|
+
selected_object = selected_object.send(*path_node.method)
|
|
18
|
+
end
|
|
19
|
+
selected_object
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_a
|
|
23
|
+
nodes.map(&:value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def current
|
|
27
|
+
nodes.last
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def inspect
|
|
31
|
+
nodes.inspect
|
|
32
|
+
string = '['
|
|
33
|
+
string << nodes.map(&:inspect).join(', ')
|
|
34
|
+
string << ']'
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Enhance
|
|
3
|
+
class PathNode
|
|
4
|
+
attr_reader :method
|
|
5
|
+
attr_reader :value
|
|
6
|
+
|
|
7
|
+
def initialize(method, value)
|
|
8
|
+
@method = method
|
|
9
|
+
@value = value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def arguments?
|
|
13
|
+
method.is_a?(Array)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def inspect
|
|
17
|
+
case value
|
|
18
|
+
when String
|
|
19
|
+
"'#{value}'"
|
|
20
|
+
else
|
|
21
|
+
value.inspect
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Arel
|
|
2
|
+
module Enhance
|
|
3
|
+
class Query
|
|
4
|
+
def self.call(node, kwargs)
|
|
5
|
+
node_attributes = %i[context parent]
|
|
6
|
+
node_args = kwargs.slice(*node_attributes)
|
|
7
|
+
object_args = kwargs.except(*node_attributes)
|
|
8
|
+
|
|
9
|
+
node.each.select do |child_node|
|
|
10
|
+
next unless matches?(child_node, node_args)
|
|
11
|
+
|
|
12
|
+
matches?(child_node.object, object_args)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.matches?(object, test)
|
|
17
|
+
case test
|
|
18
|
+
when Hash
|
|
19
|
+
case object
|
|
20
|
+
when Hash
|
|
21
|
+
test <= object
|
|
22
|
+
else
|
|
23
|
+
test.all? do |test_key, test_value|
|
|
24
|
+
next false unless object.respond_to?(test_key)
|
|
25
|
+
|
|
26
|
+
object_attribute_value = object.public_send(test_key)
|
|
27
|
+
matches? object_attribute_value, test_value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
object == test
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require_relative './context_enhancer/arel_table'
|
|
2
|
+
|
|
3
|
+
module Arel
|
|
4
|
+
module Enhance
|
|
5
|
+
# rubocop:disable Naming/MethodName
|
|
6
|
+
class Visitor < Arel::Visitors::Dot
|
|
7
|
+
DEFAULT_CONTEXT_ENHANCERS = {
|
|
8
|
+
Arel::Table => Arel::Enhance::ContextEnhancer::ArelTable,
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :context_enhancers
|
|
12
|
+
|
|
13
|
+
def accept(object, context_enhancers = DEFAULT_CONTEXT_ENHANCERS)
|
|
14
|
+
@context_enhancers = context_enhancers
|
|
15
|
+
|
|
16
|
+
root_node = Arel::Enhance::Node.new(object)
|
|
17
|
+
accept_with_root(object, root_node)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def accept_with_root(object, root_node, context_enhancers = DEFAULT_CONTEXT_ENHANCERS)
|
|
21
|
+
@context_enhancers = context_enhancers
|
|
22
|
+
|
|
23
|
+
with_node(root_node) do
|
|
24
|
+
visit object
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
root_node
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def visit_edge(object, method)
|
|
33
|
+
arel_node = object.send(method)
|
|
34
|
+
|
|
35
|
+
process_node(arel_node, Arel::Enhance::PathNode.new(method, method))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def nary(object)
|
|
39
|
+
visit_edge(object, 'children')
|
|
40
|
+
end
|
|
41
|
+
alias visit_Arel_Nodes_And nary
|
|
42
|
+
|
|
43
|
+
def visit_Hash(_object)
|
|
44
|
+
raise 'Hash is not supported'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def visit_Array(object)
|
|
48
|
+
object.each_with_index do |child, index|
|
|
49
|
+
process_node(child, Arel::Enhance::PathNode.new([:[], index], index))
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def process_node(arel_node, path_node)
|
|
54
|
+
node = Arel::Enhance::Node.new(arel_node)
|
|
55
|
+
current_node.add(path_node, node)
|
|
56
|
+
|
|
57
|
+
update_context(node)
|
|
58
|
+
|
|
59
|
+
with_node node do
|
|
60
|
+
visit arel_node
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def visit(object)
|
|
65
|
+
Arel::Visitors::Visitor.instance_method(:visit).bind(self).call(object)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def current_node
|
|
69
|
+
@node_stack.last
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def update_context(node)
|
|
73
|
+
enhancer = context_enhancers[node.object.class]
|
|
74
|
+
return if enhancer.nil?
|
|
75
|
+
|
|
76
|
+
enhancer.call(node)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
# rubocop:enable Naming/MethodName
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/arel/extensions.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'arel/extensions/dot'
|
|
1
2
|
require 'arel/extensions/unknown'
|
|
2
3
|
require 'arel/extensions/time_with_precision'
|
|
3
4
|
require 'arel/extensions/current_time'
|
|
@@ -42,11 +43,15 @@ require 'arel/extensions/modulo'
|
|
|
42
43
|
require 'arel/extensions/absolute'
|
|
43
44
|
require 'arel/extensions/bitwise_xor'
|
|
44
45
|
require 'arel/extensions/exponentiation'
|
|
46
|
+
|
|
45
47
|
require 'arel/extensions/contains'
|
|
48
|
+
unless Gem.loaded_specs.key?('postgres_ext')
|
|
49
|
+
require 'arel/extensions/contained_within_equals'
|
|
50
|
+
require 'arel/extensions/contains_equals'
|
|
51
|
+
require 'arel/extensions/overlap'
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
require 'arel/extensions/contained_by'
|
|
47
|
-
require 'arel/extensions/contained_within_equals'
|
|
48
|
-
require 'arel/extensions/contains_equals'
|
|
49
|
-
require 'arel/extensions/overlap'
|
|
50
55
|
require 'arel/extensions/select_statement'
|
|
51
56
|
require 'arel/extensions/insert_statement'
|
|
52
57
|
require 'arel/extensions/default_values'
|
|
@@ -80,7 +85,6 @@ require 'arel/extensions/jsonb_key_exists'
|
|
|
80
85
|
require 'arel/extensions/jsonb_any_key_exists'
|
|
81
86
|
require 'arel/extensions/jsonb_all_key_exists'
|
|
82
87
|
require 'arel/extensions/transaction'
|
|
83
|
-
require 'arel/extensions/unbound_column_reference'
|
|
84
88
|
require 'arel/extensions/assignment'
|
|
85
89
|
require 'arel/extensions/variable_set'
|
|
86
90
|
require 'arel/extensions/variable_show'
|
|
@@ -90,6 +94,22 @@ require 'arel/extensions/substring'
|
|
|
90
94
|
require 'arel/extensions/overlaps'
|
|
91
95
|
require 'arel/extensions/trim'
|
|
92
96
|
require 'arel/extensions/named_argument'
|
|
97
|
+
require 'arel/extensions/tree_manager'
|
|
98
|
+
require 'arel/extensions/into'
|
|
99
|
+
require 'arel/extensions/select_core'
|
|
100
|
+
require 'arel/extensions/unary'
|
|
101
|
+
require 'arel/extensions/binary'
|
|
102
|
+
require 'arel/extensions/unary_operation'
|
|
103
|
+
require 'arel/extensions/infix_operation'
|
|
104
|
+
require 'arel/extensions/values_list'
|
|
105
|
+
require 'arel/extensions/case'
|
|
106
|
+
require 'arel/extensions/current_row'
|
|
107
|
+
require 'arel/extensions/false'
|
|
108
|
+
require 'arel/extensions/true'
|
|
109
|
+
require 'arel/extensions/to_sql'
|
|
110
|
+
require 'arel/extensions/prepare'
|
|
111
|
+
require 'arel/extensions/dealocate'
|
|
112
|
+
require 'arel/extensions/active_record_type_caster_map'
|
|
93
113
|
|
|
94
114
|
module Arel
|
|
95
115
|
module Extensions
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
module Arel
|
|
5
5
|
module Nodes
|
|
6
|
-
class Array < Arel::Nodes::
|
|
7
|
-
attr_reader :items
|
|
8
|
-
|
|
9
|
-
def initialize(items)
|
|
10
|
-
super()
|
|
11
|
-
|
|
12
|
-
@items = items
|
|
13
|
-
end
|
|
6
|
+
class Array < Arel::Nodes::Unary
|
|
14
7
|
end
|
|
15
8
|
end
|
|
16
9
|
|
|
@@ -18,7 +11,7 @@ module Arel
|
|
|
18
11
|
class ToSql
|
|
19
12
|
def visit_Arel_Nodes_Array(o, collector)
|
|
20
13
|
collector << 'ARRAY['
|
|
21
|
-
inject_join(o.
|
|
14
|
+
inject_join(o.expr, collector, ', ')
|
|
22
15
|
collector << ']'
|
|
23
16
|
end
|
|
24
17
|
end
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
module Arel
|
|
5
5
|
module Nodes
|
|
6
6
|
# https://www.postgresql.org/docs/9.2/functions-datetime.html#FUNCTIONS-DATETIME-ZONECONVERT
|
|
7
|
-
class AtTimeZone < Arel::Nodes::
|
|
7
|
+
class AtTimeZone < Arel::Nodes::Node
|
|
8
8
|
attr_reader :timezone
|
|
9
|
+
attr_reader :expr
|
|
9
10
|
|
|
10
11
|
def initialize(expr, timezone)
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
@expr = expr
|
|
13
13
|
@timezone = timezone
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -23,6 +23,13 @@ module Arel
|
|
|
23
23
|
visit o.timezone, collector
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
class Dot
|
|
28
|
+
def visit_Arel_Nodes_AtTimeZone(o)
|
|
29
|
+
visit_edge o, 'expr'
|
|
30
|
+
visit_edge o, 'timezone'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
28
35
|
|
|
@@ -3,21 +3,14 @@
|
|
|
3
3
|
|
|
4
4
|
module Arel
|
|
5
5
|
module Nodes
|
|
6
|
-
class BitString < Arel::Nodes::
|
|
7
|
-
attr_reader :str
|
|
8
|
-
|
|
9
|
-
def initialize(str)
|
|
10
|
-
super()
|
|
11
|
-
|
|
12
|
-
@str = str
|
|
13
|
-
end
|
|
6
|
+
class BitString < Arel::Nodes::Unary
|
|
14
7
|
end
|
|
15
8
|
end
|
|
16
9
|
|
|
17
10
|
module Visitors
|
|
18
11
|
class ToSql
|
|
19
12
|
def visit_Arel_Nodes_BitString(o, collector)
|
|
20
|
-
collector << "B'#{o.
|
|
13
|
+
collector << "B'#{o.expr[1..-1]}'"
|
|
21
14
|
end
|
|
22
15
|
end
|
|
23
16
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# rubocop:disable Naming/MethodName
|
|
2
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
|
3
|
+
|
|
4
|
+
module Arel
|
|
5
|
+
module Visitors
|
|
6
|
+
class Dot
|
|
7
|
+
def visit_Arel_Nodes_Case(o)
|
|
8
|
+
visit_edge o, 'case'
|
|
9
|
+
visit_edge o, 'conditions'
|
|
10
|
+
visit_edge o, 'default'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# rubocop:enable Naming/MethodName
|
|
17
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
|
@@ -40,6 +40,15 @@ module Arel
|
|
|
40
40
|
end
|
|
41
41
|
# rubocop:enable Metrics/AbcSize
|
|
42
42
|
end
|
|
43
|
+
|
|
44
|
+
class Dot
|
|
45
|
+
def visit_Arel_Nodes_Conflict(o)
|
|
46
|
+
visit_edge o, 'action'
|
|
47
|
+
visit_edge o, 'infer'
|
|
48
|
+
visit_edge o, 'values'
|
|
49
|
+
visit_edge o, 'wheres'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
43
52
|
end
|
|
44
53
|
end
|
|
45
54
|
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
+
# rubocop:disable Naming/MethodName
|
|
2
|
+
# rubocop:disable Naming/UncommunicativeMethodParamName
|
|
3
|
+
|
|
1
4
|
module Arel
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
if Gem.loaded_specs.key?('postgres_ext')
|
|
6
|
+
module Visitors
|
|
7
|
+
module ContainsPatch
|
|
8
|
+
def visit_Arel_Nodes_Contains(o, collector)
|
|
9
|
+
if o.left.is_a?(Arel::Attribute)
|
|
10
|
+
super
|
|
11
|
+
else
|
|
12
|
+
infix_value o, collector, ' @> '
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
PostgreSQL.prepend(ContainsPatch)
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
module Nodes
|
|
21
|
+
# https://www.postgresql.org/docs/9.1/functions-array.html
|
|
22
|
+
class Contains < Arel::Nodes::InfixOperation
|
|
23
|
+
def initialize(left, right)
|
|
24
|
+
super(:'@>', left, right)
|
|
25
|
+
end
|
|
7
26
|
end
|
|
8
27
|
end
|
|
9
28
|
end
|
|
10
29
|
end
|
|
30
|
+
|
|
31
|
+
# rubocop:enable Naming/MethodName
|
|
32
|
+
# rubocop:enable Naming/UncommunicativeMethodParamName
|
|
@@ -4,14 +4,7 @@
|
|
|
4
4
|
module Arel
|
|
5
5
|
module Nodes
|
|
6
6
|
# https://www.postgresql.org/docs/10/sql-update.html
|
|
7
|
-
class CurrentOfExpression < Arel::Nodes::
|
|
8
|
-
attr_accessor :cursor_name
|
|
9
|
-
|
|
10
|
-
def initialize(cursor_name)
|
|
11
|
-
super()
|
|
12
|
-
|
|
13
|
-
@cursor_name = cursor_name
|
|
14
|
-
end
|
|
7
|
+
class CurrentOfExpression < Arel::Nodes::Unary
|
|
15
8
|
end
|
|
16
9
|
end
|
|
17
10
|
|
|
@@ -19,7 +12,7 @@ module Arel
|
|
|
19
12
|
class ToSql
|
|
20
13
|
def visit_Arel_Nodes_CurrentOfExpression(o, collector)
|
|
21
14
|
collector << 'CURRENT OF '
|
|
22
|
-
collector << o.
|
|
15
|
+
collector << o.expr
|
|
23
16
|
end
|
|
24
17
|
end
|
|
25
18
|
end
|