simple-sql 0.2.1 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 671eec226ce28823bcb0d2e749c3e11d7990ffca
4
- data.tar.gz: f3447b03aa97ee19fd1e3fefd566ed24b77f2a85
3
+ metadata.gz: 435908aca401185b06131575ae1b5c85570c6fe4
4
+ data.tar.gz: d238e14b1d859326b0ed4d7e9799d3717d31a821
5
5
  SHA512:
6
- metadata.gz: 1cf66f360cb3d5cd721d6f978dcdc385e6fbfac3cf3aecd40083d8d5f9a9a1b849cf52f7884a29ece948ff4ca1164bf1ccabab97076ecbc789b6e45888bbefc4
7
- data.tar.gz: d2dd45b2e4822fa591286030fd9bb874fd1a23fdc5ee93946c6859e64e4cf8413f4f880801baefdf86db96cf6b0a179221e1adb6b08f2f1be9d21f3afc8b6079
6
+ metadata.gz: f8a497b87236bb8f0cfe1f6e1db9577bbd6364e8826b41097adec7b235cddbff4cac3cd6535a832f8b6d75e452ea21af6ae64ece5f8f9e826d48adbd59087213
7
+ data.tar.gz: 98d60e38cfd0b60128da263bc8372428028e4ce31fb009e324498de16024802bc65a35e9676f863e379154c76fb56066b4a1f43a6561bd629c1fce6d33b41c3b
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple-sql (0.2.1)
4
+ simple-sql (0.2.2)
5
5
  pg (~> 0.20)
6
6
  pg_array_parser (~> 0)
7
7
 
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 through our private gem host:
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
- ```ruby
8
- gem 'simple-sql' # + version
9
- ```
16
+ gem 'simple-sql' # + version
10
17
 
11
18
  ## Usage
12
19
 
13
- This gem defines a module `Simple::SQL`, which you can use to execute SQL statements
14
- on the current ActiveRecord connection.
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
- Simple::SQL takes care of converting arguments back and forth.
59
+ ```ruby
60
+ Simple::SQL.all "SELECT * FROM users WHERE id = ANY($1)", [1,2,3]
61
+ ```
17
62
 
18
- ### Running a query
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
- In this case SQL.all returns `self`, which lets you chain function calls.
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
- ## Notes
102
+ ### Simple::SQL.record/Simple::SQL.records: fetching hashes
61
103
 
62
- Remember that Postgresql uses $1, $2, etc. as placeholders; the following is correct:
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
- ```ruby
65
- Simple::SQL.all "SELECT * FROM users WHERE email=$1", "foo@bar.local"
66
- ```
107
+ # returns a single Hash (or nil)
108
+ Simple::SQL.record("SELECT id FROM users")
67
109
 
68
- Also note that `IN(?)` is not supported by the Postgresql client library; instead you
69
- must use `= ANY`, for example:
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
- ```ruby
72
- Simple::SQL.all "SELECT * FROM users WHERE id = ANY($1)", [1,2,3]
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
 
@@ -0,0 +1 @@
1
+ require "simple/sql"
@@ -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.raw_connection if defined?(ActiveRecord)
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
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.2"
4
4
  end
5
5
  end
@@ -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 auth to v#{version}\"")
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.1
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-19 00:00:00.000000000 Z
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