ensql 0.6.2 → 0.6.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +55 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +69 -41
- data/ensql.gemspec +11 -12
- data/gemfiles/maintained.gemfile +4 -4
- data/gemfiles/maintained.gemfile.lock +1 -1
- data/gemfiles/minimum.gemfile +8 -8
- data/gemfiles/minimum.gemfile.lock +1 -1
- data/lib/ensql.rb +5 -75
- data/lib/ensql/active_record_adapter.rb +8 -8
- data/lib/ensql/adapter.rb +42 -4
- data/lib/ensql/error.rb +6 -0
- data/lib/ensql/load_sql.rb +36 -0
- data/lib/ensql/pool_wrapper.rb +0 -1
- data/lib/ensql/postgres_adapter.rb +13 -12
- data/lib/ensql/sequel_adapter.rb +9 -9
- data/lib/ensql/sql.rb +10 -11
- data/lib/ensql/transaction.rb +57 -0
- data/lib/ensql/version.rb +4 -4
- data/perf/adapter_benchmark.rb +24 -21
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d67a62c833d6468ecbb753a54f592f2b9ecb295fb2db7eb943c25184c481e4c0
|
4
|
+
data.tar.gz: 32e9c5c68c6469bd1613e4b0881b22a7545616f63dffb08f4a6ba04d2ad28fb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 563a4e08014aedec4c7a2321f78a22534e8250d6f1b4e5189ce1482b3348c5b8a25f411fcc059ece8984eece9cb53dcdc30d56691dde0856ef74aa14c61c18cc
|
7
|
+
data.tar.gz: 3e99489831703bb5aa978923fa2430bcab133ce8e2c4bcfbc84472a699aa09c70935f7d76945eee0cc5d90575c8ff2fe37a3268754e00f723143bdca2179c796
|
@@ -0,0 +1,55 @@
|
|
1
|
+
---
|
2
|
+
###########################
|
3
|
+
###########################
|
4
|
+
## Linter GitHub Actions ##
|
5
|
+
###########################
|
6
|
+
###########################
|
7
|
+
name: Lint Code Base
|
8
|
+
|
9
|
+
#
|
10
|
+
# Documentation:
|
11
|
+
# https://help.github.com/en/articles/workflow-syntax-for-github-actions
|
12
|
+
#
|
13
|
+
|
14
|
+
#############################
|
15
|
+
# Start the job on all push #
|
16
|
+
#############################
|
17
|
+
on:
|
18
|
+
push:
|
19
|
+
branches-ignore: [master]
|
20
|
+
# Remove the line above to run when pushing to master
|
21
|
+
pull_request:
|
22
|
+
branches: [master]
|
23
|
+
|
24
|
+
###############
|
25
|
+
# Set the Job #
|
26
|
+
###############
|
27
|
+
jobs:
|
28
|
+
build:
|
29
|
+
# Name the Job
|
30
|
+
name: Lint Code Base
|
31
|
+
# Set the agent to run on
|
32
|
+
runs-on: ubuntu-latest
|
33
|
+
|
34
|
+
##################
|
35
|
+
# Load all steps #
|
36
|
+
##################
|
37
|
+
steps:
|
38
|
+
##########################
|
39
|
+
# Checkout the code base #
|
40
|
+
##########################
|
41
|
+
- name: Checkout Code
|
42
|
+
uses: actions/checkout@v2
|
43
|
+
with:
|
44
|
+
# Full git history is needed to get a proper list of changed files within `super-linter`
|
45
|
+
fetch-depth: 0
|
46
|
+
|
47
|
+
################################
|
48
|
+
# Run Linter against code base #
|
49
|
+
################################
|
50
|
+
- name: Lint Code Base
|
51
|
+
uses: github/super-linter@v3
|
52
|
+
env:
|
53
|
+
VALIDATE_ALL_CODEBASE: false
|
54
|
+
DEFAULT_BRANCH: master
|
55
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [0.6.3] - 2021-03-11
|
4
|
+
|
5
|
+
- Supports transaction flow control using any flavour SQL with `Ensql.transaction` and `Ensql.rollback!`.
|
6
|
+
- Eliminates cyclic dependencies for `Error` and `Ensql.adapter`.
|
7
|
+
- Tidies specs.
|
8
|
+
- Adopts [standardrb](https://github.com/testdouble/standard).
|
9
|
+
|
3
10
|
## [0.6.2] - 2021-03-09
|
4
11
|
|
5
12
|
- Adds a specialised adapter for PostgreSQL.
|
data/Gemfile
CHANGED
@@ -6,10 +6,10 @@ source "https://rubygems.org"
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
group :adapters do
|
9
|
-
require_relative
|
9
|
+
require_relative "lib/ensql/version"
|
10
10
|
gem "activerecord", Ensql::SUPPORTED_ACTIVERECORD_VERSIONS
|
11
|
-
gem "sequel",
|
12
|
-
gem "sqlite3",
|
13
|
-
gem "pg",
|
11
|
+
gem "sequel", Ensql::SUPPORTED_SEQUEL_VERSIONS
|
12
|
+
gem "sqlite3", "~> 1.4"
|
13
|
+
gem "pg", "~> 1.2"
|
14
14
|
gem "sequel_pg"
|
15
15
|
end
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -3,30 +3,31 @@
|
|
3
3
|
[](https://badge.fury.io/rb/ensql)
|
4
4
|
[](https://github.com/danielfone/ensql/actions/workflows/specs.yml)
|
5
5
|
[](https://codeclimate.com/github/danielfone/ensql/maintainability)
|
6
|
+
[](https://github.com/testdouble/standard)
|
6
7
|
|
7
|
-
Ensql
|
8
|
-
|
8
|
+
Ensql provides a light-weight wrapper over your existing database connections, letting you write plain SQL for your
|
9
|
+
application safely and simply. Ditch your ORM and embrace the power and ease of writing SQL again.
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
* **Write exactly the SQL you want.** Don't limit your queries to what's in the Rails docs. Composable scopes and
|
12
|
+
dynamic includes can cripple performance for non-trivial queries. Break through the ORM abstraction and unlock the
|
13
|
+
power of your database with well-structured SQL and modern database features.
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
* **Keep your SQL in its own files.** Just like models or view templates, it makes sense to organise your SQL on its
|
16
|
+
own terms. Storing the queries in their own files encourages better formatted, well commented, literate SQL. It also
|
17
|
+
leverages the syntax highlighting and autocompletion available in your editor. Snippets of HTML scattered through .rb
|
18
|
+
files is an awkward code smell, and SQL is no different.
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
* **Do more with your database.** Having a place to organise clean and readable SQL encourages you to make the most of
|
21
|
+
it. In every project I've worked on I've been able to replace substantial amounts of imperative Ruby logic with a
|
22
|
+
declarative SQL query, improving performance and reducing the opportunity for type errors and untested branches.
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
* **Safely interpolate user-supplied data.** Every web developer knows the risks of SQL injection. Ensql takes a
|
25
|
+
fail-safe approach to interpolation, leveraging the underlying database adapter to turn ruby objects into properly
|
26
|
+
quoted SQL literals. As long as user-supplied input is passed as parameters, your queries will be safe and
|
27
|
+
well-formed.
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
* **Use your existing database connection.** As well as using PostrgeSQL connections directly, Ensql can work with
|
30
|
+
ActiveRecord or Sequel so you don't need to manage a separate connection to the database.
|
30
31
|
|
31
32
|
```ruby
|
32
33
|
# Run adhoc statements
|
@@ -50,22 +51,24 @@ current_results = Ensql.load_sql('results/page', results: all_results, page: 2)
|
|
50
51
|
total = Ensql.load_sql('count', subquery: all_results)
|
51
52
|
result = { data: current_results.rows, total: total.first_field }
|
52
53
|
```
|
53
|
-
|
54
|
+
Links:
|
54
55
|
|
55
56
|
* [Source Code](https://github.com/danielfone/ensql)
|
56
57
|
* [API Documentation](https://rubydoc.info/gems/ensql/Ensql/SQL)
|
57
|
-
* [
|
58
|
+
* [Ruby Gem](https://rubygems.org/gems/ensql)
|
58
59
|
|
59
60
|
## Installation
|
60
61
|
|
61
62
|
Add this gem to your Gemfile by running:
|
62
63
|
|
63
|
-
|
64
|
-
|
64
|
+
```shell
|
65
|
+
bundle add ensql
|
66
|
+
```
|
65
67
|
Or install it manually with:
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
+
```shell
|
70
|
+
gem install ensql
|
71
|
+
```
|
69
72
|
Ensql requires:
|
70
73
|
|
71
74
|
* ruby >= 2.4.0
|
@@ -95,26 +98,27 @@ Ensql.adapter = Ensql::PostgresAdapter.pool { PG.connect ENV['DATABASE_URL'] }
|
|
95
98
|
```
|
96
99
|
You can also supply your own adapter (see [the API docs](https://rubydoc.info/gems/ensql/Ensql/Adapter) for details of the interface).
|
97
100
|
|
98
|
-
|
99
101
|
SQL can be supplied directly or read from a file. You're encouraged to organise all but the most trivial statements in
|
100
102
|
their own *.sql files, for the reasons outlined above. You can organise them in whatever way makes most sense for your
|
101
103
|
project, but I've found sorting them into directories based on their purpose works well. For example:
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
105
|
+
```text
|
106
|
+
app/sql
|
107
|
+
├── analytics
|
108
|
+
│ └── results.sql
|
109
|
+
├── program_details
|
110
|
+
│ ├── widget_query.sql
|
111
|
+
│ ├── item_query.sql
|
112
|
+
│ ├── organisation_query.sql
|
113
|
+
│ └── test_query.sql
|
114
|
+
├── reports
|
115
|
+
│ ├── csv_export.sql
|
116
|
+
│ ├── filtered.sql
|
117
|
+
│ └── index.sql
|
118
|
+
├── redaction.sql
|
119
|
+
├── count.sql
|
120
|
+
└── set_timeout.sql
|
121
|
+
```
|
118
122
|
|
119
123
|
### Interpolation
|
120
124
|
|
@@ -184,6 +188,22 @@ Ensql.sql('TRUNCATE logs').run # => nil
|
|
184
188
|
Ensql.run('TRUNCATE logs') # same thing
|
185
189
|
```
|
186
190
|
|
191
|
+
### Transactions
|
192
|
+
|
193
|
+
Ensql encourages you to write pure unmediated SQL with very little procedural management. However, transaction blocks
|
194
|
+
are the exception to this rule. Any exceptions inside a transaction block will trigger a rollback, otherwise the block
|
195
|
+
will be committed. The block uses SQL-standard commands by default, but custom SQL can be supplied.
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
Ensql.transaction(start: 'BEGIN ISOLATION LEVEL SERIALIZABLE') do
|
199
|
+
do_thing1
|
200
|
+
result = check_thing2
|
201
|
+
Ensql.rollback! unless result
|
202
|
+
do_thing3
|
203
|
+
end
|
204
|
+
```
|
205
|
+
See [the API docs](https://rubydoc.info/gems/ensql/Ensql#transaction-class_method) for details.
|
206
|
+
|
187
207
|
## Things To Improve
|
188
208
|
|
189
209
|
- Interpolation syntax. I'd love to ground this in something more reasonable than ruby's custom sprintf format. Maybe we
|
@@ -204,6 +224,14 @@ experiment.
|
|
204
224
|
|
205
225
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
206
226
|
|
227
|
+
### PR Checklist
|
228
|
+
|
229
|
+
- [ ] Confirm the code works locally
|
230
|
+
- [ ] Update any relevant documentation
|
231
|
+
- [ ] Try to break it
|
232
|
+
- [ ] Tests the described behaviour
|
233
|
+
- [ ] Add a changelog entry (with version bump if needed)
|
234
|
+
|
207
235
|
### Release Checklist
|
208
236
|
|
209
237
|
- [ ] Review changes in master since last release, especially the public API.
|
@@ -215,7 +243,7 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
215
243
|
|
216
244
|
## Contributing
|
217
245
|
|
218
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/danielfone/ensql
|
246
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/danielfone/ensql>.
|
219
247
|
|
220
248
|
## License
|
221
249
|
|
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
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
|
11
|
-
spec.summary
|
12
|
-
spec.description
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
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"
|
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,8 @@ 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
|
29
|
-
spec.executables
|
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
32
|
spec.add_dependency "connection_pool", ">= 0.9.3", "<3"
|
@@ -35,5 +35,4 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_development_dependency "rspec", "~> 3.0"
|
36
36
|
spec.add_development_dependency "simplecov", "~> 0.21.2"
|
37
37
|
spec.add_development_dependency "yard", "~> 0.9.26"
|
38
|
-
|
39
38
|
end
|
data/gemfiles/maintained.gemfile
CHANGED
@@ -6,16 +6,16 @@
|
|
6
6
|
|
7
7
|
source "https://rubygems.org"
|
8
8
|
|
9
|
-
ruby
|
9
|
+
ruby "~> 2.5.0"
|
10
10
|
|
11
11
|
# Specify your gem's dependencies in ensql.gemspec
|
12
|
-
gemspec path:
|
12
|
+
gemspec path: "../"
|
13
13
|
|
14
14
|
# Optional runtime dependencies
|
15
15
|
group :adapters do
|
16
|
-
require_relative
|
16
|
+
require_relative "../lib/ensql/version"
|
17
17
|
gem "activerecord", "~> 5.2.0"
|
18
|
-
gem "sequel",
|
18
|
+
gem "sequel", "~> 5.9"
|
19
19
|
gem "sqlite3"
|
20
20
|
gem "pg"
|
21
21
|
gem "sequel_pg"
|
data/gemfiles/minimum.gemfile
CHANGED
@@ -6,21 +6,21 @@
|
|
6
6
|
|
7
7
|
source "https://rubygems.org"
|
8
8
|
|
9
|
-
ruby
|
9
|
+
ruby "2.4.0"
|
10
10
|
|
11
11
|
# Specify your gem's dependencies in ensql.gemspec
|
12
|
-
gemspec path:
|
12
|
+
gemspec path: "../"
|
13
13
|
|
14
14
|
# Downgrade simplecov for ruby 2.4 compat
|
15
|
-
gem
|
16
|
-
gem
|
15
|
+
gem "simplecov", "~> 0.18.5"
|
16
|
+
gem "connection_pool", "0.9.3"
|
17
17
|
|
18
18
|
# Optional runtime dependencies
|
19
19
|
group :adapters do
|
20
|
-
require_relative
|
20
|
+
require_relative "../lib/ensql/version"
|
21
21
|
gem "activerecord", Ensql::SUPPORTED_ACTIVERECORD_VERSIONS.to_s.scan(/\d+.\d+/).first
|
22
|
-
gem "sequel",
|
23
|
-
gem "sqlite3",
|
24
|
-
gem "pg",
|
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
25
|
gem "sequel_pg"
|
26
26
|
end
|
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,48 +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
|
-
# Get the current connection adapter. If not specified, it will try to
|
80
|
-
# autoload an adapter based on the availability of Sequel or ActiveRecord,
|
81
|
-
# in that order.
|
82
|
-
#
|
83
|
-
# @example
|
84
|
-
# require 'sequel'
|
85
|
-
# Ensql.adapter # => Ensql::SequelAdapter.new
|
86
|
-
# Ensql.adapter = Ensql::ActiveRecordAdapter.new # override adapter
|
87
|
-
# Ensql.adapter = my_tsql_adapter # supply your own adapter
|
88
|
-
#
|
89
|
-
def adapter
|
90
|
-
Thread.current[:ensql_adapter] || Thread.main[:ensql_adapter] ||= autoload_adapter
|
91
|
-
end
|
92
|
-
|
93
|
-
# Set the connection adapter to use. Must implement the interface defined in
|
94
|
-
# {Ensql::Adapter}. This uses a thread-local variable so adapters can be
|
95
|
-
# switched safely in a multi-threaded web server.
|
96
|
-
def adapter=(adapter)
|
97
|
-
if adapter.is_a?(Module) && (adapter.name == 'Ensql::SequelAdapter' || adapter.name == 'Ensql::ActiveRecordAdapter')
|
98
|
-
warn "Using `#{adapter}` as an adapter is deprecated, use `#{adapter}.new`.", uplevel: 1
|
99
|
-
end
|
100
|
-
|
101
|
-
Thread.current[:ensql_adapter] = adapter
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def autoload_adapter
|
107
|
-
if defined? Sequel
|
108
|
-
require_relative 'ensql/sequel_adapter'
|
109
|
-
SequelAdapter.new
|
110
|
-
elsif defined? ActiveRecord
|
111
|
-
require_relative 'ensql/active_record_adapter'
|
112
|
-
ActiveRecordAdapter.new
|
113
|
-
else
|
114
|
-
raise Error, "Couldn't autodetect an adapter, please specify manually."
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
48
|
end
|
119
49
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
3
|
+
require_relative "version"
|
4
|
+
require_relative "adapter"
|
5
|
+
require_relative "pool_wrapper"
|
6
6
|
|
7
7
|
# Ensure our optional dependency has a compatible version
|
8
|
-
gem
|
9
|
-
require
|
8
|
+
gem "activerecord", Ensql::SUPPORTED_ACTIVERECORD_VERSIONS
|
9
|
+
require "active_record"
|
10
10
|
|
11
11
|
module Ensql
|
12
12
|
#
|
@@ -43,7 +43,7 @@ module Ensql
|
|
43
43
|
|
44
44
|
# Support deprecated class method interface
|
45
45
|
class << self
|
46
|
-
require
|
46
|
+
require "forwardable"
|
47
47
|
extend Forwardable
|
48
48
|
|
49
49
|
delegate [:literalize, :run, :fetch_count, :fetch_each_row, :fetch_rows, :fetch_first_column, :fetch_first_field, :fetch_first_row] => :new
|
@@ -61,7 +61,7 @@ module Ensql
|
|
61
61
|
|
62
62
|
# @visibility private
|
63
63
|
def fetch_each_row(sql, &block)
|
64
|
-
return to_enum(:fetch_each_row, sql) unless
|
64
|
+
return to_enum(:fetch_each_row, sql) unless block
|
65
65
|
|
66
66
|
result = with_connection { |c| c.exec_query(sql) }
|
67
67
|
# AR populates `column_types` with the types of any columns that haven't
|
@@ -90,7 +90,7 @@ module Ensql
|
|
90
90
|
with_connection { |c| c.quote(value) }
|
91
91
|
end
|
92
92
|
|
93
|
-
|
93
|
+
private
|
94
94
|
|
95
95
|
def with_connection(&block)
|
96
96
|
@base.connection_pool.with_connection(&block)
|
data/lib/ensql/adapter.rb
CHANGED
@@ -1,8 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "error"
|
4
4
|
|
5
5
|
module Ensql
|
6
|
+
class << self
|
7
|
+
# Get the current connection adapter. If not specified, it will try to
|
8
|
+
# autoload an adapter based on the availability of Sequel or ActiveRecord,
|
9
|
+
# in that order.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# require 'sequel'
|
13
|
+
# Ensql.adapter # => Ensql::SequelAdapter.new
|
14
|
+
# Ensql.adapter = Ensql::ActiveRecordAdapter.new # override adapter
|
15
|
+
# Ensql.adapter = my_tsql_adapter # supply your own adapter
|
16
|
+
#
|
17
|
+
def adapter
|
18
|
+
Thread.current[:ensql_adapter] || Thread.main[:ensql_adapter] ||= autoload_adapter
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set the connection adapter to use. Must implement the interface defined in
|
22
|
+
# {Ensql::Adapter}. This uses a thread-local variable so adapters can be
|
23
|
+
# switched safely in a multi-threaded web server.
|
24
|
+
def adapter=(adapter)
|
25
|
+
if adapter.is_a?(Module) && (adapter.name == "Ensql::SequelAdapter" || adapter.name == "Ensql::ActiveRecordAdapter")
|
26
|
+
warn "Using `#{adapter}` as an adapter is deprecated, use `#{adapter}.new`.", uplevel: 1
|
27
|
+
end
|
28
|
+
|
29
|
+
Thread.current[:ensql_adapter] = adapter
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def autoload_adapter
|
35
|
+
if defined? Sequel
|
36
|
+
require_relative "sequel_adapter"
|
37
|
+
SequelAdapter.new
|
38
|
+
elsif defined? ActiveRecord
|
39
|
+
require_relative "active_record_adapter"
|
40
|
+
ActiveRecordAdapter.new
|
41
|
+
else
|
42
|
+
raise Error, "Couldn't autodetect an adapter, please specify manually."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
6
47
|
#
|
7
48
|
# @abstract Do not use this module directly.
|
8
49
|
#
|
@@ -11,7 +52,6 @@ module Ensql
|
|
11
52
|
# that can be improved in the adapters.
|
12
53
|
#
|
13
54
|
module Adapter
|
14
|
-
|
15
55
|
# @!group 1. Interface Methods
|
16
56
|
|
17
57
|
# @!method literalize(value)
|
@@ -65,7 +105,6 @@ module Ensql
|
|
65
105
|
|
66
106
|
# @!group 2. Predefined Methods
|
67
107
|
|
68
|
-
|
69
108
|
# Execute the query and return only the first row of the result.
|
70
109
|
# @return <Hash>
|
71
110
|
def fetch_first_row(sql)
|
@@ -82,6 +121,5 @@ module Ensql
|
|
82
121
|
def fetch_first_field(sql)
|
83
122
|
fetch_first_row(sql)&.values&.first
|
84
123
|
end
|
85
|
-
|
86
124
|
end
|
87
125
|
end
|
data/lib/ensql/error.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "sql"
|
4
|
+
|
5
|
+
module Ensql
|
6
|
+
class << self
|
7
|
+
# Path to search for *.sql queries in, defaults to "sql/". For example, if
|
8
|
+
# {sql_path} is set to 'app/queries', `load_sql('users/active')` will read
|
9
|
+
# 'app/queries/users/active.sql'.
|
10
|
+
# @see .load_sql
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Ensql.sql_path = Rails.root.join('app/queries')
|
14
|
+
#
|
15
|
+
def sql_path
|
16
|
+
@sql_path ||= "sql"
|
17
|
+
end
|
18
|
+
attr_writer :sql_path
|
19
|
+
|
20
|
+
# Load SQL from a file within {sql_path}. This is the recommended way to
|
21
|
+
# manage SQL in a non-trivial project. For details of how to write
|
22
|
+
# interpolation placeholders, see {SQL}.
|
23
|
+
#
|
24
|
+
# @see .sql_path=
|
25
|
+
# @return [Ensql::SQL]
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Ensql.load_sql('users/activity', report_params)
|
29
|
+
# Ensql.load_sql(:upsert_users, imported_users_attrs)
|
30
|
+
#
|
31
|
+
def load_sql(name, params = {})
|
32
|
+
path = File.join(sql_path, "#{name}.sql")
|
33
|
+
SQL.new(File.read(path), params, name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/ensql/pool_wrapper.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
3
|
+
require_relative "version"
|
4
|
+
require_relative "adapter"
|
5
5
|
|
6
|
-
gem
|
7
|
-
require
|
8
|
-
require
|
6
|
+
gem "pg", Ensql::SUPPORTED_PG_VERSIONS
|
7
|
+
require "pg"
|
8
|
+
require "connection_pool"
|
9
9
|
|
10
10
|
module Ensql
|
11
11
|
# Wraps a pool of PG connections to implement the {Adapter} interface. The
|
@@ -61,7 +61,7 @@ module Ensql
|
|
61
61
|
# @visibility private
|
62
62
|
def literalize(value)
|
63
63
|
case value
|
64
|
-
when NilClass then
|
64
|
+
when NilClass then "NULL"
|
65
65
|
when Numeric, TrueClass, FalseClass then value.to_s
|
66
66
|
when String then @quoter.encode(value)
|
67
67
|
else
|
@@ -92,7 +92,7 @@ module Ensql
|
|
92
92
|
|
93
93
|
# @visibility private
|
94
94
|
def fetch_each_row(sql, &block)
|
95
|
-
return to_enum(:fetch_each_row, sql) unless
|
95
|
+
return to_enum(:fetch_each_row, sql) unless block
|
96
96
|
|
97
97
|
fetch_result(sql) { |res| res.each(&block) }
|
98
98
|
end
|
@@ -102,7 +102,7 @@ module Ensql
|
|
102
102
|
fetch_result(sql, &:to_a)
|
103
103
|
end
|
104
104
|
|
105
|
-
|
105
|
+
private
|
106
106
|
|
107
107
|
def fetch_result(sql)
|
108
108
|
execute(sql) do |res|
|
@@ -117,7 +117,7 @@ module Ensql
|
|
117
117
|
|
118
118
|
# Use PG's built-in type mapping to serialize objects into SQL strings.
|
119
119
|
def serialize(value)
|
120
|
-
coder = encoder_for(value)
|
120
|
+
(coder = encoder_for(value)) || raise(TypeError, "No SQL serializer for #{value.class}")
|
121
121
|
coder.encode(value)
|
122
122
|
end
|
123
123
|
|
@@ -143,17 +143,18 @@ module Ensql
|
|
143
143
|
# :nocov:
|
144
144
|
unless defined? PG::TextEncoder::Numeric
|
145
145
|
class NumericDecoder < PG::SimpleDecoder
|
146
|
-
def decode(string, tuple=nil, field=nil)
|
146
|
+
def decode(string, tuple = nil, field = nil)
|
147
147
|
BigDecimal(string)
|
148
148
|
end
|
149
149
|
end
|
150
|
+
|
150
151
|
class NumericEncoder < PG::SimpleEncoder
|
151
152
|
def encode(decimal)
|
152
|
-
decimal.to_s(
|
153
|
+
decimal.to_s("F")
|
153
154
|
end
|
154
155
|
end
|
155
156
|
private_constant :NumericDecoder, :NumericEncoder
|
156
|
-
PG::BasicTypeRegistry.register_type(0,
|
157
|
+
PG::BasicTypeRegistry.register_type(0, "numeric", NumericEncoder, NumericDecoder)
|
157
158
|
end
|
158
159
|
# :nocov:
|
159
160
|
end
|
data/lib/ensql/sequel_adapter.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
3
|
+
require_relative "version"
|
4
|
+
require_relative "adapter"
|
5
|
+
require_relative "pool_wrapper"
|
6
|
+
require_relative "error"
|
6
7
|
|
7
8
|
# Ensure our optional dependency has a compatible version
|
8
|
-
gem
|
9
|
-
require
|
9
|
+
gem "sequel", Ensql::SUPPORTED_SEQUEL_VERSIONS
|
10
|
+
require "sequel"
|
10
11
|
|
11
12
|
module Ensql
|
12
13
|
#
|
@@ -40,7 +41,7 @@ module Ensql
|
|
40
41
|
|
41
42
|
# Support deprecated class method interface
|
42
43
|
class << self
|
43
|
-
require
|
44
|
+
require "forwardable"
|
44
45
|
extend Forwardable
|
45
46
|
|
46
47
|
delegate [:literalize, :run, :fetch_count, :fetch_each_row, :fetch_rows, :fetch_first_column, :fetch_first_field, :fetch_first_row] => :new
|
@@ -93,13 +94,12 @@ module Ensql
|
|
93
94
|
db.literal(value)
|
94
95
|
end
|
95
96
|
|
96
|
-
|
97
|
+
private
|
97
98
|
|
98
99
|
attr_reader :db
|
99
100
|
|
100
101
|
def first_configured_database
|
101
|
-
Sequel::DATABASES.first
|
102
|
+
Sequel::DATABASES.first || raise(Error, "no database found in Sequel::DATABASES")
|
102
103
|
end
|
103
|
-
|
104
104
|
end
|
105
105
|
end
|
data/lib/ensql/sql.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
3
|
+
require_relative "adapter"
|
4
|
+
require_relative "error"
|
4
5
|
|
5
6
|
module Ensql
|
6
7
|
#
|
@@ -53,9 +54,8 @@ module Ensql
|
|
53
54
|
# # SELECT * FROM users ORDER BY name asc
|
54
55
|
#
|
55
56
|
class SQL
|
56
|
-
|
57
57
|
# @!visibility private
|
58
|
-
def initialize(sql, params={}, name=
|
58
|
+
def initialize(sql, params = {}, name = "SQL")
|
59
59
|
@sql = sql
|
60
60
|
@name = name.to_s
|
61
61
|
@params = params
|
@@ -106,14 +106,14 @@ module Ensql
|
|
106
106
|
interpolate(sql, params)
|
107
107
|
end
|
108
108
|
|
109
|
-
|
109
|
+
private
|
110
110
|
|
111
111
|
attr_reader :sql, :params, :name
|
112
112
|
|
113
|
-
NESTED_LIST
|
114
|
-
LIST
|
113
|
+
NESTED_LIST = /%{(\w+)\((.+)\)}/m
|
114
|
+
LIST = /%{\((\w+)\)}/
|
115
115
|
SQL_FRAGMENT = /%{!(\w+)}/
|
116
|
-
LITERAL
|
116
|
+
LITERAL = /%{(\w+)}/
|
117
117
|
|
118
118
|
def interpolate(sql, params)
|
119
119
|
params = params.transform_keys(&:to_s)
|
@@ -132,13 +132,13 @@ module Ensql
|
|
132
132
|
Array(array)
|
133
133
|
.map { |attrs| interpolate(nested_sql, Hash(attrs)) }
|
134
134
|
.map { |sql| "(#{sql})" }
|
135
|
-
.join(
|
135
|
+
.join(", ")
|
136
136
|
end
|
137
137
|
|
138
138
|
def interpolate_list(array)
|
139
|
-
return
|
139
|
+
return "(NULL)" if Array(array).empty?
|
140
140
|
|
141
|
-
|
141
|
+
"(" + Array(array).map { |v| literalize v }.join(", ") + ")"
|
142
142
|
end
|
143
143
|
|
144
144
|
def interpolate_sql(sql)
|
@@ -158,6 +158,5 @@ module Ensql
|
|
158
158
|
def adapter
|
159
159
|
Ensql.adapter
|
160
160
|
end
|
161
|
-
|
162
161
|
end
|
163
162
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
require_relative "adapter"
|
5
|
+
|
6
|
+
module Ensql
|
7
|
+
class << self
|
8
|
+
# Wrap a block with a transaction. Uses the well supported
|
9
|
+
# SQL-standard commands for controlling a transaction by default, however database
|
10
|
+
# specific statements can be supplied. Any exceptions inside the block will
|
11
|
+
# trigger a rollback and be reraised. Alternatively, you can call
|
12
|
+
# {rollback!} to immediately exit the block and rollback the transaction.
|
13
|
+
# Returns the result of the block. If the block returns `:rollback`, the
|
14
|
+
# transaction will also be rolled back.
|
15
|
+
#
|
16
|
+
# # If `do_thing1` or `do_thing2` raise an error, no statements are committed.
|
17
|
+
# Ensql.transaction { do_thing1; do_thing2 }
|
18
|
+
#
|
19
|
+
# # If `do_thing2` is falsey, `do_thing1` is rolled back and `do_thing3` is skipped.
|
20
|
+
# Ensql.transaction { do_thing1; do_thing2 or Ensql.rollback!; do_thing3 }
|
21
|
+
#
|
22
|
+
# # Nest transactions with savepoints.
|
23
|
+
# Ensql.transaction do
|
24
|
+
# do_thing1
|
25
|
+
# Ensql.transaction(start: 'SAVEPOINT my_savepoint', commit: 'RELEASE SAVEPOINT my_savepoint', rollback: 'ROLLBACK TO SAVEPOINT my_savepoint') do
|
26
|
+
# do_thing2
|
27
|
+
# do_thing3
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Use database-specific transaction semantics.
|
32
|
+
# Ensql.transaction(start: 'BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE') { }
|
33
|
+
#
|
34
|
+
# @see rollback!
|
35
|
+
# @param start the SQL to begin the transaction.
|
36
|
+
# @param commit the SQL to commit the transaction if successful.
|
37
|
+
# @param rollback the SQL to rollback the transaction if an error is raised.
|
38
|
+
def transaction(start: "START TRANSACTION", commit: "COMMIT", rollback: "ROLLBACK", &block)
|
39
|
+
adapter.run(start)
|
40
|
+
result = catch(:rollback, &block)
|
41
|
+
adapter.run(result == :rollback ? rollback : commit)
|
42
|
+
result
|
43
|
+
# # We need to try rollback on _any_ exception. Since we reraise, rescuing this is safe.
|
44
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
45
|
+
adapter.run(rollback)
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
|
49
|
+
# Immediately rollback and exit the current transaction block. See
|
50
|
+
# {transaction}.
|
51
|
+
def rollback!
|
52
|
+
throw :rollback, :rollback
|
53
|
+
rescue UncaughtThrowError
|
54
|
+
raise Error, "not in a transaction block, can't rollback"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/ensql/version.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Ensql
|
4
4
|
# Gem version
|
5
|
-
VERSION = "0.6.
|
5
|
+
VERSION = "0.6.3"
|
6
6
|
# Versions of activerecord compatible with the {ActiveRecordAdapter}
|
7
|
-
SUPPORTED_ACTIVERECORD_VERSIONS = [
|
7
|
+
SUPPORTED_ACTIVERECORD_VERSIONS = [">= 5.0", "< 6.2"].freeze
|
8
8
|
# Versions of sequel compatible with the {SequelAdapter}
|
9
|
-
SUPPORTED_SEQUEL_VERSIONS =
|
9
|
+
SUPPORTED_SEQUEL_VERSIONS = "~> 5.9"
|
10
10
|
# Versions of pg compatibile with the {PostgresAdapter}
|
11
|
-
SUPPORTED_PG_VERSIONS = [
|
11
|
+
SUPPORTED_PG_VERSIONS = [">= 0.19", "< 2"].freeze
|
12
12
|
end
|
data/perf/adapter_benchmark.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
2
5
|
#
|
3
6
|
# Compare operations performed using each adapter
|
4
7
|
#
|
5
8
|
|
6
|
-
ENV[
|
9
|
+
ENV["TZ"] = "UTC"
|
7
10
|
|
8
|
-
require
|
11
|
+
require "benchmark/ips"
|
9
12
|
|
10
|
-
require_relative
|
11
|
-
require_relative
|
12
|
-
require_relative
|
13
|
+
require_relative "lib/ensql/active_record_adapter"
|
14
|
+
require_relative "lib/ensql/sequel_adapter"
|
15
|
+
require_relative "lib/ensql/postgres_adapter"
|
13
16
|
|
14
17
|
ActiveRecord::Base.establish_connection(adapter: "postgresql")
|
15
18
|
DB = Sequel.connect("postgresql:/")
|
@@ -20,13 +23,13 @@ adapters = {
|
|
20
23
|
'ar ': Ensql::ActiveRecordAdapter.new(ActiveRecord::Base.connection_pool),
|
21
24
|
'seq ': Ensql::SequelAdapter.new(DB),
|
22
25
|
'pg-ar ': Ensql::PostgresAdapter.new(Ensql::ActiveRecordAdapter.pool),
|
23
|
-
'pg-seq ': Ensql::PostgresAdapter.new(Ensql::SequelAdapter.pool(DB))
|
26
|
+
'pg-seq ': Ensql::PostgresAdapter.new(Ensql::SequelAdapter.pool(DB))
|
24
27
|
}
|
25
28
|
|
26
29
|
ADAPTER = adapters.values.first
|
27
30
|
|
28
|
-
ADAPTER.run(
|
29
|
-
ADAPTER.run(
|
31
|
+
ADAPTER.run("drop table if exists number_benchmark")
|
32
|
+
ADAPTER.run("create table number_benchmark as select generate_series(1,100) as number")
|
30
33
|
|
31
34
|
adapter_tests = {
|
32
35
|
'literalize (String)': [:literalize, "It's quoted"],
|
@@ -34,30 +37,30 @@ adapter_tests = {
|
|
34
37
|
'literalize (Time)': [:literalize, Time.now],
|
35
38
|
'literalize (Int)': [:literalize, 1234],
|
36
39
|
'literalize (bool)': [:literalize, true],
|
37
|
-
'run INSERT': [:run,
|
40
|
+
'run INSERT': [:run, "insert into number_benchmark values (999)"],
|
38
41
|
'run SET': [:run, "set time zone UTC"],
|
39
|
-
'run SELECT': [:run,
|
40
|
-
'count UPDATE': [:fetch_count,
|
41
|
-
'count SELECT': [:fetch_count,
|
42
|
-
'first column': [:fetch_first_column,
|
43
|
-
'first column (of many)': [:fetch_first_column,
|
44
|
-
'first field': [:fetch_first_field,
|
42
|
+
'run SELECT': [:run, "select generate_series(1,100)"],
|
43
|
+
'count UPDATE': [:fetch_count, "update number_benchmark set number = number + 1"],
|
44
|
+
'count SELECT': [:fetch_count, "select generate_series(1,100)"],
|
45
|
+
'first column': [:fetch_first_column, "select generate_series(1,100)"],
|
46
|
+
'first column (of many)': [:fetch_first_column, "select *, now() from generate_series(1,100) as number"],
|
47
|
+
'first field': [:fetch_first_field, "select 1"],
|
45
48
|
'first field with cast': [:fetch_first_field, "select cast('2021-01-01' as timestamp)"],
|
46
|
-
'first field (of many)': [:fetch_first_field,
|
49
|
+
'first field (of many)': [:fetch_first_field, "select generate_series(1,100)"],
|
47
50
|
'first row': [:fetch_first_row, "select 1, 2, 3"],
|
48
51
|
'first row (cast)': [:fetch_first_row, "select cast('2021-01-01' as timestamp), cast('[1,2,3]' as json)"],
|
49
52
|
'first row (of many)': [:fetch_first_row, "select generate_series(1, 100)"],
|
50
53
|
'rows (1)': [:fetch_rows, "select 1, 1"],
|
51
54
|
'rows (100)': [:fetch_rows, "select 1, 1, generate_series(1, 100)"],
|
52
55
|
'rows (100,cast)': [:fetch_rows, "select cast('2021-01-01' as timestamp), cast('[1,2,3]' as json), generate_series(1, 100)"],
|
53
|
-
'rows (100000)': [:fetch_rows, "select 1, 1, generate_series(1, 100000)"]
|
56
|
+
'rows (100000)': [:fetch_rows, "select 1, 1, generate_series(1, 100000)"]
|
54
57
|
}
|
55
58
|
|
56
59
|
fetch_each_row_tests = {
|
57
|
-
'each_row (1)': [:fetch_each_row, "select 1, 1"
|
60
|
+
'each_row (1)': [:fetch_each_row, "select 1, 1"],
|
58
61
|
'each_row (100)': [:fetch_each_row, "select 1, 1, generate_series(1, 100)"],
|
59
62
|
'each_row (100,cast)': [:fetch_each_row, "select cast('2021-01-01' as timestamp), cast('[1,2,3]' as json), generate_series(1, 100)"],
|
60
|
-
'each_row (100000)': [:fetch_each_row, "select 1, 1, generate_series(1, 100000)"]
|
63
|
+
'each_row (100000)': [:fetch_each_row, "select 1, 1, generate_series(1, 100000)"]
|
61
64
|
}
|
62
65
|
|
63
66
|
# Verify results are the same
|
@@ -71,7 +74,7 @@ end
|
|
71
74
|
|
72
75
|
# Compare times
|
73
76
|
adapter_tests.each do |test_name, args|
|
74
|
-
puts args.map { |a| a.inspect[0..100] }.join(
|
77
|
+
puts args.map { |a| a.inspect[0..100] }.join(" ")
|
75
78
|
|
76
79
|
Benchmark.ips(quiet: true) do |x|
|
77
80
|
x.config(stats: :bootstrap, confidence: 95, warmup: 0.2, time: 0.5)
|
@@ -96,4 +99,4 @@ fetch_each_row_tests.each do |test_name, args|
|
|
96
99
|
end
|
97
100
|
end
|
98
101
|
|
99
|
-
ADAPTER.run(
|
102
|
+
ADAPTER.run("drop table number_benchmark")
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ensql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Fone
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: connection_pool
|
@@ -94,6 +94,7 @@ executables: []
|
|
94
94
|
extensions: []
|
95
95
|
extra_rdoc_files: []
|
96
96
|
files:
|
97
|
+
- ".github/workflows/lint.yml"
|
97
98
|
- ".github/workflows/specs.yml"
|
98
99
|
- ".gitignore"
|
99
100
|
- ".rspec"
|
@@ -116,10 +117,13 @@ files:
|
|
116
117
|
- lib/ensql.rb
|
117
118
|
- lib/ensql/active_record_adapter.rb
|
118
119
|
- lib/ensql/adapter.rb
|
120
|
+
- lib/ensql/error.rb
|
121
|
+
- lib/ensql/load_sql.rb
|
119
122
|
- lib/ensql/pool_wrapper.rb
|
120
123
|
- lib/ensql/postgres_adapter.rb
|
121
124
|
- lib/ensql/sequel_adapter.rb
|
122
125
|
- lib/ensql/sql.rb
|
126
|
+
- lib/ensql/transaction.rb
|
123
127
|
- lib/ensql/version.rb
|
124
128
|
- perf/adapter_benchmark.rb
|
125
129
|
homepage: https://github.com/danielfone/ensql
|