sequel 3.43.0 → 3.44.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +32 -0
  2. data/doc/association_basics.rdoc +10 -3
  3. data/doc/release_notes/3.37.0.txt +1 -1
  4. data/doc/release_notes/3.44.0.txt +152 -0
  5. data/lib/sequel/adapters/ado.rb +0 -6
  6. data/lib/sequel/adapters/db2.rb +0 -3
  7. data/lib/sequel/adapters/dbi.rb +0 -6
  8. data/lib/sequel/adapters/ibmdb.rb +0 -3
  9. data/lib/sequel/adapters/jdbc.rb +8 -12
  10. data/lib/sequel/adapters/jdbc/as400.rb +2 -18
  11. data/lib/sequel/adapters/jdbc/derby.rb +10 -0
  12. data/lib/sequel/adapters/jdbc/h2.rb +10 -0
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +10 -0
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +5 -0
  15. data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -4
  16. data/lib/sequel/adapters/mock.rb +5 -0
  17. data/lib/sequel/adapters/mysql2.rb +4 -13
  18. data/lib/sequel/adapters/odbc.rb +0 -5
  19. data/lib/sequel/adapters/oracle.rb +3 -5
  20. data/lib/sequel/adapters/postgres.rb +2 -1
  21. data/lib/sequel/adapters/shared/access.rb +10 -0
  22. data/lib/sequel/adapters/shared/cubrid.rb +9 -0
  23. data/lib/sequel/adapters/shared/db2.rb +10 -0
  24. data/lib/sequel/adapters/shared/mssql.rb +10 -0
  25. data/lib/sequel/adapters/shared/mysql.rb +14 -0
  26. data/lib/sequel/adapters/shared/oracle.rb +15 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +26 -2
  28. data/lib/sequel/adapters/shared/sqlite.rb +15 -0
  29. data/lib/sequel/adapters/tinytds.rb +5 -32
  30. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -37
  31. data/lib/sequel/core.rb +3 -3
  32. data/lib/sequel/database/misc.rb +40 -4
  33. data/lib/sequel/database/query.rb +1 -1
  34. data/lib/sequel/database/schema_methods.rb +33 -12
  35. data/lib/sequel/dataset/actions.rb +51 -2
  36. data/lib/sequel/dataset/features.rb +0 -6
  37. data/lib/sequel/dataset/sql.rb +1 -1
  38. data/lib/sequel/exceptions.rb +22 -7
  39. data/lib/sequel/extensions/columns_introspection.rb +30 -5
  40. data/lib/sequel/extensions/pg_auto_parameterize.rb +9 -0
  41. data/lib/sequel/model/associations.rb +50 -37
  42. data/lib/sequel/model/base.rb +30 -1
  43. data/lib/sequel/plugins/eager_each.rb +17 -21
  44. data/lib/sequel/plugins/identity_map.rb +2 -1
  45. data/lib/sequel/plugins/many_through_many.rb +1 -1
  46. data/lib/sequel/plugins/single_table_inheritance.rb +2 -2
  47. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  48. data/lib/sequel/sql.rb +4 -2
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/postgres_spec.rb +32 -2
  51. data/spec/adapters/sqlite_spec.rb +20 -0
  52. data/spec/core/database_spec.rb +40 -0
  53. data/spec/core/dataset_spec.rb +91 -4
  54. data/spec/core/mock_adapter_spec.rb +2 -1
  55. data/spec/core/schema_generator_spec.rb +4 -0
  56. data/spec/core/schema_spec.rb +9 -3
  57. data/spec/extensions/association_dependencies_spec.rb +3 -3
  58. data/spec/extensions/columns_introspection_spec.rb +28 -2
  59. data/spec/extensions/eager_each_spec.rb +0 -1
  60. data/spec/extensions/identity_map_spec.rb +3 -2
  61. data/spec/extensions/migration_spec.rb +6 -0
  62. data/spec/extensions/prepared_statements_associations_spec.rb +2 -2
  63. data/spec/extensions/rcte_tree_spec.rb +2 -2
  64. data/spec/extensions/single_table_inheritance_spec.rb +3 -0
  65. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  66. data/spec/extensions/validation_class_methods_spec.rb +8 -0
  67. data/spec/integration/associations_test.rb +4 -4
  68. data/spec/integration/database_test.rb +68 -20
  69. data/spec/integration/dataset_test.rb +48 -0
  70. data/spec/integration/schema_test.rb +25 -1
  71. data/spec/model/associations_spec.rb +21 -8
  72. data/spec/model/dataset_methods_spec.rb +58 -18
  73. metadata +4 -2
data/CHANGELOG CHANGED
@@ -1,3 +1,35 @@
1
+ === 3.44.0 (2013-02-04)
2
+
3
+ * Speedup mysql2 adapter with identifier output method fetch speed by up to 50% (jeremyevans)
4
+
5
+ * Speedup tinytds adapter fetch speed by up to 60% (jeremyevans)
6
+
7
+ * Expand columns_introspection extension to consider cached schema values in the database (jeremyevans)
8
+
9
+ * Expand columns_introspection extension to handle subselects (jeremyevans)
10
+
11
+ * Have #last and #paged_each for model datasets order by the model's primary key by default (jeremyevans)
12
+
13
+ * Improve emulated offset support to handle subqueries (jeremyevans)
14
+
15
+ * Remove use of Object#extend from the eager_each plugin (jeremyevans)
16
+
17
+ * Add support for temporary views on SQLite and PostgreSQL via the :temp option to create_view (chanks, jeremyevans)
18
+
19
+ * Emulate Database#create_or_replace_view if not supported directly (jeremyevans)
20
+
21
+ * Add Dataset#paged_each, for processing entire datasets without keeping all rows in memory (jeremyevans)
22
+
23
+ * Add Sequel::ConstraintViolation exception class and subclasses for easier exception handling (jeremyevans)
24
+
25
+ * Fix use of identity_map plugin with many_to_many associations with right composite keys (chanks) (#603)
26
+
27
+ * Increase virtual row performance by using a shared VirtualRow instance (jeremyevans)
28
+
29
+ * Allow the :dataset association option to accept the association reflection as an argument (jeremyevans)
30
+
31
+ * Improve association method performance by caching intermediate dataset (jeremyevans)
32
+
1
33
  === 3.43.0 (2013-01-08)
2
34
 
3
35
  * Move the #meta_def support for Database, Dataset, and Model to the meta_def extension (jeremyevans)
@@ -1007,14 +1007,21 @@ in additon to the :clone option.
1007
1007
  ==== :dataset
1008
1008
 
1009
1009
  This is generally only specified for custom associations that aren't based on
1010
- primary/foreign key relationships. It should be a proc that is instance evaled
1010
+ primary/foreign key relationships. It should be a proc that is instance_execed
1011
1011
  to get the base dataset to use before the other options are applied.
1012
1012
 
1013
+ If the proc accepts an argument, it is passed the related association reflection.
1014
+ For best performance, it's recommended that custom associations call the
1015
+ +associated_dataset+ method on the association reflection as the starting point
1016
+ for the dataset to return. The +associated_dataset+ method will return a
1017
+ dataset based on the associated class with most of the association options
1018
+ already applied, and the proc should return a modified copy of this dataset.
1019
+
1013
1020
  Here's an example of an association of songs to artists through lyrics, where
1014
1021
  the artist can perform any one of four tasks for the lyric:
1015
1022
 
1016
- Album.one_to_many :songs, :dataset=>(proc do
1017
- Song.select_all(:songs).
1023
+ Album.one_to_many :songs, :dataset=>(proc do |r|
1024
+ r.associated_dataset.select_all(:songs).
1018
1025
  join(Lyric, :id=>:lyricid,
1019
1026
  id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])
1020
1027
  end)
@@ -162,7 +162,7 @@
162
162
 
163
163
  DB.create_table(:t) do
164
164
  ...
165
- exclusion_constraint([[:col1, '&&'], [:col2, '=']])
165
+ exclude([[:col1, '&&'], [:col2, '=']])
166
166
  # EXCLUDE USING gist (col1 WITH &&, col2 WITH =)
167
167
  end
168
168
 
@@ -0,0 +1,152 @@
1
+ = New Features
2
+
3
+ * Dataset#paged_each has been added, for processing entire datasets
4
+ without keeping all rows in memory, even if the underlying driver
5
+ keeps all query results in memory. This is implemented using
6
+ limits and offsets, and requires an order (model datasets use a
7
+ default order by primary key). It defaults to fetching 1000
8
+ rows at a time, but that can be changed via the :rows_per_fetch
9
+ option.
10
+
11
+ This method is drop-in compatible for each. Previously, the
12
+ pagination extension's each_page method could be used for a
13
+ similar purpose, but users of each_page are now encouraged to
14
+ switch to paged_each.
15
+
16
+ * Sequel now recognizes constraint violation exceptions on most
17
+ databases, and will raise specific exceptions for different
18
+ types of constraint violations, instead of the generic
19
+ Sequel::DatabaseError:
20
+
21
+ * Sequel::ConstraintViolation (generic superclass)
22
+ * Sequel::CheckConstraintViolation
23
+ * Sequel::NotNullConstraintViolation
24
+ * Sequel::ForeignKeyConstraintViolation
25
+ * Sequel::UniqueConstraintViolation
26
+ * Sequel::Postgres::ExclusionConstraintViolation
27
+
28
+ * The :dataset association option can now take accept an optional
29
+ association reflection option. Instead of doing:
30
+
31
+ Album.one_to_many :artists,
32
+ :dataset=>{Artist...}
33
+
34
+ you can now do:
35
+
36
+ Album.one_to_many :artists,
37
+ :dataset=>{|r| r.associated_dataset...}
38
+
39
+ This second form will preform better.
40
+
41
+ * Temporary views are now supported on PostgreSQL and SQLite using
42
+ the :temp option to create_view.
43
+
44
+ = Other Improvements
45
+
46
+ * Row fetching speed in the tinytds adapter has been increased by
47
+ up to 60%.
48
+
49
+ * Row fetching speed in the mysql2 adapter when using an identifier
50
+ output method has been increased by up to 50%.
51
+
52
+ * On databases where offsets are emulated via the ROW_NUMBER window
53
+ function (Oracle, DB2, Microsoft SQL Server), using an offset in
54
+ a subselect is now supported. For example, the following code
55
+ previously didn't work correctly with emulated offsets:
56
+
57
+ # Second 5 rows ordered by column2 of the second 10 rows ordered
58
+ # by column 1.
59
+ DB[:table].order(:column1).limit(10, 10).
60
+ from_self.order(:column2).limit(5, 5)
61
+
62
+ Row processing speed has been increased slightly for all adapters
63
+ that supported databases where offsets are emulated.
64
+
65
+ * Association method performance has improved by caching an
66
+ intermediate dataset. This can close to triple the performance
67
+ of the association_dataset method, and increase the performance
68
+ of the association method by close to 30%.
69
+
70
+ * Virtual Row performance has increased about 30% in the typical
71
+ case by using a shared VirtualRow instance.
72
+
73
+ * Database#create_or_replace_view is now emulated on databases that
74
+ don't support it directly by dropping the view before attempting
75
+ to create it.
76
+
77
+ * The columns_introspection extension can now introspect for simple
78
+ select * queries from subselects, and it can now use the cached
79
+ schema information in the database for simple select * queries
80
+ from tables.
81
+
82
+ * The identity_map plugin now works correctly with many-to-many
83
+ right-side composite keys.
84
+
85
+ * Dataset#last for Model datasets now works even if you don't specify
86
+ an order explicitly, giving the last entry by primary key. Note
87
+ that Dataset#first for model datasets still does not order by
88
+ default.
89
+
90
+ * The eager_each plugin no longer uses Object#extend at runtime.
91
+
92
+ * Database#remove_cached_schema is now thread-safe on non-GVL ruby
93
+ implementations.
94
+
95
+ * Connection errors in the jdbc adapter now provide slightly more
96
+ helpful messages.
97
+
98
+ * Sequel now uses the standard offset emulation code in the
99
+ jdbc/as400 adapter, instead of custom offset emulation code
100
+ specific to that adapter.
101
+
102
+ * Database#create_view with a dataset now works correctly when using
103
+ the pg_auto_parameterize extension.
104
+
105
+ * Database#columns no longer calls the row_proc.
106
+
107
+ * Dataset#schema_and_table no longer turns a literal string into a
108
+ non-literal string.
109
+
110
+ * The oracle adapter now works with a :prefetch_rows=>nil option,
111
+ which explicitly disables prefetching.
112
+
113
+ * The mock mssql adapter now sets a server_version so that more
114
+ parts of it work.
115
+
116
+ = Backwards Compatibility
117
+
118
+ * Offset emulation via ROW_NUMBER works by moving the query to a
119
+ subselect that also selects from the ROW_NUMBER window function,
120
+ and filtering on the ROW_NUMBER in the main query. Previously, the
121
+ ROW_NUMBER was also present in the output columns, and some
122
+ adapter code was needed to hide that fact. Now, the outer select
123
+ selects all of the inner columns in the subselect except for the
124
+ ROW_NUMBER, reducing the adapter code needed. This has the side
125
+ effect of potentially requiring a query (or multiple queries for
126
+ multiple subselects) to determine the columns to use. The
127
+ columns_introspection extension may reduce the number of queries
128
+ needed.
129
+
130
+ * The correlated_subquery eager limit strategy is no longer supported
131
+ on Microsoft SQL Server for many_*_many associations. As the
132
+ window_function eager limit strategy is supported there, there is
133
+ no reason to use the correlated_subquery strategy.
134
+
135
+ * The public AssociationReflection#_dataset_method method has been
136
+ removed.
137
+
138
+ * The private _*_dataset methods for associations (e.g.
139
+ _albums_dataset) have been removed.
140
+
141
+ * The private Dataset#offset_returns_row_number_column? method has
142
+ been removed.
143
+
144
+ * :conditions options for associations are now added to the
145
+ association dataset before the foreign key filters, instead of
146
+ after. This should have no effect unless you were introspecting
147
+ the dataset's opts or sql and acting on it.
148
+
149
+ * The added abilities in the columns_introspection plugin to use
150
+ cached schema for introspection can now cause it to return
151
+ incorrect results if the table's schema has changed since it was
152
+ cached by Sequel.
@@ -132,16 +132,10 @@ module Sequel
132
132
  def fetch_rows(sql)
133
133
  execute(sql) do |s|
134
134
  columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
135
- if opts[:offset] && offset_returns_row_number_column?
136
- rn = row_number_column
137
- columns = columns.dup
138
- columns.delete(rn)
139
- end
140
135
  @columns = columns
141
136
  s.getRows.transpose.each do |r|
142
137
  row = {}
143
138
  cols.each{|c| row[c] = r.shift}
144
- row.delete(rn) if rn
145
139
  yield row
146
140
  end unless s.eof
147
141
  end
@@ -181,12 +181,10 @@ module Sequel
181
181
 
182
182
  def fetch_rows(sql)
183
183
  execute(sql) do |sth|
184
- offset = @opts[:offset]
185
184
  db = @db
186
185
  i = 1
187
186
  column_info = get_column_info(sth)
188
187
  cols = column_info.map{|c| c.at(1)}
189
- cols.delete(row_number_column) if offset
190
188
  @columns = cols
191
189
  errors = [DB2CLI::SQL_NO_DATA_FOUND, DB2CLI::SQL_ERROR]
192
190
  until errors.include?(rc = DB2CLI.SQLFetch(sth))
@@ -202,7 +200,6 @@ module Sequel
202
200
  v
203
201
  end
204
202
  end
205
- row.delete(row_number_column) if offset
206
203
  yield row
207
204
  end
208
205
  end
@@ -88,16 +88,10 @@ module Sequel
88
88
  execute(sql) do |s|
89
89
  begin
90
90
  columns = cols = s.column_names.map{|c| output_identifier(c)}
91
- if opts[:offset] && offset_returns_row_number_column?
92
- rn = row_number_column
93
- columns = columns.dup
94
- columns.delete(rn)
95
- end
96
91
  @columns = columns
97
92
  s.fetch do |r|
98
93
  row = {}
99
94
  cols.each{|c| row[c] = r.shift}
100
- row.delete(rn) if rn
101
95
  yield row
102
96
  end
103
97
  ensure
@@ -408,7 +408,6 @@ module Sequel
408
408
  # Fetch the rows from the database and yield plain hashes.
409
409
  def fetch_rows(sql)
410
410
  execute(sql) do |stmt|
411
- offset = @opts[:offset]
412
411
  columns = []
413
412
  convert = convert_smallint_to_bool
414
413
  cps = db.conversion_procs
@@ -421,7 +420,6 @@ module Sequel
421
420
  columns << [key, cps[type]]
422
421
  end
423
422
  cols = columns.map{|c| c.at(0)}
424
- cols.delete(row_number_column) if offset
425
423
  @columns = cols
426
424
 
427
425
  while res = stmt.fetch_array
@@ -429,7 +427,6 @@ module Sequel
429
427
  res.zip(columns).each do |v, (k, pr)|
430
428
  row[k] = ((pr ? pr.call(v) : v) if v)
431
429
  end
432
- row.delete(row_number_column) if offset
433
430
  yield row
434
431
  end
435
432
  end
@@ -237,8 +237,10 @@ module Sequel
237
237
  raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c
238
238
  c
239
239
  rescue JavaSQL::SQLException, NativeException, StandardError => e2
240
- e.message << "\n#{e2.class.name}: #{e2.message}"
241
- raise e
240
+ unless e2.message == e.message
241
+ e2.message << "\n#{e.class.name}: #{e.message}"
242
+ end
243
+ raise e2
242
244
  end
243
245
  end
244
246
  end
@@ -742,17 +744,13 @@ module Sequel
742
744
  i = 0
743
745
  meta.getColumnCount.times{cols << [output_identifier(meta.getColumnLabel(i+=1)), i]}
744
746
  columns = cols.map{|c| c.at(0)}
745
- if opts[:offset] && offset_returns_row_number_column?
746
- rn = row_number_column
747
- columns.delete(rn)
748
- end
749
747
  @columns = columns
750
748
  ct = @convert_types
751
749
  if (ct.nil? ? db.convert_types : ct)
752
750
  cols.each{|c| c << nil}
753
- process_result_set_convert(cols, result, rn, &block)
751
+ process_result_set_convert(cols, result, &block)
754
752
  else
755
- process_result_set_no_convert(cols, result, rn, &block)
753
+ process_result_set_no_convert(cols, result, &block)
756
754
  end
757
755
  ensure
758
756
  result.close
@@ -776,7 +774,7 @@ module Sequel
776
774
  # the result of as the column's conversion proc to speed up
777
775
  # later processing. If the conversion proc exists, call it
778
776
  # and return the result, otherwise, return the object.
779
- def process_result_set_convert(cols, result, rn)
777
+ def process_result_set_convert(cols, result)
780
778
  while result.next
781
779
  row = {}
782
780
  cols.each do |n, i, p|
@@ -798,18 +796,16 @@ module Sequel
798
796
  v
799
797
  end
800
798
  end
801
- row.delete(rn) if rn
802
799
  yield row
803
800
  end
804
801
  end
805
802
 
806
803
  # Yield rows without calling any conversion procs. This
807
804
  # may yield Java values and not ruby values.
808
- def process_result_set_no_convert(cols, result, rn)
805
+ def process_result_set_no_convert(cols, result)
809
806
  while result.next
810
807
  row = {}
811
808
  cols.each{|n, i| row[n] = result.getObject(i)}
812
- row.delete(rn) if rn
813
809
  yield row
814
810
  end
815
811
  end
@@ -38,29 +38,13 @@ module Sequel
38
38
 
39
39
  # Dataset class for AS400 datasets accessed via JDBC.
40
40
  class Dataset < JDBC::Dataset
41
+ include EmulateOffsetWithRowNumber
42
+
41
43
  WILDCARD = Sequel::LiteralString.new('*').freeze
42
44
  FETCH_FIRST_ROW_ONLY = " FETCH FIRST ROW ONLY".freeze
43
45
  FETCH_FIRST = " FETCH FIRST ".freeze
44
46
  ROWS_ONLY = " ROWS ONLY".freeze
45
47
 
46
- # AS400 needs to use a couple of subselects for queries with offsets.
47
- def select_sql
48
- return super unless o = @opts[:offset]
49
- l = @opts[:limit]
50
- order = @opts[:order]
51
- dsa1 = dataset_alias(1)
52
- dsa2 = dataset_alias(2)
53
- rn = row_number_column
54
- irn = Sequel::SQL::Identifier.new(rn).qualify(dsa2)
55
- subselect_sql(unlimited.
56
- from_self(:alias=>dsa1).
57
- select_more(Sequel::SQL::QualifiedIdentifier.new(dsa1, WILDCARD),
58
- Sequel::SQL::WindowFunction.new(SQL::Function.new(:ROW_NUMBER), Sequel::SQL::Window.new(:order=>order)).as(rn)).
59
- from_self(:alias=>dsa2).
60
- select(Sequel::SQL::QualifiedIdentifier.new(dsa2, WILDCARD)).
61
- where(l ? ((irn > o) & (irn <= l + o)) : (irn > o))) # Leave off limit in case of limit(nil, offset)
62
- end
63
-
64
48
  # Modify the sql to limit the number of rows returned
65
49
  def select_limit_sql(sql)
66
50
  if l = @opts[:limit]
@@ -104,6 +104,16 @@ module Sequel
104
104
  end
105
105
  end
106
106
 
107
+ DATABASE_ERROR_REGEXPS = {
108
+ /The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index/ => UniqueConstraintViolation,
109
+ /violation of foreign key constraint/ => ForeignKeyConstraintViolation,
110
+ /The check constraint .+ was violated/ => CheckConstraintViolation,
111
+ /cannot accept a NULL value/ => NotNullConstraintViolation,
112
+ }.freeze
113
+ def database_error_regexps
114
+ DATABASE_ERROR_REGEXPS
115
+ end
116
+
107
117
  # Use IDENTITY_VAL_LOCAL() to get the last inserted id.
108
118
  def last_insert_id(conn, opts={})
109
119
  statement(conn) do |stmt|
@@ -96,6 +96,16 @@ module Sequel
96
96
  uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o
97
97
  end
98
98
 
99
+ DATABASE_ERROR_REGEXPS = {
100
+ /Unique index or primary key violation/ => UniqueConstraintViolation,
101
+ /Referential integrity constraint violation/ => ForeignKeyConstraintViolation,
102
+ /Check constraint violation/ => CheckConstraintViolation,
103
+ /NULL not allowed for column/ => NotNullConstraintViolation,
104
+ }.freeze
105
+ def database_error_regexps
106
+ DATABASE_ERROR_REGEXPS
107
+ end
108
+
99
109
  # Use IDENTITY() to get the last inserted id.
100
110
  def last_insert_id(conn, opts={})
101
111
  statement(conn) do |stmt|
@@ -52,6 +52,16 @@ module Sequel
52
52
  "#{create_table_prefix_sql(name, options)} AS (#{sql}) WITH DATA"
53
53
  end
54
54
 
55
+ DATABASE_ERROR_REGEXPS = {
56
+ /integrity constraint violation: unique constraint or index violation/ => UniqueConstraintViolation,
57
+ /integrity constraint violation: foreign key/ => ForeignKeyConstraintViolation,
58
+ /integrity constraint violation: check constraint/ => CheckConstraintViolation,
59
+ /integrity constraint violation: NOT NULL check constraint/ => NotNullConstraintViolation,
60
+ }.freeze
61
+ def database_error_regexps
62
+ DATABASE_ERROR_REGEXPS
63
+ end
64
+
55
65
  # Use IDENTITY() to get the last inserted id.
56
66
  def last_insert_id(conn, opts={})
57
67
  statement(conn) do |stmt|