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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -31
  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 -32
  29. data/lib/yesql/bindings/extract.rb +22 -19
  30. data/lib/yesql/bindings/extractor.rb +21 -21
  31. data/lib/yesql/bindings/transformed.rb +58 -0
  32. data/lib/yesql/bindings/utils.rb +7 -5
  33. data/lib/yesql/common/adapter.rb +18 -0
  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 +20 -19
  37. data/lib/yesql/errors/output_argument_error.rb +13 -4
  38. data/lib/yesql/params/output.rb +26 -0
  39. data/lib/yesql/query/performer.rb +20 -62
  40. data/lib/yesql/query/result.rb +41 -0
  41. data/lib/yesql/query/transform_result.rb +48 -0
  42. data/lib/yesql/statement.rb +44 -0
  43. data/lib/yesql/utils/read.rb +11 -8
  44. data/lib/yesql/version.rb +1 -1
  45. metadata +68 -18
  46. data/.gitignore +0 -1
  47. data/Gemfile +0 -9
  48. data/Gemfile.lock +0 -165
  49. data/Rakefile +0 -1
  50. data/bin/console +0 -14
  51. data/bin/setup +0 -8
  52. data/lib/yesql/bindings/binder.rb +0 -19
  53. data/lib/yesql/errors/cache_expiration_error.rb +0 -18
  54. data/yesql.gemspec +0 -27
@@ -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
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
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module YeSQL
3
+ module ::YeSQL
4
4
  module Bindings
5
5
  module Utils
6
+ include ::YeSQL::Utils::Read
7
+
6
8
  def statement_binds(extractor)
7
- ::YeSQL::Utils::Read.statement(file_path, readable: true)
8
- .scan(::YeSQL::BIND_REGEX).map do |(bind)|
9
- extractor[bind.to_sym][:bind].values_at(:vals, :vars)
10
- end
9
+ statement(readable: true)
10
+ .scan(::YeSQL::BIND_REGEX)
11
+ .map(&:first)
12
+ .map { extractor[_1.to_sym][:bind].values_at(:vals, :vars) }
11
13
  end
12
14
  end
13
15
  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"
11
+ def pg? = adapter == "PostgreSQL"
12
+
13
+ private
14
+
15
+ def adapter = ::ActiveRecord::Base.connection.adapter_name
16
+ end
17
+ end
18
+ end
@@ -3,20 +3,19 @@
3
3
  module ::YeSQL
4
4
  module Config
5
5
  class Configuration
6
- attr_accessor :path
6
+ attr_accessor :connection, :path
7
7
 
8
- DEFAULT_PATH = 'app/yesql'
8
+ DEFAULT_PATH = "app/yesql"
9
9
 
10
10
  def initialize
11
+ @connection = ""
11
12
  @path = DEFAULT_PATH
12
13
  end
13
14
  end
14
15
  end
15
16
 
16
17
  class << self
17
- def config
18
- @config ||= ::YeSQL::Configuration.new
19
- end
18
+ def config = @config ||= ::YeSQL::Configuration.new
20
19
 
21
20
  def configure
22
21
  yield config if block_given?
@@ -1,17 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module YeSQL
3
+ module ::YeSQL
4
4
  module Errors
5
- module FilePathDoesNotExistError
6
- def validate_file_path_existence(file_path)
7
- return if file_exists?(file_path)
5
+ class FilePathDoesNotExistError
6
+ def initialize(file_path)
7
+ @file_path = file_path
8
+ end
8
9
 
9
- raise NotImplementedError, format(MESSAGE, available_files: available_files,
10
- file_path: file_path, path: ::YeSQL.config.path)
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
+ )
11
22
  end
12
23
 
13
24
  private
14
25
 
26
+ attr_reader :file_path
27
+
15
28
  MESSAGE = <<~MSG
16
29
 
17
30
  SQL file "%<file_path>s" does not exist in %<path>s.
@@ -22,17 +35,9 @@ module YeSQL
22
35
  MSG
23
36
  private_constant :MESSAGE
24
37
 
25
- def file_exists?(file_path)
26
- path_files.any? { |filename| filename.include?("#{file_path}.sql") }
27
- end
28
-
29
- def available_files
30
- path_files.map { |file| "- #{file}\n" }.join
31
- end
32
-
33
- def path_files
34
- @path_files ||= Dir["#{::YeSQL.config.path}/**/*.sql"]
35
- end
38
+ def file_exists? = path_files.any? { _1.include?("#{file_path}.sql") }
39
+ def path_files = @path_files ||= Dir["#{::YeSQL.config.path}/**/*.sql"]
40
+ def available_files = path_files.map { "- #{_1}\n" }.join
36
41
  end
37
42
  end
38
43
  end
@@ -1,18 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module YeSQL
3
+ require "yesql/utils/read"
4
+
5
+ module ::YeSQL
4
6
  module Errors
5
- module NoBindingsProvidedError
6
- def validate_statement_bindings(binds, file_path)
7
- return unless statement_binds(file_path).size.positive?
7
+ class NoBindingsProvidedError
8
+ include ::YeSQL::Utils::Read
8
9
 
9
- format(MESSAGE, renderable_statement_binds(file_path)).tap do |message|
10
- raise ArgumentError, message unless binds.is_a?(Hash) && !binds.empty?
11
- end
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)
12
20
  end
13
21
 
14
22
  private
15
23
 
24
+ attr_reader :binds, :file_path
25
+
16
26
  MESSAGE = <<~MSG
17
27
 
18
28
  YeSQL invoked without bindings.
@@ -23,18 +33,9 @@ module YeSQL
23
33
  MSG
24
34
  private_constant :MESSAGE
25
35
 
26
- def statement_binds(file_path)
27
- ::YeSQL::Utils::Read.statement(file_path)
28
- .scan(::YeSQL::BIND_REGEX).tap do |scanned_binds|
29
- break [] if scanned_binds.size.zero?
30
-
31
- break scanned_binds.sort
32
- end
33
- end
34
-
35
- def renderable_statement_binds(file_path)
36
- statement_binds(file_path).flatten.map { |bind| "- `#{bind}`\n" }.join
37
- end
36
+ def statement_binds = statement.scan(::YeSQL::BIND_REGEX).tap { break (_1 || []).sort }
37
+ def renderable_statement_binds = statement_binds.flatten.map { "- `#{_1}`\n" }.join
38
+ def expected_binds? = binds.is_a?(::Hash) && !binds.empty?
38
39
  end
39
40
  end
40
41
  end
@@ -1,14 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module YeSQL
3
+ module ::YeSQL
4
4
  module Errors
5
- module OutputArgumentError
6
- def validate_output_options(output)
5
+ class OutputArgumentError
6
+ def initialize(output)
7
+ @output = output
8
+ end
9
+
10
+ def validate_output_options
7
11
  return if output.nil?
12
+ return if OPTIONS.include?(output.to_sym)
8
13
 
9
- raise ArgumentError, format(MESSAGE, output) unless OPTIONS.include?(output.to_sym)
14
+ raise ArgumentError, format(MESSAGE, output)
10
15
  end
11
16
 
17
+ private
18
+
19
+ attr_reader :output
20
+
12
21
  MESSAGE = <<~MSG
13
22
  Unsupported `output` option given `%s`. Possible values are:
14
23
  - `columns`: returns an array with the columns from the result.
@@ -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"
16
+ def rows? = output == "rows"
17
+ def hash? = output == "hash"
18
+
19
+ def_delegator(:output, :to_sym)
20
+
21
+ private
22
+
23
+ attr_reader :output
24
+ end
25
+ end
26
+ end
@@ -1,86 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yesql'
4
- require 'forwardable'
5
- require 'yesql/bindings/utils'
6
-
7
- module YeSQL
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
+
11
+ module ::YeSQL
8
12
  module Query
9
13
  class Performer
10
- extend Forwardable
11
-
12
14
  include ::YeSQL::Bindings::Utils
13
15
 
14
- # rubocop:disable Metrics/ParameterLists
15
- def initialize(bind_statement:,
16
- bindings: {},
17
- cache: {},
18
- file_path:,
19
- output: :rows,
20
- prepare: false)
16
+ def initialize(bind_statement:, file_path:, bindings: {}, output: :rows, prepare: false)
21
17
  @bind_statement = bind_statement
22
- @cache = cache
23
- @cache_key = cache[:key] || file_path
24
- @connection = ActiveRecord::Base.connection
25
- @expires_in = cache[:expires_in]
26
18
  @file_path = file_path
27
19
  @named_bindings = bindings.transform_keys(&:to_sym)
28
- @output = output
20
+ @output = ::YeSQL::Params::Output.new(output)
29
21
  @prepare = prepare
30
22
  end
31
- # rubocop:enable Metrics/ParameterLists
32
-
33
- def call
34
- return modified_output if cache.empty?
35
23
 
36
- Rails.cache.fetch(cache_key, expires_in: expires_in) { modified_output }
37
- end
24
+ def call = ::YeSQL::Query::TransformResult.new(output: output, result: query_result).call
38
25
 
39
26
  private
40
27
 
41
- attr_reader :bind_statement,
42
- :cache,
43
- :cache_key,
44
- :connection,
45
- :expires_in,
46
- :file_path,
47
- :named_bindings,
48
- :output,
49
- :prepare,
50
- :rows
51
-
52
- def_delegator(:query_result, :columns)
53
- private :columns
54
- def_delegator(:query_result, :rows)
55
- private :rows
56
-
57
- def modified_output
58
- @modified_output ||=
59
- begin
60
- return query_result.public_send(output) if %w[columns rows].include?(output.to_s)
61
-
62
- columns.map(&:to_sym).tap { |cols| break rows.map { |row| cols.zip(row).to_h } }
63
- end
64
- end
28
+ attr_reader :bind_statement, :file_path, :named_bindings, :output, :prepare
65
29
 
66
30
  def query_result
67
- @query_result ||= connection.exec_query(bind_statement, file_path, binds, prepare: prepare)
31
+ @query_result ||= ::YeSQL::Query::Result.new(binds: binds,
32
+ bind_statement: bind_statement,
33
+ file_path: file_path,
34
+ prepare: prepare).call
68
35
  end
69
36
 
70
- def extractor
71
- ::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
72
- end
73
-
74
- # TODO: move this somewhere else.
75
37
  def binds
76
- statement_binds(extractor)
77
- .sort_by { |_, position| position.tr('$', '').to_i }
78
- .uniq
79
- .map(&:first)
80
- .flatten
81
- .each_slice(2)
82
- .to_a
38
+ ::YeSQL::Bindings::Transformed.new(statement_binds: statement_binds(extractor)).call
83
39
  end
40
+
41
+ def extractor = ::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
84
42
  end
85
43
  end
86
44
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yesql"
4
+ require "forwardable"
5
+ require "yesql/common/adapter"
6
+
7
+ module ::YeSQL
8
+ module Query
9
+ class Result
10
+ extend ::Forwardable
11
+
12
+ include ::YeSQL::Common::Adapter
13
+
14
+ def initialize(bind_statement:, file_path:, prepare:, binds: [])
15
+ @binds = binds
16
+ @bind_statement = bind_statement
17
+ @file_path = file_path
18
+ @prepare_option = prepare
19
+ end
20
+
21
+ def call
22
+ return view_result if view?
23
+ return rails5_result if ::ActiveRecord::VERSION::MAJOR == 5 && mysql?
24
+
25
+ exec_query(bound, file_path, binds, prepare: prepare_option)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :binds, :bind_statement, :file_path, :prepare_option
31
+
32
+ def_delegators(:bind_statement, :bound, :to_s, :view?)
33
+ def_delegators(:connection, :exec_query, :raw_connection)
34
+ def_delegators(:raw_connection, :prepare)
35
+
36
+ def view_result = exec_query(bound)
37
+ def rails5_result = prepare(bound).execute(*binds)
38
+ def connection = @connection ||= ActiveRecord::Base.connection
39
+ end
40
+ end
41
+ end