jdbc-helper 0.7.6 → 0.7.7
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.
- data/CHANGELOG.markdown +8 -0
- data/README.markdown +48 -12
- data/Rakefile +0 -8
- data/jdbc-helper.gemspec +4 -4
- data/lib/jdbc-helper/connection.rb +46 -86
- data/lib/jdbc-helper/connection/callable_statement.rb +6 -6
- data/lib/jdbc-helper/connection/parameterized_statement.rb +8 -11
- data/lib/jdbc-helper/connection/prepared_statement.rb +11 -11
- data/lib/jdbc-helper/connection/result_set_enumerator.rb +3 -2
- data/lib/jdbc-helper/connection/row.rb +5 -0
- data/lib/jdbc-helper/connector/mssql.rb +1 -1
- data/lib/jdbc-helper/connector/mysql.rb +1 -1
- data/lib/jdbc-helper/connector/oracle.rb +2 -2
- data/lib/jdbc-helper/connector/postgresql.rb +1 -1
- data/lib/jdbc-helper/sql/expression.rb +2 -2
- data/lib/jdbc-helper/sql/sql.rb +3 -3
- data/lib/jdbc-helper/sql/sql_prepared.rb +5 -12
- data/lib/jdbc-helper/version.rb +1 -1
- data/lib/jdbc-helper/wrapper/function_wrapper.rb +1 -1
- data/lib/jdbc-helper/wrapper/procedure_wrapper.rb +7 -2
- data/lib/jdbc-helper/wrapper/table_wrapper.rb +58 -10
- data/test/helper.rb +5 -0
- data/test/test_connection.rb +36 -18
- data/test/test_object_wrapper.rb +61 -13
- metadata +23 -22
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
### 0.7.7 / 2013/01/0?
|
2
|
+
* `PreparedStatment`s and `TableWrapper`s now inherit the fetch size of the connection
|
3
|
+
* Added `JDBCHelper::TableWrapper#fetch_size`
|
4
|
+
* Added `JDBCHelper::TableWrapper#execute_batch`
|
5
|
+
* Added `JDBCHelper::TableWrapper#clear_batch`
|
6
|
+
* `execute_batch` method returns the sum of all update counts
|
7
|
+
* Removed undocumented operation statistics
|
8
|
+
|
1
9
|
### 0.7.6 / 2012/08/26
|
2
10
|
* Added FileMaker Pro connector (statonjr)
|
3
11
|
|
data/README.markdown
CHANGED
@@ -108,13 +108,16 @@ conn.query("SELECT a, b, c FROM T") do |row|
|
|
108
108
|
row.labels
|
109
109
|
row.rownum
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
row[
|
114
|
-
row[
|
111
|
+
a, b, c = row
|
112
|
+
a, b, c = row.a, row.b, row.c # Dot-notation
|
113
|
+
a, b, c = row[0], row[1], row[2] # Numeric index
|
114
|
+
a, b, c = row['a'], row['b'], row['c'] # String index. Case-insensitive.
|
115
|
+
a, b, c = row[:a], row[:b], row[:c] # Symbol index. Case-insensitive.
|
115
116
|
|
116
117
|
row[0..-1] # Range index. Returns an array of values.
|
117
118
|
row[0, 3] # Offset and length. Returns an array of values.
|
119
|
+
|
120
|
+
row.to_h # Row as a Hash
|
118
121
|
end
|
119
122
|
|
120
123
|
# Returns an array of rows when block is not given
|
@@ -199,6 +202,15 @@ p_upd.execute_batch
|
|
199
202
|
p_upd.close
|
200
203
|
```
|
201
204
|
|
205
|
+
### Accessing underlying Java object with `java` method
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
conn.java.setAutoCommit false
|
209
|
+
|
210
|
+
pstmt = conn.prepare(sql)
|
211
|
+
pstmt.java.getMetaData
|
212
|
+
```
|
213
|
+
|
202
214
|
### Using table wrappers (since 0.2.0)
|
203
215
|
```ruby
|
204
216
|
# For more complex examples, refer to test/test_object_wrapper.rb
|
@@ -206,6 +218,8 @@ SQL = JDBCHelper::SQL
|
|
206
218
|
|
207
219
|
# Creates a table wrapper
|
208
220
|
table = conn.table('test.data')
|
221
|
+
# Or equievalently,
|
222
|
+
table = conn['test.data']
|
209
223
|
|
210
224
|
# Counting the records in the table
|
211
225
|
table.count
|
@@ -215,8 +229,8 @@ table.where(:a => 10).count
|
|
215
229
|
table.empty?
|
216
230
|
table.where(:a => 10).empty?
|
217
231
|
|
218
|
-
# Selects the table by combining select, where, and
|
219
|
-
table.select('a apple', :b).where(:c => (1..10)).order('b desc', 'a asc') do |row|
|
232
|
+
# Selects the table by combining select, where, order and fetch_size methods
|
233
|
+
table.select('a apple', :b).where(:c => (1..10)).order('b desc', 'a asc').fetch_size(100).each do |row|
|
220
234
|
puts row.apple
|
221
235
|
end
|
222
236
|
|
@@ -239,7 +253,8 @@ with_defaults.where('a != 10 or b != 20').update # sets a => 10, b => 20
|
|
239
253
|
# Batch updates with batch method
|
240
254
|
table.batch.insert(:a => 10, :b => 20, :c => SQL.expr('10 + 20'))
|
241
255
|
table.batch.insert_ignore(:a => 10, :b => 20, :c => 30)
|
242
|
-
|
256
|
+
table.batch.where(:a => 10).update(:a => 20)
|
257
|
+
table.execute_batch :insert, :update
|
243
258
|
|
244
259
|
# Delete with conditions
|
245
260
|
table.delete(:c => 3)
|
@@ -275,27 +290,48 @@ scope = table.where(
|
|
275
290
|
scope.update(:a => 'xyz')
|
276
291
|
```
|
277
292
|
|
278
|
-
#### Invalid use of
|
293
|
+
#### Invalid use of plain String conditions
|
279
294
|
|
280
|
-
TableWrapper object internally
|
281
|
-
|
282
|
-
|
295
|
+
A TableWrapper object internally builds SQL strings
|
296
|
+
and creates JDBC PreparedStatement object for each distinct SQL.
|
297
|
+
|
298
|
+
If you build many number of where-clause Strings as shown in the following code,
|
299
|
+
soon there will be too many open PreparedStatements,
|
300
|
+
and if the number exceeds the system limit, an error will be thrown.
|
283
301
|
|
284
302
|
```ruby
|
303
|
+
table = connection['table']
|
304
|
+
|
305
|
+
# Leads to 10000 PreparedStatements !!
|
285
306
|
10000.times do |idx|
|
286
307
|
table.count("id = #{idx}")
|
308
|
+
# select count(*) from table where id = 0
|
309
|
+
# select count(*) from table where id = 1
|
310
|
+
# select count(*) from table where id = 2
|
311
|
+
# select count(*) from table where id = 3
|
312
|
+
# ...
|
287
313
|
end
|
288
314
|
```
|
289
315
|
|
290
|
-
|
316
|
+
In that case, you can `close` the table wrapper to close all the open PreparedStatements.
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
table.close
|
320
|
+
```
|
321
|
+
|
322
|
+
However, you should always prefer using much more efficient Hash or Array expression over plain String,
|
323
|
+
so you don't have to worry about the proliferation of PreparedStatements.
|
291
324
|
|
292
325
|
```ruby
|
326
|
+
# 20000 queries but only a single PreparedStatement
|
293
327
|
10000.times do |idx|
|
294
328
|
# 1. with Hash
|
295
329
|
table.count('id' => idx)
|
330
|
+
# select count(*) from table where id = ?
|
296
331
|
|
297
332
|
# 2. with Array
|
298
333
|
table.count(["id = ?", idx])
|
334
|
+
# select count(*) from table where id = ?
|
299
335
|
end
|
300
336
|
```
|
301
337
|
|
data/Rakefile
CHANGED
@@ -14,11 +14,3 @@ Rake::TestTask.new(:performance) do |test|
|
|
14
14
|
test.verbose = true
|
15
15
|
end
|
16
16
|
|
17
|
-
require 'rcov/rcovtask'
|
18
|
-
Rcov::RcovTask.new do |test|
|
19
|
-
test.libs << 'test'
|
20
|
-
test.pattern = 'test/**/test_*.rb'
|
21
|
-
test.rcov_opts << '--exclude rcov'
|
22
|
-
test.verbose = true
|
23
|
-
end
|
24
|
-
|
data/jdbc-helper.gemspec
CHANGED
@@ -15,8 +15,8 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = JDBCHelper::VERSION
|
17
17
|
|
18
|
-
gem.add_runtime_dependency 'insensitive_hash', '>= 0.2.4'
|
19
|
-
gem.add_development_dependency "bundler"
|
20
|
-
gem.add_development_dependency "
|
21
|
-
gem.add_development_dependency "test-unit"
|
18
|
+
gem.add_runtime_dependency 'insensitive_hash', '>= 0.2.4'
|
19
|
+
gem.add_development_dependency "bundler"
|
20
|
+
gem.add_development_dependency "simplecov"
|
21
|
+
gem.add_development_dependency "test-unit"
|
22
22
|
end
|
@@ -25,17 +25,17 @@ module JDBCHelper
|
|
25
25
|
# @example Prerequisites
|
26
26
|
# # Add JDBC driver of the DBMS you're willing to use to your CLASSPATH
|
27
27
|
# export CLASSPATH=$CLASSPATH:~/lib/mysql-connector-java.jar
|
28
|
-
#
|
29
|
-
#
|
28
|
+
#
|
29
|
+
#
|
30
30
|
# @example Connecting to a database
|
31
|
-
#
|
31
|
+
#
|
32
32
|
# # :driver and :url must be given
|
33
33
|
# conn = JDBCHelper::Connection.new(
|
34
34
|
# :driver => 'com.mysql.jdbc.Driver',
|
35
35
|
# :url => 'jdbc:mysql://localhost/test')
|
36
36
|
# conn.close
|
37
|
-
#
|
38
|
-
#
|
37
|
+
#
|
38
|
+
#
|
39
39
|
# # Optional :user and :password
|
40
40
|
# conn = JDBCHelper::Connection.new(
|
41
41
|
# :driver => 'com.mysql.jdbc.Driver',
|
@@ -43,12 +43,12 @@ module JDBCHelper
|
|
43
43
|
# :user => 'mysql',
|
44
44
|
# :password => '')
|
45
45
|
# conn.close
|
46
|
-
#
|
47
|
-
#
|
46
|
+
#
|
47
|
+
#
|
48
48
|
# # MySQL shortcut connector
|
49
49
|
# conn = JDBCHelper::MySQLConnector.connect('localhost', 'mysql', '', 'test')
|
50
50
|
# conn.close
|
51
|
-
#
|
51
|
+
#
|
52
52
|
# @example Querying database table
|
53
53
|
#
|
54
54
|
# conn.query("SELECT a, b, c FROM T") do | row |
|
@@ -59,11 +59,11 @@ module JDBCHelper
|
|
59
59
|
# puts row[0], row[1], row[2]
|
60
60
|
# puts row['a'], row['b'], row['c']
|
61
61
|
# end
|
62
|
-
#
|
62
|
+
#
|
63
63
|
# # Returns an array of rows when block is not given
|
64
64
|
# rows = conn.query("SELECT b FROM T")
|
65
65
|
# uniq_rows = rows.uniq
|
66
|
-
#
|
66
|
+
#
|
67
67
|
# # You can even nest queries
|
68
68
|
# conn.query("SELECT a FROM T") do | row1 |
|
69
69
|
# conn.query("SELECT * FROM T_#{row1.a}") do | row2 |
|
@@ -72,26 +72,26 @@ module JDBCHelper
|
|
72
72
|
# end
|
73
73
|
# @example Updating database table
|
74
74
|
# del_count = conn.update("DELETE FROM T")
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# @example Transaction
|
77
77
|
# committed = conn.transaction do | tx |
|
78
78
|
# # ...
|
79
79
|
# # Transaction logic here
|
80
80
|
# # ...
|
81
|
-
#
|
81
|
+
#
|
82
82
|
# if success
|
83
83
|
# tx.commit
|
84
84
|
# else
|
85
85
|
# tx.rollback
|
86
86
|
# end
|
87
87
|
# end
|
88
|
-
#
|
88
|
+
#
|
89
89
|
# @example Using batch interface
|
90
90
|
# conn.add_batch("DELETE FROM T");
|
91
91
|
# conn.execute_batch
|
92
92
|
# conn.add_batch("DELETE FROM T");
|
93
93
|
# conn.clear_batch
|
94
|
-
#
|
94
|
+
#
|
95
95
|
# @example Using prepared statements
|
96
96
|
# p_sel = conn.prepare("SELECT * FROM T WHERE b = ? and c = ?")
|
97
97
|
# p_sel.query(100, 200) do | row |
|
@@ -111,16 +111,6 @@ module JDBCHelper
|
|
111
111
|
# p_upd.execute_batch
|
112
112
|
# p_upd.close
|
113
113
|
class Connection
|
114
|
-
# Returns the statistics of the previous operation
|
115
|
-
# @return [JDBCHelper::Connection::Stat] The statistics of the previous operation.
|
116
|
-
def prev_stat
|
117
|
-
@prev_stat.dup
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns the accumulated statistics of each operation
|
121
|
-
# @return [Hash] Accumulated statistics of each type of operation
|
122
|
-
attr_reader :stats
|
123
|
-
|
124
114
|
# JDBC URL of the connection
|
125
115
|
# @return [String]
|
126
116
|
attr_reader :url
|
@@ -135,6 +125,7 @@ class Connection
|
|
135
125
|
@conn
|
136
126
|
end
|
137
127
|
alias java_obj jdbc_conn
|
128
|
+
alias java jdbc_conn
|
138
129
|
|
139
130
|
# Creates a database connection.
|
140
131
|
# - `args` hash must include :driver (or "driver") and :url (or "url")
|
@@ -163,23 +154,21 @@ class Connection
|
|
163
154
|
if timeout.is_a?(Fixnum) == false || timeout <= 0
|
164
155
|
raise ArgumentError.new("Timeout must be a positive integer")
|
165
156
|
end
|
166
|
-
java.sql.DriverManager.setLoginTimeout timeout
|
157
|
+
Java::java.sql.DriverManager.setLoginTimeout timeout
|
167
158
|
end
|
168
159
|
|
169
|
-
props = java.util.Properties.new
|
160
|
+
props = Java::java.util.Properties.new
|
170
161
|
args.each do |k, v|
|
171
162
|
props.setProperty(k.to_s, v.to_s) if v
|
172
163
|
end
|
173
|
-
|
174
|
-
@conn = java.sql.DriverManager.get_connection(@url, props)
|
164
|
+
|
165
|
+
@conn = Java::java.sql.DriverManager.get_connection(@url, props)
|
175
166
|
@spool = StatementPool.send :new, self
|
176
167
|
@bstmt = nil
|
177
|
-
|
178
|
-
@stats = Hash.new { | h, k | h[k] = Stat.new(k, 0, 0, 0) }
|
179
|
-
@prev_stat = Stat.new(nil, 0, 0, 0)
|
168
|
+
@fetch_size = nil
|
180
169
|
|
181
170
|
@pstmts = []
|
182
|
-
|
171
|
+
|
183
172
|
@table_wrappers = {}
|
184
173
|
|
185
174
|
if block_given?
|
@@ -204,8 +193,9 @@ class Connection
|
|
204
193
|
def prepare(qstr)
|
205
194
|
check_closed
|
206
195
|
|
207
|
-
pstmt = PreparedStatement.send(:new, self, qstr,
|
208
|
-
|
196
|
+
pstmt = PreparedStatement.send(:new, self, qstr, @conn.prepare_statement(qstr))
|
197
|
+
pstmt.set_fetch_size @fetch_size if @fetch_size
|
198
|
+
|
209
199
|
@pstmts << pstmt
|
210
200
|
pstmt
|
211
201
|
end
|
@@ -220,8 +210,7 @@ class Connection
|
|
220
210
|
def prepare_call(qstr)
|
221
211
|
check_closed
|
222
212
|
|
223
|
-
CallableStatement.send(:new, self, qstr,
|
224
|
-
measure_exec(:prepare_call) { @conn.prepare_call qstr })
|
213
|
+
CallableStatement.send(:new, self, qstr, @conn.prepare_call(qstr))
|
225
214
|
end
|
226
215
|
|
227
216
|
# Executes the given code block as a transaction. Returns true if the transaction is committed.
|
@@ -252,7 +241,7 @@ class Connection
|
|
252
241
|
status == :committed
|
253
242
|
end
|
254
243
|
|
255
|
-
# Executes an SQL and returns the count of the update rows or a ResultSetEnumerator object
|
244
|
+
# Executes an SQL and returns the count of the update rows or a ResultSetEnumerator object
|
256
245
|
# depending on the type of the given statement.
|
257
246
|
# If a ResultSetEnumerator is returned, it must be enumerated or closed.
|
258
247
|
# @param [String] qstr SQL string
|
@@ -262,7 +251,7 @@ class Connection
|
|
262
251
|
|
263
252
|
stmt = @spool.take
|
264
253
|
begin
|
265
|
-
if
|
254
|
+
if stmt.execute(qstr)
|
266
255
|
ResultSetEnumerator.send(:new, stmt.getResultSet) { @spool.give stmt }
|
267
256
|
else
|
268
257
|
rset = stmt.getUpdateCount
|
@@ -282,7 +271,7 @@ class Connection
|
|
282
271
|
check_closed
|
283
272
|
|
284
273
|
@spool.with do | stmt |
|
285
|
-
ret =
|
274
|
+
ret = stmt.execute_update(qstr)
|
286
275
|
end
|
287
276
|
end
|
288
277
|
|
@@ -306,7 +295,7 @@ class Connection
|
|
306
295
|
check_closed
|
307
296
|
|
308
297
|
@spool.with do | stmt |
|
309
|
-
rset =
|
298
|
+
rset = stmt.execute_query(qstr)
|
310
299
|
process_and_close_rset(rset, &blk)
|
311
300
|
end
|
312
301
|
end
|
@@ -332,7 +321,7 @@ class Connection
|
|
332
321
|
|
333
322
|
stmt = @spool.take
|
334
323
|
begin
|
335
|
-
rset =
|
324
|
+
rset = stmt.execute_query(qstr)
|
336
325
|
return ResultSetEnumerator.send(:new, rset) { @spool.give stmt }
|
337
326
|
rescue Exception
|
338
327
|
@spool.give stmt
|
@@ -351,20 +340,24 @@ class Connection
|
|
351
340
|
@bstmt.add_batch qstr
|
352
341
|
end
|
353
342
|
|
354
|
-
# Executes batched statements including prepared statements. No effect when no
|
355
|
-
# @return [
|
343
|
+
# Executes batched statements including prepared statements. No effect when no statement is added
|
344
|
+
# @return [Fixnum] Sum of all update counts
|
356
345
|
def execute_batch
|
357
346
|
check_closed
|
358
347
|
|
348
|
+
cnt = 0
|
349
|
+
|
359
350
|
if @bstmt
|
360
|
-
|
351
|
+
cnt += @bstmt.execute_batch.inject(:+) || 0
|
361
352
|
@spool.give @bstmt
|
362
353
|
@bstmt = nil
|
363
354
|
end
|
364
355
|
|
365
|
-
@pstmts.each do |
|
366
|
-
|
356
|
+
@pstmts.each do |pstmt|
|
357
|
+
cnt += pstmt.execute_batch
|
367
358
|
end
|
359
|
+
|
360
|
+
cnt
|
368
361
|
end
|
369
362
|
|
370
363
|
# Clears the batched statements including prepared statements.
|
@@ -379,7 +372,7 @@ class Connection
|
|
379
372
|
end
|
380
373
|
|
381
374
|
@pstmts.each do |stmt|
|
382
|
-
|
375
|
+
stmt.clear_batch
|
383
376
|
end
|
384
377
|
end
|
385
378
|
|
@@ -419,8 +412,11 @@ class Connection
|
|
419
412
|
# @param [String/Symbol] table_name Name of the table to be wrapped
|
420
413
|
# @return [JDBCHelper::TableWrapper]
|
421
414
|
def table table_name
|
422
|
-
|
415
|
+
table = JDBCHelper::TableWrapper.new(self, table_name)
|
416
|
+
table = table.fetch_size(@fetch_size) if @fetch_size
|
417
|
+
@table_wrappers[table_name] ||= table
|
423
418
|
end
|
419
|
+
alias [] table
|
424
420
|
|
425
421
|
# Returns a sequence wrapper for the given name
|
426
422
|
# @since 0.4.2
|
@@ -446,24 +442,13 @@ class Connection
|
|
446
442
|
JDBCHelper::ProcedureWrapper.new self, proc_name
|
447
443
|
end
|
448
444
|
|
445
|
+
# @return [String]
|
449
446
|
def inspect
|
450
447
|
InsensitiveHash[@args].merge({ :closed? => closed? }).tap { |c|
|
451
448
|
c.delete(:password)
|
452
449
|
}.inspect
|
453
450
|
end
|
454
451
|
|
455
|
-
# Statistics
|
456
|
-
class Stat
|
457
|
-
attr_accessor :type, :elapsed, :success_count, :fail_count
|
458
|
-
|
459
|
-
def initialize(t, e, s, f)
|
460
|
-
self.type = t
|
461
|
-
self.elapsed = e
|
462
|
-
self.success_count = s
|
463
|
-
self.fail_count = f
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
452
|
private
|
468
453
|
# Transaction object passed to the code block given to transaction method
|
469
454
|
class Transaction
|
@@ -479,7 +464,7 @@ private
|
|
479
464
|
@conn.rollback
|
480
465
|
raise Rollback
|
481
466
|
end
|
482
|
-
private
|
467
|
+
private
|
483
468
|
def initialize(conn) # :nodoc:
|
484
469
|
@conn = conn
|
485
470
|
end
|
@@ -517,31 +502,6 @@ private
|
|
517
502
|
@pstmts.delete pstmt
|
518
503
|
end
|
519
504
|
|
520
|
-
def update_stat(type, elapsed, success_count, fail_count) # :nodoc:
|
521
|
-
@prev_stat.type = type
|
522
|
-
@prev_stat.elapsed = elapsed
|
523
|
-
@prev_stat.success_count = success_count
|
524
|
-
@prev_stat.fail_count = fail_count
|
525
|
-
|
526
|
-
accum = @stats[type]
|
527
|
-
accum.elapsed += elapsed
|
528
|
-
accum.success_count += success_count
|
529
|
-
accum.fail_count += fail_count
|
530
|
-
end
|
531
|
-
|
532
|
-
def measure_exec(type)
|
533
|
-
begin
|
534
|
-
st = Time.now
|
535
|
-
ret = yield
|
536
|
-
elapsed = Time.now - st
|
537
|
-
update_stat(type, elapsed, 1, 0)
|
538
|
-
rescue Exception
|
539
|
-
update_stat(type, 0, 0, 1)
|
540
|
-
raise
|
541
|
-
end
|
542
|
-
ret
|
543
|
-
end
|
544
|
-
|
545
505
|
def check_closed
|
546
506
|
raise RuntimeError.new('Connection already closed') if closed?
|
547
507
|
end
|