ensql 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Gem Version](https://badge.fury.io/rb/ensql.svg)](https://badge.fury.io/rb/ensql)
|
4
4
|
[![Ruby](https://github.com/danielfone/ensql/actions/workflows/specs.yml/badge.svg)](https://github.com/danielfone/ensql/actions/workflows/specs.yml)
|
5
5
|
[![Maintainability](https://api.codeclimate.com/v1/badges/a4ab07e1a03c4d1e8043/maintainability)](https://codeclimate.com/github/danielfone/ensql/maintainability)
|
6
|
+
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](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
|