ensql 0.6.0 → 0.6.5

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