simple-sql 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +72 -23
- data/lib/simple-sql.rb +1 -0
- data/lib/simple/sql.rb +6 -4
- data/lib/simple/sql/connection.rb +76 -0
- data/lib/simple/sql/version.rb +1 -1
- data/tasks/release.rake +1 -1
- metadata +4 -3
- data/lib/simple/sql/transactions.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 435908aca401185b06131575ae1b5c85570c6fe4
|
4
|
+
data.tar.gz: d238e14b1d859326b0ed4d7e9799d3717d31a821
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8a497b87236bb8f0cfe1f6e1db9577bbd6364e8826b41097adec7b235cddbff4cac3cd6535a832f8b6d75e452ea21af6ae64ece5f8f9e826d48adbd59087213
|
7
|
+
data.tar.gz: 98d60e38cfd0b60128da263bc8372428028e4ce31fb009e324498de16024802bc65a35e9676f863e379154c76fb56066b4a1f43a6561bd629c1fce6d33b41c3b
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,21 +1,66 @@
|
|
1
1
|
# simple-sql
|
2
2
|
|
3
|
+
The simple-sql gem defines a module `Simple::SQL`, which you can use to execute
|
4
|
+
SQL statements on a Postgresql database. Care has been taken to provide the
|
5
|
+
simplest interface we could come up with.
|
6
|
+
|
7
|
+
**Note:** Databases other than Postgresql are not supported, and there are no
|
8
|
+
plans to do so. If you deem that necessary, feel free to fork this code into a
|
9
|
+
`simple-sql-<yourdatabase>` gem to provide the same or a similar interface.
|
10
|
+
|
3
11
|
## Installation
|
4
12
|
|
5
|
-
The gem is available
|
13
|
+
The gem is available on rubygems as `simple-sql`. To use it add the following
|
14
|
+
line to your `Gemfile` and bundle us usual:
|
6
15
|
|
7
|
-
|
8
|
-
gem 'simple-sql' # + version
|
9
|
-
```
|
16
|
+
gem 'simple-sql' # + version
|
10
17
|
|
11
18
|
## Usage
|
12
19
|
|
13
|
-
|
14
|
-
|
20
|
+
### Connecting to a database
|
21
|
+
|
22
|
+
Before you can send SQL commands to a database you need to connect first. `simple-sql`
|
23
|
+
gives you the following options:
|
24
|
+
|
25
|
+
1. **Use the current ActiveRecord connection:** when running inside a Rails application
|
26
|
+
you typically have a connection to Postgresql configured already. In that case you
|
27
|
+
don't need to do anything; `simple-sql` will just use the current database connection.
|
28
|
+
|
29
|
+
This is usually the right thing, especially since `simple-sql`, when called from inside
|
30
|
+
a controller action, is using the connection valid in the current context.
|
31
|
+
|
32
|
+
2. **Explicitely connect** on standalone applications you need to connect to a Postgresql
|
33
|
+
server. **Note that in this case there are no pooled connections!** simple-sql is not
|
34
|
+
thread-/fiber-safe in this mode.
|
35
|
+
|
36
|
+
You can explictely connect to a server by calling
|
37
|
+
|
38
|
+
::Simple::SQL.connect! "postgres://user:password@server[:port]/database"
|
39
|
+
|
40
|
+
Alternatively, you can have `simple-sql` figure out the details automatically:
|
41
|
+
|
42
|
+
::Simple::SQL.connect!
|
43
|
+
|
44
|
+
In that case we try to find connection parameters in the following places:
|
45
|
+
|
46
|
+
- the `DATABASE_URL` environment value
|
47
|
+
- the `config/database.yml` file from the current directory, taking `RAILS_ENV`/`RACK_ENV` into account.
|
48
|
+
|
49
|
+
### Using placeholders
|
50
|
+
|
51
|
+
Note: whenever you run a query `simple-sql` takes care of sending query parameters over the wire properly. That means that you use placeholders `$1`, `$2`, etc. to use these inside your queries; the following is a correct example:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
Simple::SQL.all "SELECT * FROM users WHERE email=$1", "foo@bar.local"
|
55
|
+
```
|
56
|
+
|
57
|
+
Also note that it is not possible to use an array as the argument for the `IN(?)` SQL construct. Instead you want to use `ANY`, for example:
|
15
58
|
|
16
|
-
|
59
|
+
```ruby
|
60
|
+
Simple::SQL.all "SELECT * FROM users WHERE id = ANY($1)", [1,2,3]
|
61
|
+
```
|
17
62
|
|
18
|
-
###
|
63
|
+
### Simple::SQL.all: Fetching all results of a query
|
19
64
|
|
20
65
|
`Simple::SQL.all` runs a query, with optional arguments, and returns the result. Usage example:
|
21
66
|
|
@@ -39,12 +84,9 @@ Simple::SQL.all "SELECT id, email FROM users" do |id, email|
|
|
39
84
|
end
|
40
85
|
```
|
41
86
|
|
42
|
-
|
43
|
-
|
44
|
-
### Getting the first result
|
45
|
-
|
46
|
-
`Simple::SQL.ask` returns runs a query, with optional arguments, and returns the first result row.
|
87
|
+
### Simple::SQL.ask: getting the first result
|
47
88
|
|
89
|
+
`Simple::SQL.ask` runs a query, with optional arguments, and returns the first result row or nil, if there was no result.
|
48
90
|
|
49
91
|
Simple::SQL.ask "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1", [1,2,3]
|
50
92
|
|
@@ -57,21 +99,28 @@ Simple::SQL.ask "SELECT id FROM users WHERE email=$1", "foo@local" # ret
|
|
57
99
|
Simple::SQL.ask "SELECT id, email FROM users WHERE email=$?", "foo@local" # returns an array `[ <id>, <email> ]` (or `nil`)
|
58
100
|
```
|
59
101
|
|
60
|
-
|
102
|
+
### Simple::SQL.record/Simple::SQL.records: fetching hashes
|
61
103
|
|
62
|
-
|
104
|
+
While `ask` and `all` convert each result row into an Array, sometimes you might want
|
105
|
+
to use Hashes or similar objects instead. To do so, you use the record and records functions:
|
63
106
|
|
64
|
-
|
65
|
-
Simple::SQL.
|
66
|
-
```
|
107
|
+
# returns a single Hash (or nil)
|
108
|
+
Simple::SQL.record("SELECT id FROM users")
|
67
109
|
|
68
|
-
|
69
|
-
|
110
|
+
If you want the returned record to be in a structure which is not a Hash, you can use
|
111
|
+
the `into: <klass>` option. The following would return an array of up to two `OpenStruct`
|
112
|
+
objects:
|
70
113
|
|
71
|
-
|
72
|
-
Simple::SQL.
|
73
|
-
|
114
|
+
sql = "SELECT id, email FROM users WHERE id = ANY($1) LIMIT 1",
|
115
|
+
Simple::SQL.records sql, [1,2,3], into: OpenStruct
|
116
|
+
|
117
|
+
### Transaction support
|
118
|
+
|
119
|
+
`simple-sql` has limited support for nested transactions. When running with a ActiveRecord
|
120
|
+
connection, we use ActiveRecord's transaction implementation (which uses savepoints for nested
|
121
|
+
transactions, so you might be able to rollback from inside a nested transaction).
|
74
122
|
|
123
|
+
When connecting via `Simple::SQL.connect!` we do not support the same level of nesting support (yet). You can still nest transactions, but raising an error terminates *all* current transactions.
|
75
124
|
|
76
125
|
## Bugs and Limitations
|
77
126
|
|
data/lib/simple-sql.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "simple/sql"
|
data/lib/simple/sql.rb
CHANGED
@@ -2,8 +2,8 @@ require_relative "sql/version.rb"
|
|
2
2
|
require_relative "sql/decoder.rb"
|
3
3
|
require_relative "sql/encoder.rb"
|
4
4
|
require_relative "sql/config.rb"
|
5
|
-
require_relative "sql/transactions.rb"
|
6
5
|
require_relative "sql/logging.rb"
|
6
|
+
require_relative "sql/connection.rb"
|
7
7
|
|
8
8
|
require "logger"
|
9
9
|
|
@@ -11,7 +11,6 @@ module Simple
|
|
11
11
|
# The Simple::SQL module
|
12
12
|
module SQL
|
13
13
|
extend self
|
14
|
-
extend Transactions
|
15
14
|
|
16
15
|
attr_accessor :logger
|
17
16
|
self.logger = Logger.new(STDERR)
|
@@ -88,6 +87,9 @@ module Simple
|
|
88
87
|
end
|
89
88
|
end
|
90
89
|
|
90
|
+
extend Forwardable
|
91
|
+
delegate :transaction => :connection
|
92
|
+
|
91
93
|
private
|
92
94
|
|
93
95
|
def exec_logged(sql, *args)
|
@@ -127,7 +129,7 @@ module Simple
|
|
127
129
|
}
|
128
130
|
|
129
131
|
def connect_to_active_record
|
130
|
-
return ActiveRecord::Base.connection
|
132
|
+
return Connection.new(ActiveRecord::Base.connection) if defined?(ActiveRecord)
|
131
133
|
|
132
134
|
STDERR.puts <<-SQL
|
133
135
|
Simple::SQL works out of the box with ActiveRecord-based postgres connections, reusing the current connection.
|
@@ -150,7 +152,7 @@ SQL
|
|
150
152
|
config = Config.parse_url(database_url)
|
151
153
|
|
152
154
|
require "pg"
|
153
|
-
connection = PG::Connection.new(config)
|
155
|
+
connection = Connection.new(PG::Connection.new(config))
|
154
156
|
self.connector = lambda { connection }
|
155
157
|
end
|
156
158
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# private
|
2
|
+
module Simple::SQL::Connection
|
3
|
+
def self.new(connection)
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
|
6
|
+
return ActiveRecordConnection.new(connection)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
SimpleConnection.new(connection)
|
11
|
+
end
|
12
|
+
|
13
|
+
class RawConnection
|
14
|
+
def initialize(raw_connection)
|
15
|
+
@raw_connection = raw_connection
|
16
|
+
end
|
17
|
+
|
18
|
+
extend Forwardable
|
19
|
+
delegate %w(exec_params exec escape) => :@raw_connection
|
20
|
+
|
21
|
+
def transaction(&block)
|
22
|
+
raise ArgumentError, "Implementation missing for #transaction"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class SimpleConnection < RawConnection
|
27
|
+
def initialize(raw_connection)
|
28
|
+
super(raw_connection)
|
29
|
+
@tx_nesting_level = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def transaction(&block)
|
35
|
+
# Notes: by using "ensure" (as opposed to rescue) we are rolling back
|
36
|
+
# both when an exception was raised and when a value was thrown. This
|
37
|
+
# also means we have to track whether or not to rollback. i.e. do roll
|
38
|
+
# back when we yielded to &block but not otherwise.
|
39
|
+
#
|
40
|
+
# Also the transaction support is a bit limited: you cannot rollback.
|
41
|
+
# Rolling back from inside a nested transaction would require SAVEPOINT
|
42
|
+
# support; without the code is simpler at least :)
|
43
|
+
|
44
|
+
if @tx_nesting_level == 0
|
45
|
+
exec "BEGIN"
|
46
|
+
tx_started = true
|
47
|
+
end
|
48
|
+
|
49
|
+
@tx_nesting_level += 1
|
50
|
+
|
51
|
+
return_value = yield
|
52
|
+
|
53
|
+
# Only commit if we started a transaction here.
|
54
|
+
if tx_started
|
55
|
+
exec "COMMIT"
|
56
|
+
tx_committed = true
|
57
|
+
end
|
58
|
+
|
59
|
+
return_value
|
60
|
+
ensure
|
61
|
+
@tx_nesting_level -= 1
|
62
|
+
if tx_started && !tx_committed
|
63
|
+
exec "ROLLBACK"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class ActiveRecordConnection < RawConnection
|
69
|
+
def initialize(connection)
|
70
|
+
super(connection.raw_connection)
|
71
|
+
@connection = connection
|
72
|
+
end
|
73
|
+
|
74
|
+
delegate :transaction => :@connection
|
75
|
+
end
|
76
|
+
end
|
data/lib/simple/sql/version.rb
CHANGED
data/tasks/release.rake
CHANGED
@@ -54,7 +54,7 @@ namespace :release do
|
|
54
54
|
Dir.chdir(GEM_ROOT) do
|
55
55
|
version = VersionNumberTracker.version
|
56
56
|
sh("git add #{VERSION_FILE_PATH}")
|
57
|
-
sh("git commit -m \"bump
|
57
|
+
sh("git commit -m \"bump to v#{version}\"")
|
58
58
|
sh("git tag -a v#{version} -m \"Tag\"")
|
59
59
|
end
|
60
60
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- radiospiel
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2018-01-
|
12
|
+
date: 2018-01-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pg_array_parser
|
@@ -165,12 +165,13 @@ files:
|
|
165
165
|
- Rakefile
|
166
166
|
- bin/rake
|
167
167
|
- config/database.yml
|
168
|
+
- lib/simple-sql.rb
|
168
169
|
- lib/simple/sql.rb
|
169
170
|
- lib/simple/sql/config.rb
|
171
|
+
- lib/simple/sql/connection.rb
|
170
172
|
- lib/simple/sql/decoder.rb
|
171
173
|
- lib/simple/sql/encoder.rb
|
172
174
|
- lib/simple/sql/logging.rb
|
173
|
-
- lib/simple/sql/transactions.rb
|
174
175
|
- lib/simple/sql/version.rb
|
175
176
|
- log/.gitkeep
|
176
177
|
- simple-sql.gemspec
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# private
|
2
|
-
module Simple::SQL::Transactions
|
3
|
-
SELF = self
|
4
|
-
|
5
|
-
def self.nesting_level
|
6
|
-
Thread.current[:nesting_level] ||= 0
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.nesting_level=(nesting_level)
|
10
|
-
Thread.current[:nesting_level] = nesting_level
|
11
|
-
end
|
12
|
-
|
13
|
-
def transaction(&block)
|
14
|
-
# Notes: by using "ensure" (as opposed to rescue) we are rolling back
|
15
|
-
# both when an exception was raised and when a value was thrown. This
|
16
|
-
# also means we have to track whether or not to rollback. i.e. do roll
|
17
|
-
# back when we yielded to &block but not otherwise.
|
18
|
-
#
|
19
|
-
# Also the transaction support is a bit limited: you cannot rollback.
|
20
|
-
# Rolling back from inside a nested transaction would require SAVEPOINT
|
21
|
-
# support; without the code is simpler at least :)
|
22
|
-
if SELF.nesting_level == 0
|
23
|
-
transaction_started = true
|
24
|
-
ask "BEGIN"
|
25
|
-
end
|
26
|
-
|
27
|
-
SELF.nesting_level += 1
|
28
|
-
|
29
|
-
return_value = yield
|
30
|
-
|
31
|
-
# Only commit if we started a transaction here.
|
32
|
-
if transaction_started
|
33
|
-
ask "COMMIT"
|
34
|
-
transaction_committed = true
|
35
|
-
end
|
36
|
-
|
37
|
-
return_value
|
38
|
-
ensure
|
39
|
-
SELF.nesting_level -= 1
|
40
|
-
if transaction_started && !transaction_committed
|
41
|
-
ask "ROLLBACK"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|