sequel 3.45.0 → 3.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG +34 -0
- data/README.rdoc +6 -0
- data/Rakefile +46 -33
- data/doc/release_notes/3.46.0.txt +122 -0
- data/doc/schema_modification.rdoc +42 -6
- data/doc/security.rdoc +379 -0
- data/doc/transactions.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/as400.rb +1 -0
- data/lib/sequel/adapters/jdbc/h2.rb +11 -0
- data/lib/sequel/adapters/mysql2.rb +3 -9
- data/lib/sequel/adapters/postgres.rb +34 -2
- data/lib/sequel/adapters/shared/cubrid.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +27 -3
- data/lib/sequel/adapters/shared/mysql.rb +25 -4
- data/lib/sequel/adapters/shared/sqlite.rb +12 -1
- data/lib/sequel/connection_pool.rb +3 -3
- data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
- data/lib/sequel/connection_pool/threaded.rb +7 -8
- data/lib/sequel/core.rb +5 -2
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/connecting.rb +7 -7
- data/lib/sequel/database/features.rb +88 -0
- data/lib/sequel/database/misc.rb +14 -64
- data/lib/sequel/database/query.rb +0 -332
- data/lib/sequel/database/schema_generator.rb +36 -3
- data/lib/sequel/database/schema_methods.rb +48 -12
- data/lib/sequel/database/transactions.rb +344 -0
- data/lib/sequel/dataset/actions.rb +24 -9
- data/lib/sequel/dataset/mutation.rb +20 -0
- data/lib/sequel/dataset/query.rb +0 -17
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/exceptions.rb +10 -6
- data/lib/sequel/extensions/_pretty_table.rb +2 -2
- data/lib/sequel/extensions/looser_typecasting.rb +10 -0
- data/lib/sequel/extensions/migration.rb +5 -2
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/associations.rb +16 -14
- data/lib/sequel/model/base.rb +14 -2
- data/lib/sequel/plugins/composition.rb +3 -3
- data/lib/sequel/plugins/dirty.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +3 -0
- data/lib/sequel/plugins/serialization.rb +7 -17
- data/lib/sequel/plugins/string_stripper.rb +2 -1
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +3 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +21 -0
- data/spec/adapters/postgres_spec.rb +35 -8
- data/spec/core/database_spec.rb +4 -0
- data/spec/core/dataset_spec.rb +48 -2
- data/spec/core/schema_generator_spec.rb +10 -1
- data/spec/core/schema_spec.rb +69 -0
- data/spec/extensions/composition_spec.rb +21 -2
- data/spec/extensions/dirty_spec.rb +17 -10
- data/spec/extensions/eager_each_spec.rb +4 -1
- data/spec/extensions/looser_typecasting_spec.rb +16 -19
- data/spec/extensions/migration_spec.rb +7 -1
- data/spec/extensions/serialization_spec.rb +22 -0
- data/spec/extensions/single_table_inheritance_spec.rb +3 -2
- data/spec/extensions/validation_helpers_spec.rb +6 -0
- data/spec/integration/dataset_test.rb +5 -0
- data/spec/integration/schema_test.rb +16 -0
- data/spec/model/associations_spec.rb +40 -0
- data/spec/model/base_spec.rb +21 -1
- data/spec/model/record_spec.rb +3 -0
- metadata +14 -10
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f41a65970b01417999c117602accf3a2eb0acb64
|
4
|
+
data.tar.gz: 70f3a0b14da5779276cea3fc752b06d69b95fdee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac4bbb6968ce5890ec6e3ba61cafc982d5508fc7aa4cab016f15fb67a237145f7461b82ad2aa6bb1cd5f90bb5327d8c616823a61fccc0d40457886cdccd3dfb6
|
7
|
+
data.tar.gz: f3c47bbc24033387b7cc225c58f73646bf19a477f69963f053eace7e8d8fb02b97fc78043a42d5a9d23d49b7ff1d1363de37fdeef396d177925f6f30face34b4
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
=== 3.46.0 (2013-04-02)
|
2
|
+
|
3
|
+
* Add Dataset#cross_apply and Dataset#outer_apply on Microsoft SQL Server (jeremyevans)
|
4
|
+
|
5
|
+
* Speed up threaded connection pools when :connection_handling=>:queue is used (jeremyevans)
|
6
|
+
|
7
|
+
* Allow external connection pool classes to be loaded automatically (jeremyevans)
|
8
|
+
|
9
|
+
* Add Dataset#with_pk! for model datasets, like #with_pk, but raising instead of returning nil (jeremyevans)
|
10
|
+
|
11
|
+
* Add Dataset#first!, like #first, but raising a Sequel::NoMatchingRow exception instead of returning nil (jeremyevans)
|
12
|
+
|
13
|
+
* Dataset #select_map, #select_order_map, and #get no longer support a plain string inside an array of arguments (jeremyevans)
|
14
|
+
|
15
|
+
* Escape ] characters in identifiers on Microsoft SQL Server (jeremyevans)
|
16
|
+
|
17
|
+
* Add security guide (jeremyevans)
|
18
|
+
|
19
|
+
* Make validates_type handle false values correctly (jeremyevans) (#636)
|
20
|
+
|
21
|
+
* Have associations, composition, serialization, and dirty plugins clear caches in some additional cases (jeremyevans) (#635)
|
22
|
+
|
23
|
+
* Add alter_table drop_foreign_key method for dropping foreign keys by column names (raxoft, jeremyevans) (#627)
|
24
|
+
|
25
|
+
* Allow creation named column constraints via :*_constraint_name column options (jeremyevans)
|
26
|
+
|
27
|
+
* Handle drop_constraint :type=>:primary_key on H2 (jeremyevans)
|
28
|
+
|
29
|
+
* Handle infinite dates in the postgres adapter using Database#convert_infinite_timestamps (jeremyevans)
|
30
|
+
|
31
|
+
* Make the looser_typecasting extension use looser typecasting for decimal columns as well as integers and floats (jeremyevans)
|
32
|
+
|
33
|
+
* Do strict typecasting of decimal columns by default, similar to integer/float typecasting (jeremyevans)
|
34
|
+
|
1
35
|
=== 3.45.0 (2013-03-01)
|
2
36
|
|
3
37
|
* Remove bad model typecasting of money type on PostgreSQL (jeremyevans) (#624)
|
data/README.rdoc
CHANGED
@@ -244,6 +244,12 @@ After filtering, you can retrieve the matching records by using any of the retri
|
|
244
244
|
|
245
245
|
See the {Dataset Filtering}[link:files/doc/dataset_filtering_rdoc.html] file for more details.
|
246
246
|
|
247
|
+
=== Security
|
248
|
+
|
249
|
+
Designing apps with security in mind is a best practice.
|
250
|
+
Please read the {Security Guide}[link:files/doc/security_rdoc.html] for details on security
|
251
|
+
issues that you should be aware of when using Sequel.
|
252
|
+
|
247
253
|
=== Summarizing Records
|
248
254
|
|
249
255
|
Counting records is easy using +count+:
|
data/Rakefile
CHANGED
@@ -11,25 +11,24 @@ SUDO = ENV['SUDO'] || 'sudo'
|
|
11
11
|
|
12
12
|
# Gem Packaging and Release
|
13
13
|
|
14
|
-
desc "
|
14
|
+
desc "Build sequel gem"
|
15
15
|
task :package=>[:clean] do |p|
|
16
|
-
|
17
|
-
Gem::Builder.new(SEQUEL_GEMSPEC).build
|
16
|
+
sh %{#{FileUtils::RUBY} -S gem build sequel.gemspec}
|
18
17
|
end
|
19
18
|
|
20
19
|
desc "Install sequel gem"
|
21
20
|
task :install=>[:package] do
|
22
|
-
sh %{#{SUDO} gem install ./#{NAME}-#{VERS.call} --local}
|
21
|
+
sh %{#{SUDO} #{FileUtils::RUBY} -S gem install ./#{NAME}-#{VERS.call} --local}
|
23
22
|
end
|
24
23
|
|
25
24
|
desc "Uninstall sequel gem"
|
26
25
|
task :uninstall=>[:clean] do
|
27
|
-
sh %{#{SUDO} gem uninstall #{NAME}}
|
26
|
+
sh %{#{SUDO} #{FileUtils::RUBY} -S gem uninstall #{NAME}}
|
28
27
|
end
|
29
28
|
|
30
|
-
desc "
|
29
|
+
desc "Publish sequel gem to rubygems.org"
|
31
30
|
task :release=>[:package] do
|
32
|
-
sh %{gem push ./#{NAME}-#{VERS.call}.gem}
|
31
|
+
sh %{#{FileUtils::RUBY} -S gem push ./#{NAME}-#{VERS.call}.gem}
|
33
32
|
end
|
34
33
|
|
35
34
|
### Website
|
@@ -46,11 +45,23 @@ end
|
|
46
45
|
|
47
46
|
### RDoc
|
48
47
|
|
49
|
-
RDOC_DEFAULT_OPTS = ["--
|
48
|
+
RDOC_DEFAULT_OPTS = ["--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
|
49
|
+
|
50
|
+
allow_website_rdoc = begin
|
51
|
+
# Sequel uses hanna-nouveau for the website RDoc.
|
52
|
+
# Due to bugs in older versions of RDoc, and the
|
53
|
+
# fact that hanna-nouveau does not support RDoc 4,
|
54
|
+
# a specific version of rdoc is required.
|
55
|
+
gem 'rdoc', '= 3.12.2'
|
56
|
+
gem 'hanna-nouveau'
|
57
|
+
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
58
|
+
true
|
59
|
+
rescue Gem::LoadError
|
60
|
+
false
|
61
|
+
end
|
50
62
|
|
51
63
|
rdoc_task_class = begin
|
52
64
|
require "rdoc/task"
|
53
|
-
RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
|
54
65
|
RDoc::Task
|
55
66
|
rescue LoadError
|
56
67
|
begin
|
@@ -67,32 +78,34 @@ if rdoc_task_class
|
|
67
78
|
rdoc.rdoc_dir = "rdoc"
|
68
79
|
rdoc.options += RDOC_OPTS
|
69
80
|
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
|
70
|
-
end if rdoc_task_class
|
71
|
-
|
72
|
-
desc "Make rdoc for website"
|
73
|
-
task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
|
74
|
-
|
75
|
-
rdoc_task_class.new(:website_rdoc_main) do |rdoc|
|
76
|
-
rdoc.rdoc_dir = "www/public/rdoc"
|
77
|
-
rdoc.options += RDOC_OPTS
|
78
|
-
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
|
79
81
|
end
|
80
82
|
|
81
|
-
|
82
|
-
rdoc
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
83
|
+
if allow_website_rdoc
|
84
|
+
desc "Make rdoc for website"
|
85
|
+
task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
|
86
|
+
|
87
|
+
rdoc_task_class.new(:website_rdoc_main) do |rdoc|
|
88
|
+
rdoc.rdoc_dir = "www/public/rdoc"
|
89
|
+
rdoc.options += RDOC_OPTS + %w'--no-ignore-invalid'
|
90
|
+
rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb lib/sequel/extensions/core_extensions.rb"
|
91
|
+
end
|
92
|
+
|
93
|
+
rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
|
94
|
+
rdoc.rdoc_dir = "www/public/rdoc-adapters"
|
95
|
+
rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
|
96
|
+
rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
|
97
|
+
end
|
98
|
+
|
99
|
+
rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
|
100
|
+
rdoc.rdoc_dir = "www/public/rdoc-plugins"
|
101
|
+
rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel --no-ignore-invalid'
|
102
|
+
rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
|
103
|
+
end
|
104
|
+
|
105
|
+
desc "Update sequel.rubyforge.org"
|
106
|
+
task :website_rf=>[:website, :website_rdoc] do
|
107
|
+
sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
|
108
|
+
end
|
96
109
|
end
|
97
110
|
end
|
98
111
|
|
@@ -0,0 +1,122 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* Dataset#first! has been added. This is identical to #first,
|
4
|
+
except where #first would return nil due to no row matching,
|
5
|
+
#first! raises a Sequel::NoMatchingRow exception. The main
|
6
|
+
benefit here is that a standard exception class is now used,
|
7
|
+
so external libraries can deal with these exceptions appropriately
|
8
|
+
(such as web applications returning a 404 error).
|
9
|
+
|
10
|
+
* Dataset#with_pk! has been added to model datasets. Similar to
|
11
|
+
#first!, this raises a Sequel::NoMatchingRow exception instead of
|
12
|
+
returning nil if there is no matching row.
|
13
|
+
|
14
|
+
* A drop_foreign_key method has been added to the alter_table
|
15
|
+
generator:
|
16
|
+
|
17
|
+
alter_table(:tab){drop_foreign_key :col}
|
18
|
+
|
19
|
+
This relies on foreign_key_list working and including the name
|
20
|
+
of the foreign key. Previously, you'd have to drop the foreign key
|
21
|
+
constraint before dropping the column in some cases.
|
22
|
+
|
23
|
+
* Column constraints can now be named using :*_constraint_name
|
24
|
+
options:
|
25
|
+
|
26
|
+
create_table(:tab) do
|
27
|
+
primary_key :id, :primary_key_constraint_name=>:pk_name
|
28
|
+
foriegn_key :t_id, :t, :foreign_key_constraint_name=>:fk_name,
|
29
|
+
:unique=>true, :unique_constraint_name=>:uk_name
|
30
|
+
end
|
31
|
+
|
32
|
+
This makes it easier to name constraints, which has always been
|
33
|
+
recommended as it makes it easier to drop such constraints in the
|
34
|
+
future.
|
35
|
+
|
36
|
+
* On Microsoft SQL Server, Dataset#cross_apply and #outer_apply have
|
37
|
+
been added to use CROSS/OUTER APPLY. These are useful if you
|
38
|
+
want to join a table to the output of a function that takes the
|
39
|
+
table as an argument.
|
40
|
+
|
41
|
+
= Other Improvements
|
42
|
+
|
43
|
+
* The connection pools are now faster when using the
|
44
|
+
:connection_handling=>:queue option.
|
45
|
+
|
46
|
+
* External connection pool classes can now be loaded automatically by
|
47
|
+
the :pool_class option.
|
48
|
+
|
49
|
+
* Database#each_server now raises if not given a block. Previously,
|
50
|
+
it just leaked Database references.
|
51
|
+
|
52
|
+
* On Microsoft SQL Server, ] characters are now escaped correctly in
|
53
|
+
identifiers.
|
54
|
+
|
55
|
+
* On PostgreSQL, infinite dates are also handled when using
|
56
|
+
Database#convert_infinite_timestamps. Previously, infinite dates
|
57
|
+
were incorrectly converted to 0000-01-01.
|
58
|
+
|
59
|
+
* The associations, composition, serialization, and dirty plugins
|
60
|
+
now clear caches stored in the instance in some additional cases,
|
61
|
+
such as when saving model instances when the dataset supports
|
62
|
+
insert_select.
|
63
|
+
|
64
|
+
* Model#validates_type in the validation_helpers plugin now handles
|
65
|
+
false values correctly.
|
66
|
+
|
67
|
+
* The string_stripper plugin has been fixed to not change the result
|
68
|
+
of Model.set_dataset.
|
69
|
+
|
70
|
+
* You can now drop primary key constraints on H2, using:
|
71
|
+
|
72
|
+
alter_table(:tab){drop_constraint :foo, :type=>:primary_key}
|
73
|
+
|
74
|
+
* The jdbc/as400 adapter has been fixed, it was broken starting in
|
75
|
+
Sequel 3.44.0.
|
76
|
+
|
77
|
+
* A Security guide has been added explaining various security issues
|
78
|
+
to think about when using Sequel.
|
79
|
+
|
80
|
+
= Backwards Compatibility
|
81
|
+
|
82
|
+
* The change to make associations, composition, serialization, and
|
83
|
+
dirty now clear caches after saving when the dataset supports
|
84
|
+
insert_select can break code that expected the previous behavior.
|
85
|
+
For example:
|
86
|
+
|
87
|
+
artist = Artist[1]
|
88
|
+
artist.has_albums # => false
|
89
|
+
|
90
|
+
album = Album.new(:artist=>artist)
|
91
|
+
def album.after_create
|
92
|
+
super
|
93
|
+
artist.update(:has_albums=>true)
|
94
|
+
end
|
95
|
+
album.save
|
96
|
+
|
97
|
+
artist.has_albums # => false
|
98
|
+
|
99
|
+
Such code should either refresh the artist after saving the album,
|
100
|
+
or use album.artist.has_albums. You already had to do that if
|
101
|
+
the dataset did not support insert_select; the impetus for this
|
102
|
+
change was to make the behavior consistent.
|
103
|
+
|
104
|
+
* Decimal/numeric columns are now strictly typecast by default,
|
105
|
+
similar to integer and real/double precision columns. If you want
|
106
|
+
the previous loose typecasting to for decimal/numeric columns,
|
107
|
+
use the looser_typecasting extension.
|
108
|
+
|
109
|
+
* External adapters that called Database.set_adapter_scheme with a
|
110
|
+
string should change to using a symbol.
|
111
|
+
|
112
|
+
* Dataset#select_map, #select_order_map, and #get now raise an
|
113
|
+
exception if they are passed a plain string inside an array.
|
114
|
+
If you do want to use a plain string, you now need to alias it:
|
115
|
+
|
116
|
+
dataset.get([Sequel.as('string', :some_alias)])
|
117
|
+
|
118
|
+
= Sequel 4 Implementation Planning
|
119
|
+
|
120
|
+
* Sequel 4 implementation planning has begun. If you want to view
|
121
|
+
and/or provide feedback on the implementation plan, see
|
122
|
+
https://github.com/jeremyevans/sequel-4-plans
|
@@ -85,8 +85,12 @@ method, the fourth argument is the options hash. The following options are supp
|
|
85
85
|
:null :: Mark the column as allowing NULL values (if true),
|
86
86
|
or not allowing NULL values (if false). If unspecified, will default
|
87
87
|
to whatever the database default is.
|
88
|
+
:primary_key :: Mark this column as the primary key. This is used instead of the
|
89
|
+
primary key method if you want a non-autoincrementing primary key.
|
90
|
+
:primary_key_constraint_name :: The name to give the primary key constraint.
|
88
91
|
:unique :: Mark the column as unique, generally has the same effect as
|
89
92
|
creating a unique index on the column.
|
93
|
+
:unique_constraint_name :: The name to give the unique key constraint.
|
90
94
|
|
91
95
|
=== Other methods
|
92
96
|
|
@@ -110,12 +114,13 @@ method:
|
|
110
114
|
create_table(:a2){String :name, :primary_key=>true} # varchar(255) primary key
|
111
115
|
|
112
116
|
If you want to create a composite primary key, you should call the +primary_key+ method with an
|
113
|
-
array of column symbols
|
117
|
+
array of column symbols. You can provide a specific name to use for the primary key constraint
|
118
|
+
via the :name option:
|
114
119
|
|
115
120
|
create_table(:items) do
|
116
121
|
Integer :group_id
|
117
122
|
Integer :position
|
118
|
-
primary_key [:group_id, :position]
|
123
|
+
primary_key [:group_id, :position], :name=>:items_pk
|
119
124
|
end
|
120
125
|
|
121
126
|
If provided with an array, +primary_key+ does not create a column, it just sets up the primary key constraint.
|
@@ -136,6 +141,7 @@ as it's third argument. A simple example is:
|
|
136
141
|
|
137
142
|
:deferrable :: Makes the foreign key constraint checks deferrable, so they aren't checked
|
138
143
|
until the end of the transaction.
|
144
|
+
:foreign_key_constraint_name :: The name to give the foreign key constraint.
|
139
145
|
:key :: For foreign key columns, the column in the associated table
|
140
146
|
that this column references. Unnecessary if this column
|
141
147
|
references the primary key of the associated table, at least
|
@@ -271,7 +277,7 @@ to <tt>Dataset#where</tt>:
|
|
271
277
|
==== +check+
|
272
278
|
|
273
279
|
+check+ operates just like +constraint+, except that it doesn't take a name
|
274
|
-
and it creates an unnamed constraint
|
280
|
+
and it creates an unnamed constraint:
|
275
281
|
|
276
282
|
create_table(:artists) do
|
277
283
|
primary_key :id
|
@@ -279,6 +285,9 @@ and it creates an unnamed constraint
|
|
279
285
|
check{char_length(name) > 2}
|
280
286
|
end
|
281
287
|
|
288
|
+
It's recommended that you use the +constraint+ method and provide a name for the
|
289
|
+
constraint, as that makes it easier to drop the constraint later if necessary.
|
290
|
+
|
282
291
|
== +create_join_table+
|
283
292
|
|
284
293
|
+create_join_table+ is a shortcut that you can use to create simple many-to-many join tables:
|
@@ -371,19 +380,46 @@ creates a new column:
|
|
371
380
|
end
|
372
381
|
|
373
382
|
If you want to add a new foreign key constraint to an existing column, you provide an
|
374
|
-
array with a single element
|
383
|
+
array with a single element. It's encouraged to provide a name when adding the constraint,
|
384
|
+
via the :name option:
|
375
385
|
|
376
386
|
alter_table(:albums) do
|
377
|
-
add_foreign_key [:artist_id], :artists
|
387
|
+
add_foreign_key [:artist_id], :artists, :name=>:albums_artist_id_fkey
|
378
388
|
end
|
379
389
|
|
380
390
|
To set up a multiple column foreign key constraint, use an array with multiple column
|
381
391
|
symbols:
|
382
392
|
|
383
393
|
alter_table(:albums) do
|
384
|
-
add_foreign_key [:artist_name, :artist_location], :artists
|
394
|
+
add_foreign_key [:artist_name, :artist_location], :artists, :name=>:albums_artist_name_location_fkey
|
395
|
+
end
|
396
|
+
|
397
|
+
=== +drop_foreign_key+
|
398
|
+
|
399
|
+
+drop_foreign_key+ is used to drop foreign keys from tables. If you provide a symbol as
|
400
|
+
the first argument, it drops both the foreign key constraint and the column:
|
401
|
+
|
402
|
+
alter_table(:albums) do
|
403
|
+
drop_foreign_key :artist_id
|
385
404
|
end
|
386
405
|
|
406
|
+
If you want to just drop the foreign key constraint without dropping the column, use
|
407
|
+
an array. It's encouraged to use the :name option to provide the constraint name to
|
408
|
+
drop, though on some databases Sequel may be able to find the name through introspection:
|
409
|
+
|
410
|
+
alter_table(:albums) do
|
411
|
+
drop_foreign_key [:artist_id], :name=>:albums_artist_id_fkey
|
412
|
+
end
|
413
|
+
|
414
|
+
An array is also used to drop a composite foreign key constraint:
|
415
|
+
|
416
|
+
alter_table(:albums) do
|
417
|
+
drop_foreign_key [:artist_name, :artist_location], :name=>:albums_artist_name_location_fkey
|
418
|
+
end
|
419
|
+
|
420
|
+
If you do not provide a :name option and Sequel is not able to determine the name
|
421
|
+
to use, it will probably raise a Sequel::Error exception.
|
422
|
+
|
387
423
|
=== +add_index+
|
388
424
|
|
389
425
|
+add_index+ works just like +create_table+'s +index+ method, creating a new index on
|
data/doc/security.rdoc
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
= Security Considerations with Sequel
|
2
|
+
|
3
|
+
When using Sequel, there are some security areas you should be aware of:
|
4
|
+
|
5
|
+
* Code Execution
|
6
|
+
* SQL Injection
|
7
|
+
* Denial of Service
|
8
|
+
* Mass Assignment
|
9
|
+
* General Parameter Handling
|
10
|
+
|
11
|
+
== Code Execution
|
12
|
+
|
13
|
+
The most serious security vulnerability you can have in any library is
|
14
|
+
a code execution vulnerability. Sequel should not be vulnerable to this,
|
15
|
+
as it never calls eval on a string that is derived from user input.
|
16
|
+
However, some Sequel methods used for creating methods via metaprogramming
|
17
|
+
could conceivably be abused to do so:
|
18
|
+
|
19
|
+
* Sequel::Schema::CreateTableGenerator.add_type_method
|
20
|
+
* Sequel::Dataset.def_mutation_method
|
21
|
+
* Sequel::Dataset.def_append_methods
|
22
|
+
* Sequel.def_adapter_method (private)
|
23
|
+
* Sequel::Model::InstanceMethods.class_attr_overridable (private)
|
24
|
+
* Sequel::Model::InstanceMethods.class_attr_reader (private)
|
25
|
+
* Sequel::SQL::Expression.to_s_method (private)
|
26
|
+
* Sequel::Plugins::HookClassMethods::ClassMethods#add_hook_type
|
27
|
+
|
28
|
+
As long as you don't call those with user input, you should not be
|
29
|
+
vulnerable to code execution.
|
30
|
+
|
31
|
+
== SQL Injection
|
32
|
+
|
33
|
+
The primary security concern in SQL database libraries is SQL injection.
|
34
|
+
Because Sequel mostly promotes using ruby objects for SQL concepts instead
|
35
|
+
of raw SQL, it is less likely to be vulnerable to SQL injection.
|
36
|
+
However, because Sequel still makes it easy to use raw SQL, misuse of the
|
37
|
+
library can result in SQL injection in your application.
|
38
|
+
|
39
|
+
There are basically two kinds of possible SQL injections in Sequel:
|
40
|
+
|
41
|
+
* SQL code injections
|
42
|
+
* SQL identifier injections
|
43
|
+
|
44
|
+
=== SQL Code Injections
|
45
|
+
|
46
|
+
==== Full SQL Strings
|
47
|
+
|
48
|
+
Some Sequel methods are designed to execute raw SQL, including:
|
49
|
+
|
50
|
+
* Sequel::Database#execute
|
51
|
+
* Sequel::Database#run
|
52
|
+
* Sequel::Database#<<
|
53
|
+
* Sequel::Database#[]
|
54
|
+
* Sequel::Database#fetch
|
55
|
+
* Sequel::Dataset#with_sql
|
56
|
+
|
57
|
+
Here are some examples of use:
|
58
|
+
|
59
|
+
DB.run 'SQL'
|
60
|
+
DB << 'SQL'
|
61
|
+
DB.execute 'SQL'
|
62
|
+
DB['SQL'].all
|
63
|
+
DB.fetch('SQL').all
|
64
|
+
DB.dataset.with_sql('SQL').all
|
65
|
+
|
66
|
+
If you pass a string to these methods that is derived from user input, you open
|
67
|
+
yourself up to SQL injection. The Sequel::Database#run, Sequel::Database#<<, and
|
68
|
+
Sequel::Database#execute methods are not designed to work at all with user input.
|
69
|
+
If you must use them with user input, you should escape the user input manually
|
70
|
+
via Sequel::Database#literal. Example:
|
71
|
+
|
72
|
+
DB.run "SOME SQL #{DB.literal(params[:user].to_s)}"
|
73
|
+
|
74
|
+
With Sequel::Database#[], Sequel::Database#fetch and Sequel::Dataset#with_sql, you should use placeholders,
|
75
|
+
in which case Sequel automatically literalizes the input:
|
76
|
+
|
77
|
+
DB['SELECT * FROM foo WHERE bar = ?', params[:user].to_s]
|
78
|
+
|
79
|
+
==== Manually Created Literal Strings
|
80
|
+
|
81
|
+
Sequel generally treats ruby strings as SQL strings (escaping them correctly), and
|
82
|
+
not as raw SQL. However, you can convert a ruby string to a literal string, and
|
83
|
+
Sequel will then treat it as raw SQL. This is typically done through String#lit
|
84
|
+
if the {core_extensions}[link:files/doc/core_extensions_rdoc.html] are in use,
|
85
|
+
or Sequel.lit[rdoc-ref:Sequel::SQL::Builders#lit] if they are not in use.
|
86
|
+
|
87
|
+
'a'.lit
|
88
|
+
Sequel.lit('a')
|
89
|
+
|
90
|
+
Using String#lit or Sequel.lit[rdoc-ref:Sequel::SQL::Builders#lit] to turn a ruby string into a literal string results
|
91
|
+
in SQL injection if the string is derived from user input. With both of these
|
92
|
+
methods, the strings can contain placeholders, which you can use to safely include
|
93
|
+
user input inside a literal string:
|
94
|
+
|
95
|
+
'a = ?'.lit(params[:user_id].to_s)
|
96
|
+
Sequel.lit('a = ?', params[:user_id].to_s)
|
97
|
+
|
98
|
+
Even though they have similar names, note that Sequel::Database#literal operates very differently from
|
99
|
+
String#lit or Sequel.lit[rdoc-ref:Sequel::SQL::Builders#lit].
|
100
|
+
Sequel::Database#literal is for taking any supported object,
|
101
|
+
and getting an SQL representation of that object, while
|
102
|
+
String#lit or Sequel.lit[rdoc-ref:Sequel::SQL::Builders#lit] are for treating
|
103
|
+
a ruby string as raw SQL. For example:
|
104
|
+
|
105
|
+
DB.literal(Date.today) # "'2013-03-22'"
|
106
|
+
DB.literal('a') # "'a'"
|
107
|
+
DB.literal(Sequel.lit('a')) # "a"
|
108
|
+
DB.literal(:a => 'a') # "(\"a\" = 'a')"
|
109
|
+
DB.literal(:a => Sequel.lit('a')) # "(\"a\" = a)"
|
110
|
+
|
111
|
+
==== SQL Filter Fragments
|
112
|
+
|
113
|
+
The most common way to use raw SQL with Sequel is in filters:
|
114
|
+
|
115
|
+
DB[:table].where("name > 'M'")
|
116
|
+
|
117
|
+
If a filter method is passed a string as the first argument, it treats the rest of
|
118
|
+
the arguments (if any) as placeholders to the string. So you should never do:
|
119
|
+
|
120
|
+
DB[:table].where("name > #{params[:id].to_s}") # SQL Injection!
|
121
|
+
|
122
|
+
Instead, you should use a placeholder:
|
123
|
+
|
124
|
+
DB[:table].where("name > ?", params[:id].to_s) # Safe
|
125
|
+
|
126
|
+
Note that for that type of query, Sequel generally encourages the following form:
|
127
|
+
|
128
|
+
DB[:table].where{|o| o.name > params[:id].to_s} # Safe
|
129
|
+
|
130
|
+
Sequel's DSL supports a wide variety of SQL concepts, so it's possible to
|
131
|
+
code most applications without every using raw SQL.
|
132
|
+
|
133
|
+
A large number of dataset methods ultimately pass down their arguments to a filter
|
134
|
+
method, even some you may not expect, so you should be careful. At least the
|
135
|
+
following methods pass their arguments to a filter method:
|
136
|
+
|
137
|
+
* Sequel::Dataset#where
|
138
|
+
* Sequel::Dataset#having
|
139
|
+
* Sequel::Dataset#filter
|
140
|
+
* Sequel::Dataset#exclude
|
141
|
+
* Sequel::Dataset#exclude_where
|
142
|
+
* Sequel::Dataset#exclude_having
|
143
|
+
* Sequel::Dataset#and
|
144
|
+
* Sequel::Dataset#or
|
145
|
+
* Sequel::Dataset#first
|
146
|
+
* Sequel::Dataset#last
|
147
|
+
* Sequel::Dataset#[]
|
148
|
+
* Sequel::Dataset#[]=
|
149
|
+
|
150
|
+
The Model.find[rdoc-ref:Sequel::Model::ClassMethods#find] and Model.find_or_create[rdoc-ref:Sequel::Model::ClassMethods#find_or_create]
|
151
|
+
class methods also call down to the filter methods.
|
152
|
+
|
153
|
+
==== SQL Fragment passed to Dataset#update
|
154
|
+
|
155
|
+
Similar to the filter methods, Sequel::Dataset#update (and alias Sequel::Dataset#set) also treat a
|
156
|
+
string argument as raw SQL:
|
157
|
+
|
158
|
+
DB[:table].update("column = 1")
|
159
|
+
|
160
|
+
So you should not do:
|
161
|
+
|
162
|
+
DB[:table].update("column = #{params[:value].to_s}") # SQL Injection!
|
163
|
+
|
164
|
+
Instead, you should do:
|
165
|
+
|
166
|
+
DB[:table].update(:column => params[:value].to_s) # Safe
|
167
|
+
|
168
|
+
=== SQL Identifier Injections
|
169
|
+
|
170
|
+
Usually, Sequel treats ruby symbols as SQL identifiers, and ruby
|
171
|
+
strings as SQL strings. However, there are some parts of Sequel
|
172
|
+
that treat ruby strings as SQL identifiers if an SQL string would
|
173
|
+
not make sense in the same context.
|
174
|
+
|
175
|
+
For example, Sequel::Database#from and Sequel::Dataset#from will treat a string as
|
176
|
+
a table name:
|
177
|
+
|
178
|
+
DB.from('t') # SELECT * FROM "t"
|
179
|
+
|
180
|
+
Another place where Sequel treats ruby strings as identifiers are
|
181
|
+
the Sequel::Dataset#insert and Sequel::Dataset#update methods:
|
182
|
+
|
183
|
+
DB[:t].update('b'=>1) # UPDATE "t" SET "b" = 1
|
184
|
+
DB[:t].insert('b'=>1) # INSERT INTO "t" ("b") VALUES (1)
|
185
|
+
|
186
|
+
Note how the identifier is still quoted in these cases. Sequel quotes identifiers by default
|
187
|
+
on most databases. However, it does not quote identifiers by default on DB2 and Informix.
|
188
|
+
On those databases using an identifier derived from user input can lead to SQL injection.
|
189
|
+
Similarly, if you turn off identifier quoting manually on other databases, you open yourself
|
190
|
+
up to SQL injection if you use identifiers derived from user input.
|
191
|
+
|
192
|
+
When Sequel quotes identifiers, using an identifier derived from user input does not lead to
|
193
|
+
SQL injection, since the identifiers are also escaped when quoting.
|
194
|
+
Exceptions to this are Oracle (can't escape <tt>"</tt>) and Microsoft Access
|
195
|
+
(can't escape <tt>]</tt>).
|
196
|
+
|
197
|
+
In general, even if doesn't lead to SQL Injection, you should avoid using identifiers
|
198
|
+
derived from user input unless absolutely necessary.
|
199
|
+
|
200
|
+
Sequel also allows you to create identifiers using
|
201
|
+
Sequel.identifier[rdoc-ref:Sequel::SQL::Builders#identifier] for plain identifiers,
|
202
|
+
Sequel.qualify[rdoc-ref:Sequel::SQL::Builders#qualify] for qualified identifiers, and
|
203
|
+
Sequel.as[rdoc-ref:Sequel::SQL::Builders#as] for aliased expressions. So if you
|
204
|
+
pass any of those values derived from user input, you are dealing with the same scenario.
|
205
|
+
|
206
|
+
Note that the issues with SQL identifiers do not just apply to places where
|
207
|
+
strings are used as identifiers, they also apply to all places where Sequel
|
208
|
+
uses symbols as identifiers. However, if you are creating symbols from user input,
|
209
|
+
you at least have a denial of service vulnerability, and possibly a more serious
|
210
|
+
vulnerability.
|
211
|
+
|
212
|
+
== Denial of Service
|
213
|
+
|
214
|
+
Sequel converts some strings to symbols. Because symbols in ruby are not
|
215
|
+
garbage collected, if the strings that are converted to symbols are
|
216
|
+
derived from user input, you have a denial of service vulnerability due to
|
217
|
+
memory exhaustion.
|
218
|
+
|
219
|
+
The strings that Sequel converts to symbols are generally not derived
|
220
|
+
from user input, so Sequel in general is not vulnerable to this. However,
|
221
|
+
users should be aware of the cases in which Sequel creates symbols, so
|
222
|
+
they do not introduce a vulnerability into their application.
|
223
|
+
|
224
|
+
=== Column Names/Aliases
|
225
|
+
|
226
|
+
Sequel returns SQL result sets as an array of hashes with symbol keys. The
|
227
|
+
keys are derived from the name that the database server gives the column. These
|
228
|
+
names are generally static. For example:
|
229
|
+
|
230
|
+
SELECT column FROM table
|
231
|
+
|
232
|
+
The database will generally use "column" as the name in the result set.
|
233
|
+
|
234
|
+
If you use an alias:
|
235
|
+
|
236
|
+
SELECT column AS alias FROM table
|
237
|
+
|
238
|
+
The database will generally use "alias" as the name in the result set. So
|
239
|
+
if you allow the user to control the alias name:
|
240
|
+
|
241
|
+
DB[:table].select(:column.as(params[:alias]))
|
242
|
+
|
243
|
+
Then you have a denial of service vulnerability. In general, such a vulnerability
|
244
|
+
is unlikely, because you are probably indexing into the returned hash(es) by name,
|
245
|
+
and if an alias was used and you didn't expect it, your application wouldn't work.
|
246
|
+
|
247
|
+
The more insidious cases are those where an explicit alias is not used at all, but
|
248
|
+
an unaliased expression is used and the database chooses which alias to use. For
|
249
|
+
example, on SQLite, the following types of queries are vulnerable to denial of service:
|
250
|
+
|
251
|
+
DB[:table].get(params[:a].to_s)
|
252
|
+
DB[:table].select_map(params[:b].to_s)
|
253
|
+
DB[:table].select_order_map(params[:c].to_s)
|
254
|
+
|
255
|
+
In these cases, the queries will work correctly, but an unused symbol will be created.
|
256
|
+
To protect against the denial of service, use an explicit alias:
|
257
|
+
|
258
|
+
DB[:table].get(Sequel.as(params[:a].to_s, :a))
|
259
|
+
DB[:table].select_map(Sequel.as(params[:b].to_s, :a))
|
260
|
+
DB[:table].select_order_map(Sequel.as(params[:c].to_s, :a))
|
261
|
+
|
262
|
+
While the above code is unlikely to be used in practice, variants that use expressions
|
263
|
+
could be. For example, if you want to select all values in a specific column, with
|
264
|
+
a suffix provided by the user:
|
265
|
+
|
266
|
+
DB[:table].select_map(Sequel.join(:column, params[:suffix].to_s))
|
267
|
+
|
268
|
+
As above, you should use an explicit alias to protect against denial of service:
|
269
|
+
|
270
|
+
DB[:table].select_map(Sequel.join(:column, params[:suffix].to_s).as(:a))
|
271
|
+
|
272
|
+
=== Database Connection Options
|
273
|
+
|
274
|
+
All database connection options are converted to symbols. For a
|
275
|
+
connection URL, the keys are generally fixed, but the scheme is turned
|
276
|
+
into a symbol and the query option keys are used as connection option
|
277
|
+
keys, so they are converted to symbols as well. For example:
|
278
|
+
|
279
|
+
postgres://host/database?option1=foo&option2=bar
|
280
|
+
|
281
|
+
Will result in :postgres, :option1, and :option2 symbols being created.
|
282
|
+
|
283
|
+
Certain option values are also converted to symbols. In the general case,
|
284
|
+
the sql_log_level option value is, but some adapters treat additional
|
285
|
+
options similarly.
|
286
|
+
|
287
|
+
This is not generally a risk unless you are allowing the user to control
|
288
|
+
the connection URLs or are connecting to arbitrary databases at runtime.
|
289
|
+
|
290
|
+
== Mass Assignment
|
291
|
+
|
292
|
+
Mass assignment is the practice of passing a hash of columns and values
|
293
|
+
to a single method, and having multiple column values for a given object set
|
294
|
+
based on the content of the hash.
|
295
|
+
The security issue here is that mass assignment may allow the user to
|
296
|
+
set columns that you didn't intend to allow.
|
297
|
+
|
298
|
+
The Model#set[rdoc-ref:Sequel::Model::InstanceMethods#set] and Model#update[rdoc-ref:Sequel::Model::InstanceMethods#update] methods do mass
|
299
|
+
assignment. The default configuration of Sequel::Model allows all model
|
300
|
+
columns except for the primary key column(s) to be set via mass assignment.
|
301
|
+
|
302
|
+
Example:
|
303
|
+
|
304
|
+
album = Album.new
|
305
|
+
album.set(params[:album]) # Mass Assignment
|
306
|
+
|
307
|
+
Both Model.new[rdoc-ref:Sequel::Model::InstanceMethods::new] and Model.create[rdoc-ref:Sequel::Model::ClassMethods#create]
|
308
|
+
call Model#set[rdoc-ref:Sequel::Model::InstanceMethods#set] internally, so
|
309
|
+
they also allow mass assignment:
|
310
|
+
|
311
|
+
Album.new(params[:album]) # Mass Assignment
|
312
|
+
Album.create(params[:album]) # Mass Assignment
|
313
|
+
|
314
|
+
Instead of these methods, it is encouraged to either use the
|
315
|
+
Model#set_only[rdoc-ref:Sequel::Model::InstanceMethods#set_only],
|
316
|
+
Model#update_only[rdoc-ref:Sequel::Model::InstanceMethods#update_only],
|
317
|
+
Model#set_fields[rdoc-ref:Sequel::Model::InstanceMethods#set_fields], or
|
318
|
+
Model#update_fields[rdoc-ref:Sequel::Model::InstanceMethods#update_fields]
|
319
|
+
methods, which allow you to specify which fields
|
320
|
+
to allow on a per-call basis. This pretty much eliminates the chance that the
|
321
|
+
user will be able to set a column you did not intend to allow:
|
322
|
+
|
323
|
+
album.set_only(params[:album], [:name, :copies_sold])
|
324
|
+
album.set_fields(params[:album], [:name, :copies_sold])
|
325
|
+
|
326
|
+
You can override the columns to allow by default during mass assignment via
|
327
|
+
the Model.set_allowed_columns[rdoc-ref:Sequel::Model::ClassMethods#set_allowed_columns] class method. This is a good
|
328
|
+
practice, though being explicit on a per-call basis is still recommended:
|
329
|
+
|
330
|
+
Album.set_allowed_columns(:name, :copies_sold)
|
331
|
+
Album.create(params[:album]) # Only name and copies_sold set
|
332
|
+
|
333
|
+
For more details on the mass assignment methods, see the {Mass Assignment Guide}[link:files/doc/mass_assignment_rdoc.html].
|
334
|
+
|
335
|
+
== General Parameter Handling
|
336
|
+
|
337
|
+
This issue isn't necessarily specific to Sequel, but it is a good general practice.
|
338
|
+
If you are using values derived from user input, it is best to be explicit about
|
339
|
+
their type. For example:
|
340
|
+
|
341
|
+
Album.where(:id=>params[:id])
|
342
|
+
|
343
|
+
is probably a bad idea. Assuming you are using a web framework, params\[:id\] could
|
344
|
+
be a string, an array, a hash, or nil.
|
345
|
+
|
346
|
+
Assuming that +id+ is an integer field, you probably want to do:
|
347
|
+
|
348
|
+
Album.where(:id=>params[:id].to_i)
|
349
|
+
|
350
|
+
If you are looking something up by name, you should try to enforce the value to be
|
351
|
+
a string:
|
352
|
+
|
353
|
+
Album.where(:name=>params[:name].to_s)
|
354
|
+
|
355
|
+
If you are trying to use an IN clause with a list of id values based on input provided
|
356
|
+
on a web form:
|
357
|
+
|
358
|
+
Album.where(:id=>params[:ids].to_a.map{|i| i.to_i})
|
359
|
+
|
360
|
+
Basically, be as explicit as possible. While there aren't any known security issues
|
361
|
+
in Sequel when you do:
|
362
|
+
|
363
|
+
Album.where(:id=>params[:id])
|
364
|
+
|
365
|
+
It allows the attacker to choose to do any of the following queries:
|
366
|
+
|
367
|
+
id IS NULL # nil
|
368
|
+
id = '1' # '1'
|
369
|
+
id IN ('1', '2', '3') # ['1', '2', '3']
|
370
|
+
id = ('a' = 'b') # {'a'=>'b'}
|
371
|
+
id = ('a' IN ('a', 'b') AND 'c' = '') # {'a'=>['a', 'b'], 'c'=>''}
|
372
|
+
|
373
|
+
While none of those allow for SQL injection, it's possible that they
|
374
|
+
might have an issue in your application. For example, a long array
|
375
|
+
or deeply nested hash might cause the database to have to do a lot of
|
376
|
+
work that could be avoided.
|
377
|
+
|
378
|
+
In general, it's best to let the attacker control as little as possible,
|
379
|
+
and explicitly specifying types helps a great deal there.
|