sql_runner 0.1.0 → 0.4.0
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 +5 -5
- data/.github/FUNDING.yml +3 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +16 -0
- data/.travis.yml +20 -4
- data/Gemfile +2 -0
- data/README.md +48 -10
- data/Rakefile +7 -2
- data/bin/console +1 -0
- data/examples/base.rb +96 -0
- data/examples/bench.rb +21 -5
- data/examples/profiling.rb +2 -0
- data/examples/test.rb +3 -84
- data/examples/test_active_record.rb +17 -0
- data/lib/sql_runner/adapters/active_record.rb +79 -0
- data/lib/sql_runner/adapters/mysql.rb +107 -0
- data/lib/sql_runner/adapters/postgresql.rb +24 -18
- data/lib/sql_runner/adapters/sqlite.rb +91 -0
- data/lib/sql_runner/adapters.rb +16 -7
- data/lib/sql_runner/configuration.rb +3 -3
- data/lib/sql_runner/connection.rb +8 -4
- data/lib/sql_runner/query/many.rb +2 -0
- data/lib/sql_runner/query/model.rb +3 -1
- data/lib/sql_runner/query/one.rb +10 -1
- data/lib/sql_runner/query.rb +26 -8
- data/lib/sql_runner/runner.rb +4 -2
- data/lib/sql_runner/version.rb +3 -1
- data/lib/sql_runner.rb +9 -2
- data/sql_runner.gemspec +23 -8
- metadata +94 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d8ae856dccc72baadf453eab3f66fc6b375757e81d042e309de58b80a3794c22
|
4
|
+
data.tar.gz: bc6cdefb9735e36c972e0ae23fc062e746c6ca44f981444fb604fd9e3db388d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1651fa64b28884dc534d878114bfab992f7d592b30c4b86cb518c24bfb63685795b61bb792db072ce07f7ea9495310b73196d5763b4250f3ea6d2fe7346efb3f
|
7
|
+
data.tar.gz: b660ace92f4e0446c8f583505a4d2da82f4854b53cdce460202d83086dd968e5a2a89af8953c484a12dfa023e12dfaa4ac68c38bb9ddb4e78f3144e27bf703bf
|
data/.github/FUNDING.yml
ADDED
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
@@ -1,13 +1,29 @@
|
|
1
|
+
---
|
1
2
|
language: ruby
|
2
3
|
cache: bundler
|
3
4
|
sudo: false
|
4
5
|
rvm:
|
5
|
-
- 2.
|
6
|
+
- 2.7.0
|
7
|
+
services:
|
8
|
+
- mysql
|
9
|
+
addons:
|
10
|
+
postgresql: "10"
|
11
|
+
apt:
|
12
|
+
packages:
|
13
|
+
- postgresql-10
|
14
|
+
- postgresql-client-10
|
15
|
+
before_script:
|
16
|
+
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
17
|
+
- chmod +x ./cc-test-reporter
|
18
|
+
- "./cc-test-reporter before-build"
|
19
|
+
after_script:
|
20
|
+
- "./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
|
6
21
|
before_install:
|
7
|
-
- gem install bundler
|
8
|
-
- createdb test
|
22
|
+
- gem install bundler
|
23
|
+
- createdb test
|
24
|
+
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
|
9
25
|
notifications:
|
10
26
|
email: false
|
11
27
|
env:
|
12
28
|
global:
|
13
|
-
secure:
|
29
|
+
secure: 4dDrl8nCItKPRKpoA2KJVWsDm3o7U0pIsdY8t19pJXbziteT3zw5pEmFh+/qWGB1VszzFFZzXcZCIoKv3NX/x24g+LU+qvL7vLYLyhUR8ZjR4rGzGkwBCgEYBWgVtqg9ncYTbYsdbAqryt6f+HvpFj8EFvGSjIWVqJyh59qHdwAVT+BUKjllEH+t5Dbjd2knIQw83af+0A5ljP2uMziNcuHD7MUBIKY1DfFnBYQ5YA50o/mZe8sG5h6bZU/sf0pdoa+z2xDIJOPO7qaCwANACetDtsfs1DN39DsubvlFLg0s2z0XCuYyWSsJQ4zhRipHgnqYn+Rmpql17t0WmhVPA1xvLyk0/4X8rjRcYyYHMM3ryga5bnrpvSiPsqG+JFNhDczpN394KGa7o+/jCuNA0MVFcCzsvW2AMkYpUbPtADY7/Tl4iUJWmFbl0nO1n1wh5dDJ7Wo7mdkPAmdV7hNmMUmHbPeh8mSjMnuS2gcMc11wDgmR56TEMu/B7LUM31og7L+JouEJAOWtzThtpdYLpqDQ3Xe1G5uMl1oZ0lNOqag5Bg+AQCAIGr1N4G2m8fI74M9lUQUfhu87L+zycYMHInOH+Czc+RamlkH3vLl9Fu6aOKoXA0+zh85sVDj3Er+X2NEHaoRe4PSkOHOBNYSkAUd36TYMt8J5MgAD0w7HqmY=
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# SQLRunner
|
2
2
|
|
3
|
-
[](https://travis-ci.org/fnando/sql_runner)
|
4
4
|
[](https://codeclimate.com/github/fnando/sql_runner)
|
5
5
|
[](https://codeclimate.com/github/fnando/sql_runner/coverage)
|
6
6
|
[](https://rubygems.org/gems/sql_runner)
|
7
7
|
[](https://rubygems.org/gems/sql_runner)
|
8
8
|
|
9
|
-
SQLRunner allows you to load your queries out of SQL files, without using ORMs.
|
9
|
+
SQLRunner allows you to load your queries out of SQL files, without using ORMs.
|
10
|
+
Available for PostgreSQL and MySQL.
|
10
11
|
|
11
12
|
## Installation
|
12
13
|
|
@@ -76,6 +77,22 @@ class GetMembers < SQLRunner::Query
|
|
76
77
|
end
|
77
78
|
```
|
78
79
|
|
80
|
+
You can use this with ActiveRecord as well. To make it work, all you need to do
|
81
|
+
is establishing the connection using `activerecord:///`:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
require "active_record"
|
85
|
+
|
86
|
+
# You probably won't need this if you're using Rails.
|
87
|
+
ActiveRecord::Base.establish_connection("postgresql:///database")
|
88
|
+
|
89
|
+
# Set the adapter to be based on ActiveRecord.
|
90
|
+
SQLRunner.connect "activerecord:///"
|
91
|
+
|
92
|
+
SQLRunner.execute "SELECT 1"
|
93
|
+
#=> <PG:Result:0x008adf4d5495b0>
|
94
|
+
```
|
95
|
+
|
79
96
|
### Plugins
|
80
97
|
|
81
98
|
#### Load just one record
|
@@ -133,7 +150,9 @@ FindUsers.call
|
|
133
150
|
|
134
151
|
### Adding new plugins
|
135
152
|
|
136
|
-
First you have to create a class/module that implements the
|
153
|
+
First you have to create a class/module that implements the
|
154
|
+
`.activate(target, options)` class method. The following example overrides the
|
155
|
+
`call(**bind_vars)` method by using `Module.prepend`.
|
137
156
|
|
138
157
|
```ruby
|
139
158
|
module ReverseRecords
|
@@ -145,8 +164,11 @@ module ReverseRecords
|
|
145
164
|
super(**bind_vars).to_a.reverse
|
146
165
|
end
|
147
166
|
end
|
167
|
+
```
|
148
168
|
|
149
|
-
|
169
|
+
#### Register the plugin.
|
170
|
+
|
171
|
+
```ruby
|
150
172
|
SQLRunner::Query.register_plugin :reverse, ReverseRecords
|
151
173
|
|
152
174
|
class Users < SQLRunner::Query
|
@@ -157,11 +179,17 @@ end
|
|
157
179
|
Users.call
|
158
180
|
```
|
159
181
|
|
160
|
-
If
|
182
|
+
If your plugin can receive options, you can call it as
|
183
|
+
`plugin reverse: options`, where `options` can be anything (e.g. `Hash`,
|
184
|
+
`Array`, `Object`, etc).
|
161
185
|
|
162
186
|
## Benchmarks
|
163
187
|
|
164
|
-
You won't gain too much performance by using this gem.
|
188
|
+
You won't gain too much performance by using this gem. The idea is making SQL
|
189
|
+
easier to read by extracting complex stuff to their own files. These are the
|
190
|
+
results against ActiveRecord using different wrapping libraries like
|
191
|
+
[virtus](https://rubygems.org/gems/virtus) and
|
192
|
+
[dry-types](https://rubygems.org/gems/dry-types).
|
165
193
|
|
166
194
|
Loading just one record:
|
167
195
|
|
@@ -183,14 +211,24 @@ activerecord - find many : 2731.5 i/s - 2.49x slower
|
|
183
211
|
|
184
212
|
## Development
|
185
213
|
|
186
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
214
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
215
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
216
|
+
prompt that will allow you to experiment.
|
187
217
|
|
188
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
218
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
219
|
+
release a new version, update the version number in `version.rb`, and then run
|
220
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
221
|
+
git commits and tags, and push the `.gem` file to
|
222
|
+
[rubygems.org](https://rubygems.org).
|
189
223
|
|
190
224
|
## Contributing
|
191
225
|
|
192
|
-
Bug reports and pull requests are welcome on GitHub at
|
226
|
+
Bug reports and pull requests are welcome on GitHub at
|
227
|
+
https://github.com/fnando/sql_runner. This project is intended to be a safe,
|
228
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
229
|
+
the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
193
230
|
|
194
231
|
## License
|
195
232
|
|
196
|
-
The gem is available as open source under the terms of the
|
233
|
+
The gem is available as open source under the terms of the
|
234
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
require "rake/testtask"
|
5
|
+
require "rubocop/rake_task"
|
3
6
|
|
4
7
|
Rake::TestTask.new(:test) do |t|
|
5
8
|
t.libs << "test"
|
6
9
|
t.libs << "lib"
|
7
|
-
t.test_files = FileList[
|
10
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
8
11
|
t.warning = false
|
9
12
|
end
|
10
13
|
|
11
|
-
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[test rubocop]
|
data/bin/console
CHANGED
data/examples/base.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
SQLRunner.execute <<~SQL
|
4
|
+
create table if not exists users (
|
5
|
+
id serial primary key not null,
|
6
|
+
name text not null,
|
7
|
+
email text not null
|
8
|
+
)
|
9
|
+
SQL
|
10
|
+
|
11
|
+
class Users < SQLRunner::Query
|
12
|
+
end
|
13
|
+
|
14
|
+
module NumericModel
|
15
|
+
def self.new(attrs)
|
16
|
+
attrs.values.first.to_i
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Numbers < SQLRunner::Query
|
21
|
+
plugin model: NumericModel
|
22
|
+
plugin :many
|
23
|
+
|
24
|
+
query <<-SQL
|
25
|
+
SELECT n FROM generate_series(1, 10) n
|
26
|
+
SQL
|
27
|
+
end
|
28
|
+
|
29
|
+
class User
|
30
|
+
include Virtus.model
|
31
|
+
|
32
|
+
attribute :id, String
|
33
|
+
attribute :name, String
|
34
|
+
attribute :email, Integer
|
35
|
+
end
|
36
|
+
|
37
|
+
class Customer < User
|
38
|
+
end
|
39
|
+
|
40
|
+
class FindUser < SQLRunner::Query
|
41
|
+
plugins :one
|
42
|
+
plugin model: User
|
43
|
+
end
|
44
|
+
|
45
|
+
class FindAllUsers < SQLRunner::Query
|
46
|
+
plugins :many
|
47
|
+
plugin model: User
|
48
|
+
end
|
49
|
+
|
50
|
+
class CreateUser < SQLRunner::Query
|
51
|
+
plugin :one
|
52
|
+
plugin model: User
|
53
|
+
end
|
54
|
+
|
55
|
+
class DeleteAllUsers < SQLRunner::Query
|
56
|
+
plugin :many
|
57
|
+
plugin model: User
|
58
|
+
end
|
59
|
+
|
60
|
+
class FindCustomer < SQLRunner::Query
|
61
|
+
query_name "find_user"
|
62
|
+
plugin :one
|
63
|
+
plugin model: Customer
|
64
|
+
end
|
65
|
+
|
66
|
+
result = SQLRunner.execute(
|
67
|
+
"select application_name from pg_stat_activity where pid = pg_backend_pid();"
|
68
|
+
)
|
69
|
+
p [:application_name, result.to_a]
|
70
|
+
|
71
|
+
result = SQLRunner.execute <<~SQL, name: "john", age: 18
|
72
|
+
select
|
73
|
+
'hello'::text as message,
|
74
|
+
:name::text as name,
|
75
|
+
:age::integer as age,
|
76
|
+
:name::text as name2
|
77
|
+
SQL
|
78
|
+
p [:select, result.to_a]
|
79
|
+
|
80
|
+
p [:delete_all_users, DeleteAllUsers.call]
|
81
|
+
p [:create_user, CreateUser.call(name: "Nando Vieira", email: "me@fnando.com")]
|
82
|
+
p [:create_user, CreateUser.call(name: "John Doe", email: "john@example.com")]
|
83
|
+
p [:numbers, Numbers.call]
|
84
|
+
p [:users, Users.call.to_a]
|
85
|
+
p [:find_user, FindUser.call(email: "me@fnando.com")]
|
86
|
+
p [:find_user, FindUser.call(email: "' OR 1=1 --me@fnando.com")]
|
87
|
+
p [:find_user, FindUser.call!(email: "me@fnando.com")]
|
88
|
+
p [:find_customer, FindCustomer.call!(email: "me@fnando.com")]
|
89
|
+
|
90
|
+
begin
|
91
|
+
FindUser.call!(email: "invalid@email")
|
92
|
+
rescue SQLRunner::RecordNotFound => error
|
93
|
+
p [:find_user, error]
|
94
|
+
end
|
95
|
+
|
96
|
+
SQLRunner.disconnect
|
data/examples/bench.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
$LOAD_PATH.push File.expand_path("#{__dir__}/../lib")
|
2
4
|
require "sql_runner"
|
3
5
|
require "virtus"
|
@@ -13,7 +15,9 @@ SQLRunner.pool = 25
|
|
13
15
|
SQLRunner.timeout = 10
|
14
16
|
SQLRunner.root_dir = "#{__dir__}/sql"
|
15
17
|
|
16
|
-
ActiveRecord::Base.establish_connection(
|
18
|
+
ActiveRecord::Base.establish_connection(
|
19
|
+
"#{connection_string}&prepared_statements=false&pool=25"
|
20
|
+
)
|
17
21
|
|
18
22
|
module Types
|
19
23
|
include Dry::Types.module
|
@@ -77,10 +81,22 @@ class Users < SQLRunner::Query
|
|
77
81
|
end
|
78
82
|
|
79
83
|
Benchmark.ips do |x|
|
80
|
-
x.report("activerecord - find one
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
+
x.report("activerecord - find one") do
|
85
|
+
User.find_by_email("me@fnando.com")
|
86
|
+
end
|
87
|
+
|
88
|
+
x.report(" sql_runner - find one (dry-types)") do
|
89
|
+
FindUserDry.call(email: "me@fnando.com")
|
90
|
+
end
|
91
|
+
|
92
|
+
x.report(" sql_runner - find one (virtus)") do
|
93
|
+
FindUserVirtus.call(email: "me@fnando.com")
|
94
|
+
end
|
95
|
+
|
96
|
+
x.report(" sql_runner - find one (raw)") do
|
97
|
+
FindUser.call(email: "me@fnando.com")
|
98
|
+
end
|
99
|
+
|
84
100
|
x.compare!
|
85
101
|
end
|
86
102
|
|
data/examples/profiling.rb
CHANGED
data/examples/test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
$LOAD_PATH.push File.expand_path("#{__dir__}/../lib")
|
2
4
|
require "sql_runner"
|
3
5
|
require "virtus"
|
@@ -7,87 +9,4 @@ SQLRunner.pool = 25
|
|
7
9
|
SQLRunner.timeout = 10
|
8
10
|
SQLRunner.root_dir = "#{__dir__}/sql"
|
9
11
|
|
10
|
-
|
11
|
-
p result.to_a
|
12
|
-
|
13
|
-
result = SQLRunner.execute <<-SQL, name: "john", age: 18
|
14
|
-
select
|
15
|
-
'hello'::text as message,
|
16
|
-
:name::text as name,
|
17
|
-
:age::integer as age,
|
18
|
-
:name::text as name2
|
19
|
-
SQL
|
20
|
-
p result.to_a
|
21
|
-
|
22
|
-
class Users < SQLRunner::Query
|
23
|
-
end
|
24
|
-
|
25
|
-
module NumericModel
|
26
|
-
def self.new(attrs)
|
27
|
-
attrs.values.first.to_i
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class Numbers < SQLRunner::Query
|
32
|
-
plugin model: NumericModel
|
33
|
-
plugin :many
|
34
|
-
|
35
|
-
query <<-SQL
|
36
|
-
SELECT n FROM generate_series(1, 10) n
|
37
|
-
SQL
|
38
|
-
end
|
39
|
-
|
40
|
-
class User
|
41
|
-
include Virtus.model
|
42
|
-
|
43
|
-
attribute :id, String
|
44
|
-
attribute :name, String
|
45
|
-
attribute :email, Integer
|
46
|
-
end
|
47
|
-
|
48
|
-
class Customer < User
|
49
|
-
end
|
50
|
-
|
51
|
-
class FindUser < SQLRunner::Query
|
52
|
-
plugins :one
|
53
|
-
plugin model: User
|
54
|
-
end
|
55
|
-
|
56
|
-
class FindAllUsers < SQLRunner::Query
|
57
|
-
plugins :many
|
58
|
-
plugin model: User
|
59
|
-
end
|
60
|
-
|
61
|
-
class CreateUser < SQLRunner::Query
|
62
|
-
plugin :one
|
63
|
-
plugin model: User
|
64
|
-
end
|
65
|
-
|
66
|
-
class DeleteAllUsers < SQLRunner::Query
|
67
|
-
plugin :many
|
68
|
-
plugin model: User
|
69
|
-
end
|
70
|
-
|
71
|
-
class FindCustomer < SQLRunner::Query
|
72
|
-
query_name "find_user"
|
73
|
-
plugin model: Customer
|
74
|
-
plugins :one
|
75
|
-
end
|
76
|
-
|
77
|
-
p DeleteAllUsers.call
|
78
|
-
p CreateUser.call(name: "Nando Vieira", email: "me@fnando.com")
|
79
|
-
p CreateUser.call(name: "John Doe", email: "john@example.com")
|
80
|
-
p Numbers.call
|
81
|
-
p Users.call.to_a
|
82
|
-
p FindUser.call(email: "me@fnando.com")
|
83
|
-
p FindUser.call(email: "' OR 1=1 --me@fnando.com")
|
84
|
-
p FindUser.call!(email: "me@fnando.com")
|
85
|
-
p FindCustomer.call!(email: "me@fnando.com")
|
86
|
-
|
87
|
-
begin
|
88
|
-
FindUser.call!(email: "me@fnando.coms")
|
89
|
-
rescue SQLRunner::RecordNotFound => error
|
90
|
-
p error
|
91
|
-
end
|
92
|
-
|
93
|
-
SQLRunner.disconnect
|
12
|
+
require_relative "base"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path("#{__dir__}/../lib")
|
4
|
+
require "sql_runner"
|
5
|
+
require "virtus"
|
6
|
+
require "active_record"
|
7
|
+
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
"postgres:///test?connect_timeout=2&application_name=myapp"
|
10
|
+
)
|
11
|
+
|
12
|
+
SQLRunner.connect "activerecord:///"
|
13
|
+
SQLRunner.pool = 25
|
14
|
+
SQLRunner.timeout = 10
|
15
|
+
SQLRunner.root_dir = "#{__dir__}/sql"
|
16
|
+
|
17
|
+
require_relative "base"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SQLRunner
|
4
|
+
module Adapters
|
5
|
+
class ActiveRecord
|
6
|
+
class PostgreSQL < SQLRunner::Adapters::PostgreSQL
|
7
|
+
def initialize(connection) # rubocop:disable Lint/MissingSuper
|
8
|
+
@connection = connection
|
9
|
+
end
|
10
|
+
|
11
|
+
def connect(*)
|
12
|
+
end
|
13
|
+
|
14
|
+
def disconnect(*)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MySQL < SQLRunner::Adapters::MySQL
|
19
|
+
def initialize(connection) # rubocop:disable Lint/MissingSuper
|
20
|
+
@connection = connection
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect(*)
|
24
|
+
end
|
25
|
+
|
26
|
+
def disconnect(*)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class SQLite < SQLRunner::Adapters::SQLite
|
31
|
+
def initialize(connection) # rubocop:disable Lint/MissingSuper
|
32
|
+
@connection = connection
|
33
|
+
end
|
34
|
+
|
35
|
+
def connect(*)
|
36
|
+
end
|
37
|
+
|
38
|
+
def disconnect(*)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ConnectionPool
|
43
|
+
def with
|
44
|
+
::ActiveRecord::Base.connection_pool.with_connection do |connection|
|
45
|
+
connection = connection.instance_variable_get(:@connection)
|
46
|
+
|
47
|
+
adapter = case connection.class.name
|
48
|
+
when "PG::Connection"
|
49
|
+
PostgreSQL.new(connection)
|
50
|
+
when "Mysql2::Client"
|
51
|
+
MySQL.new(connection)
|
52
|
+
when "SQLite3::Database"
|
53
|
+
SQLite.new(connection)
|
54
|
+
else
|
55
|
+
raise UnsupportedDatabase,
|
56
|
+
"#{connection.class.name} is not yet supported " \
|
57
|
+
"by the SQLRunner's ActiveRecord adapter"
|
58
|
+
end
|
59
|
+
|
60
|
+
yield(adapter)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def shutdown
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.load
|
69
|
+
require "active_record"
|
70
|
+
rescue LoadError
|
71
|
+
raise MissingDependency, "make sure the `activerecord` gem is available"
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.create_connection_pool(*)
|
75
|
+
ConnectionPool.new
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SQLRunner
|
4
|
+
module Adapters
|
5
|
+
class MySQL
|
6
|
+
InvalidPreparedStatement = Class.new(StandardError)
|
7
|
+
|
8
|
+
def self.load
|
9
|
+
require "mysql2"
|
10
|
+
rescue LoadError
|
11
|
+
raise MissingDependency, "make sure the `mysql2` gem is available"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create_connection_pool(timeout:, size:, connection_string:)
|
15
|
+
ConnectionPool.new(timeout: timeout, size: size) do
|
16
|
+
new(connection_string)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(connection_string)
|
21
|
+
@connection_string = connection_string
|
22
|
+
@uri = URI.parse(@connection_string)
|
23
|
+
connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def connect(started = Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
27
|
+
@connection = Mysql2::Client.new(
|
28
|
+
host: @uri.host,
|
29
|
+
port: @uri.port,
|
30
|
+
username: @uri.user,
|
31
|
+
password: @uri.password,
|
32
|
+
database: @uri.path[1..-1]
|
33
|
+
)
|
34
|
+
rescue Mysql2::Error
|
35
|
+
ended = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
36
|
+
|
37
|
+
raise unless ended - started < SQLRunner.timeout
|
38
|
+
|
39
|
+
sleep 0.1
|
40
|
+
connect(started)
|
41
|
+
end
|
42
|
+
|
43
|
+
def disconnect
|
44
|
+
@connection&.close && (@connection = nil)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reconnect
|
48
|
+
disconnect
|
49
|
+
connect
|
50
|
+
end
|
51
|
+
|
52
|
+
def execute(query, **bind_vars)
|
53
|
+
bound_query, bindings, names = parse(query, bind_vars)
|
54
|
+
validate_bindings(query, bind_vars, names)
|
55
|
+
|
56
|
+
statement = @connection.prepare(bound_query)
|
57
|
+
statement.execute(
|
58
|
+
*bindings,
|
59
|
+
cast: true,
|
60
|
+
as: :hash,
|
61
|
+
cast_booleans: true
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def active?
|
66
|
+
!@connection&.closed?
|
67
|
+
rescue Mysql2::Error
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
%[#<#{self.class.name} #{format('0x00%x', (object_id << 1))}>]
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse(query, bind_vars)
|
80
|
+
bindings = []
|
81
|
+
names = []
|
82
|
+
|
83
|
+
parsed_query = query.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
|
84
|
+
next match if Regexp.last_match(1) == ":" # skip type casting
|
85
|
+
|
86
|
+
name = match[1..-1]
|
87
|
+
sym_name = name.to_sym
|
88
|
+
names << sym_name
|
89
|
+
bindings << bind_vars[sym_name]
|
90
|
+
|
91
|
+
"?"
|
92
|
+
end
|
93
|
+
|
94
|
+
[parsed_query, bindings, names]
|
95
|
+
end
|
96
|
+
|
97
|
+
private def validate_bindings(query, bind_vars, names)
|
98
|
+
names.each do |name|
|
99
|
+
next if bind_vars.key?(name)
|
100
|
+
|
101
|
+
raise InvalidPreparedStatement,
|
102
|
+
"missing value for :#{name} in #{query}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|