em-pg-client-helper 1.2.0 → 1.3.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 +4 -4
- data/README.md +2 -2
- data/em-pg-client-helper.gemspec +2 -1
- data/lib/em-pg-client-helper.rb +159 -5
- data/lib/em-pg-client-helper/transaction.rb +17 -1
- data/spec/db_transaction_sequel_spec.rb +35 -0
- data/spec/sequel_sql_spec.rb +21 -0
- data/spec/spec_helper.rb +1 -1
- metadata +21 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f507ec3c80d6ed2c58575898e2132bfa3e05ac36
|
4
|
+
data.tar.gz: 5c311e9a44d3274689c4c6f12dfc028a3e721328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b39ee8f0cdce4de503b1793ca26f13b0bd493cd53b80390a95a65018252af5d30c837f733013fb07678a8aab32caa62fceab8d0f6a26b8c70494df3185c2173
|
7
|
+
data.tar.gz: 0af1627ff7f3bac097818c5a91f5c71467f32aadfb18fef17dd939db9e360e1a20a8559a68421b5ae24f0d74153907ca9af62c3d3d5347ebf18fda5662239cf0
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ nice solution to the problem of accessing a PostgreSQL database from within
|
|
3
3
|
[EventMachine](http://rubyeventmachine.com/), it is somewhat... spartan.
|
4
4
|
ORMs have spoiled us somewhat, and so the appeal of hand-writing SQL and
|
5
5
|
dealing with the finer details of error handling has faded. Hence the
|
6
|
-
creation of the
|
6
|
+
creation of the {PG::EM::Client::Helper} module. It contains a collection
|
7
7
|
of useful helper methods that make it somewhat easier to perform common
|
8
8
|
operations against PgSQL databases.
|
9
9
|
|
@@ -32,7 +32,7 @@ Then add this line in any classes you wish to use the helper methods in:
|
|
32
32
|
|
33
33
|
include PG::EM::Client::Helper
|
34
34
|
|
35
|
-
The module documentation for EM::
|
35
|
+
The module documentation for {PG::EM::Client::Helper} has more information on
|
36
36
|
the available methods and how to use them.
|
37
37
|
|
38
38
|
|
data/em-pg-client-helper.gemspec
CHANGED
@@ -15,8 +15,9 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.extra_rdoc_files = ["README.md"]
|
16
16
|
s.files = `git ls-files`.split("\n")
|
17
17
|
|
18
|
+
s.add_runtime_dependency "em-pg-client", "~> 0.3"
|
18
19
|
s.add_runtime_dependency "git-version-bump", "~> 0.10"
|
19
|
-
s.add_runtime_dependency "
|
20
|
+
s.add_runtime_dependency "sequel", "~> 4.0"
|
20
21
|
|
21
22
|
s.add_development_dependency 'bundler'
|
22
23
|
s.add_development_dependency 'eventmachine'
|
data/lib/em-pg-client-helper.rb
CHANGED
@@ -1,11 +1,159 @@
|
|
1
1
|
require 'pg/em'
|
2
2
|
require 'pg/em/connection_pool'
|
3
|
+
require 'sequel'
|
3
4
|
|
4
5
|
# Some helper methods to make working with em-pg-client slightly less like
|
5
6
|
# trying to build a house with a packet of seeds and a particle
|
6
7
|
# accelerator... backwards.
|
7
8
|
#
|
8
9
|
module PG::EM::Client::Helper
|
10
|
+
# Sequel-based SQL generation.
|
11
|
+
#
|
12
|
+
# While we could spend a lot of time writing code to generate various
|
13
|
+
# kinds of SQL "by hand", it would be wasted effort, since the Sequel
|
14
|
+
# database toolkit gives us a complete, and *extremely* powerful, SQL
|
15
|
+
# generation system, which is already familiar to a great many
|
16
|
+
# programmers (or, at least, many great programmers).
|
17
|
+
#
|
18
|
+
# Hence, rather than reinvent the wheel, we simply drop Sequel in.
|
19
|
+
#
|
20
|
+
# Anything you can do with an instance of `Sequel::Database` that
|
21
|
+
# produces a single SQL query, you can almost certainly do with this
|
22
|
+
# method.
|
23
|
+
#
|
24
|
+
# Usage is quite simple: calling this method will yield a pseudo-database
|
25
|
+
# object to the block you pass. You can then call whatever methods you
|
26
|
+
# like against the database object, and when you're done and the block
|
27
|
+
# you passed completes, we'll return the SQL that Sequel generated.
|
28
|
+
#
|
29
|
+
# @yield [sqldb] to allow you to call whatever Sequel methods you like to
|
30
|
+
# generate the desired SQL.
|
31
|
+
#
|
32
|
+
# @yieldparam [Sequel::Database] call whatever you like against this to
|
33
|
+
# cause Sequel to generate the result you want.
|
34
|
+
#
|
35
|
+
# @return [String] the generated SQL.
|
36
|
+
#
|
37
|
+
# @raise [PG::EM::Client::Helper::BadSequelError] if your calls against
|
38
|
+
# the yielded database object would result in no SQL being generated,
|
39
|
+
# or more than one SQL statement.
|
40
|
+
#
|
41
|
+
# @example A simple select
|
42
|
+
# sequel(db) do |sqldb|
|
43
|
+
# sqldb[:artists]
|
44
|
+
# end
|
45
|
+
# # => "SELECT * FROM artists"
|
46
|
+
#
|
47
|
+
# @example Delete some rows
|
48
|
+
# sequel(db) do |sqldb|
|
49
|
+
# sqldb[:foo].where { { col.sql_number % 3 => 0 } }.delete
|
50
|
+
# end.callback do |res|
|
51
|
+
# logger.info "Deleted #{res.cmd_tuples}"
|
52
|
+
# end
|
53
|
+
# # => "DELETE FROM foo WHERE col % 3 = 0"
|
54
|
+
#
|
55
|
+
# @example A very complicated select
|
56
|
+
# sequel do |sqldb|
|
57
|
+
# sqldb[:posts].select_all(:posts).
|
58
|
+
# select_append(
|
59
|
+
# Sequel.
|
60
|
+
# function(:array_agg, :campaigns__name).
|
61
|
+
# distinct.
|
62
|
+
# as(:campaigns_names)
|
63
|
+
# ).
|
64
|
+
# join(:posts_terms, :post_id => :posts__id).
|
65
|
+
# join(:terms, :id => :posts_terms__term_id).
|
66
|
+
# join(:categories_terms, :term_id => :terms__id).
|
67
|
+
# join(:campaigns_categories, :category_id => :categories_terms__category_id).
|
68
|
+
# join(:campaigns, :id => :campaigns_categories__campaign_id).
|
69
|
+
# group_by(:posts__id)
|
70
|
+
# end
|
71
|
+
# # ... you don't want to know.
|
72
|
+
#
|
73
|
+
def sequel_sql
|
74
|
+
sqldb = Sequel.connect("mock://postgres")
|
75
|
+
ret = yield sqldb if block_given?
|
76
|
+
sqls = sqldb.sqls
|
77
|
+
|
78
|
+
if sqls.empty?
|
79
|
+
sqls = [ret.sql] rescue []
|
80
|
+
end
|
81
|
+
|
82
|
+
if sqls.empty?
|
83
|
+
raise PG::EM::Client::Helper::BadSequelError,
|
84
|
+
"Your block did not generate an SQL statement"
|
85
|
+
end
|
86
|
+
|
87
|
+
if sqls.length > 1
|
88
|
+
raise PG::EM::Client::Helper::BadSequelError,
|
89
|
+
"Your block generated multiple SQL statements"
|
90
|
+
end
|
91
|
+
|
92
|
+
sqls.first
|
93
|
+
end
|
94
|
+
|
95
|
+
# Generate Sequel, and run it against the database connection provided.
|
96
|
+
#
|
97
|
+
# This is the all-singing variant of {#sequel_sql} -- in addition to
|
98
|
+
# generating the SQL, we also run the result against the database
|
99
|
+
# connection you pass.
|
100
|
+
#
|
101
|
+
# @see {#sequel_sql}
|
102
|
+
#
|
103
|
+
# @yield [sqldb] to allow you to call whatever sequel methods you like to
|
104
|
+
# generate the desired SQL.
|
105
|
+
#
|
106
|
+
# @yieldparam [Sequel::Database] call whatever you like against this to
|
107
|
+
# cause Sequel to generate the result you want.
|
108
|
+
#
|
109
|
+
# @return [EM::Deferrable] the callbacks attached to this deferrable will
|
110
|
+
# receive a `PG::Result` when the query completes successfully, or else
|
111
|
+
# the errbacks on this deferrable will be called in the event of an
|
112
|
+
# error.
|
113
|
+
#
|
114
|
+
# @raise [PG::EM::Client::Helper::BadSequelError] if your calls against
|
115
|
+
# the yielded database object would result in no SQL being generated,
|
116
|
+
# or more than one SQL statement.
|
117
|
+
#
|
118
|
+
# @example A simple select
|
119
|
+
# sequel(db) do |sqldb|
|
120
|
+
# sqldb[:artists]
|
121
|
+
# end.callback do |res|
|
122
|
+
# ...
|
123
|
+
# end.errback do |ex|
|
124
|
+
# logger.error "Query failed (#{ex.class}): #{ex.message}"
|
125
|
+
# end
|
126
|
+
#
|
127
|
+
# @example A very complicated select
|
128
|
+
# sequel do |sqldb|
|
129
|
+
# sqldb[:posts].select_all(:posts).
|
130
|
+
# select_append(
|
131
|
+
# Sequel.
|
132
|
+
# function(:array_agg, :campaigns__name).
|
133
|
+
# distinct.
|
134
|
+
# as(:campaigns_names)
|
135
|
+
# ).
|
136
|
+
# join(:posts_terms, :post_id => :posts__id).
|
137
|
+
# join(:terms, :id => :posts_terms__term_id).
|
138
|
+
# join(:categories_terms, :term_id => :terms__id).
|
139
|
+
# join(:campaigns_categories, :category_id => :categories_terms__category_id).
|
140
|
+
# join(:campaigns, :id => :campaigns_categories__campaign_id).
|
141
|
+
# group_by(:posts__id)
|
142
|
+
# end.callback do |res|
|
143
|
+
# ...
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# @example Delete some rows
|
147
|
+
# sequel(db) do |sqldb|
|
148
|
+
# sqldb[:foo].where { { col.sql_number % 3 => 0 } }.delete
|
149
|
+
# end.callback do |res|
|
150
|
+
# logger.info "Deleted #{res.cmd_tuples}"
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
def db_sequel(db, &blk)
|
154
|
+
db.exec_defer(sequel_sql(&blk))
|
155
|
+
end
|
156
|
+
|
9
157
|
# Generate SQL for an insert statement into `tbl`, with the fields and
|
10
158
|
# data given by the keys and values, respectively, of `params`. Returns
|
11
159
|
# a two-element array consisting of the parameterised SQL as the first
|
@@ -47,7 +195,7 @@ module PG::EM::Client::Helper
|
|
47
195
|
db.exec_defer(*insert_sql(tbl, params))
|
48
196
|
end
|
49
197
|
|
50
|
-
#
|
198
|
+
# @!macro upsert_params
|
51
199
|
#
|
52
200
|
# @param tbl [#to_s] The name of the table on which to operate.
|
53
201
|
#
|
@@ -62,8 +210,7 @@ module PG::EM::Client::Helper
|
|
62
210
|
#
|
63
211
|
# @raise [ArgumentError] if a field is specified in `key` but which
|
64
212
|
# does not exist in `data`.
|
65
|
-
|
66
|
-
#
|
213
|
+
|
67
214
|
# An "upsert" is a kind of crazy hybrid "update if the record exists,
|
68
215
|
# insert it if it doesn't" query. It isn't part of the SQL standard,
|
69
216
|
# but it is such a common idiom that we're keen to support it.
|
@@ -77,7 +224,7 @@ module PG::EM::Client::Helper
|
|
77
224
|
# As an added bonus, the SQL that this method generates will, when executed,
|
78
225
|
# return the complete row that has been inserted or updated.
|
79
226
|
#
|
80
|
-
#
|
227
|
+
# @macro upsert_params
|
81
228
|
#
|
82
229
|
# @return [Array<String, Array<Object>>] A two-element array, the first
|
83
230
|
# of which is a string containing the literal SQL to be executed, while
|
@@ -128,7 +275,7 @@ module PG::EM::Client::Helper
|
|
128
275
|
# @param db [PG::EM::Client, PG::EM::ConnectionPool] the connection
|
129
276
|
# against which all database operations will be run.
|
130
277
|
#
|
131
|
-
#
|
278
|
+
# @macro upsert_params
|
132
279
|
#
|
133
280
|
# @return [EM::Deferrable] the deferrable in which the query is being
|
134
281
|
# called; this means you should attach the code to run after the query
|
@@ -236,6 +383,13 @@ module PG::EM::Client::Helper
|
|
236
383
|
def quote_identifier(id)
|
237
384
|
"\"#{id.gsub(/"/, '""')}\""
|
238
385
|
end
|
386
|
+
|
387
|
+
# Indicates that the sequel mock-database was not manipulated in such a way
|
388
|
+
# as to produce exactly one SQL statement.
|
389
|
+
#
|
390
|
+
# @see {#sequel_sql}
|
391
|
+
#
|
392
|
+
class BadSequelError < StandardError; end
|
239
393
|
end
|
240
394
|
|
241
395
|
require 'em-pg-client-helper/transaction'
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# Represents a database transaction, and contains all of the methods which
|
2
|
+
# can be used to execute queries within the transaction connection.
|
3
|
+
#
|
1
4
|
class PG::EM::Client::Helper::Transaction
|
2
5
|
include ::PG::EM::Client::Helper
|
3
6
|
include ::EventMachine::Deferrable
|
@@ -108,6 +111,17 @@ class PG::EM::Client::Helper::Transaction
|
|
108
111
|
end
|
109
112
|
end
|
110
113
|
|
114
|
+
# Generate SQL statements via Sequel, and run the result against the
|
115
|
+
# database. Very chic.
|
116
|
+
#
|
117
|
+
# @see {PG::EM::Client::Helper#sequel_sql}
|
118
|
+
#
|
119
|
+
# @return [EM::Deferrable]
|
120
|
+
#
|
121
|
+
def sequel(&blk)
|
122
|
+
exec(sequel_sql(&blk))
|
123
|
+
end
|
124
|
+
|
111
125
|
# Insert a row of data into the database table `tbl`, using the keys
|
112
126
|
# from the `params` hash as the field names, and the values from the
|
113
127
|
# `params` hash as the field data. Once the query has completed,
|
@@ -139,7 +153,7 @@ class PG::EM::Client::Helper::Transaction
|
|
139
153
|
# given block will be called if and when the query completes
|
140
154
|
# successfully.
|
141
155
|
#
|
142
|
-
# @
|
156
|
+
# @return [EM::Deferrable] A deferrable that will be completed when this
|
143
157
|
# specific query finishes.
|
144
158
|
#
|
145
159
|
def exec(sql, values=[], &blk)
|
@@ -158,6 +172,8 @@ class PG::EM::Client::Helper::Transaction
|
|
158
172
|
end
|
159
173
|
alias_method :exec_defer, :exec
|
160
174
|
|
175
|
+
# Trace queries as they happen, if `ENV['EM_PG_TXN_TRACE']` is set.
|
176
|
+
#
|
161
177
|
def trace_query(q, v=nil)
|
162
178
|
$stderr.puts "#{@conn.inspect}: #{q} #{v.inspect}" if ENV['EM_PG_TXN_TRACE']
|
163
179
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative './spec_helper'
|
2
|
+
|
3
|
+
describe "PG::EM::Client::Helper::Transaction#sequel" do
|
4
|
+
let(:mock_conn) { double(PG::EM::Client) }
|
5
|
+
|
6
|
+
it "runs a simple UPSERT correctly" do
|
7
|
+
in_em do
|
8
|
+
expect_query("BEGIN")
|
9
|
+
expect_query("SELECT * FROM \"foo\"")
|
10
|
+
expect_query("COMMIT")
|
11
|
+
in_transaction do |txn|
|
12
|
+
txn.sequel do |db|
|
13
|
+
db[:foo]
|
14
|
+
end.callback do
|
15
|
+
txn.commit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "rolls back after a failed attempt" do
|
22
|
+
in_em do
|
23
|
+
expect_query("BEGIN")
|
24
|
+
expect_query_failure("SELECT * FROM \"foo\"")
|
25
|
+
expect_query("ROLLBACK")
|
26
|
+
in_transaction do |txn|
|
27
|
+
txn.sequel do |db|
|
28
|
+
db[:foo]
|
29
|
+
end.callback do
|
30
|
+
txn.commit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative './spec_helper'
|
2
|
+
|
3
|
+
describe "PG::EM::Client::Helper#sequel_sql" do
|
4
|
+
it "assembles a simple post-return query correctly" do
|
5
|
+
expect(sequel_sql { |db| db[:foo] }).
|
6
|
+
to eq("SELECT * FROM \"foo\"")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "assembles a pre-complete query correctly" do
|
10
|
+
expect(
|
11
|
+
sequel_sql do |db|
|
12
|
+
db[:foo].where { id > 20 }.delete
|
13
|
+
end
|
14
|
+
).to eq("DELETE FROM \"foo\" WHERE (\"id\" > 20)")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "bombs if we don't do anything" do
|
18
|
+
expect { sequel_sql }.
|
19
|
+
to raise_error(PG::EM::Client::Helper::BadSequelError)
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: em-pg-client-helper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: em-pg-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: git-version-bump
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -25,19 +39,19 @@ dependencies:
|
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0.10'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: sequel
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0
|
47
|
+
version: '4.0'
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0
|
54
|
+
version: '4.0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bundler
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -217,10 +231,12 @@ files:
|
|
217
231
|
- lib/em-pg-client-helper/deferrable_group.rb
|
218
232
|
- lib/em-pg-client-helper/transaction.rb
|
219
233
|
- spec/db_insert_spec.rb
|
234
|
+
- spec/db_transaction_sequel_spec.rb
|
220
235
|
- spec/db_transaction_spec.rb
|
221
236
|
- spec/db_transaction_upsert_spec.rb
|
222
237
|
- spec/db_upsert_spec.rb
|
223
238
|
- spec/insert_sql_spec.rb
|
239
|
+
- spec/sequel_sql_spec.rb
|
224
240
|
- spec/spec_helper.rb
|
225
241
|
- spec/txn_helper.rb
|
226
242
|
- spec/upsert_sql_spec.rb
|