sequel 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +40 -0
  2. data/Rakefile +1 -1
  3. data/doc/opening_databases.rdoc +7 -0
  4. data/doc/release_notes/3.3.0.txt +192 -0
  5. data/lib/sequel/adapters/ado.rb +34 -39
  6. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  7. data/lib/sequel/adapters/jdbc.rb +27 -4
  8. data/lib/sequel/adapters/jdbc/h2.rb +14 -3
  9. data/lib/sequel/adapters/jdbc/mssql.rb +51 -0
  10. data/lib/sequel/adapters/mysql.rb +28 -12
  11. data/lib/sequel/adapters/odbc.rb +36 -30
  12. data/lib/sequel/adapters/odbc/mssql.rb +44 -0
  13. data/lib/sequel/adapters/shared/mssql.rb +185 -10
  14. data/lib/sequel/adapters/shared/mysql.rb +9 -9
  15. data/lib/sequel/adapters/shared/sqlite.rb +45 -47
  16. data/lib/sequel/connection_pool.rb +8 -5
  17. data/lib/sequel/core.rb +2 -8
  18. data/lib/sequel/database.rb +9 -10
  19. data/lib/sequel/database/schema_sql.rb +3 -2
  20. data/lib/sequel/dataset.rb +1 -0
  21. data/lib/sequel/dataset/sql.rb +15 -6
  22. data/lib/sequel/extensions/schema_dumper.rb +7 -7
  23. data/lib/sequel/model/associations.rb +16 -14
  24. data/lib/sequel/model/base.rb +25 -7
  25. data/lib/sequel/plugins/association_proxies.rb +41 -0
  26. data/lib/sequel/plugins/many_through_many.rb +0 -1
  27. data/lib/sequel/sql.rb +8 -11
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/adapters/mysql_spec.rb +42 -38
  30. data/spec/adapters/sqlite_spec.rb +0 -4
  31. data/spec/core/database_spec.rb +22 -1
  32. data/spec/core/dataset_spec.rb +37 -12
  33. data/spec/core/expression_filters_spec.rb +5 -0
  34. data/spec/core/schema_spec.rb +15 -8
  35. data/spec/extensions/association_proxies_spec.rb +47 -0
  36. data/spec/extensions/caching_spec.rb +2 -2
  37. data/spec/extensions/hook_class_methods_spec.rb +6 -6
  38. data/spec/extensions/many_through_many_spec.rb +13 -0
  39. data/spec/extensions/schema_dumper_spec.rb +12 -4
  40. data/spec/extensions/validation_class_methods_spec.rb +3 -3
  41. data/spec/integration/dataset_test.rb +47 -17
  42. data/spec/integration/prepared_statement_test.rb +5 -5
  43. data/spec/integration/schema_test.rb +111 -34
  44. data/spec/model/associations_spec.rb +128 -11
  45. data/spec/model/hooks_spec.rb +7 -6
  46. data/spec/model/model_spec.rb +54 -4
  47. data/spec/model/record_spec.rb +2 -3
  48. data/spec/model/validations_spec.rb +4 -4
  49. metadata +109 -101
  50. data/spec/adapters/ado_spec.rb +0 -93
data/CHANGELOG CHANGED
@@ -1,3 +1,43 @@
1
+ === 3.3.0 (2009-08-03)
2
+
3
+ * Add an assocation_proxies plugin that uses proxies for associations (jeremyevans)
4
+
5
+ * Have the add/remove/remove_all methods take additional arguments and pass them to the internal methods (clivecrous)
6
+
7
+ * Move convert_tinyint_to_bool method from Sequel to Sequel::MySQL (jeremyevans)
8
+
9
+ * Model associations now default to associating to classes in the same scope (jeremyevans, nougad) (#274)
10
+
11
+ * Add Dataset#unlimited, similar to unfiltered and unordered (jeremyevans)
12
+
13
+ * Make Dataset#from_self take an options hash and respect an :alias option, giving the alias to use (Phrogz)
14
+
15
+ * Make the JDBC adapter accept a :convert_types option to turn off Java type conversion and double performance (jeremyevans)
16
+
17
+ * Slight increase in ConnectionPool performance (jeremyevans)
18
+
19
+ * SQL::WindowFunction can now be aliased/casted etc. just like SQL::Function (jeremyevans)
20
+
21
+ * Model#save no longer attempts to update primary key columns (jeremyevans)
22
+
23
+ * Sequel will now unescape values provided in connection strings (e.g. ado:///db?host=server%5cinstance) (jeremyevans)
24
+
25
+ * Significant improvements to the ODBC and ADO adapters in general (jeremyevans)
26
+
27
+ * The ADO adapter no longer attempts to use database transactions, since they never worked (jeremyevans)
28
+
29
+ * Much better support for Microsoft SQL Server using the ADO, ODBC, and JDBC adapters (jeremyevans)
30
+
31
+ * Support rename_column, set_column_null, set_column_type, and add_foreign_key on H2 (jeremyevans)
32
+
33
+ * Support adding a column with a primary key or unique constraint to an existing table on SQLite (jeremyevans)
34
+
35
+ * Support altering a column's type, null status, or default on SQLite (jeremyevans)
36
+
37
+ * Fix renaming a NOT NULL column without a default on MySQL (nougad, jeremyevans) (#273)
38
+
39
+ * Don't swallow DatabaseConnectionErrors when creating model subclasses (tommy.midttveit)
40
+
1
41
  === 3.2.0 (2009-07-02)
2
42
 
3
43
  * In the STI plugin, don't overwrite the STI field if it is already set (jeremyevans)
data/Rakefile CHANGED
@@ -168,7 +168,7 @@ begin
168
168
  t.spec_opts = spec_opts.call
169
169
  end
170
170
 
171
- %w'postgres sqlite mysql informix oracle ado'.each do |adapter|
171
+ %w'postgres sqlite mysql informix oracle firebird'.each do |adapter|
172
172
  desc "Run #{adapter} specs without coverage"
173
173
  Spec::Rake::SpecTask.new("spec_#{adapter}") do |t|
174
174
  t.spec_files = ["spec/adapters/#{adapter}_spec.rb"] + Dir["spec/integration/*_test.rb"]
@@ -203,6 +203,13 @@ Example connections strings:
203
203
  jdbc:mysql://localhost/test?user=root&password=root
204
204
  jdbc:h2:mem:
205
205
 
206
+ The following additional options are supported:
207
+
208
+ * :convert_types - If set to false, does not attempt to convert some Java types to ruby types.
209
+ Setting to false roughly doubles performance when selecting large numbers of rows.
210
+ Note that you can't provide this option inside the connection string (as that is passed
211
+ directly to JDBC), you have to pass it as a separate option.
212
+
206
213
  === mysql
207
214
 
208
215
  The MySQL adapter does not support the pure-ruby MySQL adapter that ships with
@@ -0,0 +1,192 @@
1
+ New Features
2
+ ------------
3
+
4
+ * An association_proxies plugin has been added. This is not a
5
+ full-blown proxy implemention, but it allows you to write code
6
+ such as:
7
+
8
+ artist.albums.filter{num_tracks > 10}
9
+
10
+ Without the plugin, you have to call filter specifically on the
11
+ association's dataset:
12
+
13
+ artist.albums_dataset.filter{num_tracks > 10}
14
+
15
+ The plugin works by proxying array methods to the array of
16
+ associated objects, and all other methods to the association's
17
+ dataset. This results in the following behavior:
18
+
19
+ # Will load the associated objects (unless they are already
20
+ # cached), and return the length of the array
21
+ artist.albums.length
22
+
23
+ # Will issue an SQL query with COUNT (even if the association
24
+ # is already cached), and return the result
25
+ artist.albums.count
26
+
27
+ * The add_*/remove_*/remove_all_* association methods now take
28
+ additional arguments that are passed down to the
29
+ _add_*/_remove_*/_remove_all_* methods. One of the things this
30
+ allows you to do is update additional columns in join tables for
31
+ many_to_many associations:
32
+
33
+ class Album
34
+ many_to_many :artists
35
+ def _add_artist(artist, values={})
36
+ DB[:albums_artists].
37
+ insert(values.merge(:album_id=>id,
38
+ :artist_id=>artist.id))
39
+ end
40
+ end
41
+
42
+ album = Album[1]
43
+ artist1 = Artist[2]
44
+ artist2 = Artist[3]
45
+ album.add_artist(artist1, :relationship=>'composer')
46
+ album.add_artist(artist2, :relationship=>'arranger')
47
+
48
+ * The JDBC adapter now accepts a :convert_types option to turn off
49
+ Java type conversion. The option is true by default for
50
+ backwards compatibility and correctness, but can be set to false
51
+ to double performance. The option can be set at the database
52
+ and dataset levels:
53
+
54
+ DB = Sequel.jdbc('jdbc:postgresql://host/database',
55
+ :convert_types=>false)
56
+ DB.convert_types = true
57
+ ds = DB[:table]
58
+ ds.convert_types = false
59
+
60
+ * Dataset#from_self now takes an option hash and respects an
61
+ :alias option, giving the table alias to use.
62
+
63
+ * Dataset#unlimited was added, similar to unfiltered and unordered.
64
+
65
+ * SQL::WindowFunction is now a subclass of SQL::GenericExpression,
66
+ so you can alias it and treat it like any other SQL::Function.
67
+
68
+ Other Improvements
69
+ ------------------
70
+
71
+ * Microsoft SQL Server support is much, much better in Sequel 3.3.0
72
+ than in previous versions. Support is pretty good with the ODBC,
73
+ ADO, and JDBC adapters, close to the level of support for
74
+ PostreSQL, MySQL, SQLite, and H2. Improvements are too numerous
75
+ to list, but here are some highlights:
76
+
77
+ * Dataset#insert now returns the primary key (identity field), so
78
+ it can be used easier with models.
79
+
80
+ * Transactions can now use savepoints (except on ADO).
81
+
82
+ * Offsets are supported when using SQL Server 2005 or 2008, using
83
+ a ROW_NUMBER window function. However, you must specify an
84
+ order for your dataset (which you probably are already doing if
85
+ you are using offsets).
86
+
87
+ * Schema parsing has been implemented, though it doesn't support
88
+ primary key parsing (except on JDBC, since the JDBC support is
89
+ used there).
90
+
91
+ * The SQL syntax Sequel uses is now much more compatible, and
92
+ most schema modification methods and database types now work
93
+ correctly.
94
+
95
+ * The ADO and ODBC adapters both work much better now. The ADO
96
+ adapter no longer attempts to use transactions, since I've found
97
+ that ADO does not give a stable native connection (and hence
98
+ transactions weren't possible). I strongly recommend against
99
+ using the ADO adapter in production.
100
+
101
+ * The H2 JDBC subadapter now supports rename_column, set_column_null,
102
+ set_column_type, and add_foreign_key.
103
+
104
+ * Altering a columns type, null status, or default is now supported
105
+ on SQLite. You can also add primary keys and unique columns.
106
+
107
+ * Both the ADO and ODBC adapters now catch the native exception
108
+ classes and raise Sequel::DatabaseErrors.
109
+
110
+ * Model classes now default to associating to other classes in the
111
+ same scope. This makes it easier to use namespaced models.
112
+
113
+ * The schema parser and schema dumper now support the following
114
+ types: nchar, nvarchar, ntext, smalldatetime, smallmoney, binary,
115
+ and varbinary.
116
+
117
+ * You can now specify the null status for a column using :allow_null
118
+ in addition to :null. This is to make it easier to use the
119
+ table creation methods with the results of the schema parser.
120
+
121
+ * Renaming a NOT NULL column without a default now works on MySQL.
122
+
123
+ * Model class initialization now raises an exception if there is a
124
+ problem connecting to the database.
125
+
126
+ * Connection pool performance has been increased slightly.
127
+
128
+ * The literal_time method in the ODBC adapter has been fixed.
129
+
130
+ * An unlikely but potential bug in the MySQL adapter has been fixed.
131
+
132
+ Backwards Compatibility
133
+ -----------------------
134
+
135
+ * The convert_tinyint_to_bool setting moved from the main Sequel
136
+ module to the Sequel::MySQL module. The native MySQL adapter is
137
+ the only adapter that converted tinyint columns to booleans when
138
+ the rows are returned, so you can only use the setting with the
139
+ native MySQL adapter.
140
+
141
+ Additionally, the setting's behavior has changed. When parsing
142
+ the schema, now only tinyint(1) columns are now considered as
143
+ boolean, instead of all tinyint columns. This allows you to use
144
+ tinyint(4) columns for storing small integers and tinyint(1)
145
+ columns as booleans, and not have the schema parsing support
146
+ consider the tinyint(4) columns as booleans. Unfortunately,
147
+ due to limitations in the native MySQL driver, all tinyint
148
+ column values are converted to booleans upon retrieval, not just
149
+ tinyint(1) column values.
150
+
151
+ Unfortunately, the previous Sequel behavior was to use the
152
+ default tinyint size (tinyint(4)) when creating boolean columns
153
+ (using the TrueClass or FalseClass generic types). If you were
154
+ using the generic type support to create the columns, you should
155
+ modify your database to change the column type from tinyint(4) to
156
+ tinyint(1).
157
+
158
+ If you use MySQL with tinyint columns, these changes have the
159
+ potential to break applications. Care should be taken when
160
+ upgrading if these changes apply to you.
161
+
162
+ * Model classes now default to associating to other classes in the
163
+ same scope. It's highly unlikely anyone was relying on the
164
+ previous behavior, but if you have a model inside a module that
165
+ you are associating to a model outside of a module, you now need
166
+ to specify the associated class using the :class option.
167
+
168
+ * Model#save no longer includes the primary key fields in the SET
169
+ clause of the UPDATE query, only in the WHERE clause. I'm not
170
+ sure if this affects backwards compatibility of production code,
171
+ but it can break tests that expect specific SQL.
172
+
173
+ * Behavior to handle empty identifiers has now been standardized.
174
+ If any database adapter returns an empty identifier, Sequel will
175
+ use 'untitled' as the identifier. This can break backwards
176
+ compatibility if the adapter previously used another default and
177
+ you were relying on that default. This was necessary to fix any
178
+ possible "interning empty string" exceptions.
179
+
180
+ * On MSSQL, Sequel now uses the datetime type instead of the
181
+ timestamp type for generic DateTimes. It now uses bit for the
182
+ TrueClass and FalseClass generic types, and image for the File
183
+ generic type.
184
+
185
+ * Sequel now unescapes URL parts:
186
+
187
+ Sequel.connect(ado:///db?host=server%5cinstance)
188
+
189
+ However, this can break backward compatibility if you previously
190
+ expected it not to be unescaped.
191
+
192
+ * The columns_for private SQLite Database method has been removed.
@@ -1,15 +1,7 @@
1
1
  require 'win32ole'
2
2
 
3
3
  module Sequel
4
- # The ADO adapter provides connectivity to ADO databases in Windows. ADO
5
- # databases can be opened using a URL with the ado schema:
6
- #
7
- # DB = Sequel.connect('ado://mydb')
8
- #
9
- # or using the Sequel.ado method:
10
- #
11
- # DB = Sequel.ado('mydb')
12
- #
4
+ # The ADO adapter provides connectivity to ADO databases in Windows.
13
5
  module ADO
14
6
  class Database < Sequel::Database
15
7
  set_adapter_scheme :ado
@@ -19,20 +11,19 @@ module Sequel
19
11
  opts[:driver] ||= 'SQL Server'
20
12
  case opts[:driver]
21
13
  when 'SQL Server'
22
- Sequel.require 'adapters/shared/mssql'
23
- extend Sequel::MSSQL::DatabaseMethods
14
+ Sequel.require 'adapters/ado/mssql'
15
+ extend Sequel::ADO::MSSQL::DatabaseMethods
24
16
  end
25
17
  end
26
18
 
27
19
  # Connect to the database. In addition to the usual database options,
28
- # the following option has effect:
20
+ # the following options have an effect:
29
21
  #
30
22
  # * :command_timeout - Sets the time in seconds to wait while attempting
31
- # to execute a command before cancelling the attempt and generating
32
- # an error. Specifically, it sets the ADO CommandTimeout property.
33
- # If this property is not set, the default of 30 seconds is used.
23
+ # to execute a command before cancelling the attempt and generating
24
+ # an error. Specifically, it sets the ADO CommandTimeout property.
25
+ # If this property is not set, the default of 30 seconds is used.
34
26
  # * :provider - Sets the Provider of this ADO connection (for example, "SQLOLEDB")
35
-
36
27
  def connect(server)
37
28
  opts = server_opts(server)
38
29
  s = "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
@@ -50,14 +41,35 @@ module Sequel
50
41
  def execute(sql, opts={})
51
42
  log_info(sql)
52
43
  synchronize(opts[:server]) do |conn|
53
- r = conn.Execute(sql)
54
- yield(r) if block_given?
55
- r
44
+ begin
45
+ r = conn.Execute(sql)
46
+ yield(r) if block_given?
47
+ rescue ::WIN32OLERuntimeError => e
48
+ raise_error(e)
49
+ end
56
50
  end
51
+ nil
57
52
  end
58
- alias_method :do, :execute
53
+ alias do execute
59
54
 
60
55
  private
56
+
57
+ # The ADO adapter doesn't support transactions, since it appears not to
58
+ # use a single native connection for each connection in the pool
59
+ def _transaction(conn)
60
+ th = Thread.current
61
+ begin
62
+ @transactions << th
63
+ yield conn
64
+ rescue Sequel::Rollback
65
+ ensure
66
+ @transactions.delete(th)
67
+ end
68
+ end
69
+
70
+ def connection_pool_default_options
71
+ super.merge(:pool_convert_exceptions=>false)
72
+ end
61
73
 
62
74
  def disconnect_connection(conn)
63
75
  conn.Close
@@ -67,25 +79,8 @@ module Sequel
67
79
  class Dataset < Sequel::Dataset
68
80
  def fetch_rows(sql)
69
81
  execute(sql) do |s|
70
- @columns = s.Fields.extend(Enumerable).map do |column|
71
- name = column.Name.empty? ? '(no column name)' : column.Name
72
- output_identifier(name)
73
- end
74
-
75
- unless s.eof
76
- s.moveFirst
77
- s.getRows.transpose.each {|r| yield hash_row(r)}
78
- end
79
- end
80
- self
81
- end
82
-
83
- private
84
-
85
- def hash_row(row)
86
- @columns.inject({}) do |m, c|
87
- m[c] = row.shift
88
- m
82
+ @columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
83
+ s.getRows.transpose.each{|r| yield cols.inject({}){|m,c| m[c] = r.shift; m}} unless s.eof
89
84
  end
90
85
  end
91
86
  end
@@ -0,0 +1,30 @@
1
+ Sequel.require 'adapters/shared/mssql'
2
+
3
+ module Sequel
4
+ module ADO
5
+ # Database and Dataset instance methods for MSSQL specific
6
+ # support via ADO.
7
+ module MSSQL
8
+ module DatabaseMethods
9
+ include Sequel::MSSQL::DatabaseMethods
10
+
11
+ # Return instance of Sequel::ADO::MSSQL::Dataset with the given opts.
12
+ def dataset(opts=nil)
13
+ Sequel::ADO::MSSQL::Dataset.new(self, opts)
14
+ end
15
+ end
16
+
17
+ class Dataset < ADO::Dataset
18
+ include Sequel::MSSQL::DatasetMethods
19
+
20
+ # Use a nasty hack of multiple SQL statements in the same call and
21
+ # having the last one return the most recently inserted id. This
22
+ # is necessary as ADO doesn't provide a consistent native connection.
23
+ def insert(values={})
24
+ return super if @opts[:sql]
25
+ with_sql("SET NOCOUNT ON; #{insert_sql(values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -58,8 +58,8 @@ module Sequel
58
58
  Java::oracle.jdbc.driver.OracleDriver
59
59
  end,
60
60
  :sqlserver=>proc do |db|
61
- Sequel.require 'adapters/shared/mssql'
62
- db.extend(Sequel::MSSQL::DatabaseMethods)
61
+ Sequel.require 'adapters/jdbc/mssql'
62
+ db.extend(Sequel::JDBC::MSSQL::DatabaseMethods)
63
63
  com.microsoft.sqlserver.jdbc.SQLServerDriver
64
64
  end,
65
65
  :h2=>proc do |db|
@@ -88,12 +88,18 @@ module Sequel
88
88
  # The type of database we are connecting to
89
89
  attr_reader :database_type
90
90
 
91
+ # Whether to convert some Java types to ruby types when retrieving rows.
92
+ # True by default, can be set to false to roughly double performance when
93
+ # fetching rows.
94
+ attr_accessor :convert_types
95
+
91
96
  # Call the DATABASE_SETUP proc directly after initialization,
92
97
  # so the object always uses sub adapter specific code. Also,
93
98
  # raise an error immediately if the connection doesn't have a
94
99
  # uri, since JDBC requires one.
95
100
  def initialize(opts)
96
101
  @opts = opts
102
+ @convert_types = opts.include?(:convert_types) ? typecast_value_boolean(opts[:convert_types]) : true
97
103
  raise(Error, "No connection string specified") unless uri
98
104
  if match = /\Ajdbc:([^:]+)/.match(uri) and prok = DATABASE_SETUP[match[1].to_sym]
99
105
  prok.call(self)
@@ -279,7 +285,7 @@ module Sequel
279
285
  cps.execute
280
286
  when :insert
281
287
  cps.executeUpdate
282
- last_insert_id(conn, opts)
288
+ last_insert_id(conn, opts.merge(:prepared=>true))
283
289
  else
284
290
  cps.executeUpdate
285
291
  end
@@ -418,6 +424,17 @@ module Sequel
418
424
  end
419
425
  end
420
426
 
427
+ # Whether to convert some Java types to ruby types when retrieving rows.
428
+ # Uses the database's setting by default, can be set to false to roughly
429
+ # double performance when fetching rows.
430
+ attr_accessor :convert_types
431
+
432
+ # Use the convert_types default setting from the database
433
+ def initialize(db, opts={})
434
+ @convert_types = db.convert_types
435
+ super
436
+ end
437
+
421
438
  # Correctly return rows from the database and return them as hashes.
422
439
  def fetch_rows(sql, &block)
423
440
  execute(sql){|result| process_result_set(result, &block)}
@@ -470,10 +487,16 @@ module Sequel
470
487
  i = 0
471
488
  meta.getColumnCount.times{cols << [output_identifier(meta.getColumnLabel(i+=1)), i]}
472
489
  @columns = cols.map{|c| c.at(0)}
490
+ row = {}
491
+ blk = if @convert_types
492
+ lambda{|n, i| row[n] = convert_type(result.getObject(i))}
493
+ else
494
+ lambda{|n, i| row[n] = result.getObject(i)}
495
+ end
473
496
  # get rows
474
497
  while result.next
475
498
  row = {}
476
- cols.each{|n, i| row[n] = convert_type(result.getObject(i))}
499
+ cols.each(&blk)
477
500
  yield row
478
501
  end
479
502
  end