jdbc-helper 0.7.6 → 0.7.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|