yesql 0.1.5 → 0.2.1

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 +26 -31
  29. data/lib/yesql/bindings/extract.rb +15 -20
  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 +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 +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 -64
  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 +67 -18
  46. data/.gitignore +0 -1
  47. data/Gemfile +0 -9
  48. data/Gemfile.lock +0 -167
  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 -28
@@ -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
@@ -1,25 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
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
- # `adapter` might be a complex object, but
11
- # for the sake of brevity it's just a string
12
- def adapter
13
- ::ActiveRecord::Base.connection.adapter_name
14
- end
10
+ def mysql? = adapter == "Mysql2"
11
+ def pg? = adapter == "PostgreSQL"
15
12
 
16
- def mysql?
17
- adapter == 'Mysql2'
18
- end
13
+ private
19
14
 
20
- def pg?
21
- adapter == 'PostgreSQL'
22
- end
15
+ def adapter = ::ActiveRecord::Base.connection.adapter_name
23
16
  end
24
17
  end
25
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,88 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yesql'
4
- require 'forwardable'
5
- require 'yesql/bindings/utils'
6
- require 'yesql/common/adapter'
7
-
8
- 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
9
12
  module Query
10
13
  class Performer
11
- extend Forwardable
12
-
13
14
  include ::YeSQL::Bindings::Utils
14
- include ::YeSQL::Common::Adapter
15
15
 
16
- # rubocop:disable Metrics/ParameterLists
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
- @connection = ActiveRecord::Base.connection
27
- @expires_in = cache[:expires_in]
28
18
  @file_path = file_path
29
19
  @named_bindings = bindings.transform_keys(&:to_sym)
30
- @output = output
20
+ @output = ::YeSQL::Params::Output.new(output)
31
21
  @prepare = prepare
32
22
  end
33
- # rubocop:enable Metrics/ParameterLists
34
-
35
- def call
36
- return modified_output if cache.empty?
37
23
 
38
- Rails.cache.fetch(cache_key, expires_in: expires_in) { modified_output }
39
- end
24
+ def call = ::YeSQL::Query::TransformResult.new(output: output, result: query_result).call
40
25
 
41
26
  private
42
27
 
43
- attr_reader :bind_statement,
44
- :cache,
45
- :cache_key,
46
- :connection,
47
- :expires_in,
48
- :file_path,
49
- :named_bindings,
50
- :output,
51
- :prepare,
52
- :rows
53
-
54
- def_delegator(:query_result, :columns)
55
- private :columns
56
- def_delegator(:query_result, :rows)
57
- private :rows
58
-
59
- def modified_output
60
- @modified_output ||=
61
- begin
62
- return query_result.public_send(output) if %w[columns rows].include?(output.to_s)
63
-
64
- columns.map(&:to_sym).tap { |cols| break rows.map { |row| cols.zip(row).to_h } }
65
- end
66
- end
28
+ attr_reader :bind_statement, :file_path, :named_bindings, :output, :prepare
67
29
 
68
30
  def query_result
69
- @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
70
35
  end
71
36
 
72
- def extractor
73
- ::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
74
- end
75
-
76
- # TODO: move this somewhere else.
77
37
  def binds
78
- statement_binds(extractor)
79
- .sort_by { |_, position| position.to_s.tr('$', '').to_i }
80
- .tap { |x| break(mysql? ? x : x.uniq) }
81
- .map(&:first)
82
- .flatten
83
- .each_slice(2)
84
- .to_a
38
+ ::YeSQL::Bindings::Transformed.new(statement_binds: statement_binds(extractor)).call
85
39
  end
40
+
41
+ def extractor = ::YeSQL::Bindings::Extractor.new(bindings: named_bindings).call
86
42
  end
87
43
  end
88
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
@@ -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&.symbolize_keys || _1 }
40
+ end
41
+
42
+ def columns = fields || result.columns
43
+ def rows_values = to_a.map { _1.respond_to?(:values) ? _1.values : _1 }
44
+ def hashed_rows(rows) = rows.map { columns.zip(_1).to_h }
45
+ def rails_5? = ::ActiveRecord::VERSION::MAJOR == 5
46
+ end
47
+ end
48
+ end