yesql 0.1.8 → 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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -22
  3. data/lib/.rbnext/2.3/yesql/errors/file_path_does_not_exist_error.rb +40 -0
  4. data/lib/.rbnext/2.3/yesql/errors/no_bindings_provided_error.rb +38 -0
  5. data/lib/.rbnext/2.3/yesql/errors/output_argument_error.rb +31 -0
  6. data/lib/.rbnext/2.3/yesql/query/transform_result.rb +48 -0
  7. data/lib/.rbnext/2.7/yesql/bindings/extract.rb +71 -0
  8. data/lib/.rbnext/2.7/yesql/bindings/extractor.rb +38 -0
  9. data/lib/.rbnext/2.7/yesql/bindings/transformed.rb +58 -0
  10. data/lib/.rbnext/2.7/yesql/bindings/utils.rb +16 -0
  11. data/lib/.rbnext/2.7/yesql/errors/file_path_does_not_exist_error.rb +43 -0
  12. data/lib/.rbnext/2.7/yesql/errors/no_bindings_provided_error.rb +41 -0
  13. data/lib/.rbnext/2.7/yesql/query/transform_result.rb +48 -0
  14. data/lib/.rbnext/2.7/yesql/statement.rb +44 -0
  15. data/lib/.rbnext/2.7/yesql/utils/read.rb +20 -0
  16. data/lib/.rbnext/3.0/yesql/bindings/extract.rb +71 -0
  17. data/lib/.rbnext/3.0/yesql/bindings/transformed.rb +58 -0
  18. data/lib/.rbnext/3.0/yesql/common/adapter.rb +18 -0
  19. data/lib/.rbnext/3.0/yesql/config/configuration.rb +32 -0
  20. data/lib/.rbnext/3.0/yesql/errors/file_path_does_not_exist_error.rb +43 -0
  21. data/lib/.rbnext/3.0/yesql/errors/no_bindings_provided_error.rb +41 -0
  22. data/lib/.rbnext/3.0/yesql/params/output.rb +26 -0
  23. data/lib/.rbnext/3.0/yesql/query/performer.rb +44 -0
  24. data/lib/.rbnext/3.0/yesql/query/result.rb +41 -0
  25. data/lib/.rbnext/3.0/yesql/query/transform_result.rb +48 -0
  26. data/lib/.rbnext/3.0/yesql/statement.rb +44 -0
  27. data/lib/.rbnext/3.0/yesql/utils/read.rb +20 -0
  28. data/lib/yesql.rb +25 -31
  29. data/lib/yesql/bindings/extract.rb +15 -21
  30. data/lib/yesql/bindings/extractor.rb +21 -21
  31. data/lib/yesql/bindings/transformed.rb +8 -11
  32. data/lib/yesql/bindings/utils.rb +7 -5
  33. data/lib/yesql/common/adapter.rb +6 -13
  34. data/lib/yesql/config/configuration.rb +4 -5
  35. data/lib/yesql/errors/file_path_does_not_exist_error.rb +22 -17
  36. data/lib/yesql/errors/no_bindings_provided_error.rb +18 -19
  37. data/lib/yesql/errors/output_argument_error.rb +13 -4
  38. data/lib/yesql/params/output.rb +6 -14
  39. data/lib/yesql/query/performer.rb +13 -42
  40. data/lib/yesql/query/result.rb +12 -17
  41. data/lib/yesql/query/transform_result.rb +11 -21
  42. data/lib/yesql/statement.rb +19 -24
  43. data/lib/yesql/utils/read.rb +11 -8
  44. data/lib/yesql/version.rb +1 -1
  45. metadata +47 -17
  46. data/.gitignore +0 -4
  47. data/.rubocop.yml +0 -20
  48. data/Gemfile +0 -9
  49. data/Gemfile.lock +0 -167
  50. data/Rakefile +0 -1
  51. data/bin/console +0 -14
  52. data/bin/setup +0 -8
  53. data/lib/yesql/errors/cache_expiration_error.rb +0 -18
  54. data/yesql.gemspec +0 -28
@@ -0,0 +1,41 @@
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
+
28
+ YeSQL invoked without bindings.
29
+
30
+ Expected bindings are:
31
+
32
+ %s
33
+ MSG
34
+ private_constant :MESSAGE
35
+
36
+ def statement_binds ; statement.scan(::YeSQL::BIND_REGEX).tap { |_1| break (_1 || []).sort }; end
37
+ def renderable_statement_binds ; statement_binds.flatten.map { |_1| "- `#{_1}`\n" }.join; end
38
+ def expected_binds? ; binds.is_a?(::Hash) && !binds.empty?; end
39
+ end
40
+ end
41
+ 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| _1&.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,44 @@
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
+ include ::YeSQL::Utils::Read
17
+ # Give access to the quote method.
18
+ include ::ActiveRecord::ConnectionAdapters::Quoting
19
+
20
+ def initialize(bindings = {}, file_path)
21
+ @bindings = bindings
22
+ @file_path = file_path
23
+ end
24
+
25
+ def bound
26
+ to_s.gsub(::YeSQL::BIND_REGEX) { |_1| extract_bind_values(extractor[_1[/(\w+)/].to_sym]) }
27
+ end
28
+
29
+ def to_s ; @to_s ||= statement(readable: true); end
30
+ def view? ; to_s =~ /^create\s.*view\s/i; end
31
+
32
+ private
33
+
34
+ attr_reader :bindings, :file_path
35
+
36
+ def extract_bind_values(match)
37
+ return quote(match[:value]) if view?
38
+
39
+ match[:bind][:vars]
40
+ end
41
+
42
+ def extractor ; @extractor ||= ::YeSQL::Bindings::Extractor.new(bindings: bindings).call; end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::YeSQL
4
+ module Utils
5
+ module Read
6
+ def statement(readable: false) ; read_file(found_file, readable); end
7
+
8
+ private
9
+
10
+ def read_file(file, readable)
11
+ return File.readlines(file, chomp: true).join(" ") if readable == true
12
+
13
+ File.read(file)
14
+ end
15
+
16
+ def found_file ; dir_sql_files.find { |_1| _1.include?("#{file_path}.sql") }; end
17
+ def dir_sql_files ; Dir["./#{::YeSQL.config.path}/**/*.sql"]; end
18
+ end
19
+ end
20
+ 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 { [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) { "$#{_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,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
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 { _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,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module ::YeSQL
6
+ module Common
7
+ module Adapter
8
+ extend ::Forwardable
9
+
10
+ def mysql? ; adapter == "Mysql2"; end
11
+ def pg? ; adapter == "PostgreSQL"; end
12
+
13
+ private
14
+
15
+ def adapter ; ::ActiveRecord::Base.connection.adapter_name; end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::YeSQL
4
+ module Config
5
+ class Configuration
6
+ attr_accessor :connection, :path
7
+
8
+ DEFAULT_PATH = "app/yesql"
9
+
10
+ def initialize
11
+ @connection = ""
12
+ @path = DEFAULT_PATH
13
+ end
14
+ end
15
+ end
16
+
17
+ class << self
18
+ def config ; @config ||= ::YeSQL::Configuration.new; end
19
+
20
+ def configure
21
+ yield config if block_given?
22
+ end
23
+
24
+ def reset_config
25
+ tap do |conf|
26
+ conf.configure do |configuration|
27
+ configuration.path = ::YeSQL::Configuration::DEFAULT_PATH
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,43 @@
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
+
30
+ SQL file "%<file_path>s" does not exist in %<path>s.
31
+
32
+ Available SQL files are:
33
+
34
+ %<available_files>s
35
+ MSG
36
+ private_constant :MESSAGE
37
+
38
+ def file_exists? ; path_files.any? { _1.include?("#{file_path}.sql") }; end
39
+ def path_files ; @path_files ||= Dir["#{::YeSQL.config.path}/**/*.sql"]; end
40
+ def available_files ; path_files.map { "- #{_1}\n" }.join; end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,41 @@
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
+
28
+ YeSQL invoked without bindings.
29
+
30
+ Expected bindings are:
31
+
32
+ %s
33
+ MSG
34
+ private_constant :MESSAGE
35
+
36
+ def statement_binds ; statement.scan(::YeSQL::BIND_REGEX).tap { break (_1 || []).sort }; end
37
+ def renderable_statement_binds ; statement_binds.flatten.map { "- `#{_1}`\n" }.join; end
38
+ def expected_binds? ; binds.is_a?(::Hash) && !binds.empty?; end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yesql"
4
+ require "forwardable"
5
+
6
+ module ::YeSQL
7
+ module Params
8
+ class Output
9
+ extend ::Forwardable
10
+
11
+ def initialize(output)
12
+ @output = output.to_s
13
+ end
14
+
15
+ def columns? ; output == "columns"; end
16
+ def rows? ; output == "rows"; end
17
+ def hash? ; output == "hash"; end
18
+
19
+ def_delegator(:output, :to_sym)
20
+
21
+ private
22
+
23
+ attr_reader :output
24
+ end
25
+ end
26
+ end