yesql 0.1.4 → 0.2.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/README.md +14 -31
- data/lib/.rbnext/2.3/yesql/errors/file_path_does_not_exist_error.rb +40 -0
- data/lib/.rbnext/2.3/yesql/errors/no_bindings_provided_error.rb +38 -0
- data/lib/.rbnext/2.3/yesql/errors/output_argument_error.rb +31 -0
- data/lib/.rbnext/2.3/yesql/query/transform_result.rb +48 -0
- data/lib/.rbnext/2.7/yesql/bindings/extract.rb +71 -0
- data/lib/.rbnext/2.7/yesql/bindings/extractor.rb +38 -0
- data/lib/.rbnext/2.7/yesql/bindings/transformed.rb +58 -0
- data/lib/.rbnext/2.7/yesql/bindings/utils.rb +16 -0
- data/lib/.rbnext/2.7/yesql/errors/file_path_does_not_exist_error.rb +43 -0
- data/lib/.rbnext/2.7/yesql/errors/no_bindings_provided_error.rb +41 -0
- data/lib/.rbnext/2.7/yesql/query/transform_result.rb +48 -0
- data/lib/.rbnext/2.7/yesql/statement.rb +44 -0
- data/lib/.rbnext/2.7/yesql/utils/read.rb +20 -0
- data/lib/.rbnext/3.0/yesql/bindings/extract.rb +71 -0
- data/lib/.rbnext/3.0/yesql/bindings/transformed.rb +58 -0
- data/lib/.rbnext/3.0/yesql/common/adapter.rb +18 -0
- data/lib/.rbnext/3.0/yesql/config/configuration.rb +32 -0
- data/lib/.rbnext/3.0/yesql/errors/file_path_does_not_exist_error.rb +43 -0
- data/lib/.rbnext/3.0/yesql/errors/no_bindings_provided_error.rb +41 -0
- data/lib/.rbnext/3.0/yesql/params/output.rb +26 -0
- data/lib/.rbnext/3.0/yesql/query/performer.rb +44 -0
- data/lib/.rbnext/3.0/yesql/query/result.rb +41 -0
- data/lib/.rbnext/3.0/yesql/query/transform_result.rb +48 -0
- data/lib/.rbnext/3.0/yesql/statement.rb +44 -0
- data/lib/.rbnext/3.0/yesql/utils/read.rb +20 -0
- data/lib/yesql.rb +25 -32
- data/lib/yesql/bindings/extract.rb +22 -19
- data/lib/yesql/bindings/extractor.rb +21 -21
- data/lib/yesql/bindings/transformed.rb +58 -0
- data/lib/yesql/bindings/utils.rb +7 -5
- data/lib/yesql/common/adapter.rb +18 -0
- data/lib/yesql/config/configuration.rb +4 -5
- data/lib/yesql/errors/file_path_does_not_exist_error.rb +22 -17
- data/lib/yesql/errors/no_bindings_provided_error.rb +20 -19
- data/lib/yesql/errors/output_argument_error.rb +13 -4
- data/lib/yesql/params/output.rb +26 -0
- data/lib/yesql/query/performer.rb +20 -62
- data/lib/yesql/query/result.rb +41 -0
- data/lib/yesql/query/transform_result.rb +48 -0
- data/lib/yesql/statement.rb +44 -0
- data/lib/yesql/utils/read.rb +11 -8
- data/lib/yesql/version.rb +1 -1
- metadata +68 -18
- data/.gitignore +0 -1
- data/Gemfile +0 -9
- data/Gemfile.lock +0 -165
- data/Rakefile +0 -1
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/yesql/bindings/binder.rb +0 -19
- data/lib/yesql/errors/cache_expiration_error.rb +0 -18
- data/yesql.gemspec +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a975eb880308df8b6528635fef23c0765a4a55575b3fd701889c35d3b31df597
|
4
|
+
data.tar.gz: e20a7b8f884fcb1ac6f122d2b260681128bcb811e4f4013e96c6095cbe430ca8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b06e530938239c35dbd9d73c1b185bdfab8055aef33fe6949ed4cd2ae87b631be7be66c48a78df207e9306f0c76abfa095a92afc3adba3228c82972c0600f8a
|
7
|
+
data.tar.gz: 79b81dde78f7d749257f9eafe0e5afb7575b38ed89e4aa017996d03b2d2d9fc2003eeff6124456623025e0f5c17b2c59e89f8ca4652808db2bbb2e365cec00f8
|
data/README.md
CHANGED
@@ -70,7 +70,6 @@ YeSQL('top_10_users_in_x_country', { country: 'Cuba', country_id: 1, limit: 6 })
|
|
70
70
|
```
|
71
71
|
|
72
72
|
- If the query doesn't have bindings, but they're provided they're just omitted.
|
73
|
-
TODO: update this with link to the error.
|
74
73
|
- If the query has bindings, but nothing is provided, it raises a `NotImplementedError` exception.
|
75
74
|
|
76
75
|
|
@@ -121,45 +120,23 @@ ActiveRecord::Base.connection.execute('SELECT * FROM pg_prepared_statements').to
|
|
121
120
|
```
|
122
121
|
|
123
122
|
|
124
|
-
#### `cache`
|
125
|
-
|
126
|
-
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:
|
127
|
-
|
128
|
-
```ruby
|
129
|
-
YeSQL('users', cache: { key: 'users', expires_in: 1.hour })
|
130
|
-
```
|
131
|
-
|
132
|
-
That's enough to cache the result of the query for 1 hour with the cache key "users".
|
133
|
-
|
134
|
-
If no `key` key/value is used, then the cache key is the name of the file containing the SQL code, and
|
135
|
-
|
136
|
-
```ruby
|
137
|
-
YeSQL('users', cache: { key: 'users', expires_in: 1.hour })
|
138
|
-
```
|
139
|
-
|
140
|
-
is the same as
|
141
|
-
|
142
|
-
```ruby
|
143
|
-
YeSQL('users', cache: { expires_in: 1.hour })
|
144
|
-
```
|
145
|
-
|
146
123
|
## Configuration
|
147
124
|
|
148
|
-
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/`
|
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:
|
149
126
|
|
150
127
|
```ruby
|
151
|
-
YeSQL.config.path = 'path'
|
128
|
+
::YeSQL.configure { |config| config.path = 'path' }
|
152
129
|
```
|
153
130
|
|
154
131
|
After saving the file and restarting the server the files are going to be read from the given folder.
|
155
132
|
|
156
|
-
You can check at anytime what's the configuration path by inspecting the YeSQL `config` object:
|
133
|
+
You can check at anytime what's the configuration path by inspecting the ::YeSQL `config` object:
|
157
134
|
|
158
135
|
```ruby
|
159
|
-
YeSQL.config
|
160
|
-
#
|
161
|
-
|
162
|
-
# "
|
136
|
+
::YeSQL.config
|
137
|
+
# => #<YeSQL::Config::Configuration:0x00007feea1aa2ef8 @path="app/yesql">
|
138
|
+
::YeSQL.config.path
|
139
|
+
# => "app/yesql"
|
163
140
|
```
|
164
141
|
|
165
142
|
|
@@ -167,13 +144,19 @@ Yesql.config.path
|
|
167
144
|
|
168
145
|
- Clone the repository.
|
169
146
|
- Install the gem dependencies.
|
170
|
-
- Make sure to create
|
147
|
+
- Make sure to create both databases used in the dummy Rails applications (mysql, pg) in the spec/ folder.
|
171
148
|
- Run the tests.
|
172
149
|
|
173
150
|
## Contributing
|
174
151
|
|
175
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).
|
176
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
|
+
|
177
160
|
|
178
161
|
## License
|
179
162
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ::YeSQL
|
4
|
+
module Errors
|
5
|
+
class FilePathDoesNotExistError
|
6
|
+
def initialize(file_path)
|
7
|
+
@file_path = file_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_file_path_existence
|
11
|
+
return if file_exists?
|
12
|
+
|
13
|
+
raise(
|
14
|
+
::NotImplementedError,
|
15
|
+
format(
|
16
|
+
MESSAGE,
|
17
|
+
available_files: available_files,
|
18
|
+
file_path: file_path,
|
19
|
+
path: ::YeSQL.config.path
|
20
|
+
)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :file_path
|
27
|
+
|
28
|
+
MESSAGE = <<-MSG
|
29
|
+
SQL file "%<file_path>s" does not exist in %<path>s.
|
30
|
+
Available SQL files are:
|
31
|
+
%<available_files>s
|
32
|
+
MSG
|
33
|
+
private_constant :MESSAGE
|
34
|
+
|
35
|
+
def file_exists? ; path_files.any? { |_1| _1.include?("#{file_path}.sql") }; end
|
36
|
+
def path_files ; @path_files ||= Dir["#{::YeSQL.config.path}/**/*.sql"]; end
|
37
|
+
def available_files ; path_files.map { |_1| "- #{_1}\n" }.join; end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yesql/utils/read"
|
4
|
+
|
5
|
+
module ::YeSQL
|
6
|
+
module Errors
|
7
|
+
class NoBindingsProvidedError
|
8
|
+
include ::YeSQL::Utils::Read
|
9
|
+
|
10
|
+
def initialize(binds, file_path)
|
11
|
+
@binds = binds
|
12
|
+
@file_path = file_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_statement_bindings
|
16
|
+
return unless statement_binds.size.positive?
|
17
|
+
return if expected_binds?
|
18
|
+
|
19
|
+
raise ::ArgumentError, format(MESSAGE, renderable_statement_binds)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :binds, :file_path
|
25
|
+
|
26
|
+
MESSAGE = <<-MSG
|
27
|
+
YeSQL invoked without bindings.
|
28
|
+
Expected bindings are:
|
29
|
+
%s
|
30
|
+
MSG
|
31
|
+
private_constant :MESSAGE
|
32
|
+
|
33
|
+
def statement_binds ; statement.scan(::YeSQL::BIND_REGEX).tap { |_1| break (_1 || []).sort }; end
|
34
|
+
def renderable_statement_binds ; statement_binds.flatten.map { |_1| "- `#{_1}`\n" }.join; end
|
35
|
+
def expected_binds? ; binds.is_a?(::Hash) && !binds.empty?; end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ::YeSQL
|
4
|
+
module Errors
|
5
|
+
class OutputArgumentError
|
6
|
+
def initialize(output)
|
7
|
+
@output = output
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_output_options
|
11
|
+
return if output.nil?
|
12
|
+
return if OPTIONS.include?(output.to_sym)
|
13
|
+
|
14
|
+
raise ArgumentError, format(MESSAGE, output)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :output
|
20
|
+
|
21
|
+
MESSAGE = <<-MSG
|
22
|
+
Unsupported `output` option given `%s`. Possible values are:
|
23
|
+
- `columns`: returns an array with the columns from the result.
|
24
|
+
- `hash`: returns an array of hashes combining both, the columns and rows from the statement result.
|
25
|
+
- `rows`: returns an array of arrays for each row from the given SQL statement.
|
26
|
+
MSG
|
27
|
+
OPTIONS = %i[columns hash rows].freeze
|
28
|
+
private_constant :MESSAGE, :OPTIONS
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yesql"
|
4
|
+
require "yesql/common/adapter"
|
5
|
+
require "forwardable"
|
6
|
+
|
7
|
+
module ::YeSQL
|
8
|
+
module Query
|
9
|
+
class TransformResult
|
10
|
+
extend ::Forwardable
|
11
|
+
|
12
|
+
include ::YeSQL::Common::Adapter
|
13
|
+
|
14
|
+
def initialize(output:, result:)
|
15
|
+
@output = output
|
16
|
+
@result = result
|
17
|
+
end
|
18
|
+
|
19
|
+
def call
|
20
|
+
if rails_5? && mysql?
|
21
|
+
return columns if columns?
|
22
|
+
return rows_values if rows?
|
23
|
+
return array_of_symbol_hashes if hash?
|
24
|
+
end
|
25
|
+
|
26
|
+
return result.public_send(output.to_sym) if columns? || rows?
|
27
|
+
|
28
|
+
to_a.map(&:symbolize_keys)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :output, :result
|
34
|
+
|
35
|
+
def_delegators(:result, :fields, :rows, :to_a)
|
36
|
+
def_delegators(:output, :columns?, :hash?, :rows?)
|
37
|
+
|
38
|
+
def array_of_symbol_hashes
|
39
|
+
to_a.tap { break hashed_rows(_1) if rails_5? }.map { |_1| ((((__safe_lvar__ = _1) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.symbolize_keys) || _1 }
|
40
|
+
end
|
41
|
+
|
42
|
+
def columns ; fields || result.columns; end
|
43
|
+
def rows_values ; to_a.map { |_1| _1.respond_to?(:values) ? _1.values : _1 }; end
|
44
|
+
def hashed_rows(rows) ; rows.map { |_1| columns.zip(_1).to_h }; end
|
45
|
+
def rails_5? ; ::ActiveRecord::VERSION::MAJOR == 5; end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yesql"
|
4
|
+
require "yesql/common/adapter"
|
5
|
+
|
6
|
+
module ::YeSQL
|
7
|
+
module Bindings
|
8
|
+
class Extract
|
9
|
+
include ::YeSQL::Common::Adapter
|
10
|
+
|
11
|
+
def initialize(hash, indexed_bindings, index, value)
|
12
|
+
@hash = hash
|
13
|
+
@index = index
|
14
|
+
@indexed_bindings = indexed_bindings
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def bind_vals
|
19
|
+
return [nil, value] unless array?
|
20
|
+
|
21
|
+
value.map { |_1| [nil, _1] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def bind_vars
|
25
|
+
if mysql?
|
26
|
+
return "?" unless array?
|
27
|
+
|
28
|
+
Array.new(size, "?").join(", ")
|
29
|
+
elsif pg?
|
30
|
+
return "$#{last_val}" unless array?
|
31
|
+
|
32
|
+
value.map.with_index(bind_index) { |_1, _2| "$#{_2}" }.join(", ")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def prev
|
37
|
+
return if first?
|
38
|
+
|
39
|
+
indexed_bindings[index - 2].first
|
40
|
+
end
|
41
|
+
|
42
|
+
def last_val ; prev_last_val + current_val_size; end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_reader :hash, :index, :indexed_bindings, :value
|
47
|
+
|
48
|
+
def current_val_size
|
49
|
+
return size if array?
|
50
|
+
|
51
|
+
1
|
52
|
+
end
|
53
|
+
|
54
|
+
def prev_last_val
|
55
|
+
return 0 if first?
|
56
|
+
|
57
|
+
hash[prev][:last_val]
|
58
|
+
end
|
59
|
+
|
60
|
+
def bind_index
|
61
|
+
return 1 if first?
|
62
|
+
|
63
|
+
prev_last_val + 1
|
64
|
+
end
|
65
|
+
|
66
|
+
def size ; value.size; end
|
67
|
+
def first? ; index == 1; end
|
68
|
+
def array? ; value.is_a?(Array); end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yesql/bindings/extract"
|
4
|
+
|
5
|
+
module ::YeSQL
|
6
|
+
module Bindings
|
7
|
+
class Extractor
|
8
|
+
def initialize(bindings: {})
|
9
|
+
@bindings = bindings
|
10
|
+
@indexed_bindings = bindings.to_a
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
bindings
|
15
|
+
.each_with_object({})
|
16
|
+
.with_index(1) do |((key, value), hash), index|
|
17
|
+
hash[key] = binding_extracts(hash, indexed_bindings, index, key, value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :bindings, :indexed_bindings
|
24
|
+
|
25
|
+
def binding_extracts(hash, indexed_bindings, index, key, value)
|
26
|
+
::YeSQL::Bindings::Extract.new(hash, indexed_bindings, index, value).tap do |_1|
|
27
|
+
break {
|
28
|
+
bind: { vals: _1.bind_vals, vars: _1.bind_vars },
|
29
|
+
last_val: _1.last_val,
|
30
|
+
match: key,
|
31
|
+
prev: _1.prev,
|
32
|
+
value: value
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yesql"
|
4
|
+
require "yesql/common/adapter"
|
5
|
+
|
6
|
+
module ::YeSQL
|
7
|
+
module Bindings
|
8
|
+
class Transformed
|
9
|
+
include ::YeSQL::Common::Adapter
|
10
|
+
|
11
|
+
def initialize(statement_binds:)
|
12
|
+
@statement_binds = statement_binds
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
return mysql_rails5_binds if rails5? && mysql?
|
17
|
+
return mysql_binds if !rails5? && mysql?
|
18
|
+
|
19
|
+
pg_binds
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :statement_binds
|
25
|
+
|
26
|
+
def rails5? ; ::ActiveRecord::VERSION::MAJOR == 5; end
|
27
|
+
|
28
|
+
def mysql_rails5_binds
|
29
|
+
statement_binds
|
30
|
+
.flat_map(&:first)
|
31
|
+
.each_slice(2)
|
32
|
+
.flat_map do |_1, _2|
|
33
|
+
next [_1, _2].compact.map(&:last) if _1.is_a?(Array)
|
34
|
+
|
35
|
+
_2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def mysql_binds
|
40
|
+
statement_binds
|
41
|
+
.map(&:first)
|
42
|
+
.flatten
|
43
|
+
.each_slice(2)
|
44
|
+
.to_a
|
45
|
+
end
|
46
|
+
|
47
|
+
def pg_binds
|
48
|
+
statement_binds
|
49
|
+
.sort_by { |_1, _2| _2.to_s.tr("$", "").to_i }
|
50
|
+
.uniq
|
51
|
+
.map(&:first)
|
52
|
+
.flatten
|
53
|
+
.each_slice(2)
|
54
|
+
.to_a
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ::YeSQL
|
4
|
+
module Bindings
|
5
|
+
module Utils
|
6
|
+
include ::YeSQL::Utils::Read
|
7
|
+
|
8
|
+
def statement_binds(extractor)
|
9
|
+
statement(readable: true)
|
10
|
+
.scan(::YeSQL::BIND_REGEX)
|
11
|
+
.map(&:first)
|
12
|
+
.map { |_1| extractor[_1.to_sym][:bind].values_at(:vals, :vars) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|