ensql 0.6.0 → 0.6.5

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.
data/ensql.gemspec CHANGED
@@ -3,15 +3,15 @@
3
3
  require_relative "lib/ensql/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "ensql"
7
- spec.version = Ensql::VERSION
8
- spec.authors = ["Daniel Fone"]
9
- spec.email = ["daniel@fone.net.nz"]
10
-
11
- spec.summary = "Write SQL the safe and simple way"
12
- spec.description = "Ditch your ORM and embrace the power and simplicity of writing plain SQL again."
13
- spec.homepage = "https://github.com/danielfone/ensql"
14
- spec.license = "MIT"
6
+ spec.name = "ensql"
7
+ spec.version = Ensql::VERSION
8
+ spec.authors = ["Daniel Fone"]
9
+ spec.email = ["daniel@fone.net.nz"]
10
+
11
+ spec.summary = "Write SQL the safe and simple way"
12
+ spec.description = "Escape your ORM and embrace the power and simplicity of writing plain SQL again."
13
+ spec.homepage = "https://github.com/danielfone/ensql"
14
+ spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
16
 
17
17
  # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
@@ -25,8 +25,14 @@ Gem::Specification.new do |spec|
25
25
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
26
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
27
  end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
+ spec.add_dependency "connection_pool", ">= 0.9.3", "<3"
33
+
34
+ spec.add_development_dependency "rake", "~> 13.0"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ spec.add_development_dependency "simplecov", "~> 0.21.2"
37
+ spec.add_development_dependency "yard", "~> 0.9.26"
32
38
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This specifies the oldest maintained versions of our dependencies
5
+ #
6
+
7
+ source "https://rubygems.org"
8
+
9
+ ruby "~> 2.5.0"
10
+
11
+ # Specify your gem's dependencies in ensql.gemspec
12
+ gemspec path: "../"
13
+
14
+ # Optional runtime dependencies
15
+ group :adapters do
16
+ require_relative "../lib/ensql/version"
17
+ gem "activerecord", "~> 5.2.0"
18
+ gem "sequel", "~> 5.9"
19
+ gem "sqlite3"
20
+ gem "pg"
21
+ gem "sequel_pg"
22
+ end
@@ -0,0 +1,81 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ ensql (0.6.5)
5
+ connection_pool (>= 0.9.3, < 3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.2.4.5)
11
+ activesupport (= 5.2.4.5)
12
+ activerecord (5.2.4.5)
13
+ activemodel (= 5.2.4.5)
14
+ activesupport (= 5.2.4.5)
15
+ arel (>= 9.0)
16
+ activesupport (5.2.4.5)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 0.7, < 2)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ arel (9.0.0)
22
+ concurrent-ruby (1.1.8)
23
+ connection_pool (2.2.3)
24
+ diff-lcs (1.4.4)
25
+ docile (1.3.5)
26
+ i18n (1.8.9)
27
+ concurrent-ruby (~> 1.0)
28
+ minitest (5.14.4)
29
+ pg (1.2.3)
30
+ rake (13.0.3)
31
+ rspec (3.10.0)
32
+ rspec-core (~> 3.10.0)
33
+ rspec-expectations (~> 3.10.0)
34
+ rspec-mocks (~> 3.10.0)
35
+ rspec-core (3.10.1)
36
+ rspec-support (~> 3.10.0)
37
+ rspec-expectations (3.10.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-mocks (3.10.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.10.0)
43
+ rspec-support (3.10.2)
44
+ sequel (5.42.0)
45
+ sequel_pg (1.14.0)
46
+ pg (>= 0.18.0, != 1.2.0)
47
+ sequel (>= 4.38.0)
48
+ simplecov (0.21.2)
49
+ docile (~> 1.1)
50
+ simplecov-html (~> 0.11)
51
+ simplecov_json_formatter (~> 0.1)
52
+ simplecov-html (0.12.3)
53
+ simplecov_json_formatter (0.1.2)
54
+ sqlite3 (1.4.2)
55
+ thread_safe (0.3.6)
56
+ tzinfo (1.2.9)
57
+ thread_safe (~> 0.1)
58
+ yard (0.9.26)
59
+
60
+ PLATFORMS
61
+ ruby
62
+ x86_64-darwin-18
63
+ x86_64-linux
64
+
65
+ DEPENDENCIES
66
+ activerecord (~> 5.2.0)
67
+ ensql!
68
+ pg
69
+ rake (~> 13.0)
70
+ rspec (~> 3.0)
71
+ sequel (~> 5.9)
72
+ sequel_pg
73
+ simplecov (~> 0.21.2)
74
+ sqlite3
75
+ yard (~> 0.9.26)
76
+
77
+ RUBY VERSION
78
+ ruby 2.5.8p224
79
+
80
+ BUNDLED WITH
81
+ 2.2.9
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This specifies the oldest compatible versions of our dependencies
5
+ #
6
+
7
+ source "https://rubygems.org"
8
+
9
+ ruby "2.4.0"
10
+
11
+ # Specify your gem's dependencies in ensql.gemspec
12
+ gemspec path: "../"
13
+
14
+ # Downgrade simplecov for ruby 2.4 compat
15
+ gem "simplecov", "~> 0.18.5"
16
+ gem "connection_pool", "0.9.3"
17
+
18
+ # Optional runtime dependencies
19
+ group :adapters do
20
+ require_relative "../lib/ensql/version"
21
+ gem "activerecord", Ensql::SUPPORTED_ACTIVERECORD_VERSIONS.to_s.scan(/\d+.\d+/).first
22
+ gem "sequel", Ensql::SUPPORTED_SEQUEL_VERSIONS.to_s.scan(/\d+.\d+/).first
23
+ gem "sqlite3", "~> 1.3.6" # AR version constraint
24
+ gem "pg", Ensql::SUPPORTED_PG_VERSIONS.to_s.scan(/\d+.\d+/).first
25
+ gem "sequel_pg"
26
+ end
@@ -0,0 +1,76 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ ensql (0.6.5)
5
+ connection_pool (>= 0.9.3, < 3)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activemodel (5.0.0)
11
+ activesupport (= 5.0.0)
12
+ activerecord (5.0.0)
13
+ activemodel (= 5.0.0)
14
+ activesupport (= 5.0.0)
15
+ arel (~> 7.0)
16
+ activesupport (5.0.0)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (~> 0.7)
19
+ minitest (~> 5.1)
20
+ tzinfo (~> 1.1)
21
+ arel (7.1.4)
22
+ concurrent-ruby (1.1.8)
23
+ connection_pool (0.9.3)
24
+ diff-lcs (1.4.4)
25
+ docile (1.3.5)
26
+ i18n (0.9.5)
27
+ concurrent-ruby (~> 1.0)
28
+ minitest (5.14.4)
29
+ pg (0.19.0)
30
+ rake (13.0.3)
31
+ rspec (3.10.0)
32
+ rspec-core (~> 3.10.0)
33
+ rspec-expectations (~> 3.10.0)
34
+ rspec-mocks (~> 3.10.0)
35
+ rspec-core (3.10.1)
36
+ rspec-support (~> 3.10.0)
37
+ rspec-expectations (3.10.1)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.10.0)
40
+ rspec-mocks (3.10.2)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.10.0)
43
+ rspec-support (3.10.2)
44
+ sequel (5.9.0)
45
+ sequel_pg (1.14.0)
46
+ pg (>= 0.18.0, != 1.2.0)
47
+ sequel (>= 4.38.0)
48
+ simplecov (0.18.5)
49
+ docile (~> 1.1)
50
+ simplecov-html (~> 0.11)
51
+ simplecov-html (0.12.3)
52
+ sqlite3 (1.3.13)
53
+ thread_safe (0.3.6)
54
+ tzinfo (1.2.9)
55
+ thread_safe (~> 0.1)
56
+ yard (0.9.26)
57
+
58
+ PLATFORMS
59
+ x86_64-darwin-18
60
+ x86_64-linux
61
+
62
+ DEPENDENCIES
63
+ activerecord (= 5.0)
64
+ connection_pool (= 0.9.3)
65
+ ensql!
66
+ pg (= 0.19)
67
+ rake (~> 13.0)
68
+ rspec (~> 3.0)
69
+ sequel (= 5.9)
70
+ sequel_pg
71
+ simplecov (~> 0.18.5)
72
+ sqlite3 (~> 1.3.6)
73
+ yard (~> 0.9.26)
74
+
75
+ BUNDLED WITH
76
+ 2.2.9
data/lib/ensql.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "ensql/version"
4
+ require_relative "ensql/adapter"
4
5
  require_relative "ensql/sql"
6
+ require_relative "ensql/transaction"
7
+ require_relative "ensql/load_sql"
5
8
 
6
9
  #
7
10
  # Primary interface for loading, interpolating and executing SQL statements
@@ -24,46 +27,13 @@ require_relative "ensql/sql"
24
27
  # Ensql.sql('select * from users where id = %{id}', id: 1).first_row # => { "id" => 1, "email" => "test@example.com" }
25
28
  #
26
29
  module Ensql
27
- # Wrapper for errors raised by Ensql
28
- class Error < StandardError; end
29
-
30
30
  class << self
31
-
32
31
  # (see SQL)
33
32
  # @return [Ensql::SQL] SQL statement
34
- def sql(sql, params={})
33
+ def sql(sql, params = {})
35
34
  SQL.new(sql, params)
36
35
  end
37
36
 
38
- # Path to search for *.sql queries in, defaults to "sql/". For example, if
39
- # {sql_path} is set to 'app/queries', `load_sql('users/active')` will read
40
- # 'app/queries/users/active.sql'.
41
- # @see .load_sql
42
- #
43
- # @example
44
- # Ensql.sql_path = Rails.root.join('app/queries')
45
- #
46
- def sql_path
47
- @sql_path ||= 'sql'
48
- end
49
- attr_writer :sql_path
50
-
51
- # Load SQL from a file within {sql_path}. This is the recommended way to
52
- # manage SQL in a non-trivial project. For details of how to write
53
- # interpolation placeholders, see {SQL}.
54
- #
55
- # @see .sql_path=
56
- # @return [Ensql::SQL]
57
- #
58
- # @example
59
- # Ensql.load_sql('users/activity', report_params)
60
- # Ensql.load_sql(:upsert_users, imported_users_attrs)
61
- #
62
- def load_sql(name, params={})
63
- path = File.join(sql_path, "#{name}.sql")
64
- SQL.new(File.read(path), params, name)
65
- end
66
-
67
37
  # Convenience method to interpolate and run the supplied SQL on the current
68
38
  # adapter.
69
39
  # @return [void]
@@ -72,38 +42,8 @@ module Ensql
72
42
  # Ensql.run("DELETE FROM users WHERE id = %{id}", id: user.id)
73
43
  # Ensql.run("ALTER TABLE test RENAME TO old_test")
74
44
  #
75
- def run(sql, params={})
45
+ def run(sql, params = {})
76
46
  SQL.new(sql, params).run
77
47
  end
78
-
79
- # Connection adapter to use. Must implement the interface defined in
80
- # {Ensql::Adapter}. If not specified, it will try to autoload an adapter
81
- # based on the availability of Sequel or ActiveRecord, in that order.
82
- #
83
- # @example
84
- # require 'sequel'
85
- # Ensql.adapter # => Ensql::SequelAdapter
86
- # Ensql.adapter = Ensql::ActiveRecordAdapter # override adapter
87
- # Ensql.adapter = CustomMSSQLAdapater # supply your own adapter
88
- #
89
- def adapter
90
- @adapter ||= autoload_adapter
91
- end
92
- attr_writer :adapter
93
-
94
- private
95
-
96
- def autoload_adapter
97
- if defined? Sequel
98
- require_relative 'ensql/sequel_adapter'
99
- SequelAdapter
100
- elsif defined? ActiveRecord
101
- require_relative 'ensql/active_record_adapter'
102
- ActiveRecordAdapter
103
- else
104
- raise Error, "Couldn't autodetect an adapter, please specify manually."
105
- end
106
- end
107
-
108
48
  end
109
49
  end
@@ -1,60 +1,104 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'version'
4
- require_relative 'adapter'
3
+ require_relative "version"
4
+ require_relative "adapter"
5
+ require_relative "pool_wrapper"
5
6
 
6
7
  # Ensure our optional dependency has a compatible version
7
- gem 'activerecord', Ensql::ACTIVERECORD_VERSION
8
- require 'active_record'
8
+ gem "activerecord", Ensql::SUPPORTED_ACTIVERECORD_VERSIONS
9
+ require "active_record"
9
10
 
10
11
  module Ensql
11
12
  #
12
- # Implements the {Adapter} interface for ActiveRecord. Requires an
13
- # ActiveRecord connection to be configured and established. Uses
14
- # ActiveRecord::Base for the connection.
13
+ # Wraps an ActiveRecord connection pool to implement the {Adapter} interface
14
+ # for ActiveRecord. Requires an ActiveRecord connection to be configured and
15
+ # established. By default, uses the connection pool on ActiveRecord::Base.
16
+ # Other pools can be passed to the constructor.
15
17
  #
16
18
  # @example
17
19
  # require 'active_record'
18
20
  # ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'mydb')
19
- # Ensql.adapter = Ensql::ActiveRecordAdapter
21
+ # Ensql.adapter = Ensql::ActiveRecordAdapter.new
22
+ # # Use database configuration for the Widget model instead
23
+ # Ensql.adapter = Ensql::ActiveRecordAdapter.new(Widget)
20
24
  #
21
- # @see Adapter
22
- # @see ACTIVERECORD_VERSION Required gem version
25
+ # @see SUPPORTED_ACTIVERECORD_VERSIONS
23
26
  #
24
- module ActiveRecordAdapter
25
- extend Adapter
26
-
27
- # @!visibility private
28
- def self.fetch_rows(sql)
29
- result = connection.exec_query(sql)
30
- result.map do |row|
31
- # Deserialize column types if needed
32
- row.each_with_object({}) do |(column, value), hash|
33
- hash[column] = result.column_types[column] ? result.column_types[column].deserialize(value) : value
34
- end
27
+ class ActiveRecordAdapter
28
+ include Adapter
29
+
30
+ # Wrap the raw connections from an Active Record connection pool. This
31
+ # allows us to safely checkout the underlying database connection for use in
32
+ # a database specific adapter.
33
+ #
34
+ # Ensql.adapter = MySqliteAdapter.new(ActiveRecordAdapter.pool)
35
+ #
36
+ # @param base [Class] an ActiveRecord class to source connections from
37
+ # @return [PoolWrapper] a pool adapter for raw connections
38
+ def self.pool(base = ActiveRecord::Base)
39
+ PoolWrapper.new do |client_block|
40
+ base.connection_pool.with_connection { |connection| client_block.call connection.raw_connection }
35
41
  end
36
42
  end
37
43
 
38
- # @!visibility private
39
- def self.run(sql)
40
- connection.execute(sql)
44
+ # Support deprecated class method interface
45
+ class << self
46
+ require "forwardable"
47
+ extend Forwardable
48
+
49
+ delegate [:literalize, :run, :fetch_count, :fetch_each_row, :fetch_rows, :fetch_first_column, :fetch_first_field, :fetch_first_row] => :new
41
50
  end
42
51
 
43
- # @!visibility private
44
- def self.fetch_count(sql)
45
- connection.exec_update(sql)
52
+ # @param base [Class] an ActiveRecord class to source connections from
53
+ def initialize(base = ActiveRecord::Base)
54
+ @base = base
46
55
  end
47
56
 
48
- # @!visibility private
49
- def self.literalize(value)
50
- connection.quote(value)
57
+ # @visibility private
58
+ def fetch_rows(sql)
59
+ fetch_each_row(sql).to_a
51
60
  end
52
61
 
53
- def self.connection
54
- ActiveRecord::Base.connection
62
+ # @visibility private
63
+ def fetch_each_row(sql, &block)
64
+ return to_enum(:fetch_each_row, sql) unless block
65
+
66
+ result = with_connection { |c| c.exec_query(sql) }
67
+ # AR populates `column_types` with the types of any columns that haven't
68
+ # already been type casted by pg decoders. If present, we need to
69
+ # deserialize them now.
70
+ if result.column_types.any?
71
+ result.each { |row| yield deserialize_types(row, result.column_types) }
72
+ else
73
+ result.each(&block)
74
+ end
55
75
  end
56
76
 
57
- private_class_method :connection
77
+ # @visibility private
78
+ def run(sql)
79
+ with_connection { |c| c.execute(sql) }
80
+ nil
81
+ end
82
+
83
+ # @visibility private
84
+ def fetch_count(sql)
85
+ with_connection { |c| c.exec_update(sql) }
86
+ end
58
87
 
88
+ # @visibility private
89
+ def literalize(value)
90
+ with_connection { |c| c.quote(value) }
91
+ end
92
+
93
+ private
94
+
95
+ def with_connection(&block)
96
+ @base.connection_pool.with_connection(&block)
97
+ end
98
+
99
+ def deserialize_types(row, column_types)
100
+ column_types.each { |column, type| row[column] = type.deserialize(row[column]) }
101
+ row
102
+ end
59
103
  end
60
104
  end