sequel 3.23.0 → 3.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG +64 -0
  2. data/doc/association_basics.rdoc +43 -5
  3. data/doc/model_hooks.rdoc +64 -27
  4. data/doc/prepared_statements.rdoc +8 -4
  5. data/doc/reflection.rdoc +8 -2
  6. data/doc/release_notes/3.23.0.txt +1 -1
  7. data/doc/release_notes/3.24.0.txt +420 -0
  8. data/lib/sequel/adapters/db2.rb +8 -1
  9. data/lib/sequel/adapters/firebird.rb +25 -9
  10. data/lib/sequel/adapters/informix.rb +4 -19
  11. data/lib/sequel/adapters/jdbc.rb +34 -17
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  14. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  15. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  16. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  18. data/lib/sequel/adapters/postgres.rb +30 -1
  19. data/lib/sequel/adapters/shared/access.rb +10 -0
  20. data/lib/sequel/adapters/shared/informix.rb +45 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +82 -8
  22. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  23. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  24. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  25. data/lib/sequel/adapters/sqlite.rb +8 -3
  26. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  27. data/lib/sequel/ast_transformer.rb +190 -0
  28. data/lib/sequel/core.rb +1 -1
  29. data/lib/sequel/database/misc.rb +6 -0
  30. data/lib/sequel/database/query.rb +33 -3
  31. data/lib/sequel/database/schema_methods.rb +6 -2
  32. data/lib/sequel/dataset/features.rb +6 -0
  33. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  34. data/lib/sequel/dataset/query.rb +17 -0
  35. data/lib/sequel/dataset/sql.rb +2 -53
  36. data/lib/sequel/exceptions.rb +4 -0
  37. data/lib/sequel/extensions/to_dot.rb +95 -83
  38. data/lib/sequel/model.rb +5 -0
  39. data/lib/sequel/model/associations.rb +80 -14
  40. data/lib/sequel/model/base.rb +182 -55
  41. data/lib/sequel/model/exceptions.rb +3 -1
  42. data/lib/sequel/plugins/association_pks.rb +6 -4
  43. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  44. data/lib/sequel/plugins/many_through_many.rb +8 -3
  45. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  46. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  47. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  48. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  49. data/lib/sequel/sql.rb +8 -0
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +43 -18
  52. data/spec/core/connection_pool_spec.rb +56 -77
  53. data/spec/core/database_spec.rb +25 -0
  54. data/spec/core/dataset_spec.rb +127 -16
  55. data/spec/core/expression_filters_spec.rb +13 -0
  56. data/spec/core/schema_spec.rb +6 -1
  57. data/spec/extensions/association_pks_spec.rb +7 -0
  58. data/spec/extensions/defaults_setter_spec.rb +64 -0
  59. data/spec/extensions/many_through_many_spec.rb +60 -4
  60. data/spec/extensions/nested_attributes_spec.rb +1 -0
  61. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  62. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  63. data/spec/extensions/prepared_statements_spec.rb +72 -0
  64. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  65. data/spec/extensions/to_dot_spec.rb +3 -5
  66. data/spec/integration/associations_test.rb +155 -1
  67. data/spec/integration/dataset_test.rb +8 -1
  68. data/spec/integration/plugin_test.rb +119 -0
  69. data/spec/integration/prepared_statement_test.rb +72 -1
  70. data/spec/integration/schema_test.rb +66 -8
  71. data/spec/integration/transaction_test.rb +40 -0
  72. data/spec/model/associations_spec.rb +349 -8
  73. data/spec/model/base_spec.rb +59 -0
  74. data/spec/model/hooks_spec.rb +161 -0
  75. data/spec/model/record_spec.rb +24 -0
  76. metadata +21 -4
data/CHANGELOG CHANGED
@@ -1,3 +1,67 @@
1
+ === 3.24.0 (2011-06-01)
2
+
3
+ * Add prepared_statements_association plugin, for using prepared statements by default for regular association loading (jeremyevans)
4
+
5
+ * Add prepared_statements_safe plugin, for making prepared statement use with models more safe (jeremyevans)
6
+
7
+ * Add prepared_statements_with_pk plugin, for using prepared statements for dataset lookups by primary key (jeremyevans)
8
+
9
+ * Fix bug in emulated prepared statement support not supporting nil or false as bound values (jeremyevans)
10
+
11
+ * Add Dataset#unbind for unbinding values from a dataset, for use with creating prepared statements (jeremyevans)
12
+
13
+ * Add prepared_statements plugin for using prepared statements for updates, inserts, deletes, and lookups by primary key (jeremyevans)
14
+
15
+ * Make Dataset#[] for model datasets consider a single integer argument as a lookup by primary key (jeremyevans)
16
+
17
+ * Add Dataset#with_pk for model datasets, for finding first record with matching primary key value (jeremyevans)
18
+
19
+ * Add defaults_setter plugin for setting default values when initializing model instances (jeremyevans)
20
+
21
+ * Add around hooks (e.g. around_save) to Sequel::Model (jeremyevans)
22
+
23
+ * Add Model#initialize_set private method to ease extension writing (jeremyevans)
24
+
25
+ * Only typecast bit fields to booleans on MSSQL, the MySQL bit type is a bitfield, not a boolean (jeremyevans)
26
+
27
+ * Set SQL_AUTO_IS_NULL=0 by default when connecting to MySQL via the swift and jdbc adapters (jeremyevans)
28
+
29
+ * Fix bug in multiple column IN/NOT IN emulation when a model dataset is used (jeremyevans)
30
+
31
+ * Add support for filtering and excluding by association datasets (jeremyevans)
32
+
33
+ * Fix literalization of boolean values in filters on SQLite and MSSQL (jeremyevans)
34
+
35
+ * Add support for filtering and excluding by multiple associations (jeremyevans)
36
+
37
+ * Add support for inverting some SQL::Constant instances such as TRUE, FALSE, NULL, and NOTNULL (jeremyevans)
38
+
39
+ * Add support for excluding by associations to model datasets (jeremyevans)
40
+
41
+ * The Sequel::Postgres.use_iso_date_format setting now only affects future Database objects (jeremyevans)
42
+
43
+ * Add Sequel::Postgres::PG_NAMED_TYPES hash for extensions to register type conversions for non-standard types (jeremyevans, pvh)
44
+
45
+ * Make create_table? use IF NOT EXISTS instead of using SELECT to determine existence, if supported (jeremyevans)
46
+
47
+ * Fix bug in association_pks plugin when associated table has a different primary key column name (jfirebaugh)
48
+
49
+ * Fix limiting rows when connecting to DB2 (semmons99)
50
+
51
+ * Exclude columns from tables in the INFORMATION_SCHEMA when parsing table schema on JDBC (jeremyevans)
52
+
53
+ * Fix limiting rows when connecting to Microsoft Access (jeremyevans)
54
+
55
+ * Add Database#views for getting an array of symbols of view names for the database (jeremyevans, christian.michon)
56
+
57
+ * Make Datbase#tables no longer include view names on MySQL (jeremyevans)
58
+
59
+ * Convert Java CLOB objects to ruby strings when using the JDBC JTDS subadapter (christian.michon)
60
+
61
+ * If Thread#kill is called on a thread with an open transaction, roll the transaction back on ruby 1.8 and rubinius (jeremyevans)
62
+
63
+ * Split informix adapter into shared/specific parts, add JDBC informix subadapter (jeremyevans)
64
+
1
65
  === 3.23.0 (2011-05-02)
2
66
 
3
67
  * Migrate issue tracker from Google Code to GitHub Issues (jeremyevans)
@@ -421,13 +421,51 @@ This doesn't just work for +many_to_one+ associations, it also works for
421
421
 
422
422
  Note that for +one_to_many+ and +many_to_many+ associations, you still
423
423
  use the plural form even though only a single model object is given.
424
- You cannot use an array of model objects as the value, only a single model
425
- object. To use separate model objects for the same association, you can
426
- use the array form of condition specifiers:
427
424
 
428
- Album.filter([[:tags, Tag[1]], [:tags, Tag[2]]])
425
+ You can also exclude by associations:
429
426
 
430
- That will return albums associated with both tag 1 and tag 2.
427
+ Album.exclude(:artist=>@artist).all
428
+
429
+ This will return all albums not by that artist.
430
+
431
+ You can also provide an array with multiple model objects:
432
+
433
+ Album.filter(:artist=>[@artist1, @artist2]).all
434
+
435
+ Similar to using an array of integers or strings, this will return
436
+ all albums whose artist is one of those two artists. You can also
437
+ use +exclude+ if you want all albums not by either of those artists:
438
+
439
+ Album.exclude(:artist=>[@artist1, @artist2]).all
440
+
441
+ If you are using a +one_to_many+ or +many_to_many+ association, you
442
+ may want to return records where the records matches all of multiple
443
+ records, instead of matching any of them. For example:
444
+
445
+ Album.filter(:tags=>[@tag1, @tag2])
446
+
447
+ This matches albums that are associated with either @tag1 or @tag2 or
448
+ both. If you only want ones that you are associated with both, you can
449
+ use separate filter calls:
450
+
451
+ Album.filter(:tags=>@tag1).filter(:tags=>@tag2)
452
+
453
+ Or the the array form of condition specifiers:
454
+
455
+ Album.filter([[:tags, @tag1], [:tags, @tag2]])
456
+
457
+ These will return albums associated with both @tag1 and @tag2.
458
+
459
+ You can also provide a dataset value when filtering by associations:
460
+
461
+ Album.filter(:artist=>Artist.filter(:name.like('A%'))).all
462
+
463
+ This will return all albums whose artist starts with 'A'. Like
464
+ the other forms, this can be inverted:
465
+
466
+ Album.exclude(:artist=>Artist.filter(:name.like('A%'))).all
467
+
468
+ This will return all albums whose artist does not start with 'A'.
431
469
 
432
470
  Note that filtering by associations only works correctly for simple
433
471
  associations (ones without conditions).
data/doc/model_hooks.rdoc CHANGED
@@ -4,7 +4,7 @@ This guide is based on http://guides.rubyonrails.org/activerecord_validations_ca
4
4
 
5
5
  == Overview
6
6
 
7
- Model hooks, also known as model callbacks, are used to specify actions that occur at a given point in a model instance's lifecycle, such as before or after the model object is saved, created, updated, destroyed, or validated.
7
+ Model hooks, also known as model callbacks, are used to specify actions that occur at a given point in a model instance's lifecycle, such as before or after the model object is saved, created, updated, destroyed, or validated. There are also around hooks for all types, which wrap the before hooks, the behavior, and the after hooks.
8
8
 
9
9
  == Basic Usage
10
10
 
@@ -23,41 +23,51 @@ The one important thing to note here is the call to +super+ inside the hook. Wh
23
23
 
24
24
  Sequel calls hooks in the following order when saving/creating a new object (one that does not already exist in the database):
25
25
 
26
- * +before_validation+
27
- * +after_validation+
28
- * +before_save+
29
- * +before_create+
30
- * INSERT QUERY
31
- * +after_create+
32
- * +after_save+
26
+ * +around_validation+
27
+ * +before_validation+
28
+ * +validate+ method called
29
+ * +after_validation+
30
+ * +around_save+
31
+ * +before_save+
32
+ * +around_create+
33
+ * +before_create+
34
+ * INSERT QUERY
35
+ * +after_create+
36
+ * +after_save+
33
37
 
34
38
  Sequel calls hooks in the following order when saving an existing object:
35
39
 
36
- * +before_validation+
37
- * +after_validation+
38
- * +before_save+
39
- * +before_update+
40
- * UPDATE QUERY
41
- * +after_update+
42
- * +after_save+
40
+ * +around_validation+
41
+ * +before_validation+
42
+ * +validate+ method called
43
+ * +after_validation+
44
+ * +around_save+
45
+ * +before_save+
46
+ * +around_update+
47
+ * +before_update+
48
+ * INSERT QUERY
49
+ * +after_update+
50
+ * +after_save+
43
51
 
44
- Note that all of the hook calls are the same, except that +before_create+ and +after_create+ are used for a new object, and +before_update+ and +after_update+ are used for an existing object. Note that +before_save+ is called in both cases, before either +before_create+ or +before_update+, and that +after_save+ is also called in both cases, after either +after_create+ or +after_update+.
52
+ Note that all of the hook calls are the same, except that +around_create+, +before_create+ and +after_create+ are used for a new object, and +around_update+, +before_update+ and +after_update+ are used for an existing object. Note that +around_save+, +before_save+, and +after_save+ are called in both cases.
45
53
 
46
54
  Also note that the validation hooks are not called if the <tt>:validate => false</tt> option is passed to save. However, the validation hooks are called if you call <tt>Model#valid?</tt> manually:
47
55
 
48
- * +before_validation+
49
- * VALIDATION HAPPENS
50
- * +after_validation+
56
+ * +around_validation+
57
+ * +before_validation+
58
+ * +validate+ method called
59
+ * +after_validation+
51
60
 
52
61
  Sequel calls hooks in the following order when destroying an existing object:
53
62
 
54
- * +before_destroy+
55
- * DELETE QUERY
56
- * +after_destroy+
63
+ * +around_destroy+
64
+ * +before_destroy+
65
+ * DELETE QUERY
66
+ * +after_destroy+
57
67
 
58
68
  Note that these hooks are only called when using <tt>Model#destroy</tt>, they are not called if you use <tt>Model#delete</tt>.
59
69
 
60
- <tt>Sequel::Model</tt> does support one additional hook, +after_intialize+, which is called after the model object has been initalized. It can be used to set default attribute values for new objects, since by default new <tt>Sequel::Model</tt> objects have no attributes, and the attributes are not filled in until the model object is saved. You should be careful when you are using +after_initialize+, since it is called for every created record. So if you run a query that returns 1000 model objects, it will be called 1000 times.
70
+ <tt>Sequel::Model</tt> does support one additional hook, +after_intialize+, which is called after the model object has been initalized. It can be used to set default attribute values for new objects, since by default new <tt>Sequel::Model</tt> objects have no attributes, and the attributes are not filled in until the model object is saved. You should be careful when you are using +after_initialize+, since it is called for every created record. So if you run a query that returns 1000 model objects, it will be called 1000 times. If you only want to change the behavior for new records, you can override the +initialize_set+ private method, which is called with the hash passed to +initialize+.
61
71
 
62
72
  == Running Hooks
63
73
 
@@ -98,7 +108,7 @@ If you want to insert a row into the model's table without running the creation
98
108
 
99
109
  == Halting Hook Processing
100
110
 
101
- Sequel uses a convention that if any <tt>before_*</tt> hook method returns false (but not nil), that the action will be canceled. You can use this to implement validation-like behavior, that will run even if validations are skipped. For example:
111
+ Sequel uses a convention that if any <tt>before_*</tt> hook method returns false (but not nil), that the action will be canceled and a <tt>Sequel::HookFailed</tt> raised (or +nil+ to be returned by +save+ if +raise_on_save_failure+ is +false+). You can use this to implement validation-like behavior, that will run even if validations are skipped. For example:
102
112
 
103
113
  class Album < Sequel::Model
104
114
  def before_save
@@ -107,9 +117,9 @@ Sequel uses a convention that if any <tt>before_*</tt> hook method returns false
107
117
  end
108
118
  end
109
119
 
110
- While returning false is not really recommended, you should be aware of this behavior so that you do not inadvertently return false.
120
+ While returning false is not really recommended, you should be aware of this behavior so that you do not inadvertently return false. For around hooks, neglecting to call +super+ halts hook processing in the same way as returning +false+ in a before hook. You can't halt hook processing in after hooks, since by then the main processing has already taken place.
111
121
 
112
- By default, Sequel runs hooks other than validation hooks inside a transaction, so if you abort the hook by returning false in a before hook or by raising an exception in the hook, Sequel will rollback the transaction. However, note that the implicit use of transactions when saving and destroying model objects is conditional (it depends on the model instance's +use_transactions+ setting).
122
+ By default, Sequel runs hooks other than validation hooks inside a transaction, so if you abort the hook by returning false in a before hook or by raising an exception in any hook, Sequel will rollback the transaction. However, note that the implicit use of transactions when saving and destroying model objects is conditional (it depends on the model instance's +use_transactions+ setting and the <tt>:transaction</tt> option passed to save).
113
123
 
114
124
  == Conditional Hooks
115
125
 
@@ -183,6 +193,7 @@ This allows the following general principles to be true:
183
193
 
184
194
  * before hooks are run in reverse order of inclusion
185
195
  * after hooks are run in order of inclusion
196
+ * returning false in any before hook will pass the false value down the hook method chain, halting the hook processing.
186
197
 
187
198
  So if you define the same before hook in both a model and a plugin that the model uses, the hooks will be called in this order:
188
199
 
@@ -193,6 +204,32 @@ So if you define the same before hook in both a model and a plugin that the mode
193
204
 
194
205
  Again, Sequel does not enforce that, and you are free to call +super+ in an order other than the recommended one (just make sure that you call it).
195
206
 
207
+ == Around Hooks
208
+
209
+ Around hooks should only be used if you cannot accomplish the same results with before and after hooks. For example, if you want to catch database errors caused by the +INSERT+ or +UPDATE+ query when saving a model object and raise them as validation errors, you cannot use a before or after hook. You have use an +around_save+ hook:
210
+
211
+ class Album < Sequel::Model
212
+ def around_save
213
+ super
214
+ rescue Sequel::DatabaseError => e
215
+ # parse database error, set error on self, and reraise a Sequel::ValidationFailed
216
+ end
217
+ end
218
+
219
+ Likewise, let's say that upon retrieval, you associate an object with a file descriptor, and you want to ensure that the file descriptor is closed after the object is saved to the database. Let's assume you are always saving the object and you are not using validations. You could not use an +after_save+ hook safely, since if the database raises an error, the +after_save+ method will not be called. In this case, an +around_save+ hook is also the correct choice:
220
+
221
+ class Album < Sequel::Model
222
+ def around_save
223
+ super
224
+ ensure
225
+ @file_descriptor.close
226
+ end
227
+ end
228
+
196
229
  == +hook_class_methods+
197
230
 
198
- While it's recommended to write your hooks as instance methods, Sequel ships with a +hook_class_methods+ plugin that allows you to define hooks via class methods. It exists mostly for legacy compatibility, but is still supported.
231
+ While it's recommended to write your hooks as instance methods, Sequel ships with a +hook_class_methods+ plugin that allows you to define hooks via class methods. It exists mostly for legacy compatibility, but is still supported. However, it does not implement around hooks.
232
+
233
+ == +instance_hooks+
234
+
235
+ Sequel also ships with an +instance_hooks+ plugin that allows you to define before and after hooks on a per instance basis. It's very useful as it allows you to delay action on an instance until before or after saving. This can be important if you want to modify a group of related objects together (which is how the +nested_attributes+ plugin uses +instance_hooks+).
@@ -5,7 +5,7 @@ database you are using, the Sequel prepared statement/bound variable API remains
5
5
  the same. There is native support for prepared statements/bound variables on
6
6
  the following databases:
7
7
 
8
- * PostgreSQL (using the pg driver, requires type specifiers)
8
+ * PostgreSQL (using the pg driver, may require type specifiers)
9
9
  * MySQL (prepared statements only, as the ruby mysql driver doesn't support
10
10
  bound variables)
11
11
  * SQLite
@@ -13,6 +13,10 @@ the following databases:
13
13
 
14
14
  Support on other databases is emulated via string interpolation.
15
15
 
16
+ You can use the prepared_statements model plugin to automatically use prepared
17
+ statements for some common model actions such as saving or deleting a model
18
+ instance, or looking up a model based on a primary key.
19
+
16
20
  == Placeholders
17
21
 
18
22
  Generally, when using prepared statements (and certainly when using bound
@@ -74,9 +78,9 @@ and update queries, the hash to insert/update is passed to +prepare+:
74
78
 
75
79
  If you are using the ruby-postgres or postgres-pr driver, PostgreSQL uses the
76
80
  default emulated support. If you are using ruby-pg, there is native support,
77
- but it requires type specifiers most of the time. This is easy if you have
81
+ but it may require type specifiers. This is easy if you have
78
82
  direct control over the SQL string, but since Sequel abstracts that, the types
79
- have to be specified another way. This is done by adding a __* suffix to the
83
+ can be specified another way. This is done by adding a __* suffix to the
80
84
  placeholder symbol (e.g. :$name__text, which will be compiled to "$1::text"
81
85
  in the SQL). Prepared statements are always server side.
82
86
 
@@ -87,7 +91,7 @@ statements are cached per connection.
87
91
 
88
92
  === MySQL
89
93
 
90
- The MySQL ruby driver does not support bound variables, so the the bound
94
+ The MySQL ruby driver does not support bound variables, so the bound
91
95
  variable methods fall back to string interpolation. It uses server side
92
96
  prepared statements.
93
97
 
data/doc/reflection.rdoc CHANGED
@@ -16,13 +16,19 @@ In some cases, the adapter scheme will be the same as the database to which you
16
16
 
17
17
  == Tables in the Database
18
18
 
19
- On many database types/adapters, Database#tables exists and gives an array of table name symbols:
19
+ Database#tables gives an array of table name symbols:
20
+
21
+ DB.tables # [:table1, :table2, :table3, ...]
22
+
23
+ == Views in the Database
24
+
25
+ Database#views and gives an array of view name symbols:
20
26
 
21
27
  DB.tables # [:table1, :table2, :table3, ...]
22
28
 
23
29
  == Indexes on a table
24
30
 
25
- On a few database types/adapters, Database#indexes takes a table name gives a hash of index information. Keys are index names, values are subhashes with the keys :columns and :unique :
31
+ Database#indexes takes a table name gives a hash of index information. Keys are index names, values are subhashes with the keys :columns and :unique :
26
32
 
27
33
  DB.indexes(:table1) # {:index1=>{:columns=>[:column1], :unique=>false}, :index2=>{:columns=>[:column2, :column3], :unique=>true}}
28
34
 
@@ -11,7 +11,7 @@
11
11
  And you only want to eagerly load albums where the id is greater
12
12
  than or equal to some number provided by the user, you do:
13
13
 
14
- min = params[:min]
14
+ min = params[:min].to_i
15
15
  Artist.eager(:albums=>proc{|ds| ds.where{id > min}})
16
16
 
17
17
  This also works when eager loading via eager_graph:
@@ -0,0 +1,420 @@
1
+ = Prepared Statement Plugins
2
+
3
+ * The prepared_statements plugin makes Sequel::Model classes use
4
+ prepared statements for creating, updating, and destroying model
5
+ instances, as well as looking up model objects by primary key.
6
+ With this plugin, all of the following will use prepared
7
+ statements:
8
+
9
+ Artist.plugin :prepared_statements
10
+ Artist.create(:name=>'Foo')
11
+ a = Artist[1]
12
+ a.update(:name=>'Bar')
13
+ a.destroy
14
+
15
+ * The prepared_statements_safe plugin reduces the number of
16
+ prepared statements that can be created by doing two things. First,
17
+ it makes the INSERT statements used when creating instances to use
18
+ as many columns as possible, setting specific values for all
19
+ columns with parseable default values. Second, it changes
20
+ save_changes to just use save, saving all columns instead of just
21
+ the changed ones.
22
+
23
+ The reason for this plugin is that Sequel's default behavior of
24
+ using only the values specifically set when creating instances
25
+ and having update only set changed columns by default can lead
26
+ to a large number of prepared statements being created.
27
+
28
+ For prepared statements to be used, each set of columns in the
29
+ insert and update statements needs to have its own prepared
30
+ statement. If you have a table with 1 primary key column and
31
+ 4 other columns, you can have up to 2^4 = 16 prepared statements
32
+ created, one for each subset of the 4 columns. If you have 1
33
+ primary key column and 20 other columns, there are over a million
34
+ subsets, and you could hit your database limit for prepared
35
+ statements (a denial of service attack).
36
+
37
+ Using the prepared_statements_safe plugin mitigates this
38
+ issue by reducing the number of columns that may or may not be
39
+ present in the query, in many cases making sure that each model
40
+ will only have a single INSERT and a single UPDATE prepared
41
+ statement.
42
+
43
+ * The prepared_statements_associations plugin allows normal
44
+ association method calls to use prepared statements if possible.
45
+ For example:
46
+
47
+ Artist.plugin :prepared_statements_associations
48
+ Artist.many_to_one :albums
49
+ Artist[1].albums
50
+
51
+ Will use a prepared statement to return the albums for that artist.
52
+ This plugin works for all supported association types. There are
53
+ some associations (filtered and custom associations) that Sequel
54
+ cannot currently use a prepared statement reliably, for those
55
+ Sequel will use a regular query.
56
+
57
+ * The prepared_statements_with_pk plugin allows the new
58
+ Dataset#with_pk method (explained below) to use prepared statements.
59
+ For example:
60
+
61
+ Artist.plugin :prepared_statements_with_pk
62
+ Artist.filter(...).with_pk(1)
63
+
64
+ Will use a prepared statement for this query. The most benefit
65
+ from prepared statements come from queries that are expensive to
66
+ parse and plan but quick to execute, so using this plugin with
67
+ a complex filter can in certain cases yield significant performance
68
+ improvements.
69
+
70
+ However, this plugin should be considered unsafe as it is possible
71
+ that it will create an unbounded number of prepared statements. It
72
+ extracts parameters from the dataset using Dataset#unbind
73
+ (explained below), so if your code has conditions that vary per
74
+ query but that Dataset#unbind does not handle, an unbounded number
75
+ of prepared statements can be created. For example:
76
+
77
+ Artist.filter(:a=>params[:b].to_i).with_pk[1]
78
+ Artist.exclude{a > params[:b].to_i}.with_pk[1]
79
+
80
+ are safe, but:
81
+
82
+ Artist.filter(:a=>[1, params[:b].to_i]).with_pk[1]
83
+ Artist.exclude{a > params[:b].to_i + 2}.with_pk[1]
84
+
85
+ are not. For queries that are not safe, Dataset#with_pk should
86
+ not be used with this plugin, you should switch to looking up by
87
+ primary key manually (for a regular query):
88
+
89
+ Artist.filter(:a=>[1, params[:b].to_i])[:id=>1]
90
+
91
+ or using the prepared statement API to create a custom prepared
92
+ statement:
93
+
94
+ # PS = {}
95
+ PS[:name] ||= Artist.filter(:a=>[1, :$b], :id=>:$id).
96
+ prepare(:select, :name)
97
+ PS[:name].call(:b=>params[:b].to_i, :id=>1)
98
+
99
+ = Other New Features
100
+
101
+ * Filtering by associations got a lot more powerful. Sequel 3.23.0
102
+ introduced filtering by associations:
103
+
104
+ Album.filter(:artist=>artist)
105
+
106
+ This capability is much expanded in 3.24.0, allowing you to
107
+ exclude by associations:
108
+
109
+ Album.exclude(:artist=>artist)
110
+
111
+ This will match all albums not by that artist.
112
+
113
+ You can also filter or exclude by multiple associated objects:
114
+
115
+ Album.filter(:artist=>[artist1, artist2])
116
+ Album.exclude(:artist=>[artist1, artist2])
117
+
118
+ The filtered dataset will match all albums by either of those
119
+ two artists, and the excluded dataset will match all albums not
120
+ by either of those two artists.
121
+
122
+ You can also filter or exclude by using a model dataset:
123
+
124
+ Album.filter(:artist=>Artist.filter(:name.like('A%'))).all
125
+ Album.exclude(:artist=>Artist.filter(:name.like('A%'))).all
126
+
127
+ Here the filtered dataset will match all albums where the
128
+ associated artist has a name that begins with A, and the excluded
129
+ dataset will match all albums where the associated artist does not
130
+ have a name that begins with A.
131
+
132
+ All of these types of filtering and excluding work with all of
133
+ association types that ship with Sequel, even the many_through_many
134
+ plugin.
135
+
136
+ * Sequel now supports around hooks, which wrap the related before
137
+ hook, behavior, and after hook. Like other Sequel hooks, these
138
+ are implemented as instance methods. For example, if you wanted
139
+ to log DatabaseErrors raised during save:
140
+
141
+ class Artist < Sequel::Model
142
+ def around_save
143
+ super
144
+ rescue Sequel::DatabaseError => e
145
+ # log the error
146
+ raise
147
+ end
148
+ end
149
+
150
+ All around hooks should call super, not yield. If an around hook
151
+ doesn't call super or yield, it is treated as a hook failure,
152
+ similar to before hooks returning false.
153
+
154
+ For around_validation, the return value of super should be whether
155
+ the object is valid. For other around hooks, the return value of
156
+ super is currently true, but it's possible that will change in the
157
+ future.
158
+
159
+ * Dataset#with_pk has been added to model datasets that allows you
160
+ to find the object with the matching primary key:
161
+
162
+ Artist.filter(:name.like('A%')).with_pk(1)
163
+
164
+ This should make easier the common case where you want to find
165
+ a particular object that is associated to another object:
166
+
167
+ Artist[1].albums_dataset.with_pk(2)
168
+
169
+ Before, there was no way to do that without manually specifying
170
+ the primary key:
171
+
172
+ Artist[1].albums_dataset[:id=>2]
173
+
174
+ To use a composite primary key with with_pk, you have to provide
175
+ an array:
176
+
177
+ Artist[1].albums_dataset.with_pk([1, 2])
178
+
179
+ * Dataset#[] for model datasets will now call with_pk if given a
180
+ single Integer argument. This makes the above case even easier:
181
+
182
+ Artist[1].albums_dataset[2]
183
+
184
+ Note that for backwards compatibility, this only works for
185
+ single integer primary keys. If you have a composite primary key
186
+ or a string/varchar primary key, you have to use with_pk.
187
+
188
+ * Dataset#unbind has been added, which allows you to take a dataset
189
+ that uses static bound values and convert them to placeholders.
190
+ Currently, the only cases handled are SQL::ComplexExpression
191
+ objects that use a =, !=, <, >, <=, or >= operator where the first
192
+ argument is a Symbol, SQL::Indentifier, or
193
+ SQL::QualifiedIdentifier, and the second argument is a Numeric,
194
+ String, Date, or Time. Dataset#unbind returns a two element array,
195
+ where the first element is a modified copy of the receiver, and the
196
+ second element is a bound variable hash:
197
+
198
+ ds, bv = DB[:table].filter(:a=>1).unbind
199
+ ds # DB[:table].filter(:a=>:$a)
200
+ bv # {:a=>1}
201
+
202
+ The purpose of doing this is that you can then use prepare or call
203
+ on the returned dataset with the returned bound variables:
204
+
205
+ ds.call(:select, bv)
206
+ # SELECT * FROM table WHERE (a = ?); [1]
207
+
208
+ ps = ds.prepare(:select, :ps_name)
209
+ # PREPARE ps_name AS SELECT * FROM table WHERE (a = ?)
210
+ ps.call(bv)
211
+ # EXECUTE ps_name(1)
212
+
213
+ Basically, Dataset#unbind takes a specific statement and attempts
214
+ to turn it into a generic statement, along with the placeholder
215
+ values it extracted.
216
+
217
+ Unfortunately, Dataset#unbind cannot handle all cases. For
218
+ example:
219
+
220
+ DB[:table].filter{a + 1 > 10}.unbind
221
+
222
+ will not unbind any values. Also, if you have a query with
223
+ multiple different values for a variable, it will raise an
224
+ UnbindDuplicate exception:
225
+
226
+ DB[:table].filter(:a=>1).or(:a=>2).unbind
227
+
228
+ * A defaults_setter plugin has been added that makes it easy to
229
+ automatically set default values when creating new objects. This
230
+ plugin makes Sequel::Model behave more like ActiveRecord in that
231
+ new model instances (before saving) will have default values
232
+ parsed from the database. Unlike ActiveRecord, only values with
233
+ non-NULL defaults are set. Also, Sequel allows you to easily
234
+ modify the default values used:
235
+
236
+ Album.plugin :default_values
237
+ Album.new.values # {:copies_sold => 0}
238
+ Album.default_values[:copies_sold] = 42
239
+ Album.new.values # {:copies_sold => 42}
240
+
241
+ Before, this was commonly done in an after_initialize hook, but
242
+ that's slower as it is also called for model instances loaded from
243
+ the database.
244
+
245
+ * A Database#views method has been added that returns an array
246
+ of symbols representing view names in the database. This works
247
+ just like Database#tables except it returns views.
248
+
249
+ * A Sequel::ASTTransformer class was added that makes it easy to
250
+ write custom transformers of Sequel's internal abstract syntax
251
+ trees. Dataset#qualify now uses a subclass of ASTTransformer to do
252
+ its transformations, as does the new Dataset#unbind.
253
+
254
+ = Other Improvements
255
+
256
+ * Database#create_table? now uses a single query with IF NOT EXISTS
257
+ if the database supports such syntax. Previously, it issued a
258
+ SELECT query to determine table existence. Sequel currently
259
+ supports this syntax on MySQL, H2, and SQLite 3.3.0+.
260
+
261
+ The Database#supports_create_table_if_not_exists? method was added
262
+ to allow users to determine whether this syntax is supported.
263
+
264
+ * Multiple column IN/NOT IN emulation now works correctly with
265
+ model datasets (or other datasets that use a row_proc).
266
+
267
+ * You can now correctly invert SQL::Constant instances:
268
+
269
+ Sequel::NULL # NULL
270
+ ~Sequel::NULL # NOT NULL
271
+ Sequel::TRUE # TRUE
272
+ ~Sequel::TRUE # FALSE
273
+
274
+ * A bug in the association_pks plugin has been fixed in the case
275
+ where the associated table had a different primary key column name
276
+ than the current table.
277
+
278
+ * The emulated prepared statement support now supports nil and false
279
+ as bound values.
280
+
281
+ * The to_dot extension was refactored for greater readability. The
282
+ only change was a small fix in the display for SQL::Subscript
283
+ instances.
284
+
285
+ * The Dataset#supports_insert_select? method is now available to let
286
+ you know if the dataset supports insert_select. You should use
287
+ this method instead of respond_to? for checking for insert_select
288
+ support.
289
+
290
+ * Prepared statements/bound variable can now use a new :insert_select
291
+ type for preparing a statement that will insert a row and return
292
+ the row inserted, if the dataset supports insert_select.
293
+
294
+ * The Model#initialize_set private method now exists for easier plugin
295
+ writing. It is only called for new model objects, with the hash
296
+ given to initialize. By default, it just calls set.
297
+
298
+ * A small bug when creating anonymous subclasses of Sequel::Model on
299
+ ruby 1.9 has been fixed.
300
+
301
+ * If Thread#kill is used inside a transaction on ruby 1.8 or
302
+ rubinius, the transaction is rolled back. This situation is not
303
+ handled correctly on JRuby or ruby 1.9, and I'm not sure it's
304
+ possible to handle correctly on those implementations.
305
+
306
+ * The postgres adapter now supports the
307
+ Sequel::Postgres::PG_NAMED_TYPES hash for associating conversion
308
+ procs for custom types that don't necessarily have the same type
309
+ oid on different databases. This hash uses symbol keys and
310
+ proc values:
311
+
312
+ Sequel::Postgres::PG_NAMED_TYPES[:interval] = proc{|v| ...}
313
+
314
+ The conversion procs now use a separate hash per Database object
315
+ instead of a hash shared across all Database objects. You
316
+ can now modify the types for a particular Database object, but
317
+ you have to use the type oid:
318
+
319
+ DB.conversion_procs[42] = proc{|v| ...}
320
+
321
+ * On SQLite and MSSQL, literalization of true and false values given
322
+ directly to Dataset#filter has been fixed. So the following now
323
+ works correctly on those databases:
324
+
325
+ DB[:table].filter(true)
326
+ DB[:table].filter(false)
327
+
328
+ Unfortunately, because SQLite and MSSQL don't have a real boolean
329
+ type, these will not work:
330
+
331
+ DB[:table].filter{a & true}
332
+ DB[:table].filter{a & false}
333
+
334
+ You currently have to work around the issue by doing:
335
+
336
+ DB[:table].filter{a & Sequel::TRUE}
337
+ DB[:table].filter{a & Sequel::FALSE}
338
+
339
+ It is possible that a future version of Sequel will remove the need
340
+ for this workaround, but that requires having a separate
341
+ literalization method specific to filters.
342
+
343
+ * The MySQL bit type is no longer treated as a boolean. On MySQL, the
344
+ bit type is a bitfield, which is very different than the MSSQL bit
345
+ type, which is the closest thing to a boolean on MSSQL.
346
+
347
+ * The bool database type is now recognized as a boolean. Some SQLite
348
+ databases use bool, such as the ones used in Firefox.
349
+
350
+ * SQL_AUTO_IS_NULL=0 is now set by default when connecting to MySQL
351
+ using the swift or jdbc adapters. Previously, it was only set by
352
+ default when using the mysql or mysql2 adapters.
353
+
354
+ * Dataset#limit now works correctly on Access, using the TOP syntax.
355
+
356
+ * Dataset#limit now works correctly on DB2, using the FETCH FIRST
357
+ syntax.
358
+
359
+ * The jdbc mssql subadapter was split into separate subadapters for
360
+ sqlserver (using Microsoft's driver) and jtds (using the open
361
+ source JTDS driver).
362
+
363
+ * The jdbc jtds subadapter now supports converting Java CLOB
364
+ objects to ruby strings.
365
+
366
+ * Tables from the INFORMATION_SCHEMA are now ignored when parsing
367
+ schema on JDBC.
368
+
369
+ * The informix adapter has been split into shared/specific parts, and
370
+ a jdbc informix subadapter has been added.
371
+
372
+ * Dataset#insert_select now works correctly on MSSQL when the core
373
+ extensions are disabled.
374
+
375
+ * The sqlite adapter now logs when preparing a statement.
376
+
377
+ * You no longer need to be a PostgreSQL superuser to run the postgres
378
+ adapter specs.
379
+
380
+ * The connection pool specs are now about 10 times faster and not
381
+ subject to race conditions due to using Queues instead of
382
+ sleeping.
383
+
384
+ = Backwards Compatibility
385
+
386
+ * Model#save no longer calls Model#valid?. It now calls the
387
+ Model#_valid? private method that Model#valid? also calls. To mark
388
+ a model instance invalid, you should override the Model#validate
389
+ method and add validation errors to the object.
390
+
391
+ * The BeforeHookFailure exception class has been renamed to
392
+ HookFailure since hook failures can now be raised by around hooks
393
+ that don't call super. BeforeHookFailure is now an alias to
394
+ HookFailure, so no code should break, but you should update your
395
+ code to reflect the new name.
396
+
397
+ * Any custom argument mappers used for prepared statements now need
398
+ to implement the prepared_arg? private instance method and have it
399
+ return true.
400
+
401
+ * If your databases uses bit as a boolean type and isn't MSSQL, it's
402
+ possible that those columns will no longer be treated as booleans.
403
+ Please report such an issue on the bugtracker.
404
+
405
+ * It is possible that the filtering and excluding by association
406
+ datasets will break backwards compatibility in some apps. This can
407
+ only occur if you are using a symbol with the same name as an
408
+ association with a model dataset whose model is the same as the
409
+ associated class. As associations almost never have the same names
410
+ as columns, this would require either aliasing or joining to
411
+ another table. If for some reason this does break your app, you
412
+ can work around it by changing the symbol to an SQL::Identifier or
413
+ a literal string.
414
+
415
+ * The Sequel::Postgres.use_iso_date_format= method now only affects
416
+ future Database objects.
417
+
418
+ * On MySQL, Database#tables no longer returns view names, it only
419
+ returns table names. You have to use Database#views to get view
420
+ names now.