sequel 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ require 'sequel'
6
+
7
+ db_opts = {}
8
+ echo = nil
9
+ env = nil
10
+ logfile = nil
11
+ migrate_dir = nil
12
+ migrate_ver = nil
13
+
14
+ opts = OptionParser.new do |opts|
15
+ opts.banner = "Sequel: The Database Toolkit for Ruby"
16
+ opts.define_head "Usage: sequel <uri|path> [options]"
17
+ opts.separator ""
18
+ opts.separator "Examples:"
19
+ opts.separator " sequel sqlite://blog.db"
20
+ opts.separator " sequel postgres://localhost/my_blog"
21
+ opts.separator " sequel config/database.yml"
22
+ opts.separator ""
23
+ opts.separator "For more information see http://sequel.rubyforge.org"
24
+ opts.separator ""
25
+ opts.separator "Options:"
26
+
27
+ opts.on_tail("-?", "--help", "Show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+
32
+ opts.on("-l", "--log logfile", "log SQL statements to log file") do |v|
33
+ logfile = v
34
+ end
35
+
36
+ opts.on("-e", "--env ENV", "use environment config for database") do |v|
37
+ env = v
38
+ end
39
+
40
+ opts.on("-E", "--echo", "echo SQL statements") do
41
+ echo = true
42
+ end
43
+
44
+ opts.on("-m", "--migrate-directory DIR", "run the migrations in directory") do |v|
45
+ migrate_dir = v
46
+ end
47
+
48
+ opts.on("-M", "--migrate-version VER", "migrate the database to version given") do |v|
49
+ migrate_ver = Integer(v)
50
+ end
51
+
52
+ opts.on_tail("-v", "--version", "Show version") do
53
+ class << Gem; attr_accessor :loaded_specs; end
54
+ begin
55
+ specs = Gem.loaded_specs['sequel']
56
+ puts "sequel #{specs.version} (#{specs.date.strftime '%Y-%m-%d'})"
57
+ rescue
58
+ puts "No gem version found"
59
+ end
60
+ exit
61
+ end
62
+ end
63
+ opts.parse!
64
+
65
+ db = ARGV.shift
66
+
67
+ if db.blank?
68
+ puts opts
69
+ exit 1
70
+ end
71
+
72
+ if logfile || echo
73
+ require 'logger'
74
+ db_opts[:loggers] = []
75
+ db_opts[:loggers] << Logger.new(logfile) if logfile
76
+ db_opts[:loggers] << Logger.new($stdout) if echo
77
+ end
78
+
79
+ if File.exist?(db)
80
+ require 'yaml'
81
+ db_config = YAML.load_file(db)[env || "development"]
82
+ db_config.each {|(k,v)| db_config[k.to_sym] = db_config.delete(k)}
83
+ db_config.merge!(db_opts)
84
+ end
85
+
86
+ begin
87
+ if db_config
88
+ opts = [db_config]
89
+ else
90
+ opts = [db, db_opts]
91
+ end
92
+ DB = Sequel.connect(*opts)
93
+ DB.test_connection
94
+ if migrate_dir
95
+ Sequel::Migrator.apply(DB, migrate_dir, migrate_ver)
96
+ exit
97
+ end
98
+ rescue => e
99
+ puts e.message
100
+ puts e.backtrace.first
101
+ exit 1
102
+ end
103
+
104
+ require 'irb'
105
+ puts "Your database is stored in DB..."
106
+ IRB.start
@@ -0,0 +1,225 @@
1
+ = Cheat Sheet
2
+
3
+ == Open a database
4
+
5
+ require 'rubygems'
6
+ require 'sequel'
7
+
8
+ DB = Sequel.sqlite 'my_blog.db'
9
+ DB = Sequel('postgres://user:password@localhost/my_db')
10
+ DB = Sequel.mysql 'my_db', :user => 'user', :password => 'password', :host => 'localhost'
11
+ DB = Sequel.ado 'mydb'
12
+
13
+ == Open an SQLite memory database
14
+
15
+ Without a filename argument, the sqlite adapter will setup a new sqlite database in RAM.
16
+
17
+ DB = Sequel.sqlite
18
+
19
+ == Logging SQL statements
20
+
21
+ require 'logger'
22
+ DB = Sequel.sqlite '', :loggers => [Logger.new($stdout)]
23
+ # or
24
+ DB.loggers << Logger.new(...)
25
+
26
+ == Using raw SQL
27
+
28
+ DB << "CREATE TABLE users (name VARCHAR(255) NOT NULL, age INT(3) NOT NULL)"
29
+ DB.fetch("SELECT name FROM users") do |row|
30
+ p r[:name]
31
+ end
32
+ dataset = DB["SELECT age FROM users"]
33
+ dataset.print
34
+ dataset.map(:age)
35
+
36
+ == Create a dataset
37
+
38
+ dataset = DB[:items]
39
+ dataset = DB.dataset.from(:items)
40
+
41
+ == Most dataset methods are chainable
42
+
43
+ dataset = DB[:managers].where(:salary => 5000..10000).order(:name, :department)
44
+ # or
45
+ dataset = DB.query do
46
+ from :managers
47
+ where :salary => 5000..10000
48
+ order :name, :department
49
+ end
50
+
51
+ == Insert rows
52
+
53
+ dataset.insert(:name => 'Sharon', :grade => 50)
54
+ dataset << {:name => 'Sharon', :grade => 50} # same effect as above
55
+
56
+ == Retrieve rows
57
+
58
+ dataset.each {|r| p r}
59
+ dataset.all #=> [{...}, {...}, ...]
60
+ dataset.first
61
+ dataset.order(:name).last # works only for ordered datasets
62
+
63
+ == Update/Delete rows
64
+
65
+ dataset.filter(:active => false).delete
66
+ dataset.filter('price < ?', 100).update(:active => true)
67
+
68
+ == Datasets are Enumerable
69
+
70
+ dataset.map {|r| r[:name]}
71
+ dataset.map(:name) # same effect as above
72
+
73
+ dataset.inject {|sum, r| sum + r[:value]}
74
+
75
+ == Filtering (see also doc/dataset_filtering.rdoc)
76
+
77
+ dataset.filter(:name => 'abc')
78
+ dataset.filter('name = ?', 'abc')
79
+ dataset.filter(:value > 100)
80
+ dataset.exclude(:value <= 100)
81
+
82
+ dataset.filter(:value => 50..100)
83
+ dataset.where((:value >= 50) & (:value <= 100))
84
+
85
+ dataset.where('value IN ?', [50,75,100])
86
+
87
+ # Get the first record that matches a condition
88
+ dataset[:name => 'abc'] # Same as:
89
+ dataset.filter(:name => 'abc').first
90
+
91
+ # Filter using a subquery
92
+ dataset.filter('price > ?', dataset.select('AVG(price) + 100'))
93
+
94
+ === Advanced filtering using ruby expressions without blocks
95
+
96
+ Available as of Sequel 2.0:
97
+
98
+ DB[:items].filter(:price < 100).sql
99
+ #=> "SELECT * FROM items WHERE (price < 100)"
100
+
101
+ DB[:items].filter(:name.like('AL%')).sql
102
+ #=> "SELECT * FROM items WHERE (name LIKE 'AL%')"
103
+
104
+ There's support for nested expressions with AND, OR and NOT:
105
+
106
+ DB[:items].filter((:x > 5) & (:y > 10)).sql
107
+ #=> "SELECT * FROM items WHERE ((x > 5) AND (y > 10))"
108
+
109
+ DB[:items].filter({:x => 1, :y => 2}.sql_or & ~{:z => 3}).sql
110
+ #=> "SELECT * FROM items WHERE (((x = 1) OR (y = 2)) AND (z != 3))"
111
+
112
+ You can use arithmetic operators and specify SQL functions:
113
+
114
+ DB[:items].filter((:x + :y) > :z).sql
115
+ #=> "SELECT * FROM items WHERE ((x + y) > z)"
116
+
117
+ DB[:items].filter(:price < :AVG[:price] + 100).sql
118
+ #=> "SELECT * FROM items WHERE (price < (AVG(price) + 100))"
119
+
120
+ == Ordering
121
+
122
+ dataset.order(:kind)
123
+ dataset.reverse_order(:kind)
124
+ dataset.order(:kind.desc, :name)
125
+
126
+ == Row ranges
127
+
128
+ dataset.limit(30) # LIMIT 30
129
+ dataset.limit(30, 10) # LIMIT 30 OFFSET 10
130
+
131
+ == Pagination
132
+
133
+ paginated = dataset.paginate(1, 10) # first page, 10 rows per page
134
+ paginated.page_count #=> number of pages in dataset
135
+ paginated.current_page #=> 1
136
+ paginated.next_page #=> next page number or nil
137
+ paginated.prev_page #=> previous page number or nil
138
+ paginated.first_page? #=> true if page number = 1
139
+ paginated.last_page? #=> true if page number = page_count
140
+
141
+ == Joins
142
+
143
+ DB[:items].left_outer_join(:categories, :id => :category_id).sql #=>
144
+ "SELECT * FROM items LEFT OUTER JOIN categories ON categories.id = items.category_id"
145
+
146
+ == Summarizing
147
+
148
+ dataset.count #=> record count
149
+ dataset.max(:price)
150
+ dataset.min(:price)
151
+ dataset.avg(:price)
152
+ dataset.sum(:stock)
153
+
154
+ dataset.group(:category).select(:category, :AVG[:price])
155
+
156
+ == SQL Functions / Literals
157
+
158
+ dataset.update(:updated_at => :NOW[])
159
+ dataset.update(:updated_at => 'NOW()'.lit)
160
+
161
+ dataset.update(:updated_at => "DateValue('1/1/2001')".lit)
162
+ dataset.update(:updated_at => :DateValue['1/1/2001'])
163
+
164
+ == Schema Manipulation
165
+
166
+ DB.create_table :items do
167
+ primary_key :id
168
+ text :name, :unique => true, :null => false
169
+ boolean :active, :default => true
170
+ foreign_key :category_id, :categories
171
+
172
+ index :grade
173
+ end
174
+
175
+ DB.drop_table :items
176
+
177
+ DB.create_table :test do
178
+ varchar :zipcode, :size => 10
179
+ enum :system, :elements => ['mac', 'linux', 'windows']
180
+ end
181
+
182
+ == Aliasing
183
+
184
+ DB[:items].select(:name.as(:item_name))
185
+ DB[:items].select(:name => :item_name)
186
+ DB[:items].select(:name___item_name)
187
+ DB[:items___items_table].select(:items_table__name___item_name)
188
+ # => "SELECT items_table.name AS item_name FROM items AS items_table"
189
+
190
+ == Transactions
191
+
192
+ DB.transaction do
193
+ dataset << {:first_name => 'Inigo', :last_name => 'Montoya'}
194
+ dataset << {:first_name => 'Farm', :last_name => 'Boy'}
195
+ end # Either both are inserted or neither are inserted
196
+
197
+ Database#transaction is re-entrant:
198
+
199
+ DB.transaction do # BEGIN issued only here
200
+ DB.transaction
201
+ dataset << {:first_name => 'Inigo', :last_name => 'Montoya'}
202
+ end
203
+ end # COMMIT issued only here
204
+
205
+ Transactions are aborted if an error is raised:
206
+
207
+ DB.transaction do
208
+ raise "some error occurred"
209
+ end # ROLLBACK issued and the error is re-raised
210
+
211
+ Transactions can also be aborted by raising Sequel::Error::Rollback:
212
+
213
+ DB.transaction do
214
+ raise(Sequel::Error::Rollback) if something_bad_happened
215
+ end # ROLLBACK issued and no error raised
216
+
217
+ Miscellaneous:
218
+
219
+ dataset.sql #=> "SELECT * FROM items"
220
+ dataset.delete_sql #=> "DELETE FROM items"
221
+ dataset.where(:name => 'sequel').exists #=> "EXISTS ( SELECT 1 FROM items WHERE name = 'sequel' )"
222
+ dataset.print #=> pretty table print to $stdout
223
+ dataset.columns #=> array of columns in the result set, does a SELECT
224
+ DB.schema_for_table(:items) => [[:id, {:type=>:integer, ...}], [:name, {:type=>:string, ...}], ...]
225
+ # Works on PostgreSQL, MySQL, SQLite, and possibly elsewhere
@@ -0,0 +1,182 @@
1
+ = Dataset Filtering
2
+
3
+ Sequel offers unparalleled flexibility when it comes to filtering records. You can specify your conditions as a custom string, as a string with parameters, as a hash of values to compare against, or as ruby code that Sequel translates into SQL expressions.
4
+
5
+ == Filtering using a custom filter string
6
+
7
+ If you do not wish to lose control over your SQL WHERE clauses, you can just supply it to the dataset's #filter method:
8
+
9
+ items.filter('x < 10').sql
10
+ #=> "SELECT * FROM items WHERE x < 10"
11
+
12
+ In order to prevent SQL injection, you can replace literal values with question marks and supply the values as additional arguments:
13
+
14
+ items.filter('category = ?', 'ruby').sql
15
+ #=> "SELECT * FROM items WHERE category = 'ruby'"
16
+
17
+ == An aside: column references in Sequel
18
+
19
+ Sequel expects column names to be specified using symbols. In addition, tuples always use symbols as their keys. This allows you to freely mix literal values and column references. For example, the two following lines produce equivalent SQL:
20
+
21
+ items.filter(:x => 1) #=> "SELECT * FROM items WHERE (x = 1)"
22
+ items.filter(1 => :x) #=> "SELECT * FROM items WHERE (1 = x)"
23
+
24
+ === Qualifying column names
25
+
26
+ Column references can be qualified by using the double underscore special notation :table__column:
27
+
28
+ items.literal(:items__price) #=> "items.price"
29
+
30
+ === Column aliases
31
+
32
+ You can also alias columns by using the triple undersecore special notation :column___alias or :table__column___alias:
33
+
34
+ items.literal(:price___p) #=> "price AS p"
35
+ items.literal(:items__price___p) #=> "items.price AS p"
36
+
37
+ Another way to alias columns is to use the #AS method:
38
+
39
+ items.literal(:price.as(:p)) #=> "price AS p"
40
+
41
+ === Specifying SQL functions
42
+
43
+ Sequel also allows you to specify functions by using the Symbol#[] method:
44
+
45
+ items.literal(:avg[:price]) #=> "avg(price)"
46
+
47
+ == Filtering using a hash
48
+
49
+ If you just need to compare records against values, you can supply a hash:
50
+
51
+ items.filter(:category => 'ruby').sql
52
+ #=> "SELECT * FROM items WHERE (category = 'ruby')"
53
+
54
+ Sequel can check for null values:
55
+
56
+ items.filter(:category => nil).sql
57
+ #=> "SELECT * FROM items WHERE (category IS NULL)"
58
+
59
+ Or compare two columns:
60
+
61
+ items.filter(:x => :some_table__y).sql
62
+ #=> "SELECT * FROM items WHERE (x = some_table.y)"
63
+
64
+ And also compare against multiple values:
65
+
66
+ items.filter(:category => ['ruby', 'perl']).sql
67
+ #=> "SELECT * FROM items WHERE (category IN ('ruby', 'perl'))"
68
+
69
+ Ranges (both inclusive and exclusive) can also be used:
70
+
71
+ items.filter(:price => 100..200).sql
72
+ #=> "SELECT * FROM items WHERE (price >= 100 AND price <= 200)"
73
+
74
+ items.filter(:price => 100...200).sql
75
+ #=> "SELECT * FROM items WHERE (price >= 100 AND price < 200)"
76
+
77
+ == Filtering using expressions
78
+
79
+ New in Sequel 2.0 is the ability to use ruby expressions directly in the call to filter, without using a block:
80
+
81
+ items.filter(:price < 100).sql
82
+ #=> "SELECT * FROM items WHERE (price < 100)
83
+
84
+ This works for the standard inequality and arithmetic operators:
85
+
86
+ items.filter(:price + 100 < 200).sql
87
+ #=> "SELECT * FROM items WHERE ((price + 100) < 200)
88
+
89
+ items.filter(:price - 100 > 200).sql
90
+ #=> "SELECT * FROM items WHERE ((price - 100) > 200)
91
+
92
+ items.filter(:price * 100 <= 200).sql
93
+ #=> "SELECT * FROM items WHERE ((price * 100) <= 200)
94
+
95
+ items.filter(:price / 100 >= 200).sql
96
+ #=> "SELECT * FROM items WHERE ((price / 100) >= 200)
97
+
98
+ You use the overloaded bitwise and (&) and or (|) operators to combine expressions:
99
+
100
+ items.filter((:price + 100 < 200) & (:price * 100 <= 200)).sql
101
+ #=> "SELECT * FROM items WHERE (((price + 100) < 200) AND ((price * 100) <= 200))
102
+
103
+ items.filter((:price - 100 > 200) | (:price / 100 >= 200)).sql
104
+ #=> "SELECT * FROM items WHERE (((price - 100) > 200) OR ((price / 100) >= 200))
105
+
106
+ To filter by equality, you use the standard hash, which can be combined with other operators:
107
+
108
+ items.filter({:category => 'ruby'} & (:price + 100 < 200)).sql
109
+ #=> "SELECT * FROM items WHERE ((category = 'ruby') AND ((price + 100) < 200))"
110
+
111
+ This works with other hash values, such as arrays and ranges:
112
+
113
+ items.filter({:category => ['ruby', 'other']} | (:price - 100 > 200)).sql
114
+ #=> "SELECT * FROM items WHERE ((category IN ('ruby', 'other')) OR ((price - 100) <= 200))"
115
+
116
+ items.filter({:price => (100..200)} & :active).sql
117
+ #=> "SELECT * FROM items WHERE ((price >= 100 AND price <= 200) AND active)"
118
+
119
+ === Negating conditions
120
+
121
+ You can use the negation operator (~) in most cases:
122
+
123
+ items.filter(~{:category => 'ruby'}).sql
124
+ #=> "SELECT * FROM items WHERE (category != 'ruby')"
125
+
126
+ items.filter {~:active}.sql
127
+ #=> "SELECT * FROM items WHERE NOT active"
128
+
129
+ items.filter(~(:price / 100 >= 200)).sql
130
+ #=> "SELECT * FROM items WHERE ((price / 100) < 200)
131
+
132
+ === Comparing against column references
133
+
134
+ You can also compare against other columns:
135
+
136
+ items.filter(:credit > :debit).sql
137
+ #=> "SELECT * FROM items WHERE (credit > debit)
138
+
139
+ Or against SQL functions:
140
+
141
+ items.filter(:price < :max[:price] + 100).sql
142
+ #=> "SELECT * FROM items WHERE (price < (max(price) + 100))"
143
+
144
+ == String search functions
145
+
146
+ You can search SQL strings using the #like method:
147
+
148
+ items.filter(:name.like('Acme%')).sql
149
+ #=> "SELECT * FROM items WHERE (name LIKE 'Acme%')"
150
+
151
+ You can specify a Regexp as a like argument, but this will probably only work
152
+ on PostgreSQL and MySQL:
153
+
154
+ items.filter(:name.like(/Acme.*/)).sql
155
+ #=> "SELECT * FROM items WHERE (name ~ 'Acme.*')"
156
+
157
+ Like can also take more than one argument:
158
+
159
+ items.filter(:name.like('Acme%', /Beta.*/)).sql
160
+ #=> "SELECT * FROM items WHERE ((name LIKE 'Acme%') OR (name ~ 'Beta.*'))"
161
+
162
+ == String concatenation
163
+
164
+ You can concatenate SQL strings using Array#sql_string_join:
165
+
166
+ items.filter([:name, :comment].sql_string_join.like('%acme%')).sql
167
+ #=> "SELECT * FROM items WHERE ((name || comment) LIKE 'Acme%')"
168
+
169
+ Array#sql_string_join also takes a join argument:
170
+
171
+ items.filter([:name, :comment].sql_string_join(' ').like('%acme%')).sql
172
+ #=> "SELECT * FROM items WHERE ((name || ' ' || comment) LIKE 'Acme%')"
173
+
174
+ == Filtering using sub-queries
175
+
176
+ One of the best features of Sequel is the ability to use datasets as sub-queries. Sub-queries can be very useful for filtering records, and many times provide a simpler alternative to table joins. Sub-queries can be used in all forms of filters:
177
+
178
+ refs = consumer_refs.filter(:logged_in => true).select(:consumer_id)
179
+ consumers.filter(:id => refs).sql
180
+ #=> "SELECT * FROM consumers WHERE (id IN (SELECT consumer_id FROM consumer_refs WHERE (logged_in = 't')))"
181
+
182
+ Note that if you compare against a sub-query, you must select a single column in the sub-query.