em-pg-client-helper 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|