sequel 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -41,13 +41,14 @@ module Sequel
41
41
 
42
42
  private_class_method :uri_to_options
43
43
 
44
- def connect
45
- dbname = @opts[:database]
44
+ def connect(server)
45
+ opts = server_opts(server)
46
+ dbname = opts[:database]
46
47
  if dbname !~ /^DBI:/ then
47
48
  dbname = "DBI:#{dbname}"
48
- [:host, :port].each{|sym| dbname += ";#{sym}=#{@opts[sym]}" unless @opts[sym].blank?}
49
+ [:host, :port].each{|sym| dbname += ";#{sym}=#{opts[sym]}" unless opts[sym].blank?}
49
50
  end
50
- ::DBI.connect(dbname, @opts[:user], @opts[:password])
51
+ ::DBI.connect(dbname, opts[:user], opts[:password])
51
52
  end
52
53
 
53
54
  def disconnect
@@ -58,18 +59,18 @@ module Sequel
58
59
  DBI::Dataset.new(self, opts)
59
60
  end
60
61
 
61
- def execute(sql)
62
+ def execute(sql, opts={})
62
63
  log_info(sql)
63
- @pool.hold do |conn|
64
- conn.execute(sql)
64
+ synchronize(opts[:server]) do |conn|
65
+ r = conn.execute(sql)
66
+ yield(r) if block_given?
67
+ r
65
68
  end
66
69
  end
67
70
 
68
- def do(sql)
71
+ def do(sql, opts={})
69
72
  log_info(sql)
70
- @pool.hold do |conn|
71
- conn.do(sql)
72
- end
73
+ synchronize(opts[:server]){|conn| conn.do(sql)}
73
74
  end
74
75
  alias_method :execute_dui, :do
75
76
 
@@ -92,8 +93,7 @@ module Sequel
92
93
  end
93
94
 
94
95
  def fetch_rows(sql, &block)
95
- @db.synchronize do
96
- s = @db.execute sql
96
+ execute(sql) do |s|
97
97
  begin
98
98
  @columns = s.column_names.map do |c|
99
99
  @db.lowercase ? c.downcase.to_sym : c.to_sym
@@ -106,6 +106,8 @@ module Sequel
106
106
  self
107
107
  end
108
108
 
109
+ private
110
+
109
111
  def hash_row(stmt, row)
110
112
  @columns.inject({}) do |m, c|
111
113
  m[c] = row.shift
@@ -11,12 +11,13 @@ module Sequel
11
11
  # AUTO_INCREMENT
12
12
  # end
13
13
 
14
- def connect
15
- ::Informix.connect(@opts[:database], @opts[:user], @opts[:password])
14
+ def connect(server)
15
+ opts = server_opts(server)
16
+ ::Informix.connect(opts[:database], opts[:user], opts[:password])
16
17
  end
17
18
 
18
19
  def disconnect
19
- @pool.disconnect {|c| c.close}
20
+ @pool.disconnect{|c| c.close}
20
21
  end
21
22
 
22
23
  def dataset(opts = nil)
@@ -24,15 +25,15 @@ module Sequel
24
25
  end
25
26
 
26
27
  # Returns number of rows affected
27
- def execute_dui(sql)
28
+ def execute_dui(sql, opts={})
28
29
  log_info(sql)
29
- @pool.hold {|c| c.immediate(sql)}
30
+ synchronize(opts[:server]){|c| c.immediate(sql)}
30
31
  end
31
32
  alias_method :do, :execute_dui
32
33
 
33
- def execute(sql, &block)
34
+ def execute(sql, opts={})
34
35
  log_info(sql)
35
- @pool.hold {|c| block[c.cursor(sql)]}
36
+ synchronize(opts[:server]){|c| yield c.cursor(sql)}
36
37
  end
37
38
  alias_method :query, :execute
38
39
  end
@@ -62,13 +63,11 @@ module Sequel
62
63
  end
63
64
 
64
65
  def fetch_rows(sql, &block)
65
- @db.synchronize do
66
- @db.execute(sql) do |cursor|
67
- begin
68
- cursor.open.each_hash(&block)
69
- ensure
70
- cursor.drop
71
- end
66
+ execute(sql) do |cursor|
67
+ begin
68
+ cursor.open.each_hash(&block)
69
+ ensure
70
+ cursor.drop
72
71
  end
73
72
  end
74
73
  self
@@ -1,9 +1,38 @@
1
1
  require 'java'
2
2
 
3
3
  module Sequel
4
+ # Houses Sequel's JDBC support when running on JRuby.
5
+ # Support for individual database types is done using sub adapters.
6
+ # PostgreSQL, MySQL, SQLite, and MSSQL all have relatively good support,
7
+ # close the the level supported by the native adapter.
8
+ # PostgreSQL, MySQL, SQLite can load necessary support using
9
+ # the jdbc-* gem, if it is installed, though they will work if you
10
+ # have the correct .jar in your CLASSPATH. Oracle and MSSQL should
11
+ # load the necessary support if you have the .jar in your CLASSPATH.
12
+ # For all other databases, the Java class should be loaded manually
13
+ # before calling Sequel.connect.
14
+ #
15
+ # Note that when using a JDBC adapter, the best way to use Sequel
16
+ # is via Sequel.connect, NOT Sequel.jdbc. Use the JDBC connection
17
+ # string when connecting, which will be in a different format than
18
+ # the native connection string. The connection string should start
19
+ # with 'jdbc:'. For PostgreSQL, use 'jdbc:postgresql:', and for
20
+ # SQLite you do not need 2 preceding slashes for the database name
21
+ # (use no preceding slashes for a relative path, and one preceding
22
+ # slash for an absolute path).
4
23
  module JDBC
5
- module JavaLang; include_package 'java.lang'; end
6
- module JavaSQL; include_package 'java.sql'; end
24
+ # Make it accesing the java.lang hierarchy more ruby friendly.
25
+ module JavaLang
26
+ include_package 'java.lang'
27
+ end
28
+
29
+ # Make it accesing the java.sql hierarchy more ruby friendly.
30
+ module JavaSQL
31
+ include_package 'java.sql'
32
+ end
33
+
34
+ # Contains procs keyed on sub adapter type that extend the
35
+ # given database object so it supports the correct database type.
7
36
  DATABASE_SETUP = {:postgresql=>proc do |db|
8
37
  require 'sequel_core/adapters/jdbc/postgresql'
9
38
  db.extend(Sequel::JDBC::Postgres::DatabaseMethods)
@@ -23,9 +52,15 @@ module Sequel
23
52
  org.sqlite.JDBC
24
53
  end,
25
54
  :oracle=>proc{oracle.jdbc.driver.OracleDriver},
26
- :sqlserver=>proc{com.microsoft.sqlserver.jdbc.SQLServerDriver}
55
+ :sqlserver=>proc do |db|
56
+ require 'sequel_core/adapters/shared/mssql'
57
+ db.extend(Sequel::MSSQL::DatabaseMethods)
58
+ com.microsoft.sqlserver.jdbc.SQLServerDriver
59
+ end
27
60
  }
28
61
 
62
+ # Allowing loading the necessary JDBC support via a gem, which
63
+ # works for PostgreSQL, MySQL, and SQLite.
29
64
  def self.load_gem(name)
30
65
  begin
31
66
  require "jdbc/#{name}"
@@ -34,12 +69,18 @@ module Sequel
34
69
  end
35
70
  end
36
71
 
72
+ # JDBC Databases offer a fairly uniform interface that does not change
73
+ # much based on the sub adapter.
37
74
  class Database < Sequel::Database
38
75
  set_adapter_scheme :jdbc
39
76
 
40
77
  # The type of database we are connecting to
41
78
  attr_reader :database_type
42
79
 
80
+ # Call the DATABASE_SETUP proc directly after initialization,
81
+ # so the object always uses sub adapter specific code. Also,
82
+ # raise an error immediately if the connection doesn't have a
83
+ # uri, since JDBC requires one.
43
84
  def initialize(opts)
44
85
  super(opts)
45
86
  raise(Error, "No connection string specified") unless uri
@@ -48,38 +89,42 @@ module Sequel
48
89
  end
49
90
  end
50
91
 
51
- def connect
52
- setup_connection(JavaSQL::DriverManager.getConnection(uri))
92
+ # Connect to the database using JavaSQL::DriverManager.getConnection.
93
+ def connect(server)
94
+ setup_connection(JavaSQL::DriverManager.getConnection(uri(server_opts(server))))
53
95
  end
54
96
 
97
+ # Return instances of JDBC::Dataset with the given opts.
55
98
  def dataset(opts = nil)
56
99
  JDBC::Dataset.new(self, opts)
57
100
  end
58
101
 
102
+ # Close all adapter connections
59
103
  def disconnect
60
104
  @pool.disconnect {|c| c.close}
61
105
  end
62
106
 
63
- def execute(sql)
64
- log_info(sql)
65
- @pool.hold do |conn|
66
- stmt = conn.createStatement
67
- begin
68
- yield stmt.executeQuery(sql)
69
- rescue NativeException, JavaSQL::SQLException => e
70
- raise Error, e.message
71
- ensure
72
- stmt.close
73
- end
74
- end
75
- end
76
-
77
- def execute_ddl(sql)
107
+ # Execute the given SQL. If a block is given, if should be a SELECT
108
+ # statement or something else that returns rows.
109
+ def execute(sql, opts={}, &block)
110
+ return execute_prepared_statement(sql, opts, &block) if sql.is_one_of?(Symbol, Dataset)
78
111
  log_info(sql)
79
- @pool.hold do |conn|
112
+ synchronize(opts[:server]) do |conn|
80
113
  stmt = conn.createStatement
81
114
  begin
82
- stmt.execute(sql)
115
+ if block_given?
116
+ yield stmt.executeQuery(sql)
117
+ else
118
+ case opts[:type]
119
+ when :ddl
120
+ stmt.execute(sql)
121
+ when :insert
122
+ stmt.executeUpdate(sql)
123
+ last_insert_id(conn, opts)
124
+ else
125
+ stmt.executeUpdate(sql)
126
+ end
127
+ end
83
128
  rescue NativeException, JavaSQL::SQLException => e
84
129
  raise Error, e.message
85
130
  ensure
@@ -87,28 +132,25 @@ module Sequel
87
132
  end
88
133
  end
89
134
  end
135
+ alias execute_dui execute
90
136
 
91
- def execute_dui(sql)
92
- log_info(sql)
93
- @pool.hold do |conn|
94
- stmt = conn.createStatement
95
- begin
96
- stmt.executeUpdate(sql)
97
- rescue NativeException, JavaSQL::SQLException => e
98
- raise Error, e.message
99
- ensure
100
- stmt.close
101
- end
102
- end
137
+ # Execute the given DDL SQL, which should not return any
138
+ # values or rows.
139
+ def execute_ddl(sql, opts={})
140
+ execute(sql, {:type=>:ddl}.merge(opts))
103
141
  end
104
142
 
105
- def setup_connection(conn)
106
- conn
143
+ # Execute the given INSERT SQL, returning the last inserted
144
+ # row id.
145
+ def execute_insert(sql, opts={})
146
+ execute(sql, {:type=>:insert}.merge(opts))
107
147
  end
108
148
 
109
- def transaction
110
- @pool.hold do |conn|
111
- @transactions ||= []
149
+ # Default transaction method that should work on most JDBC
150
+ # databases. Does not use the JDBC transaction methods, uses
151
+ # SQL BEGIN/ROLLBACK/COMMIT statements instead.
152
+ def transaction(server=nil)
153
+ synchronize(server) do |conn|
112
154
  return yield(conn) if @transactions.include?(Thread.current)
113
155
  stmt = conn.createStatement
114
156
  begin
@@ -131,55 +173,196 @@ module Sequel
131
173
  end
132
174
  end
133
175
 
134
- def uri
135
- ur = @opts[:uri] || @opts[:url] || @opts[:database]
176
+ # The uri for this connection. You can specify the uri
177
+ # using the :uri, :url, or :database options. You don't
178
+ # need to worry about this if you use Sequel.connect
179
+ # with the JDBC connectrion strings.
180
+ def uri(opts={})
181
+ opts = @opts.merge(opts)
182
+ ur = opts[:uri] || opts[:url] || opts[:database]
136
183
  ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}"
137
184
  end
138
185
  alias url uri
139
186
 
140
187
  private
141
188
 
189
+ # Execute the prepared statement. If the provided name is a
190
+ # dataset, use that as the prepared statement, otherwise use
191
+ # it as a key to look it up in the prepared_statements hash.
192
+ # If the connection we are using has already prepared an identical
193
+ # statement, use that statement instead of creating another.
194
+ # Otherwise, prepare a new statement for the connection, bind the
195
+ # variables, and execute it.
196
+ def execute_prepared_statement(name, opts={})
197
+ args = opts[:arguments]
198
+ if Dataset === name
199
+ ps = name
200
+ name = ps.prepared_statement_name
201
+ else
202
+ ps = prepared_statements[name]
203
+ end
204
+ sql = ps.prepared_sql
205
+ synchronize(opts[:server]) do |conn|
206
+ if name and cps = conn.prepared_statements[name] and cps[0] == sql
207
+ cps = cps[1]
208
+ else
209
+ if cps
210
+ log_info("Closing #{name}")
211
+ cps[1].close
212
+ end
213
+ log_info("Preparing#{" #{name}:" if name} #{sql}")
214
+ cps = conn.prepareStatement(sql)
215
+ conn.prepared_statements[name] = [sql, cps] if name
216
+ end
217
+ i = 0
218
+ args.each{|arg| set_ps_arg(cps, arg, i+=1)}
219
+ log_info("Executing#{" #{name}" if name}", args)
220
+ begin
221
+ if block_given?
222
+ yield cps.executeQuery
223
+ else
224
+ case opts[:type]
225
+ when :ddl
226
+ cps.execute
227
+ when :insert
228
+ cps.executeUpdate
229
+ last_insert_id(conn, opts)
230
+ else
231
+ cps.executeUpdate
232
+ end
233
+ end
234
+ rescue NativeException, JavaSQL::SQLException => e
235
+ raise Error, e.message
236
+ ensure
237
+ cps.close unless name
238
+ end
239
+ end
240
+ end
241
+
242
+ # By default, there is no support for determining the last inserted
243
+ # id, so return nil. This method should be overridden in
244
+ # sub adapters.
245
+ def last_insert_id(conn, opts)
246
+ nil
247
+ end
248
+
249
+ # Java being java, you need to specify the type of each argument
250
+ # for the prepared statement, and bind it individually. This
251
+ # guesses which JDBC method to use, and hopefully JRuby will convert
252
+ # things properly for us.
253
+ def set_ps_arg(cps, arg, i)
254
+ case arg
255
+ when Integer
256
+ cps.setInt(i, arg)
257
+ when String
258
+ cps.setString(i, arg)
259
+ when Date, Java::JavaSql::Date
260
+ cps.setDate(i, arg)
261
+ when Time, DateTime, Java::JavaSql::Timestamp
262
+ cps.setTimestamp(i, arg)
263
+ when Float
264
+ cps.setDouble(i, arg)
265
+ when nil
266
+ cps.setNull(i, JavaSQL::Types::NULL)
267
+ end
268
+ end
269
+
270
+ # Add a prepared_statements accessor to the connection,
271
+ # and set it to an empty hash. This is used to store
272
+ # adapter specific prepared statements.
273
+ def setup_connection(conn)
274
+ conn.meta_eval{attr_accessor :prepared_statements}
275
+ conn.prepared_statements = {}
276
+ conn
277
+ end
278
+
279
+ # The JDBC adapter should not need the pool to convert exceptions.
142
280
  def connection_pool_default_options
143
281
  super.merge(:pool_convert_exceptions=>false)
144
282
  end
145
283
  end
146
284
 
147
285
  class Dataset < Sequel::Dataset
286
+ # Use JDBC PreparedStatements instead of emulated ones. Statements
287
+ # created using #prepare are cached at the connection level to allow
288
+ # reuse. This also supports bind variables by using unnamed
289
+ # prepared statements created using #call.
290
+ module PreparedStatementMethods
291
+ include Sequel::Dataset::UnnumberedArgumentMapper
292
+
293
+ private
294
+
295
+ # Execute the prepared SQL using the stored type and
296
+ # arguments derived from the hash passed to call.
297
+ def execute(sql, opts={}, &block)
298
+ super(self, {:arguments=>bind_arguments, :type=>sql_query_type}.merge(opts), &block)
299
+ end
300
+
301
+ # Same as execute, explicit due to intricacies of alias and super.
302
+ def execute_dui(sql, opts={}, &block)
303
+ super(self, {:arguments=>bind_arguments, :type=>sql_query_type}.merge(opts), &block)
304
+ end
305
+
306
+ # Same as execute, explicit due to intricacies of alias and super.
307
+ def execute_insert(sql, opts={}, &block)
308
+ super(self, {:arguments=>bind_arguments, :type=>sql_query_type}.merge(opts), &block)
309
+ end
310
+ end
311
+
312
+ # Create an unnamed prepared statement and call it. Allows the
313
+ # use of bind variables.
314
+ def call(type, hash, values=nil, &block)
315
+ prepare(type, nil, values).call(hash, &block)
316
+ end
317
+
318
+ # Correctly return rows from the database and return them as hashes.
319
+ def fetch_rows(sql, &block)
320
+ execute(sql) do |result|
321
+ # get column names
322
+ meta = result.getMetaData
323
+ column_count = meta.getColumnCount
324
+ @columns = []
325
+ column_count.times {|i| @columns << meta.getColumnName(i+1).to_sym}
326
+
327
+ # get rows
328
+ while result.next
329
+ row = {}
330
+ @columns.each_with_index {|v, i| row[v] = result.getObject(i+1)}
331
+ yield row
332
+ end
333
+ end
334
+ self
335
+ end
336
+
337
+ # Use the ISO values for dates and times.
148
338
  def literal(v)
149
339
  case v
150
340
  when Time
151
341
  literal(v.iso8601)
152
- when Date, DateTime, Java::JavaSql::Timestamp
342
+ when Date, DateTime, Java::JavaSql::Timestamp, Java::JavaSql::Date
153
343
  literal(v.to_s)
154
344
  else
155
345
  super
156
346
  end
157
347
  end
158
-
159
- def fetch_rows(sql, &block)
160
- @db.synchronize do
161
- @db.execute(sql) do |result|
162
- # get column names
163
- meta = result.getMetaData
164
- column_count = meta.getColumnCount
165
- @columns = []
166
- column_count.times {|i| @columns << meta.getColumnName(i+1).to_sym}
167
-
168
- # get rows
169
- while result.next
170
- row = {}
171
- @columns.each_with_index {|v, i| row[v] = result.getObject(i+1)}
172
- yield row
173
- end
174
- end
348
+
349
+ # Create a named prepared statement that is stored in the
350
+ # database (and connection) for reuse.
351
+ def prepare(type, name, values=nil)
352
+ ps = to_prepared_statement(type, values)
353
+ ps.extend(PreparedStatementMethods)
354
+ if name
355
+ ps.prepared_statement_name = name
356
+ db.prepared_statements[name] = ps
175
357
  end
176
- self
358
+ ps
177
359
  end
178
360
  end
179
361
  end
180
362
  end
181
363
 
182
364
  class Java::JavaSQL::Timestamp
365
+ # Add a usec method in order to emulate Time values.
183
366
  def usec
184
367
  getNanos/1000
185
368
  end