jdbc-helper 0.4.10 → 0.5.0

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