yesql 0.1.6 → 0.2.2
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 +3 -0
- data/.rubocop.yml +12 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +23 -127
- data/README.md +6 -22
- data/Rakefile +2 -0
- data/lib/yesql.rb +13 -20
- data/lib/yesql/bindings/extract.rb +6 -5
- data/lib/yesql/bindings/extractor.rb +1 -3
- data/lib/yesql/bindings/transformed.rb +6 -7
- data/lib/yesql/bindings/utils.rb +5 -4
- data/lib/yesql/common/adapter.rb +4 -4
- data/lib/yesql/config/configuration.rb +3 -2
- data/lib/yesql/errors/no_bindings_provided_error.rb +5 -3
- data/lib/yesql/params/output.rb +5 -5
- data/lib/yesql/query/performer.rb +14 -39
- data/lib/yesql/query/result.rb +14 -10
- data/lib/yesql/query/transform_result.rb +11 -6
- data/lib/yesql/statement.rb +50 -0
- data/lib/yesql/utils/read.rb +1 -1
- data/lib/yesql/version.rb +1 -1
- data/yesql.gemspec +17 -17
- metadata +10 -11
- data/lib/yesql/bindings/binder.rb +0 -19
- data/lib/yesql/errors/cache_expiration_error.rb +0 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4cad8cd91b4afba6c7a8722f9f0dfd6092552d2f4ce735c6880425241122b43
|
|
4
|
+
data.tar.gz: b8be652b46ee03e081ec2d8bc3bcc2321e26e2b7fcd55dcbf2a2bb288ddac139
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4dc794c44484b4fcc8815fa866023c6cf5d2f4b82d4f2c45c6de7e86082eaa3ba838c55e750e3f2fd53b0312b4be699c7ddc900400c8445c377fd7c6d1139190
|
|
7
|
+
data.tar.gz: 88b860a5ce91655366f9d73336b522678a8f709938881e1ff150a13565b57cd12b65c753531ecfb886528d162405c36ca9036c1c3cba52a62aa2e3e3993e76fb
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
require: rubocop-rspec
|
|
2
2
|
|
|
3
|
+
Metrics/BlockLength:
|
|
4
|
+
Exclude:
|
|
5
|
+
- spec/**/*_spec.rb
|
|
6
|
+
- spec/spec_helper.rb
|
|
7
|
+
- spec/yesql/shared/*
|
|
8
|
+
|
|
3
9
|
RSpec/FilePath:
|
|
4
10
|
Exclude:
|
|
5
11
|
- spec/**/*_spec.rb
|
|
6
12
|
|
|
7
13
|
RSpec/NestedGroups:
|
|
8
14
|
Enabled: False
|
|
15
|
+
|
|
16
|
+
RSpec/ExampleLength:
|
|
17
|
+
Enabled: False
|
|
18
|
+
|
|
19
|
+
RSpec/BeforeAfterAll:
|
|
20
|
+
Enabled: False
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,159 +1,55 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
yesql (0.
|
|
5
|
-
|
|
4
|
+
yesql (0.2.2)
|
|
5
|
+
activerecord (>= 4.0.0.beta1)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
activejob (= 6.0.3.4)
|
|
17
|
-
activerecord (= 6.0.3.4)
|
|
18
|
-
activestorage (= 6.0.3.4)
|
|
19
|
-
activesupport (= 6.0.3.4)
|
|
20
|
-
mail (>= 2.7.1)
|
|
21
|
-
actionmailer (6.0.3.4)
|
|
22
|
-
actionpack (= 6.0.3.4)
|
|
23
|
-
actionview (= 6.0.3.4)
|
|
24
|
-
activejob (= 6.0.3.4)
|
|
25
|
-
mail (~> 2.5, >= 2.5.4)
|
|
26
|
-
rails-dom-testing (~> 2.0)
|
|
27
|
-
actionpack (6.0.3.4)
|
|
28
|
-
actionview (= 6.0.3.4)
|
|
29
|
-
activesupport (= 6.0.3.4)
|
|
30
|
-
rack (~> 2.0, >= 2.0.8)
|
|
31
|
-
rack-test (>= 0.6.3)
|
|
32
|
-
rails-dom-testing (~> 2.0)
|
|
33
|
-
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
34
|
-
actiontext (6.0.3.4)
|
|
35
|
-
actionpack (= 6.0.3.4)
|
|
36
|
-
activerecord (= 6.0.3.4)
|
|
37
|
-
activestorage (= 6.0.3.4)
|
|
38
|
-
activesupport (= 6.0.3.4)
|
|
39
|
-
nokogiri (>= 1.8.5)
|
|
40
|
-
actionview (6.0.3.4)
|
|
41
|
-
activesupport (= 6.0.3.4)
|
|
42
|
-
builder (~> 3.1)
|
|
43
|
-
erubi (~> 1.4)
|
|
44
|
-
rails-dom-testing (~> 2.0)
|
|
45
|
-
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
46
|
-
activejob (6.0.3.4)
|
|
47
|
-
activesupport (= 6.0.3.4)
|
|
48
|
-
globalid (>= 0.3.6)
|
|
49
|
-
activemodel (6.0.3.4)
|
|
50
|
-
activesupport (= 6.0.3.4)
|
|
51
|
-
activerecord (6.0.3.4)
|
|
52
|
-
activemodel (= 6.0.3.4)
|
|
53
|
-
activesupport (= 6.0.3.4)
|
|
54
|
-
activestorage (6.0.3.4)
|
|
55
|
-
actionpack (= 6.0.3.4)
|
|
56
|
-
activejob (= 6.0.3.4)
|
|
57
|
-
activerecord (= 6.0.3.4)
|
|
58
|
-
marcel (~> 0.3.1)
|
|
59
|
-
activesupport (6.0.3.4)
|
|
10
|
+
activemodel (6.1.3.1)
|
|
11
|
+
activesupport (= 6.1.3.1)
|
|
12
|
+
activerecord (6.1.3.1)
|
|
13
|
+
activemodel (= 6.1.3.1)
|
|
14
|
+
activesupport (= 6.1.3.1)
|
|
15
|
+
activesupport (6.1.3.1)
|
|
60
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
61
|
-
i18n (>=
|
|
62
|
-
minitest (
|
|
63
|
-
tzinfo (~>
|
|
64
|
-
zeitwerk (~> 2.
|
|
65
|
-
builder (3.2.4)
|
|
17
|
+
i18n (>= 1.6, < 2)
|
|
18
|
+
minitest (>= 5.1)
|
|
19
|
+
tzinfo (~> 2.0)
|
|
20
|
+
zeitwerk (~> 2.3)
|
|
66
21
|
coderay (1.1.3)
|
|
67
|
-
concurrent-ruby (1.1.
|
|
68
|
-
crass (1.0.6)
|
|
22
|
+
concurrent-ruby (1.1.8)
|
|
69
23
|
diff-lcs (1.4.4)
|
|
70
|
-
|
|
71
|
-
globalid (0.4.2)
|
|
72
|
-
activesupport (>= 4.2.0)
|
|
73
|
-
i18n (1.8.5)
|
|
24
|
+
i18n (1.8.10)
|
|
74
25
|
concurrent-ruby (~> 1.0)
|
|
75
|
-
loofah (2.7.0)
|
|
76
|
-
crass (~> 1.0.2)
|
|
77
|
-
nokogiri (>= 1.5.9)
|
|
78
|
-
mail (2.7.1)
|
|
79
|
-
mini_mime (>= 0.1.1)
|
|
80
|
-
marcel (0.3.3)
|
|
81
|
-
mimemagic (~> 0.3.2)
|
|
82
26
|
method_source (1.0.0)
|
|
83
|
-
|
|
84
|
-
mini_mime (1.0.2)
|
|
85
|
-
mini_portile2 (2.4.0)
|
|
86
|
-
minitest (5.14.2)
|
|
27
|
+
minitest (5.14.4)
|
|
87
28
|
mysql2 (0.5.3)
|
|
88
|
-
nio4r (2.5.4)
|
|
89
|
-
nokogiri (1.10.10)
|
|
90
|
-
mini_portile2 (~> 2.4.0)
|
|
91
29
|
pg (1.2.3)
|
|
92
30
|
pry (0.13.1)
|
|
93
31
|
coderay (~> 1.1)
|
|
94
32
|
method_source (~> 1.0)
|
|
95
|
-
rack (2.2.3)
|
|
96
|
-
rack-test (1.1.0)
|
|
97
|
-
rack (>= 1.0, < 3)
|
|
98
|
-
rails (6.0.3.4)
|
|
99
|
-
actioncable (= 6.0.3.4)
|
|
100
|
-
actionmailbox (= 6.0.3.4)
|
|
101
|
-
actionmailer (= 6.0.3.4)
|
|
102
|
-
actionpack (= 6.0.3.4)
|
|
103
|
-
actiontext (= 6.0.3.4)
|
|
104
|
-
actionview (= 6.0.3.4)
|
|
105
|
-
activejob (= 6.0.3.4)
|
|
106
|
-
activemodel (= 6.0.3.4)
|
|
107
|
-
activerecord (= 6.0.3.4)
|
|
108
|
-
activestorage (= 6.0.3.4)
|
|
109
|
-
activesupport (= 6.0.3.4)
|
|
110
|
-
bundler (>= 1.3.0)
|
|
111
|
-
railties (= 6.0.3.4)
|
|
112
|
-
sprockets-rails (>= 2.0.0)
|
|
113
|
-
rails-dom-testing (2.0.3)
|
|
114
|
-
activesupport (>= 4.2.0)
|
|
115
|
-
nokogiri (>= 1.6)
|
|
116
|
-
rails-html-sanitizer (1.3.0)
|
|
117
|
-
loofah (~> 2.3)
|
|
118
|
-
railties (6.0.3.4)
|
|
119
|
-
actionpack (= 6.0.3.4)
|
|
120
|
-
activesupport (= 6.0.3.4)
|
|
121
|
-
method_source
|
|
122
|
-
rake (>= 0.8.7)
|
|
123
|
-
thor (>= 0.20.3, < 2.0)
|
|
124
|
-
rake (13.0.1)
|
|
125
33
|
rspec (3.9.0)
|
|
126
34
|
rspec-core (~> 3.9.0)
|
|
127
35
|
rspec-expectations (~> 3.9.0)
|
|
128
36
|
rspec-mocks (~> 3.9.0)
|
|
129
|
-
rspec-core (3.9.
|
|
37
|
+
rspec-core (3.9.3)
|
|
130
38
|
rspec-support (~> 3.9.3)
|
|
131
|
-
rspec-expectations (3.9.
|
|
39
|
+
rspec-expectations (3.9.4)
|
|
132
40
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
133
41
|
rspec-support (~> 3.9.0)
|
|
134
42
|
rspec-mocks (3.9.1)
|
|
135
43
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
136
44
|
rspec-support (~> 3.9.0)
|
|
137
|
-
rspec-support (3.9.
|
|
138
|
-
|
|
45
|
+
rspec-support (3.9.4)
|
|
46
|
+
tzinfo (2.0.4)
|
|
139
47
|
concurrent-ruby (~> 1.0)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
actionpack (>= 4.0)
|
|
143
|
-
activesupport (>= 4.0)
|
|
144
|
-
sprockets (>= 3.0.0)
|
|
145
|
-
thor (1.0.1)
|
|
146
|
-
thread_safe (0.3.6)
|
|
147
|
-
tzinfo (1.2.7)
|
|
148
|
-
thread_safe (~> 0.1)
|
|
149
|
-
websocket-driver (0.7.3)
|
|
150
|
-
websocket-extensions (>= 0.1.0)
|
|
151
|
-
websocket-extensions (0.1.5)
|
|
152
|
-
yard (0.9.25)
|
|
153
|
-
zeitwerk (2.4.0)
|
|
48
|
+
yard (0.9.26)
|
|
49
|
+
zeitwerk (2.4.2)
|
|
154
50
|
|
|
155
51
|
PLATFORMS
|
|
156
|
-
|
|
52
|
+
x86_64-linux
|
|
157
53
|
|
|
158
54
|
DEPENDENCIES
|
|
159
55
|
mysql2 (~> 0.5.3)
|
|
@@ -164,4 +60,4 @@ DEPENDENCIES
|
|
|
164
60
|
yesql!
|
|
165
61
|
|
|
166
62
|
BUNDLED WITH
|
|
167
|
-
2.
|
|
63
|
+
2.2.3
|
data/README.md
CHANGED
|
@@ -120,28 +120,6 @@ ActiveRecord::Base.connection.execute('SELECT * FROM pg_prepared_statements').to
|
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
#### `cache`
|
|
124
|
-
|
|
125
|
-
If you need to cache your query, you can use the `cache` option. `cache` must be a Hash containing __at least__ the `expires_in` key with an `ActiveSupport::Duration` object, e.g:
|
|
126
|
-
|
|
127
|
-
```ruby
|
|
128
|
-
YeSQL('users', cache: { key: 'users', expires_in: 1.hour })
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
That's enough to cache the result of the query for 1 hour with the cache key "users".
|
|
132
|
-
|
|
133
|
-
If no `key` key/value is used, then the cache key is the name of the file containing the SQL code, and
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
YeSQL('users', cache: { key: 'users', expires_in: 1.hour })
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
is the same as
|
|
140
|
-
|
|
141
|
-
```ruby
|
|
142
|
-
YeSQL('users', cache: { expires_in: 1.hour })
|
|
143
|
-
```
|
|
144
|
-
|
|
145
123
|
## Configuration
|
|
146
124
|
|
|
147
125
|
For default `YeSQL` looks for the _.sql_ files defined under the `app/yesql/` folder but you can update it to use any folder you need. For that you can create a Ruby file under the `config/initializers/` with the following content:
|
|
@@ -173,6 +151,12 @@ You can check at anytime what's the configuration path by inspecting the ::YeSQL
|
|
|
173
151
|
|
|
174
152
|
Bug reports and pull requests are welcome on GitHub at https://github.com/sebastian-palma/yesql. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/sebastian-palma/yesql/blob/master/CODE_OF_CONDUCT.md).
|
|
175
153
|
|
|
154
|
+
## TODO
|
|
155
|
+
|
|
156
|
+
- [ ] Allow comments in .sql files.
|
|
157
|
+
- [ ] Improve errors.
|
|
158
|
+
- [ ] Auto convert `x IN (xs)` queries to `(x = x' OR x = x'')`.
|
|
159
|
+
|
|
176
160
|
|
|
177
161
|
## License
|
|
178
162
|
|
data/Rakefile
CHANGED
data/lib/yesql.rb
CHANGED
|
@@ -1,47 +1,40 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require 'yesql/bindings/binder'
|
|
3
|
+
require "yesql/statement"
|
|
4
|
+
require "yesql/version"
|
|
5
|
+
require "yesql/config/configuration"
|
|
6
|
+
require "yesql/query/performer"
|
|
7
|
+
require "yesql/errors/file_path_does_not_exist_error"
|
|
8
|
+
require "yesql/errors/no_bindings_provided_error"
|
|
9
|
+
require "yesql/errors/output_argument_error"
|
|
11
10
|
|
|
12
11
|
module YeSQL
|
|
13
12
|
include ::YeSQL::Config
|
|
14
|
-
include ::YeSQL::Errors::CacheExpirationError
|
|
15
13
|
include ::YeSQL::Errors::FilePathDoesNotExistError
|
|
16
14
|
include ::YeSQL::Errors::NoBindingsProvidedError
|
|
17
15
|
include ::YeSQL::Errors::OutputArgumentError
|
|
18
16
|
|
|
19
|
-
BIND_REGEX = /(?<!:):(\w+)(?=\b)
|
|
17
|
+
BIND_REGEX = /(?<!:):(\w+)(?=\b)/
|
|
20
18
|
|
|
21
|
-
# rubocop:disable Naming/MethodName
|
|
22
19
|
def YeSQL(file_path, bindings = {}, options = {})
|
|
23
20
|
output = options[:output] || :rows
|
|
24
|
-
cache = options[:cache] || {}
|
|
25
21
|
|
|
26
|
-
validate(bindings,
|
|
27
|
-
execute(bindings,
|
|
22
|
+
validate(bindings, file_path, output)
|
|
23
|
+
execute(bindings, file_path, output, options)
|
|
28
24
|
end
|
|
29
|
-
# rubocop:enable Naming/MethodName
|
|
30
25
|
|
|
31
26
|
private
|
|
32
27
|
|
|
33
|
-
def validate(bindings,
|
|
28
|
+
def validate(bindings, file_path, output)
|
|
34
29
|
validate_file_path_existence(file_path)
|
|
35
30
|
validate_statement_bindings(bindings, file_path)
|
|
36
31
|
validate_output_options(output)
|
|
37
|
-
validate_cache_expiration(cache[:expires_in]) unless cache.empty?
|
|
38
32
|
end
|
|
39
33
|
|
|
40
|
-
def execute(bindings,
|
|
34
|
+
def execute(bindings, file_path, output, options)
|
|
41
35
|
::YeSQL::Query::Performer.new(
|
|
42
36
|
bindings: bindings,
|
|
43
|
-
bind_statement: ::YeSQL::
|
|
44
|
-
cache: cache,
|
|
37
|
+
bind_statement: ::YeSQL::Statement.new(bindings, file_path),
|
|
45
38
|
file_path: file_path,
|
|
46
39
|
output: output,
|
|
47
40
|
prepare: options[:prepare]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "yesql/common/adapter"
|
|
4
5
|
|
|
5
6
|
module ::YeSQL
|
|
6
7
|
module Bindings
|
|
@@ -8,9 +9,9 @@ module ::YeSQL
|
|
|
8
9
|
include ::YeSQL::Common::Adapter
|
|
9
10
|
|
|
10
11
|
def initialize(indexed_bindings, hash, index, value)
|
|
11
|
-
@indexed_bindings = indexed_bindings
|
|
12
12
|
@hash = hash
|
|
13
13
|
@index = index
|
|
14
|
+
@indexed_bindings = indexed_bindings
|
|
14
15
|
@value = value
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -22,13 +23,13 @@ module ::YeSQL
|
|
|
22
23
|
|
|
23
24
|
def bind_vars
|
|
24
25
|
if mysql?
|
|
25
|
-
return
|
|
26
|
+
return "?" unless array?
|
|
26
27
|
|
|
27
|
-
Array.new(value.size,
|
|
28
|
+
Array.new(value.size, "?").join(", ")
|
|
28
29
|
elsif pg?
|
|
29
30
|
return "$#{last_val}" unless array?
|
|
30
31
|
|
|
31
|
-
value.map.with_index(bind_index) { |_, i| "$#{i}" }.join(
|
|
32
|
+
value.map.with_index(bind_index) { |_, i| "$#{i}" }.join(", ")
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "yesql/bindings/extract"
|
|
4
4
|
|
|
5
5
|
module YeSQL
|
|
6
6
|
module Bindings
|
|
@@ -10,7 +10,6 @@ module YeSQL
|
|
|
10
10
|
@indexed_bindings = (bindings || {}).to_a
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# rubocop:disable Metrics/MethodLength
|
|
14
13
|
def call
|
|
15
14
|
bindings.each_with_object({}).with_index(1) do |((key, value), hash), index|
|
|
16
15
|
hash[key] =
|
|
@@ -28,7 +27,6 @@ module YeSQL
|
|
|
28
27
|
end
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
|
-
# rubocop:enable Metrics/MethodLength
|
|
32
30
|
|
|
33
31
|
private
|
|
34
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "yesql/common/adapter"
|
|
5
5
|
|
|
6
6
|
module ::YeSQL
|
|
7
7
|
module Bindings
|
|
@@ -24,16 +24,15 @@ module ::YeSQL
|
|
|
24
24
|
attr_reader :statement_binds
|
|
25
25
|
|
|
26
26
|
def rails5?
|
|
27
|
-
::
|
|
27
|
+
::ActiveRecord::VERSION::MAJOR == 5
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def mysql_rails5_binds
|
|
31
31
|
statement_binds
|
|
32
|
-
.
|
|
33
|
-
.flatten(1)
|
|
32
|
+
.flat_map(&:first)
|
|
34
33
|
.each_slice(2)
|
|
35
34
|
.flat_map do |first, last|
|
|
36
|
-
next [first, last].map(&:last) if first.is_a?(Array)
|
|
35
|
+
next [first, last].compact.map(&:last) if first.is_a?(Array)
|
|
37
36
|
|
|
38
37
|
last
|
|
39
38
|
end
|
|
@@ -49,7 +48,7 @@ module ::YeSQL
|
|
|
49
48
|
|
|
50
49
|
def pg_binds
|
|
51
50
|
statement_binds
|
|
52
|
-
.sort_by { |_, position| position.to_s.tr(
|
|
51
|
+
.sort_by { |_, position| position.to_s.tr("$", "").to_i }
|
|
53
52
|
.uniq
|
|
54
53
|
.map(&:first)
|
|
55
54
|
.flatten
|
data/lib/yesql/bindings/utils.rb
CHANGED
|
@@ -4,10 +4,11 @@ module YeSQL
|
|
|
4
4
|
module Bindings
|
|
5
5
|
module Utils
|
|
6
6
|
def statement_binds(extractor)
|
|
7
|
-
::YeSQL::Utils::Read
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
::YeSQL::Utils::Read
|
|
8
|
+
.statement(file_path, readable: true)
|
|
9
|
+
.scan(::YeSQL::BIND_REGEX).map do |(bind)|
|
|
10
|
+
extractor[bind.to_sym][:bind].values_at(:vals, :vars)
|
|
11
|
+
end
|
|
11
12
|
end
|
|
12
13
|
end
|
|
13
14
|
end
|
data/lib/yesql/common/adapter.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "forwardable"
|
|
4
4
|
|
|
5
5
|
module ::YeSQL
|
|
6
6
|
module Common
|
|
7
7
|
module Adapter
|
|
8
|
-
extend Forwardable
|
|
8
|
+
extend ::Forwardable
|
|
9
9
|
|
|
10
10
|
# `adapter` might be a complex object, but
|
|
11
11
|
# for the sake of brevity it's just a string
|
|
@@ -14,11 +14,11 @@ module ::YeSQL
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def mysql?
|
|
17
|
-
adapter ==
|
|
17
|
+
adapter == "Mysql2"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def pg?
|
|
21
|
-
adapter ==
|
|
21
|
+
adapter == "PostgreSQL"
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "yesql/utils/read"
|
|
4
|
+
|
|
5
|
+
module ::YeSQL
|
|
4
6
|
module Errors
|
|
5
7
|
module NoBindingsProvidedError
|
|
6
8
|
def validate_statement_bindings(binds, file_path)
|
|
7
9
|
return unless statement_binds(file_path).size.positive?
|
|
8
10
|
|
|
9
11
|
format(MESSAGE, renderable_statement_binds(file_path)).tap do |message|
|
|
10
|
-
raise ArgumentError, message unless binds.is_a?(Hash) && !binds.empty?
|
|
12
|
+
raise ::ArgumentError, message unless binds.is_a?(::Hash) && !binds.empty?
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
|
|
@@ -25,7 +27,7 @@ module YeSQL
|
|
|
25
27
|
|
|
26
28
|
def statement_binds(file_path)
|
|
27
29
|
::YeSQL::Utils::Read.statement(file_path)
|
|
28
|
-
|
|
30
|
+
.scan(::YeSQL::BIND_REGEX).tap do |scanned_binds|
|
|
29
31
|
break [] if scanned_binds.size.zero?
|
|
30
32
|
|
|
31
33
|
break scanned_binds.sort
|
data/lib/yesql/params/output.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "forwardable"
|
|
5
5
|
|
|
6
6
|
module ::YeSQL
|
|
7
7
|
module Params
|
|
@@ -13,15 +13,15 @@ module ::YeSQL
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def columns?
|
|
16
|
-
output ==
|
|
16
|
+
output == "columns"
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def rows?
|
|
20
|
-
output ==
|
|
20
|
+
output == "rows"
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def hash?
|
|
24
|
-
output ==
|
|
24
|
+
output == "hash"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def_delegator(:output, :to_sym)
|
|
@@ -1,58 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "yesql/bindings/utils"
|
|
5
|
+
require "yesql/common/adapter"
|
|
6
|
+
require "yesql/bindings/transformed"
|
|
7
|
+
require "yesql/query/result"
|
|
8
|
+
require "yesql/query/transform_result"
|
|
9
|
+
require "yesql/params/output"
|
|
10
10
|
|
|
11
11
|
module YeSQL
|
|
12
12
|
module Query
|
|
13
13
|
class Performer
|
|
14
14
|
include ::YeSQL::Bindings::Utils
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
def initialize(bind_statement:,
|
|
18
|
-
bindings: {},
|
|
19
|
-
cache: {},
|
|
20
|
-
file_path:,
|
|
21
|
-
output: :rows,
|
|
22
|
-
prepare: false)
|
|
16
|
+
def initialize(bind_statement:, file_path:, bindings: {}, output: :rows, prepare: false)
|
|
23
17
|
@bind_statement = bind_statement
|
|
24
|
-
@cache = cache
|
|
25
|
-
@cache_key = cache[:key] || file_path
|
|
26
|
-
@expires_in = cache[:expires_in]
|
|
27
18
|
@file_path = file_path
|
|
28
19
|
@named_bindings = bindings.transform_keys(&:to_sym)
|
|
29
20
|
@output = ::YeSQL::Params::Output.new(output)
|
|
30
21
|
@prepare = prepare
|
|
31
22
|
end
|
|
32
|
-
# rubocop:enable Metrics/ParameterLists
|
|
33
23
|
|
|
34
24
|
def call
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Rails.cache.fetch(cache_key, expires_in: expires_in) { modified_output }
|
|
25
|
+
::YeSQL::Query::TransformResult.new(output: output, result: query_result).call
|
|
38
26
|
end
|
|
39
27
|
|
|
40
28
|
private
|
|
41
29
|
|
|
42
|
-
attr_reader :bind_statement,
|
|
43
|
-
:cache,
|
|
44
|
-
:cache_key,
|
|
45
|
-
:expires_in,
|
|
46
|
-
:file_path,
|
|
47
|
-
:named_bindings,
|
|
48
|
-
:output,
|
|
49
|
-
:prepare,
|
|
50
|
-
:rows
|
|
51
|
-
|
|
52
|
-
def modified_output
|
|
53
|
-
@modified_output ||=
|
|
54
|
-
::YeSQL::Query::TransformResult.new(output: output, result: query_result).call
|
|
55
|
-
end
|
|
30
|
+
attr_reader :bind_statement, :file_path, :named_bindings, :output, :prepare, :rows
|
|
56
31
|
|
|
57
32
|
def query_result
|
|
58
33
|
@query_result ||= ::YeSQL::Query::Result.new(binds: binds,
|
|
@@ -61,13 +36,13 @@ module YeSQL
|
|
|
61
36
|
prepare: prepare).call
|
|
62
37
|
end
|
|
63
38
|
|
|
64
|
-
def extractor
|
|
65
|
-
::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
|
|
66
|
-
end
|
|
67
|
-
|
|
68
39
|
def binds
|
|
69
40
|
::YeSQL::Bindings::Transformed.new(statement_binds: statement_binds(extractor)).call
|
|
70
41
|
end
|
|
42
|
+
|
|
43
|
+
def extractor
|
|
44
|
+
::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
|
|
45
|
+
end
|
|
71
46
|
end
|
|
72
47
|
end
|
|
73
48
|
end
|
data/lib/yesql/query/result.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
require "yesql/common/adapter"
|
|
6
6
|
|
|
7
7
|
module ::YeSQL
|
|
8
8
|
module Query
|
|
@@ -11,32 +11,36 @@ module ::YeSQL
|
|
|
11
11
|
|
|
12
12
|
include ::YeSQL::Common::Adapter
|
|
13
13
|
|
|
14
|
-
def initialize(
|
|
14
|
+
def initialize(bind_statement:, file_path:, prepare:, binds: [])
|
|
15
15
|
@binds = binds
|
|
16
16
|
@bind_statement = bind_statement
|
|
17
|
+
@connection = ActiveRecord::Base.connection
|
|
17
18
|
@file_path = file_path
|
|
18
19
|
@prepare_option = prepare
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def call
|
|
22
|
-
return
|
|
23
|
+
return view_result if view?
|
|
24
|
+
return rails5_result if ::ActiveRecord::VERSION::MAJOR == 5 && mysql?
|
|
23
25
|
|
|
24
|
-
exec_query(
|
|
26
|
+
exec_query(bound, file_path, binds, prepare: prepare_option)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
attr_reader :binds, :bind_statement, :file_path, :prepare_option
|
|
31
|
+
attr_reader :binds, :bind_statement, :connection, :file_path, :prepare_option
|
|
30
32
|
|
|
33
|
+
def_delegators(:bind_statement, :bound, :to_s, :view?)
|
|
31
34
|
def_delegators(:connection, :exec_query, :raw_connection)
|
|
32
35
|
def_delegators(:raw_connection, :prepare)
|
|
33
36
|
|
|
34
|
-
def
|
|
35
|
-
|
|
37
|
+
def view_result
|
|
38
|
+
exec_query(bound)
|
|
36
39
|
end
|
|
37
40
|
|
|
41
|
+
# TODO: recheck this case
|
|
38
42
|
def rails5_result
|
|
39
|
-
prepare(
|
|
43
|
+
prepare(bound).execute(*binds)
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "yesql"
|
|
4
|
+
require "yesql/common/adapter"
|
|
5
|
+
require "forwardable"
|
|
5
6
|
|
|
6
7
|
module ::YeSQL
|
|
7
8
|
module Query
|
|
8
9
|
class TransformResult
|
|
9
10
|
extend Forwardable
|
|
10
11
|
|
|
12
|
+
include ::YeSQL::Common::Adapter
|
|
13
|
+
|
|
11
14
|
def initialize(output:, result:)
|
|
12
15
|
@output = output
|
|
13
16
|
@result = result
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def call
|
|
17
|
-
if ::
|
|
20
|
+
if ::ActiveRecord::VERSION::MAJOR == 5 && mysql?
|
|
18
21
|
return columns if columns?
|
|
19
22
|
return rows_values if rows?
|
|
23
|
+
return array_of_symbol_hashes if hash?
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
return result.public_send(output.to_sym) if columns? || rows?
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
to_a.map(&:symbolize_keys)
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
private
|
|
@@ -36,8 +40,9 @@ module ::YeSQL
|
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
def array_of_symbol_hashes
|
|
39
|
-
to_a
|
|
40
|
-
|
|
43
|
+
to_a
|
|
44
|
+
.tap { |rows| break hashed_rows(rows) if ::ActiveRecord::VERSION::MAJOR == 5 }
|
|
45
|
+
.map { |e| e.respond_to?(:symbolize_keys) ? e.symbolize_keys : e }
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
def hashed_rows(rows)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
|
|
5
|
+
require "forwardable"
|
|
6
|
+
|
|
7
|
+
require "yesql/utils/read"
|
|
8
|
+
require "yesql/bindings/extractor"
|
|
9
|
+
require "yesql/common/adapter"
|
|
10
|
+
|
|
11
|
+
module ::YeSQL
|
|
12
|
+
class Statement
|
|
13
|
+
extend ::Forwardable
|
|
14
|
+
|
|
15
|
+
include ::YeSQL::Common::Adapter
|
|
16
|
+
# Give access to the quote method.
|
|
17
|
+
include ::ActiveRecord::ConnectionAdapters::Quoting
|
|
18
|
+
|
|
19
|
+
def initialize(bindings = {}, file_path)
|
|
20
|
+
@bindings = bindings
|
|
21
|
+
@file_path = file_path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def bound
|
|
25
|
+
to_s.gsub(::YeSQL::BIND_REGEX) do |match|
|
|
26
|
+
extractor[match[/(\w+)/].to_sym].tap do |extract|
|
|
27
|
+
break quote(extract[:value]) if view?
|
|
28
|
+
|
|
29
|
+
break extract[:bind][:vars]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_s
|
|
35
|
+
@to_s ||= ::YeSQL::Utils::Read.statement(file_path, readable: true)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def view?
|
|
39
|
+
to_s =~ /^create\s.*view\s/i
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :bindings, :file_path
|
|
45
|
+
|
|
46
|
+
def extractor
|
|
47
|
+
::YeSQL::Bindings::Extractor.new(bindings: bindings).call
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/yesql/utils/read.rb
CHANGED
|
@@ -7,7 +7,7 @@ module YeSQL
|
|
|
7
7
|
Dir["./#{::YeSQL.config.path}/**/*.sql"]
|
|
8
8
|
.find { |dir_file_path| dir_file_path.include?("#{file_path}.sql") }
|
|
9
9
|
.tap do |sql_file_path|
|
|
10
|
-
break File.readlines(sql_file_path, chomp: true).join(
|
|
10
|
+
break File.readlines(sql_file_path, chomp: true).join(" ") if readable == true
|
|
11
11
|
|
|
12
12
|
break File.read(sql_file_path)
|
|
13
13
|
end
|
data/lib/yesql/version.rb
CHANGED
data/yesql.gemspec
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "lib/yesql/version"
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = "yesql"
|
|
7
7
|
spec.version = YeSQL::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
10
|
-
spec.summary =
|
|
8
|
+
spec.authors = ["Sebastián Palma"]
|
|
9
|
+
spec.email = ["vnhnhm.github@gmail.com"]
|
|
10
|
+
spec.summary = "Ruby library to use SQL"
|
|
11
11
|
spec.description = 'SQL "raw" for Rails projects'
|
|
12
|
-
spec.homepage =
|
|
13
|
-
spec.license =
|
|
14
|
-
spec.metadata[
|
|
15
|
-
spec.metadata[
|
|
16
|
-
spec.metadata[
|
|
12
|
+
spec.homepage = "https://github.com/sebastian-palma/yesql"
|
|
13
|
+
spec.license = "MIT"
|
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
15
|
+
spec.metadata["source_code_uri"] = "https://github.com/sebastian-palma/yesql"
|
|
16
|
+
spec.metadata["changelog_uri"] = "https://github.com/sebastian-palma/yesql"
|
|
17
17
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
18
18
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
19
|
end
|
|
20
|
-
spec.bindir =
|
|
20
|
+
spec.bindir = "exe"
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
22
|
-
spec.require_paths = [
|
|
23
|
-
spec.add_dependency
|
|
24
|
-
spec.add_development_dependency
|
|
25
|
-
spec.add_development_dependency
|
|
26
|
-
spec.add_development_dependency
|
|
27
|
-
spec.add_development_dependency
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
spec.add_dependency "activerecord", ">= 4.0.0.beta1"
|
|
24
|
+
spec.add_development_dependency "mysql2", "~> 0.5.3"
|
|
25
|
+
spec.add_development_dependency "pg", ">= 0.18"
|
|
26
|
+
spec.add_development_dependency "pry", "~> 0.13.1"
|
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.9.0"
|
|
28
28
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yesql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastián Palma
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2021-04-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activerecord
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 4.0.0.beta1
|
|
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: 4.0.0.beta1
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: mysql2
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -97,14 +97,12 @@ files:
|
|
|
97
97
|
- bin/console
|
|
98
98
|
- bin/setup
|
|
99
99
|
- lib/yesql.rb
|
|
100
|
-
- lib/yesql/bindings/binder.rb
|
|
101
100
|
- lib/yesql/bindings/extract.rb
|
|
102
101
|
- lib/yesql/bindings/extractor.rb
|
|
103
102
|
- lib/yesql/bindings/transformed.rb
|
|
104
103
|
- lib/yesql/bindings/utils.rb
|
|
105
104
|
- lib/yesql/common/adapter.rb
|
|
106
105
|
- lib/yesql/config/configuration.rb
|
|
107
|
-
- lib/yesql/errors/cache_expiration_error.rb
|
|
108
106
|
- lib/yesql/errors/file_path_does_not_exist_error.rb
|
|
109
107
|
- lib/yesql/errors/no_bindings_provided_error.rb
|
|
110
108
|
- lib/yesql/errors/output_argument_error.rb
|
|
@@ -112,6 +110,7 @@ files:
|
|
|
112
110
|
- lib/yesql/query/performer.rb
|
|
113
111
|
- lib/yesql/query/result.rb
|
|
114
112
|
- lib/yesql/query/transform_result.rb
|
|
113
|
+
- lib/yesql/statement.rb
|
|
115
114
|
- lib/yesql/utils/read.rb
|
|
116
115
|
- lib/yesql/version.rb
|
|
117
116
|
- yesql.gemspec
|
|
@@ -122,7 +121,7 @@ metadata:
|
|
|
122
121
|
homepage_uri: https://github.com/sebastian-palma/yesql
|
|
123
122
|
source_code_uri: https://github.com/sebastian-palma/yesql
|
|
124
123
|
changelog_uri: https://github.com/sebastian-palma/yesql
|
|
125
|
-
post_install_message:
|
|
124
|
+
post_install_message:
|
|
126
125
|
rdoc_options: []
|
|
127
126
|
require_paths:
|
|
128
127
|
- lib
|
|
@@ -137,8 +136,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
137
136
|
- !ruby/object:Gem::Version
|
|
138
137
|
version: '0'
|
|
139
138
|
requirements: []
|
|
140
|
-
rubygems_version: 3.
|
|
141
|
-
signing_key:
|
|
139
|
+
rubygems_version: 3.2.3
|
|
140
|
+
signing_key:
|
|
142
141
|
specification_version: 4
|
|
143
142
|
summary: Ruby library to use SQL
|
|
144
143
|
test_files: []
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'yesql/utils/read'
|
|
4
|
-
require 'yesql/bindings/extractor'
|
|
5
|
-
|
|
6
|
-
module YeSQL
|
|
7
|
-
module Bindings
|
|
8
|
-
class Binder
|
|
9
|
-
def self.bind_statement(file_path, bindings)
|
|
10
|
-
::YeSQL::Bindings::Extractor.new(bindings: bindings).call.tap do |extractor|
|
|
11
|
-
break ::YeSQL::Utils::Read.statement(file_path, readable: true)
|
|
12
|
-
.gsub(::YeSQL::BIND_REGEX) do |match|
|
|
13
|
-
extractor[match[/(\w+)/].to_sym][:bind][:vars]
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module YeSQL
|
|
4
|
-
module Errors
|
|
5
|
-
module CacheExpirationError
|
|
6
|
-
def validate_cache_expiration(expires_in)
|
|
7
|
-
raise ArgumentError, MESSAGE unless expires_in.is_a?(ActiveSupport::Duration)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
MESSAGE = <<~MSG
|
|
11
|
-
Missing mandatory `expires_in` option for `cache`.
|
|
12
|
-
|
|
13
|
-
Can not cache the result of the query without an expiration date.
|
|
14
|
-
MSG
|
|
15
|
-
private_constant :MESSAGE
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|