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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a783cd49553e23ba7a8c35e1b1d23414fa9610c
4
- data.tar.gz: 36f31e34a174a42f42a584d4d9e3a3db3b19ecd9
3
+ metadata.gz: f507ec3c80d6ed2c58575898e2132bfa3e05ac36
4
+ data.tar.gz: 5c311e9a44d3274689c4c6f12dfc028a3e721328
5
5
  SHA512:
6
- metadata.gz: bd1b02760aabac6d0a688e6b548a4b845be2e7c779cb3282c3158bc82abe04bf9a29764d74a5d1b84e8cc81bee679a92191e6a80eb93e4a1bf80edf063bd5b78
7
- data.tar.gz: e747e1fea3787a2836268da4708cdcfa7233f9a3ffbed67147a74096b7c9a0fd3c83b3ea4c8dd444125b9232effd92ccccda93082769f3cf13da46a19936c10a
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 `EM::PG::Client::Helper` module. It contains a collection
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::PG::Client::Helper has more information on
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
 
@@ -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 "em-pg-client", "~> 0.3"
20
+ s.add_runtime_dependency "sequel", "~> 4.0"
20
21
 
21
22
  s.add_development_dependency 'bundler'
22
23
  s.add_development_dependency 'eventmachine'
@@ -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
- # @macro upsert_params
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
- # @!macro upsert_params
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
- # @!macro upsert_params
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
- # @returns [EM::Deferrable] A deferrable that will be completed when this
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
@@ -5,7 +5,7 @@ Spork.prefork do
5
5
  Bundler.setup(:default, :development, :test)
6
6
  require 'rspec/core'
7
7
  require 'rspec/mocks'
8
- require 'txn_helper'
8
+ require_relative './txn_helper'
9
9
 
10
10
  require 'pry'
11
11
 
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.2.0
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-01-27 00:00:00.000000000 Z
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: em-pg-client
42
+ name: sequel
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0.3'
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.3'
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