sequel 3.23.0 → 3.24.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- 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)
|
data/doc/association_basics.rdoc
CHANGED
@@ -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
|
-
|
425
|
+
You can also exclude by associations:
|
429
426
|
|
430
|
-
|
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
|
-
* +
|
27
|
-
* +
|
28
|
-
* +
|
29
|
-
* +
|
30
|
-
*
|
31
|
-
* +
|
32
|
-
* +
|
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
|
-
* +
|
37
|
-
* +
|
38
|
-
* +
|
39
|
-
* +
|
40
|
-
*
|
41
|
-
* +
|
42
|
-
* +
|
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 +
|
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
|
-
* +
|
49
|
-
*
|
50
|
-
* +
|
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
|
-
* +
|
55
|
-
*
|
56
|
-
*
|
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
|
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,
|
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
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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.
|