sequel 3.43.0 → 3.44.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 (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|