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 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
- row.a, row.b, row.c # Dot-notation
112
- row[0], row[1], row[2] # Numeric index
113
- row['a'], row['b'], row['c'] # String index. Case-insensitive.
114
- row[:a], row[:b], row[:c] # Symbol index. Case-insensitive.
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 order methods
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
- conn.execute_batch
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 dynamic conditions
293
+ #### Invalid use of plain String conditions
279
294
 
280
- TableWrapper object internally creates JDBC PreparedStatements.
281
- If you dynamically build many condition-strings as the following example,
282
- it would soon fail because there will be too many open PreparedStatements.
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
- Correct ways of doing the same would be as follows.
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', '< 0.4.0'
19
- gem.add_development_dependency "bundler", "~> 1.1.4"
20
- gem.add_development_dependency "rcov", "~> 0.9"
21
- gem.add_development_dependency "test-unit", ">= 2.3.0"
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
- measure_exec(:prepare) { @conn.prepare_statement(qstr) })
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 measure_exec(:execute) { stmt.execute(qstr) }
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 = measure_exec(:update) { stmt.execute_update(qstr) }
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 = measure_exec(:query) { stmt.execute_query(qstr) }
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 = measure_exec(:query) { stmt.execute_query(qstr) }
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 statment is added
355
- # @return [NilClass]
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
- measure_exec(:execute_batch) { @bstmt.execute_batch }
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 |stmt|
366
- measure_exec(:execute_batch) { stmt.execute_batch }
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
- measure_exec(:execute_batch) { stmt.clear_batch }
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
- @table_wrappers[table_name] ||= JDBCHelper::TableWrapper.new self, table_name
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