yesql 0.1.4 → 0.2.0

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 +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