yesql 0.1.5 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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