sequel 3.2.0 → 3.3.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.
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