1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420
|
= Prepared Statement Plugins
* The prepared_statements plugin makes Sequel::Model classes use
prepared statements for creating, updating, and destroying model
instances, as well as looking up model objects by primary key.
With this plugin, all of the following will use prepared
statements:
Artist.plugin :prepared_statements
Artist.create(:name=>'Foo')
a = Artist[1]
a.update(:name=>'Bar')
a.destroy
* The prepared_statements_safe plugin reduces the number of
prepared statements that can be created by doing two things. First,
it makes the INSERT statements used when creating instances to use
as many columns as possible, setting specific values for all
columns with parseable default values. Second, it changes
save_changes to just use save, saving all columns instead of just
the changed ones.
The reason for this plugin is that Sequel's default behavior of
using only the values specifically set when creating instances
and having update only set changed columns by default can lead
to a large number of prepared statements being created.
For prepared statements to be used, each set of columns in the
insert and update statements needs to have its own prepared
statement. If you have a table with 1 primary key column and
4 other columns, you can have up to 2^4 = 16 prepared statements
created, one for each subset of the 4 columns. If you have 1
primary key column and 20 other columns, there are over a million
subsets, and you could hit your database limit for prepared
statements (a denial of service attack).
Using the prepared_statements_safe plugin mitigates this
issue by reducing the number of columns that may or may not be
present in the query, in many cases making sure that each model
will only have a single INSERT and a single UPDATE prepared
statement.
* The prepared_statements_associations plugin allows normal
association method calls to use prepared statements if possible.
For example:
Artist.plugin :prepared_statements_associations
Artist.many_to_one :albums
Artist[1].albums
Will use a prepared statement to return the albums for that artist.
This plugin works for all supported association types. There are
some associations (filtered and custom associations) that Sequel
cannot currently use a prepared statement reliably, for those
Sequel will use a regular query.
* The prepared_statements_with_pk plugin allows the new
Dataset#with_pk method (explained below) to use prepared statements.
For example:
Artist.plugin :prepared_statements_with_pk
Artist.filter(...).with_pk(1)
Will use a prepared statement for this query. The most benefit
from prepared statements come from queries that are expensive to
parse and plan but quick to execute, so using this plugin with
a complex filter can in certain cases yield significant performance
improvements.
However, this plugin should be considered unsafe as it is possible
that it will create an unbounded number of prepared statements. It
extracts parameters from the dataset using Dataset#unbind
(explained below), so if your code has conditions that vary per
query but that Dataset#unbind does not handle, an unbounded number
of prepared statements can be created. For example:
Artist.filter(:a=>params[:b].to_i).with_pk[1]
Artist.exclude{a > params[:b].to_i}.with_pk[1]
are safe, but:
Artist.filter(:a=>[1, params[:b].to_i]).with_pk[1]
Artist.exclude{a > params[:b].to_i + 2}.with_pk[1]
are not. For queries that are not safe, Dataset#with_pk should
not be used with this plugin, you should switch to looking up by
primary key manually (for a regular query):
Artist.filter(:a=>[1, params[:b].to_i])[:id=>1]
or using the prepared statement API to create a custom prepared
statement:
# PS = {}
PS[:name] ||= Artist.filter(:a=>[1, :$b], :id=>:$id).
prepare(:select, :name)
PS[:name].call(:b=>params[:b].to_i, :id=>1)
= Other New Features
* Filtering by associations got a lot more powerful. Sequel 3.23.0
introduced filtering by associations:
Album.filter(:artist=>artist)
This capability is much expanded in 3.24.0, allowing you to
exclude by associations:
Album.exclude(:artist=>artist)
This will match all albums not by that artist.
You can also filter or exclude by multiple associated objects:
Album.filter(:artist=>[artist1, artist2])
Album.exclude(:artist=>[artist1, artist2])
The filtered dataset will match all albums by either of those
two artists, and the excluded dataset will match all albums not
by either of those two artists.
You can also filter or exclude by using a model dataset:
Album.filter(:artist=>Artist.filter(:name.like('A%'))).all
Album.exclude(:artist=>Artist.filter(:name.like('A%'))).all
Here the filtered dataset will match all albums where the
associated artist has a name that begins with A, and the excluded
dataset will match all albums where the associated artist does not
have a name that begins with A.
All of these types of filtering and excluding work with all of
association types that ship with Sequel, even the many_through_many
plugin.
* Sequel now supports around hooks, which wrap the related before
hook, behavior, and after hook. Like other Sequel hooks, these
are implemented as instance methods. For example, if you wanted
to log DatabaseErrors raised during save:
class Artist < Sequel::Model
def around_save
super
rescue Sequel::DatabaseError => e
# log the error
raise
end
end
All around hooks should call super, not yield. If an around hook
doesn't call super or yield, it is treated as a hook failure,
similar to before hooks returning false.
For around_validation, the return value of super should be whether
the object is valid. For other around hooks, the return value of
super is currently true, but it's possible that will change in the
future.
* Dataset#with_pk has been added to model datasets that allows you
to find the object with the matching primary key:
Artist.filter(:name.like('A%')).with_pk(1)
This should make easier the common case where you want to find
a particular object that is associated to another object:
Artist[1].albums_dataset.with_pk(2)
Before, there was no way to do that without manually specifying
the primary key:
Artist[1].albums_dataset[:id=>2]
To use a composite primary key with with_pk, you have to provide
an array:
Artist[1].albums_dataset.with_pk([1, 2])
* Dataset#[] for model datasets will now call with_pk if given a
single Integer argument. This makes the above case even easier:
Artist[1].albums_dataset[2]
Note that for backwards compatibility, this only works for
single integer primary keys. If you have a composite primary key
or a string/varchar primary key, you have to use with_pk.
* Dataset#unbind has been added, which allows you to take a dataset
that uses static bound values and convert them to placeholders.
Currently, the only cases handled are SQL::ComplexExpression
objects that use a =, !=, <, >, <=, or >= operator where the first
argument is a Symbol, SQL::Indentifier, or
SQL::QualifiedIdentifier, and the second argument is a Numeric,
String, Date, or Time. Dataset#unbind returns a two element array,
where the first element is a modified copy of the receiver, and the
second element is a bound variable hash:
ds, bv = DB[:table].filter(:a=>1).unbind
ds # DB[:table].filter(:a=>:$a)
bv # {:a=>1}
The purpose of doing this is that you can then use prepare or call
on the returned dataset with the returned bound variables:
ds.call(:select, bv)
# SELECT * FROM table WHERE (a = ?); [1]
ps = ds.prepare(:select, :ps_name)
# PREPARE ps_name AS SELECT * FROM table WHERE (a = ?)
ps.call(bv)
# EXECUTE ps_name(1)
Basically, Dataset#unbind takes a specific statement and attempts
to turn it into a generic statement, along with the placeholder
values it extracted.
Unfortunately, Dataset#unbind cannot handle all cases. For
example:
DB[:table].filter{a + 1 > 10}.unbind
will not unbind any values. Also, if you have a query with
multiple different values for a variable, it will raise an
UnbindDuplicate exception:
DB[:table].filter(:a=>1).or(:a=>2).unbind
* A defaults_setter plugin has been added that makes it easy to
automatically set default values when creating new objects. This
plugin makes Sequel::Model behave more like ActiveRecord in that
new model instances (before saving) will have default values
parsed from the database. Unlike ActiveRecord, only values with
non-NULL defaults are set. Also, Sequel allows you to easily
modify the default values used:
Album.plugin :default_values
Album.new.values # {:copies_sold => 0}
Album.default_values[:copies_sold] = 42
Album.new.values # {:copies_sold => 42}
Before, this was commonly done in an after_initialize hook, but
that's slower as it is also called for model instances loaded from
the database.
* A Database#views method has been added that returns an array
of symbols representing view names in the database. This works
just like Database#tables except it returns views.
* A Sequel::ASTTransformer class was added that makes it easy to
write custom transformers of Sequel's internal abstract syntax
trees. Dataset#qualify now uses a subclass of ASTTransformer to do
its transformations, as does the new Dataset#unbind.
= Other Improvements
* Database#create_table? now uses a single query with IF NOT EXISTS
if the database supports such syntax. Previously, it issued a
SELECT query to determine table existence. Sequel currently
supports this syntax on MySQL, H2, and SQLite 3.3.0+.
The Database#supports_create_table_if_not_exists? method was added
to allow users to determine whether this syntax is supported.
* Multiple column IN/NOT IN emulation now works correctly with
model datasets (or other datasets that use a row_proc).
* You can now correctly invert SQL::Constant instances:
Sequel::NULL # NULL
~Sequel::NULL # NOT NULL
Sequel::TRUE # TRUE
~Sequel::TRUE # FALSE
* A bug in the association_pks plugin has been fixed in the case
where the associated table had a different primary key column name
than the current table.
* The emulated prepared statement support now supports nil and false
as bound values.
* The to_dot extension was refactored for greater readability. The
only change was a small fix in the display for SQL::Subscript
instances.
* The Dataset#supports_insert_select? method is now available to let
you know if the dataset supports insert_select. You should use
this method instead of respond_to? for checking for insert_select
support.
* Prepared statements/bound variable can now use a new :insert_select
type for preparing a statement that will insert a row and return
the row inserted, if the dataset supports insert_select.
* The Model#initialize_set private method now exists for easier plugin
writing. It is only called for new model objects, with the hash
given to initialize. By default, it just calls set.
* A small bug when creating anonymous subclasses of Sequel::Model on
ruby 1.9 has been fixed.
* If Thread#kill is used inside a transaction on ruby 1.8 or
rubinius, the transaction is rolled back. This situation is not
handled correctly on JRuby or ruby 1.9, and I'm not sure it's
possible to handle correctly on those implementations.
* The postgres adapter now supports the
Sequel::Postgres::PG_NAMED_TYPES hash for associating conversion
procs for custom types that don't necessarily have the same type
oid on different databases. This hash uses symbol keys and
proc values:
Sequel::Postgres::PG_NAMED_TYPES[:interval] = proc{|v| ...}
The conversion procs now use a separate hash per Database object
instead of a hash shared across all Database objects. You
can now modify the types for a particular Database object, but
you have to use the type oid:
DB.conversion_procs[42] = proc{|v| ...}
* On SQLite and MSSQL, literalization of true and false values given
directly to Dataset#filter has been fixed. So the following now
works correctly on those databases:
DB[:table].filter(true)
DB[:table].filter(false)
Unfortunately, because SQLite and MSSQL don't have a real boolean
type, these will not work:
DB[:table].filter{a & true}
DB[:table].filter{a & false}
You currently have to work around the issue by doing:
DB[:table].filter{a & Sequel::TRUE}
DB[:table].filter{a & Sequel::FALSE}
It is possible that a future version of Sequel will remove the need
for this workaround, but that requires having a separate
literalization method specific to filters.
* The MySQL bit type is no longer treated as a boolean. On MySQL, the
bit type is a bitfield, which is very different than the MSSQL bit
type, which is the closest thing to a boolean on MSSQL.
* The bool database type is now recognized as a boolean. Some SQLite
databases use bool, such as the ones used in Firefox.
* SQL_AUTO_IS_NULL=0 is now set by default when connecting to MySQL
using the swift or jdbc adapters. Previously, it was only set by
default when using the mysql or mysql2 adapters.
* Dataset#limit now works correctly on Access, using the TOP syntax.
* Dataset#limit now works correctly on DB2, using the FETCH FIRST
syntax.
* The jdbc mssql subadapter was split into separate subadapters for
sqlserver (using Microsoft's driver) and jtds (using the open
source JTDS driver).
* The jdbc jtds subadapter now supports converting Java CLOB
objects to ruby strings.
* Tables from the INFORMATION_SCHEMA are now ignored when parsing
schema on JDBC.
* The informix adapter has been split into shared/specific parts, and
a jdbc informix subadapter has been added.
* Dataset#insert_select now works correctly on MSSQL when the core
extensions are disabled.
* The sqlite adapter now logs when preparing a statement.
* You no longer need to be a PostgreSQL superuser to run the postgres
adapter specs.
* The connection pool specs are now about 10 times faster and not
subject to race conditions due to using Queues instead of
sleeping.
= Backwards Compatibility
* Model#save no longer calls Model#valid?. It now calls the
Model#_valid? private method that Model#valid? also calls. To mark
a model instance invalid, you should override the Model#validate
method and add validation errors to the object.
* The BeforeHookFailure exception class has been renamed to
HookFailure since hook failures can now be raised by around hooks
that don't call super. BeforeHookFailure is now an alias to
HookFailure, so no code should break, but you should update your
code to reflect the new name.
* Any custom argument mappers used for prepared statements now need
to implement the prepared_arg? private instance method and have it
return true.
* If your databases uses bit as a boolean type and isn't MSSQL, it's
possible that those columns will no longer be treated as booleans.
Please report such an issue on the bugtracker.
* It is possible that the filtering and excluding by association
datasets will break backwards compatibility in some apps. This can
only occur if you are using a symbol with the same name as an
association with a model dataset whose model is the same as the
associated class. As associations almost never have the same names
as columns, this would require either aliasing or joining to
another table. If for some reason this does break your app, you
can work around it by changing the symbol to an SQL::Identifier or
a literal string.
* The Sequel::Postgres.use_iso_date_format= method now only affects
future Database objects.
* On MySQL, Database#tables no longer returns view names, it only
returns table names. You have to use Database#views to get view
names now.
|