jdbc-helper 0.4.10 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/jdbc-helper.rb CHANGED
@@ -16,6 +16,7 @@ module JavaSql # :nodoc:
16
16
  end
17
17
 
18
18
  require 'jdbc-helper/sql'
19
+ require 'jdbc-helper/sql_prepared'
19
20
  require 'jdbc-helper/constants'
20
21
  require 'jdbc-helper/connection'
21
22
  require 'jdbc-helper/connector'
@@ -144,7 +144,8 @@ class Connection
144
144
  # @param [Hash] args
145
145
  def initialize(args = {})
146
146
  # Subsequent deletes should not affect the input
147
- args = args.dup
147
+ @args = args.dup
148
+ args = @args.dup
148
149
 
149
150
  # String/Symbol
150
151
  %w[driver url user password timeout].each do | strk |
@@ -179,6 +180,8 @@ class Connection
179
180
  @stats = Hash.new { | h, k | h[k] = Stat.new(k, 0, 0, 0) }
180
181
  @prev_stat = Stat.new(nil, 0, 0, 0)
181
182
 
183
+ @pstmts = []
184
+
182
185
  if block_given?
183
186
  begin
184
187
  yield self
@@ -188,15 +191,30 @@ class Connection
188
191
  end
189
192
  end
190
193
 
194
+ # Creates another connection with the same parameters as this Connection.
195
+ # @return [JDBCHelper::Connection]
196
+ def clone
197
+ nc = JDBCHelper::Connection.new @args
198
+ nc.fetch_size = @fetch_size if @fetch_size
199
+ nc
200
+ end
201
+
191
202
  # Creates a prepared statement, which is also an encapsulation of Java PreparedStatement object
192
203
  # @param [String] qstr SQL string
193
204
  def prepare(qstr)
194
205
  check_closed
195
206
 
196
- PreparedStatement.send(:new, self, qstr,
207
+ pstmt = PreparedStatement.send(:new, self, qstr,
197
208
  measure_exec(:prepare) { @conn.prepare_statement(qstr) })
209
+ @pstmts << pstmt
210
+ pstmt
198
211
  end
199
212
 
213
+ # @return [Array] Prepared statements currently opened for this connection
214
+ def prepared_statements
215
+ @pstmts
216
+ end
217
+
200
218
  # Creates a callable statement.
201
219
  # @param [String] qstr SQL string
202
220
  def prepare_call(qstr)
@@ -311,27 +329,36 @@ class Connection
311
329
  @bstmt.add_batch qstr
312
330
  end
313
331
 
314
- # Executes batched statements. No effect when no statment is added
332
+ # Executes batched statements including prepared statements. No effect when no statment is added
315
333
  # @return [NilClass]
316
334
  def execute_batch
317
335
  check_closed
318
336
 
319
- return unless @bstmt
320
- ret = measure_exec(:execute_batch) { @bstmt.execute_batch }
321
- @spool.give @bstmt
322
- @bstmt = nil
323
- ret
337
+ if @bstmt
338
+ measure_exec(:execute_batch) { @bstmt.execute_batch }
339
+ @spool.give @bstmt
340
+ @bstmt = nil
341
+ end
342
+
343
+ @pstmts.each do |stmt|
344
+ measure_exec(:execute_batch) { stmt.execute_batch }
345
+ end
324
346
  end
325
347
 
326
- # Clears the batched statements
348
+ # Clears the batched statements including prepared statements.
327
349
  # @return [NilClass]
328
350
  def clear_batch
329
351
  check_closed
330
352
 
331
- return unless @bstmt
332
- @bstmt.clear_batch
333
- @spool.give @bstmt
334
- @bstmt = nil
353
+ if @bstmt
354
+ @bstmt.clear_batch
355
+ @spool.give @bstmt
356
+ @bstmt = nil
357
+ end
358
+
359
+ @pstmts.each do |stmt|
360
+ measure_exec(:execute_batch) { stmt.clear_batch }
361
+ end
335
362
  end
336
363
 
337
364
  # Gives the JDBC driver a hint of the number of rows to fetch from the database by a single interaction.
@@ -458,6 +485,10 @@ private
458
485
  end
459
486
  end
460
487
 
488
+ def close_pstmt pstmt
489
+ @pstmts.delete pstmt
490
+ end
491
+
461
492
  def update_stat(type, elapsed, success_count, fail_count) # :nodoc:
462
493
  @prev_stat.type = type
463
494
  @prev_stat.elapsed = elapsed
@@ -19,6 +19,13 @@ class PreparedStatement < ParameterizedStatement
19
19
  @pmd.get_parameter_count
20
20
  end
21
21
 
22
+ # @return [NilClass]
23
+ def close
24
+ @conn.send(:close_pstmt, self)
25
+ @java_obj.close
26
+ @java_obj = nil
27
+ end
28
+
22
29
  # @return [Fixnum]
23
30
  def update(*params)
24
31
  check_closed
@@ -24,7 +24,7 @@ class StatementPool
24
24
 
25
25
  def take
26
26
  if @free.empty?
27
- raise Exception.new("Nesting too deep") if
27
+ raise Exception.new("Statement nesting level is too deep (likely a bug)") if
28
28
  @occupied.length >= @max_size
29
29
  @occupied << nstmt = @conn.send(:create_statement)
30
30
  nstmt
@@ -6,9 +6,12 @@ module JDBCHelper
6
6
  # Generate SQL snippet, prevents the string from being quoted.
7
7
  # @param [String] SQL snippet
8
8
  # @return [JDBCHelper::SQL]
9
- def self.SQL str
9
+ def self.sql str
10
10
  JDBCHelper::SQL.new str
11
11
  end
12
+ class << self
13
+ alias_method :SQL, :sql
14
+ end
12
15
 
13
16
  # Class representing an SQL snippet. Also has many SQL generator class methods.
14
17
  class SQL
@@ -44,6 +47,9 @@ class SQL
44
47
  where_clause.empty? ? where_clause : check(where_clause)
45
48
  end
46
49
 
50
+ def self.where_prepared *conds
51
+ end
52
+
47
53
  # Generates SQL order by cluase with the given conditions.
48
54
  def self.order *criteria
49
55
  str = criteria.map(&:to_s).reject(&:empty?).join(', ')
@@ -120,7 +126,15 @@ class SQL
120
126
  @expr
121
127
  end
122
128
 
123
- private
129
+ def == other
130
+ self.to_s == other.to_s
131
+ end
132
+
133
+ def eql? other
134
+ self.class == other.class && self.to_s == other.to_s
135
+ end
136
+
137
+ protected
124
138
  def self.esc str
125
139
  str.gsub("'", "''")
126
140
  end
@@ -177,7 +191,7 @@ private
177
191
  def initialize str
178
192
  @expr = JDBCHelper::SQL.check str
179
193
  end
180
-
194
+
181
195
  # Class to represent "(IS) NOT NULL" expression in SQL
182
196
  class NotNilClass
183
197
  # Returns the singleton object of NotNilClass
@@ -0,0 +1,159 @@
1
+ # encoding: UTF-8
2
+ # Junegunn Choi (junegunn.c@gmail.com)
3
+
4
+ module JDBCHelper
5
+ # SQL generator class methods for prepared operations.
6
+ # WARNING: Does not perform SQL.check to minimize performance overhead
7
+ class SQLPrepared < JDBCHelper::SQL
8
+ # Generates SQL where cluase with the given conditions.
9
+ # Parameter can be either Hash of String.
10
+ def self.where *conds
11
+ where_internal conds
12
+ end
13
+
14
+ # SQL Helpers
15
+ # ===========
16
+ # Generates insert SQL with hash
17
+ def self.insert table, data_hash
18
+ insert_internal 'insert', table, data_hash
19
+ end
20
+
21
+ # Generates insert ignore SQL (Non-standard syntax)
22
+ def self.insert_ignore table, data_hash
23
+ insert_internal 'insert ignore', table, data_hash
24
+ end
25
+
26
+ # Generates replace SQL (Non-standard syntax)
27
+ def self.replace table, data_hash
28
+ insert_internal 'replace', table, data_hash
29
+ end
30
+
31
+ # Generates update SQL with hash.
32
+ # :where element of the given hash is taken out to generate where clause.
33
+ def self.update table, data_hash, where
34
+ where_clause, where_binds = where_internal where
35
+
36
+ col_binds = []
37
+ sql = ("update #{table} set " + data_hash.map { |k, v|
38
+ case v
39
+ when JDBCHelper::SQL
40
+ "#{k} = #{v}"
41
+ else
42
+ col_binds << v
43
+ "#{k} = ?"
44
+ end
45
+ }.join(', ') + " #{where_clause}").strip
46
+
47
+ return sql, col_binds + where_binds
48
+ end
49
+
50
+ # Generates select SQL with the given conditions
51
+ def self.select table, opts = {}
52
+ opts = opts.reject { |k, v| v.nil? }
53
+ w_c, w_b = where_internal(opts.fetch(:where, {}))
54
+ sql = [
55
+ "select #{opts.fetch(:select, ['*']).join(', ')} from #{table}",
56
+ w_c.to_s,
57
+ order(opts.fetch(:order, []).join(', '))
58
+ ].reject(&:empty?).join(' ')
59
+
60
+ return sql, w_b
61
+ end
62
+
63
+ # Generates count SQL with the given conditions
64
+ def self.count table, conds = nil
65
+ w_c, w_b = where_internal(conds)
66
+ sql = "select count(*) from #{table} #{w_c}".strip
67
+
68
+ return sql, w_b
69
+ end
70
+
71
+ # Generates delete SQL with the given conditions
72
+ def self.delete table, conds = nil
73
+ w_c, w_b = where_internal(conds)
74
+ sql = "delete from #{table} #{w_c}".strip
75
+ return sql, w_b
76
+ end
77
+
78
+ private
79
+ def self.where_internal conds
80
+ conds = [conds] unless conds.is_a? Array
81
+
82
+ binds = []
83
+ clauses = []
84
+ conds.compact.each do |cond|
85
+ c, b = where_unit cond
86
+ next if c.empty?
87
+
88
+ binds += b
89
+ clauses << c
90
+ end
91
+ where_clause = clauses.join(' and ')
92
+ if where_clause.empty?
93
+ return nil, []
94
+ else
95
+ where_clause = 'where ' + where_clause
96
+ return where_clause, binds
97
+ end
98
+ end
99
+
100
+ def self.where_unit conds
101
+ binds = []
102
+
103
+ clause = case conds
104
+ when String
105
+ conds = conds.strip
106
+ conds.empty? ? '' : "(#{conds})"
107
+ when Hash
108
+ conds.map { |k, v|
109
+ "#{k} " +
110
+ case v
111
+ when NilClass
112
+ "is null"
113
+ when NotNilClass
114
+ "is not null"
115
+ when JDBCHelper::SQL
116
+ "= #{v}"
117
+ when Fixnum, Bignum, Float
118
+ binds << v
119
+ "= ?"
120
+ when Range
121
+ binds << v.begin << v.end
122
+ ">= ? and #{k} <#{'=' unless v.exclude_end?} ?"
123
+ when Array
124
+ "in (" +
125
+ v.map { |e|
126
+ case e
127
+ when String
128
+ "'#{esc e}'"
129
+ else
130
+ e.to_s
131
+ end }.join(', ') + ")"
132
+ else
133
+ binds << v
134
+ "= ?"
135
+ end
136
+ }.join(' and ')
137
+ else
138
+ raise NotImplementedError.new("Parameter to where must be either Hash or String")
139
+ end
140
+ return clause, binds
141
+ end
142
+
143
+ def self.insert_internal cmd, table, data_hash
144
+ binds = []
145
+ values = data_hash.values.map { |v|
146
+ case v
147
+ when JDBCHelper::SQL
148
+ v
149
+ else
150
+ binds << v
151
+ '?'
152
+ end
153
+ }
154
+ sql = "#{cmd} into #{table} (#{data_hash.keys.join ', '}) values (#{values.join(', ')})"
155
+ return sql, binds
156
+ end
157
+ end#SQL
158
+ end#JDBCHelper
159
+
@@ -50,7 +50,9 @@ class TableWrapper < ObjectWrapper
50
50
  # @param [List of Hash/String] where Filter conditions
51
51
  # @return [Fixnum] Count of the records.
52
52
  def count *where
53
- @connection.query(JDBCHelper::SQL.count(name, @query_where + where))[0][0].to_i
53
+ sql, binds = JDBCHelper::SQLPrepared.count(name, @query_where + where)
54
+ pstmt = prepare :count, sql
55
+ pstmt.query(*binds)[0][0].to_i
54
56
  end
55
57
 
56
58
  # Sees if the table is empty
@@ -64,7 +66,9 @@ class TableWrapper < ObjectWrapper
64
66
  # @param [Hash] data_hash Column values in Hash
65
67
  # @return [Fixnum] Number of affected records
66
68
  def insert data_hash = {}
67
- @connection.send @update_method, JDBCHelper::SQL.insert(name, @query_default.merge(data_hash))
69
+ sql, binds = JDBCHelper::SQLPrepared.insert(name, @query_default.merge(data_hash))
70
+ pstmt = prepare :insert, sql
71
+ pstmt.send @update_method, *binds
68
72
  end
69
73
 
70
74
  # Inserts a record into the table with the given hash.
@@ -73,7 +77,9 @@ class TableWrapper < ObjectWrapper
73
77
  # @param [Hash] data_hash Column values in Hash
74
78
  # @return [Fixnum] Number of affected records
75
79
  def insert_ignore data_hash = {}
76
- @connection.send @update_method, JDBCHelper::SQL.insert_ignore(name, @query_default.merge(data_hash))
80
+ sql, binds = JDBCHelper::SQLPrepared.insert_ignore(name, @query_default.merge(data_hash))
81
+ pstmt = prepare :insert, sql
82
+ pstmt.send @update_method, *binds
77
83
  end
78
84
 
79
85
  # Replaces a record in the table with the new one with the same unique key.
@@ -81,7 +87,9 @@ class TableWrapper < ObjectWrapper
81
87
  # @param [Hash] data_hash Column values in Hash
82
88
  # @return [Fixnum] Number of affected records
83
89
  def replace data_hash = {}
84
- @connection.send @update_method, JDBCHelper::SQL.replace(name, @query_default.merge(data_hash))
90
+ sql, binds = JDBCHelper::SQLPrepared.replace(name, @query_default.merge(data_hash))
91
+ pstmt = prepare :insert, sql
92
+ pstmt.send @update_method, *binds
85
93
  end
86
94
 
87
95
  # Executes update with the given hash.
@@ -92,15 +100,20 @@ class TableWrapper < ObjectWrapper
92
100
  def update data_hash_with_where = {}
93
101
  where_ext = data_hash_with_where.delete(:where)
94
102
  where_ext = [where_ext] unless where_ext.is_a? Array
95
- @connection.send @update_method,
96
- JDBCHelper::SQL.update(name, @query_default.merge(data_hash_with_where), @query_where + where_ext.compact)
103
+ sql, binds = JDBCHelper::SQLPrepared.update(name,
104
+ @query_default.merge(data_hash_with_where),
105
+ @query_where + where_ext.compact)
106
+ pstmt = prepare :update, sql
107
+ pstmt.send @update_method, *binds
97
108
  end
98
109
 
99
110
  # Deletes records matching given condtion
100
111
  # @param [List of Hash/String] where Delete filters
101
112
  # @return [Fixnum] Number of affected records
102
113
  def delete *where
103
- @connection.send @update_method, JDBCHelper::SQL.delete(name, @query_where + where)
114
+ sql, binds = JDBCHelper::SQLPrepared.delete(name, @query_where + where)
115
+ pstmt = prepare :delete, sql
116
+ pstmt.send @update_method, *binds
104
117
  end
105
118
 
106
119
  # Empties the table.
@@ -181,19 +194,26 @@ class TableWrapper < ObjectWrapper
181
194
  # @return [JDBCHelper::Connection::ResultSetEnumerator]
182
195
  # @since 0.4.0
183
196
  def each &block
184
- @connection.enumerate sql, &block
197
+ sql, binds = JDBCHelper::SQLPrepared.select(
198
+ name,
199
+ :select => @query_select,
200
+ :where => @query_where,
201
+ :order => @query_order)
202
+ pstmt = prepare :select, sql
203
+ pstmt.enumerate *binds, &block
185
204
  end
186
205
 
187
206
  # Returns a new TableWrapper object whose subsequent inserts, updates,
188
207
  # and deletes are added to batch for JDBC batch-execution. The actual execution
189
208
  # is deferred until JDBCHelper::Connection#execute_batch method is called.
190
209
  # Self is returned when batch is called more than once.
191
- # @return [JDBCHelper::Connection::ResultSetEnumerator]
210
+ # @return [JDBCHelper::TableWrapper]
192
211
  # @since 0.4.0
193
212
  def batch
194
213
  if batch?
195
214
  self
196
215
  else
216
+ # dup makes @pstmts to be shared
197
217
  obj = self.dup
198
218
  obj.instance_variable_set :@update_method, :add_batch
199
219
  obj
@@ -223,8 +243,37 @@ class TableWrapper < ObjectWrapper
223
243
  @update_method = :update
224
244
  @query_default = {}
225
245
  @query_where = []
246
+ @pstmts = {
247
+ :select => {},
248
+ :insert => {},
249
+ :delete => {},
250
+ :count => {},
251
+ :update => {}
252
+ }
226
253
  end
254
+
255
+ # Closes the prepared statements
256
+ # @since 0.5.0
257
+ def close
258
+ @pstmts.each do |typ, hash|
259
+ hash.each do |sql, pstmt|
260
+ pstmt.close if pstmt
261
+ end
262
+ @pstmts[typ] = {}
263
+ end
264
+ end
265
+
266
+ # @return [Hash] Prepared statements for this wrapper
267
+ # @since 0.5.0
268
+ def prepared_statements
269
+ @pstmts
270
+ end
271
+
227
272
  private
273
+ def prepare type, sql
274
+ @pstmts[type][sql] ||= @connection.prepare(JDBCHelper::SQL.check(sql))
275
+ end
276
+
228
277
  def ret obj, &block
229
278
  if block_given?
230
279
  obj.each &block
data/test/helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
+ require 'pry'
3
4
  begin
4
5
  Bundler.setup(:default, :development)
5
6
  rescue Bundler::BundlerError => e
@@ -68,7 +68,7 @@ class TestConnection < Test::Unit::TestCase
68
68
 
69
69
  # ---------------------------------------------------------------
70
70
 
71
- def test_connect_and_close
71
+ def test_connect_clone_and_close
72
72
  config.each do | db, conn_info_org |
73
73
  4.times do | i |
74
74
  conn_info = conn_info_org.reject { |k,v| k == 'database' }
@@ -89,6 +89,9 @@ class TestConnection < Test::Unit::TestCase
89
89
  assert_equal(conn.driver, conn_info[:driver] || conn_info['driver'])
90
90
  assert_equal(conn.url, conn_info[:url] || conn_info['url'])
91
91
 
92
+ conn.fetch_size = 100
93
+ assert_equal 100, conn.fetch_size
94
+
92
95
  conn.close
93
96
  assert_equal(conn.closed?, true)
94
97
  [ :query, :update, :add_batch, :prepare ].each do | met |
@@ -98,10 +101,23 @@ class TestConnection < Test::Unit::TestCase
98
101
  assert_raise(RuntimeError) { conn.send met }
99
102
  end
100
103
 
104
+ new_conn = conn.clone
105
+ assert new_conn.java_obj != conn.java_obj
106
+ assert new_conn.closed? == false
107
+ assert_equal 100, new_conn.fetch_size
108
+ new_conn.close
109
+
101
110
  # initialize with execution block
102
111
  conn = JDBCHelper::Connection.new(conn_info) do | c |
112
+ c2 = c.clone
113
+ assert c2.java_obj != c.java_obj
114
+
103
115
  c.query('select 1 from dual')
116
+ c2.query('select 1 from dual')
104
117
  assert_equal c.closed?, false
118
+
119
+ c2.close
120
+ assert c2.closed?
105
121
  end
106
122
  assert conn.closed?
107
123
  end
@@ -155,6 +171,8 @@ class TestConnection < Test::Unit::TestCase
155
171
  iq = lambda do | i |
156
172
  "insert into #{TEST_TABLE} values (#{i}, 'A')"
157
173
  end
174
+ ins1 = conn.prepare "insert into #{TEST_TABLE} values (? + #{count}, 'B')"
175
+ ins2 = conn.prepare "insert into #{TEST_TABLE} values (? + #{count * 2}, 'C')"
158
176
 
159
177
  # update
160
178
  assert_equal 1, conn.update(iq.call 0)
@@ -165,17 +183,24 @@ class TestConnection < Test::Unit::TestCase
165
183
 
166
184
  count.times do | p |
167
185
  conn.add_batch iq.call(p)
186
+ ins1.add_batch p
187
+ ins2.add_batch p
168
188
  end
169
189
  conn.execute_batch
170
- assert_equal count, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
190
+ assert_equal count * 3, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
171
191
 
172
192
  # add_batch clear_batch
173
193
  reset_test_table conn
174
194
 
175
195
  count.times do | p |
176
196
  conn.add_batch iq.call(p)
197
+ ins1.add_batch p
198
+ ins2.add_batch p
177
199
  end
178
200
  conn.clear_batch
201
+ # Already cleared, no effect
202
+ ins1.execute_batch
203
+ ins2.execute_batch
179
204
  assert_equal 0, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
180
205
  end
181
206
  end
@@ -184,6 +209,7 @@ class TestConnection < Test::Unit::TestCase
184
209
  each_connection do | conn |
185
210
  sel = conn.prepare get_one_two
186
211
  assert sel.closed? == false
212
+ assert_equal conn.prepared_statements.first, sel
187
213
 
188
214
  # Fetch size
189
215
  assert_nil conn.fetch_size
@@ -232,6 +258,7 @@ class TestConnection < Test::Unit::TestCase
232
258
  reset_test_table conn
233
259
  ins = conn.prepare "insert into #{TEST_TABLE} values (?, ?)"
234
260
  assert_equal 2, ins.parameter_count
261
+ assert_equal conn.prepared_statements.first, ins
235
262
 
236
263
  count = 100
237
264
 
@@ -240,31 +267,49 @@ class TestConnection < Test::Unit::TestCase
240
267
  assert_equal 1, ins.update(0, 'A')
241
268
  assert_equal 1, conn.prev_stat.success_count
242
269
  ins.close
270
+ assert_equal 0, conn.prepared_statements.length
243
271
 
244
272
  # add_batch execute_batch
245
- reset_test_table conn
246
- ins = conn.prepare "insert into #{TEST_TABLE} values (?, ?)"
247
-
248
- count.times do | p |
249
- ins.add_batch(p + 1, 'A')
250
- end
251
- ins.execute_batch
252
- assert_equal count, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
253
- ins.close
273
+ 2.times do |iter|
274
+ reset_test_table conn
275
+ ins = conn.prepare "insert into #{TEST_TABLE} values (?, ?)"
276
+ assert_equal conn.prepared_statements.first, ins
277
+
278
+ count.times do | p |
279
+ ins.add_batch(p + 1, 'A')
280
+ end
281
+ if iter == 0
282
+ ins.execute_batch
283
+ else
284
+ conn.execute_batch
285
+ end
286
+ assert_equal count, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
287
+ ins.close
288
+ assert_equal 0, conn.prepared_statements.length
289
+ end
254
290
 
255
291
  # add_batch clear_batch
256
292
  reset_test_table conn
257
293
  ins = conn.prepare "insert into #{TEST_TABLE} values (?, ?)"
258
-
259
- count.times do | p |
260
- ins.add_batch(p + 1, 'A')
261
- end
262
- ins.clear_batch
263
- assert_equal 0, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
294
+ assert_equal conn.prepared_statements.first, ins
295
+
296
+ # clear_batch
297
+ 2.times do |iter|
298
+ count.times do | p |
299
+ ins.add_batch(p + 1, 'A')
300
+ end
301
+ if iter == 0
302
+ ins.clear_batch
303
+ else
304
+ conn.clear_batch
305
+ end
306
+ assert_equal 0, conn.query("select count(*) from #{TEST_TABLE}")[0][0]
307
+ end
264
308
 
265
309
  # close closed?
266
310
  assert ins.closed? == false
267
311
  ins.close
312
+ assert_equal 0, conn.prepared_statements.length
268
313
  assert ins.closed?
269
314
  [ :query, :update, :add_batch, :execute_batch, :clear_batch ].each do | met |
270
315
  assert_raise(RuntimeError) { ins.send met }
@@ -363,21 +363,24 @@ class TestObjectWrapper < Test::Unit::TestCase
363
363
  # Batch updates
364
364
  table.batch.delete
365
365
  assert_equal 100, table.count
366
+ conn.execute_batch
367
+ assert_equal 0, table.count
366
368
 
367
369
  insert table.batch, 50
368
- assert_equal 100, table.count
370
+ assert_equal 0, table.count
371
+ conn.execute_batch
372
+ assert_equal 50, table.count
369
373
 
370
374
  table.batch.update(:alpha => JDBCHelper::SQL('alpha * 2'))
371
375
  assert_equal 100, table.select(:alpha).to_a.first.alpha.to_i
372
376
 
373
377
  # Independent update inbetween
374
378
  table.delete(:id => 1..10)
375
- assert_equal 90, table.count
379
+ assert_equal 40, table.count
376
380
 
377
381
  # Finally
378
382
  conn.execute_batch
379
383
 
380
- assert_equal 50, table.count
381
384
  assert_equal 200, table.select(:alpha).to_a.first.alpha.to_i
382
385
  end
383
386
  end
@@ -429,5 +432,49 @@ class TestObjectWrapper < Test::Unit::TestCase
429
432
  seq.drop!
430
433
  end
431
434
  end
435
+
436
+ def test_prepared_statements
437
+ each_connection do |conn|
438
+ create_table conn
439
+
440
+ # No duplicate preparations
441
+ t = conn.table(@table_name)
442
+ t.count(:id => 1)
443
+ t.count('1 = 0')
444
+ bt = t.batch
445
+
446
+ assert_equal 2, t.prepared_statements[:count].length
447
+ assert_equal 2, bt.prepared_statements[:count].length
448
+
449
+ t.count(:id => 2)
450
+ t.count('2 = 0')
451
+ bt.count('3 = 0')
452
+ assert_equal 4, t.prepared_statements[:count].length
453
+ assert_equal 4, bt.prepared_statements[:count].length
454
+
455
+ t.count(:id => 3)
456
+ t.batch.count('4 = 0')
457
+ assert_equal 5, t.prepared_statements[:count].length
458
+ assert_equal 5, bt.prepared_statements[:count].length
459
+ assert_equal 5, t.batch.prepared_statements[:count].length
460
+ assert_equal 5, bt.batch.prepared_statements[:count].length
461
+
462
+ t.close
463
+ assert_equal 0, t.prepared_statements[:count].length
464
+ assert_equal 0, bt.prepared_statements[:count].length
465
+ assert_equal 0, t.batch.prepared_statements[:count].length
466
+ assert_equal 0, bt.batch.prepared_statements[:count].length
467
+
468
+ t.batch.batch.batch.count(:id => 1)
469
+ assert_equal 1, t.prepared_statements[:count].length
470
+ assert_equal 1, bt.prepared_statements[:count].length
471
+ assert_equal 1, bt.batch.prepared_statements[:count].length
472
+ assert_equal 1, t.batch.where('1 = 2').select(:a, :b).prepared_statements[:count].length
473
+
474
+ # Should be OK
475
+ bt.close
476
+ t.batch.close
477
+ end
478
+ end
432
479
  end
433
480
 
@@ -7,7 +7,7 @@ class TestPerformance < Test::Unit::TestCase
7
7
  def setup
8
8
  @table = 'tmp_jdbc_helper'
9
9
  @range = 'aaa'..'aaz'
10
- @count = 100#00 # Increase this for performance measurement
10
+ @count = 10000 # Increase this for performance measurement
11
11
  end
12
12
 
13
13
  def teardown
@@ -26,6 +26,17 @@ class TestPerformance < Test::Unit::TestCase
26
26
  end
27
27
  }.real}"
28
28
 
29
+ puts "Normal inserts (batch & chuck-transactional): #{Benchmark.measure {
30
+ (0...@count).each_slice(50) do |slice|
31
+ conn.transaction do
32
+ slice.each do |i|
33
+ conn.add_batch "insert into #{@table} values (#{@range.map{rand @count}.join ','})"
34
+ end
35
+ conn.execute_batch
36
+ end
37
+ end
38
+ }.real}"
39
+
29
40
  puts "Prepared inserts: #{Benchmark.measure {
30
41
  pins = conn.prepare "insert into #{@table} values (#{@range.map{'?'}.join ','})"
31
42
  @count.times do |i|
@@ -56,17 +67,18 @@ class TestPerformance < Test::Unit::TestCase
56
67
 
57
68
  puts "Inserts with hash (batch & chunk-transactional): #{Benchmark.measure {
58
69
  table = conn.table(@table)
70
+ btable = table.batch
59
71
  (0...@count).each_slice(50) do |slice|
60
72
  conn.transaction do
61
73
  slice.each do |i|
62
- table.batch.insert @range.inject({}) { |hash, key| hash[key] = rand; hash }
74
+ btable.insert @range.inject({}) { |hash, key| hash[key] = rand; hash }
63
75
  end
64
76
  conn.execute_batch
65
77
  end
66
78
  end
67
79
  }.real}"
68
80
 
69
- assert_equal @count * 5, conn.table(@table).count
81
+ assert_equal @count * 6, conn.table(@table).count
70
82
 
71
83
  conn.query("select * from #{@table}") do |row|
72
84
  # ...
data/test/test_sql.rb CHANGED
@@ -16,7 +16,6 @@ class TestSQL < Test::Unit::TestCase
16
16
  assert_equal "'sysdate'", SQL.value('sysdate')
17
17
  assert_equal "'A''s'", SQL.value("A's")
18
18
  assert_equal "sysdate", SQL.value(JDBCHelper::SQL('sysdate'))
19
-
20
19
  end
21
20
 
22
21
  def test_order
@@ -71,6 +70,29 @@ class TestSQL < Test::Unit::TestCase
71
70
  assert_raise(ArgumentError) { SQL.where(:a => JDBCHelper::SQL(' aab`bb`` ')) }
72
71
  end
73
72
 
73
+ def test_where_prepared
74
+ assert_equal ["where a = ?", [1]], SQLPrepared.where(:a => 1)
75
+ assert_equal ["where a = ?", [1.2]], SQLPrepared.where(:a => 1.2)
76
+ assert_equal ["where a = ?", [9999999999999999999]], SQLPrepared.where(:a => 9999999999999999999)
77
+ assert_equal ["where a >= ? and a <= ?", [1,2]], SQLPrepared.where(:a => 1..2)
78
+ assert_equal ["where a >= ? and a < ?", [1,2]], SQLPrepared.where(:a => 1...2)
79
+ assert_equal ["where a = ?", ["A's"]], SQLPrepared.where(:a => "A's")
80
+ assert_equal ["where a is null", []], SQLPrepared.where(:a => nil)
81
+ assert_equal ["where a is not null", []], SQLPrepared.where(:a => SQL.not_nil)
82
+ assert_equal ["where a is not null", []], SQLPrepared.where(:a => SQL.not_null)
83
+ assert_equal ["where a = sysdate", []], SQLPrepared.where(:a => JDBCHelper::SQL('sysdate'))
84
+ assert_equal ["where sysdate = sysdate", []], SQLPrepared.where(JDBCHelper::SQL('sysdate') => JDBCHelper::SQL('sysdate'))
85
+ assert_equal ["where a in ('aa', 'bb', 'cc')", []], SQLPrepared.where(:a => %w[aa bb cc])
86
+ assert_equal ["where a = ? and b = ?", [1, "A's"]], SQLPrepared.where(:a => 1, :b => "A's")
87
+ assert_equal ["where (a = 1 or b = 1)", []], SQLPrepared.where("a = 1 or b = 1")
88
+ assert_equal ["where (a = 1 or b = 1) and c = ?", [2]], SQLPrepared.where("a = 1 or b = 1", :c => 2)
89
+ assert_equal ["where c = ? and (a = 1 or b = 1)", [2]], SQLPrepared.where({:c => 2}, "a = 1 or b = 1")
90
+ assert_equal ["where c = ? and (a = 1 or b = 1) and (e = 2) and f = ?", [2, 3]],
91
+ SQLPrepared.where({:c => 2}, "a = 1 or b = 1", nil, "", "e = 2", nil, {:f => 3}, {})
92
+ assert_equal [nil, []], SQLPrepared.where(nil)
93
+ assert_equal [nil, []], SQLPrepared.where(" ")
94
+ end
95
+
74
96
  def test_select
75
97
  assert_equal "select * from a.b", SQL.select('a.b')
76
98
  assert_equal "select aa, bb from a.b where a is not null",
@@ -86,11 +108,17 @@ class TestSQL < Test::Unit::TestCase
86
108
  def test_count
87
109
  assert_equal "select count(*) from a.b", SQL.count('a.b')
88
110
  assert_equal "select count(*) from a.b where a is not null", SQL.count('a.b', :a => SQL.not_nil)
111
+
112
+ assert_equal ["select count(*) from a.b", []], SQLPrepared.count('a.b')
113
+ assert_equal ["select count(*) from a.b where a = ?", [1]], SQLPrepared.count('a.b', :a => 1)
89
114
  end
90
115
 
91
116
  def test_delete
92
117
  assert_equal "delete from a.b", SQL.delete('a.b')
93
118
  assert_equal "delete from a.b where a is not null", SQL.delete('a.b', :a => SQL.not_nil)
119
+
120
+ assert_equal ["delete from a.b", []], SQLPrepared.delete('a.b')
121
+ assert_equal ["delete from a.b where a = ?", [1]], SQLPrepared.delete('a.b', :a => 1)
94
122
  end
95
123
 
96
124
  def test_update
@@ -99,11 +127,33 @@ class TestSQL < Test::Unit::TestCase
99
127
 
100
128
  assert_equal "update a.b set a = 1, b = 'A''s', c = now() where a is not null",
101
129
  SQL.update('a.b', {:a => 1, :b => "A's", :c => JDBCHelper::SQL('now()')}, { :a => SQL.not_nil })
130
+
131
+ assert_equal ["update a.b set a = ?, b = ?, c = now()", [1, "A's"]],
132
+ SQLPrepared.update('a.b', {:a => 1, :b => "A's", :c => JDBCHelper::SQL('now()')}, {})
133
+
134
+ assert_equal ["update a.b set a = ?, b = ?, c = now() where a = ?", [1, "A's", 2]],
135
+ SQLPrepared.update('a.b', {:a => 1, :b => "A's", :c => JDBCHelper::SQL('now()')}, { :a => 2 })
102
136
  end
103
137
 
104
138
  def test_insert
105
139
  assert_equal "insert into a.b (a, b, c) values (1, 'A''s', null)",
106
140
  SQL.insert('a.b', :a => 1, :b => "A's", :c => nil)
141
+
142
+ assert_equal ["insert into a.b (a, b, c) values (?, ?, ?)", [1, "A's", nil]],
143
+ SQLPrepared.insert('a.b', :a => 1, :b => "A's", :c => nil)
107
144
  end
145
+
146
+ def test_sql_equality
147
+ assert_equal "a = b", JDBCHelper.SQL('a = b').to_s
148
+ assert_equal JDBCHelper.SQL('a = b'), JDBCHelper.SQL('a = b')
149
+
150
+ # type conversion across ==, but not across eql (TODO TBD)
151
+ assert JDBCHelper.SQL('a = b') == (JDBCHelper.SQL('a = b'))
152
+ assert JDBCHelper.SQL('a = b') == 'a = b'
153
+ assert JDBCHelper.SQL('a = b').eql?(JDBCHelper.SQL('a = b'))
154
+ assert_false JDBCHelper.SQL('a = b').eql?('a = b')
155
+
156
+ assert JDBCHelper.SQL('a = b') != JDBCHelper.SQL('a = c')
157
+ end
108
158
  end
109
159
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: jdbc-helper
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.4.10
5
+ version: 0.5.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Junegunn Choi
@@ -10,7 +10,8 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-08-09 00:00:00 Z
13
+ date: 2011-08-29 00:00:00 +09:00
14
+ default_executable:
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: bundler
@@ -56,6 +57,17 @@ dependencies:
56
57
  requirement: *id004
57
58
  prerelease: false
58
59
  type: :development
60
+ - !ruby/object:Gem::Dependency
61
+ name: pry
62
+ version_requirements: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ requirement: *id005
69
+ prerelease: false
70
+ type: :development
59
71
  description: A JDBC helper for JRuby/Database developers.
60
72
  email: junegunn.c@gmail.com
61
73
  executables: []
@@ -80,6 +92,7 @@ files:
80
92
  - lib/jdbc-helper/connector/oracle_connector.rb
81
93
  - lib/jdbc-helper/constants.rb
82
94
  - lib/jdbc-helper/sql.rb
95
+ - lib/jdbc-helper/sql_prepared.rb
83
96
  - lib/jdbc-helper/wrapper/function_wrapper.rb
84
97
  - lib/jdbc-helper/wrapper/object_wrapper.rb
85
98
  - lib/jdbc-helper/wrapper/procedure_wrapper.rb
@@ -95,6 +108,7 @@ files:
95
108
  - test/test_object_wrapper.rb
96
109
  - test/test_performance.rb
97
110
  - test/test_sql.rb
111
+ has_rdoc: true
98
112
  homepage: http://github.com/junegunn/jdbc-helper
99
113
  licenses:
100
114
  - MIT
@@ -121,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
135
  requirements: []
122
136
 
123
137
  rubyforge_project:
124
- rubygems_version: 1.8.6
138
+ rubygems_version: 1.5.1
125
139
  signing_key:
126
140
  specification_version: 3
127
141
  summary: A JDBC helper for JRuby/Database developers.