sequel 3.45.0 → 3.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +6 -0
  4. data/Rakefile +46 -33
  5. data/doc/release_notes/3.46.0.txt +122 -0
  6. data/doc/schema_modification.rdoc +42 -6
  7. data/doc/security.rdoc +379 -0
  8. data/doc/transactions.rdoc +1 -1
  9. data/lib/sequel/adapters/jdbc/as400.rb +1 -0
  10. data/lib/sequel/adapters/jdbc/h2.rb +11 -0
  11. data/lib/sequel/adapters/mysql2.rb +3 -9
  12. data/lib/sequel/adapters/postgres.rb +34 -2
  13. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +27 -3
  15. data/lib/sequel/adapters/shared/mysql.rb +25 -4
  16. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  17. data/lib/sequel/connection_pool.rb +3 -3
  18. data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
  19. data/lib/sequel/connection_pool/threaded.rb +7 -8
  20. data/lib/sequel/core.rb +5 -2
  21. data/lib/sequel/database.rb +1 -1
  22. data/lib/sequel/database/connecting.rb +7 -7
  23. data/lib/sequel/database/features.rb +88 -0
  24. data/lib/sequel/database/misc.rb +14 -64
  25. data/lib/sequel/database/query.rb +0 -332
  26. data/lib/sequel/database/schema_generator.rb +36 -3
  27. data/lib/sequel/database/schema_methods.rb +48 -12
  28. data/lib/sequel/database/transactions.rb +344 -0
  29. data/lib/sequel/dataset/actions.rb +24 -9
  30. data/lib/sequel/dataset/mutation.rb +20 -0
  31. data/lib/sequel/dataset/query.rb +0 -17
  32. data/lib/sequel/dataset/sql.rb +7 -0
  33. data/lib/sequel/exceptions.rb +10 -6
  34. data/lib/sequel/extensions/_pretty_table.rb +2 -2
  35. data/lib/sequel/extensions/looser_typecasting.rb +10 -0
  36. data/lib/sequel/extensions/migration.rb +5 -2
  37. data/lib/sequel/model.rb +1 -1
  38. data/lib/sequel/model/associations.rb +16 -14
  39. data/lib/sequel/model/base.rb +14 -2
  40. data/lib/sequel/plugins/composition.rb +3 -3
  41. data/lib/sequel/plugins/dirty.rb +6 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +3 -0
  43. data/lib/sequel/plugins/serialization.rb +7 -17
  44. data/lib/sequel/plugins/string_stripper.rb +2 -1
  45. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  46. data/lib/sequel/sql.rb +3 -0
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/mssql_spec.rb +21 -0
  49. data/spec/adapters/postgres_spec.rb +35 -8
  50. data/spec/core/database_spec.rb +4 -0
  51. data/spec/core/dataset_spec.rb +48 -2
  52. data/spec/core/schema_generator_spec.rb +10 -1
  53. data/spec/core/schema_spec.rb +69 -0
  54. data/spec/extensions/composition_spec.rb +21 -2
  55. data/spec/extensions/dirty_spec.rb +17 -10
  56. data/spec/extensions/eager_each_spec.rb +4 -1
  57. data/spec/extensions/looser_typecasting_spec.rb +16 -19
  58. data/spec/extensions/migration_spec.rb +7 -1
  59. data/spec/extensions/serialization_spec.rb +22 -0
  60. data/spec/extensions/single_table_inheritance_spec.rb +3 -2
  61. data/spec/extensions/validation_helpers_spec.rb +6 -0
  62. data/spec/integration/dataset_test.rb +5 -0
  63. data/spec/integration/schema_test.rb +16 -0
  64. data/spec/model/associations_spec.rb +40 -0
  65. data/spec/model/base_spec.rb +21 -1
  66. data/spec/model/record_spec.rb +3 -0
  67. metadata +14 -10
@@ -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)
@@ -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 "Packages sequel"
14
+ desc "Build sequel gem"
15
15
  task :package=>[:clean] do |p|
16
- load './sequel.gemspec'
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 "Upload sequel gem to gemcutter"
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 = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
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
- rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
82
- rdoc.rdoc_dir = "www/public/rdoc-adapters"
83
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
84
- rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
85
- end
86
-
87
- rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
88
- rdoc.rdoc_dir = "www/public/rdoc-plugins"
89
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
90
- rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
91
- end
92
-
93
- desc "Update sequel.rubyforge.org"
94
- task :website_rf=>[:website, :website_rdoc] do
95
- sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
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
@@ -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.