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 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