colincasey-sequel 2.10.0 → 2.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +7 -1
- data/doc/advanced_associations.rdoc +614 -0
- data/doc/cheat_sheet.rdoc +223 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/schema.rdoc +29 -0
- data/doc/sharding.rdoc +113 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel_core/adapters/ado.rb +89 -0
- data/lib/sequel_core/adapters/db2.rb +143 -0
- data/lib/sequel_core/adapters/dbi.rb +112 -0
- data/lib/sequel_core/adapters/do/mysql.rb +38 -0
- data/lib/sequel_core/adapters/do/postgres.rb +92 -0
- data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
- data/lib/sequel_core/adapters/do.rb +205 -0
- data/lib/sequel_core/adapters/firebird.rb +298 -0
- data/lib/sequel_core/adapters/informix.rb +85 -0
- data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel_core/adapters/jdbc.rb +491 -0
- data/lib/sequel_core/adapters/mysql.rb +369 -0
- data/lib/sequel_core/adapters/odbc.rb +174 -0
- data/lib/sequel_core/adapters/openbase.rb +68 -0
- data/lib/sequel_core/adapters/oracle.rb +107 -0
- data/lib/sequel_core/adapters/postgres.rb +456 -0
- data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
- data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
- data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
- data/lib/sequel_core/adapters/shared/progress.rb +31 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
- data/lib/sequel_core/adapters/sqlite.rb +248 -0
- data/lib/sequel_core/connection_pool.rb +258 -0
- data/lib/sequel_core/core_ext.rb +217 -0
- data/lib/sequel_core/core_sql.rb +202 -0
- data/lib/sequel_core/database/schema.rb +164 -0
- data/lib/sequel_core/database.rb +691 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +237 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +1010 -0
- data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
- data/lib/sequel_core/dataset/unsupported.rb +43 -0
- data/lib/sequel_core/dataset.rb +511 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +44 -0
- data/lib/sequel_core/migration.rb +212 -0
- data/lib/sequel_core/object_graph.rb +230 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema/generator.rb +320 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/sql.rb +887 -0
- data/lib/sequel_core/version.rb +11 -0
- data/lib/sequel_core.rb +172 -0
- data/lib/sequel_model/association_reflection.rb +267 -0
- data/lib/sequel_model/associations.rb +499 -0
- data/lib/sequel_model/base.rb +523 -0
- data/lib/sequel_model/caching.rb +82 -0
- data/lib/sequel_model/dataset_methods.rb +26 -0
- data/lib/sequel_model/eager_loading.rb +370 -0
- data/lib/sequel_model/exceptions.rb +7 -0
- data/lib/sequel_model/hooks.rb +101 -0
- data/lib/sequel_model/inflector.rb +281 -0
- data/lib/sequel_model/plugins.rb +62 -0
- data/lib/sequel_model/record.rb +568 -0
- data/lib/sequel_model/schema.rb +49 -0
- data/lib/sequel_model/validations.rb +429 -0
- data/lib/sequel_model.rb +91 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +881 -0
- data/spec/adapters/oracle_spec.rb +244 -0
- data/spec/adapters/postgres_spec.rb +687 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +555 -0
- data/spec/integration/dataset_test.rb +134 -0
- data/spec/integration/eager_loader_test.rb +696 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +180 -0
- data/spec/integration/spec_helper.rb +58 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/rcov.opts +6 -0
- data/spec/sequel_core/connection_pool_spec.rb +526 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +522 -0
- data/spec/sequel_core/database_spec.rb +1188 -0
- data/spec/sequel_core/dataset_spec.rb +3481 -0
- data/spec/sequel_core/expression_filters_spec.rb +363 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +272 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +167 -0
- data/spec/sequel_core/schema_spec.rb +780 -0
- data/spec/sequel_core/spec_helper.rb +55 -0
- data/spec/sequel_core/version_spec.rb +7 -0
- data/spec/sequel_model/association_reflection_spec.rb +93 -0
- data/spec/sequel_model/associations_spec.rb +1767 -0
- data/spec/sequel_model/base_spec.rb +419 -0
- data/spec/sequel_model/caching_spec.rb +215 -0
- data/spec/sequel_model/dataset_methods_spec.rb +78 -0
- data/spec/sequel_model/eager_loading_spec.rb +1165 -0
- data/spec/sequel_model/hooks_spec.rb +485 -0
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/sequel_model/model_spec.rb +588 -0
- data/spec/sequel_model/plugins_spec.rb +80 -0
- data/spec/sequel_model/record_spec.rb +1184 -0
- data/spec/sequel_model/schema_spec.rb +90 -0
- data/spec/sequel_model/spec_helper.rb +78 -0
- data/spec/sequel_model/validations_spec.rb +1067 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +177 -3
@@ -0,0 +1,223 @@
|
|
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.connect('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 WHERE name = ?", name]
|
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(0){|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{|o| o.value > 100}
|
80
|
+
dataset.exclude{|o| o.value <= 100}
|
81
|
+
|
82
|
+
dataset.filter(:value => 50..100)
|
83
|
+
dataset.where{|o| (o.value >= 50) & (o.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
|
95
|
+
|
96
|
+
DB[:items].filter{|o| o.price < 100}.sql
|
97
|
+
#=> "SELECT * FROM items WHERE (price < 100)"
|
98
|
+
|
99
|
+
DB[:items].filter(:name.like('AL%')).sql
|
100
|
+
#=> "SELECT * FROM items WHERE (name LIKE 'AL%')"
|
101
|
+
|
102
|
+
There's support for nested expressions with AND, OR and NOT:
|
103
|
+
|
104
|
+
DB[:items].filter{|o| (o.x > 5) & (o.y > 10)}.sql
|
105
|
+
#=> "SELECT * FROM items WHERE ((x > 5) AND (y > 10))"
|
106
|
+
|
107
|
+
DB[:items].filter({:x => 1, :y => 2}.sql_or & ~{:z => 3}).sql
|
108
|
+
#=> "SELECT * FROM items WHERE (((x = 1) OR (y = 2)) AND (z != 3))"
|
109
|
+
|
110
|
+
You can use arithmetic operators and specify SQL functions:
|
111
|
+
|
112
|
+
DB[:items].filter((:x + :y) > :z).sql
|
113
|
+
#=> "SELECT * FROM items WHERE ((x + y) > z)"
|
114
|
+
|
115
|
+
DB[:items].filter{|o| :price - 100 < o.AVG(:price)}.sql
|
116
|
+
#=> "SELECT * FROM items WHERE ((price - 100) < AVG(price))"
|
117
|
+
|
118
|
+
== Ordering
|
119
|
+
|
120
|
+
dataset.order(:kind)
|
121
|
+
dataset.reverse_order(:kind)
|
122
|
+
dataset.order(:kind.desc, :name)
|
123
|
+
|
124
|
+
== Row ranges
|
125
|
+
|
126
|
+
dataset.limit(30) # LIMIT 30
|
127
|
+
dataset.limit(30, 10) # LIMIT 30 OFFSET 10
|
128
|
+
|
129
|
+
== Pagination
|
130
|
+
|
131
|
+
paginated = dataset.paginate(1, 10) # first page, 10 rows per page
|
132
|
+
paginated.page_count #=> number of pages in dataset
|
133
|
+
paginated.current_page #=> 1
|
134
|
+
paginated.next_page #=> next page number or nil
|
135
|
+
paginated.prev_page #=> previous page number or nil
|
136
|
+
paginated.first_page? #=> true if page number = 1
|
137
|
+
paginated.last_page? #=> true if page number = page_count
|
138
|
+
|
139
|
+
== Joins
|
140
|
+
|
141
|
+
DB[:items].left_outer_join(:categories, :id => :category_id).sql #=>
|
142
|
+
"SELECT * FROM items LEFT OUTER JOIN categories ON categories.id = items.category_id"
|
143
|
+
|
144
|
+
== Summarizing
|
145
|
+
|
146
|
+
dataset.count #=> record count
|
147
|
+
dataset.max(:price)
|
148
|
+
dataset.min(:price)
|
149
|
+
dataset.avg(:price)
|
150
|
+
dataset.sum(:stock)
|
151
|
+
|
152
|
+
dataset.group(:category).select(:category, :AVG.sql_function(:price))
|
153
|
+
|
154
|
+
== SQL Functions / Literals
|
155
|
+
|
156
|
+
dataset.update(:updated_at => :NOW.sql_function)
|
157
|
+
dataset.update(:updated_at => 'NOW()'.lit)
|
158
|
+
|
159
|
+
dataset.update(:updated_at => "DateValue('1/1/2001')".lit)
|
160
|
+
dataset.update(:updated_at => :DateValue.sql_function('1/1/2001'))
|
161
|
+
|
162
|
+
== Schema Manipulation
|
163
|
+
|
164
|
+
DB.create_table :items do
|
165
|
+
primary_key :id
|
166
|
+
String :name, :unique => true, :null => false
|
167
|
+
boolean :active, :default => true
|
168
|
+
foreign_key :category_id, :categories
|
169
|
+
Time :created_at
|
170
|
+
|
171
|
+
index :grade
|
172
|
+
end
|
173
|
+
|
174
|
+
DB.drop_table :items
|
175
|
+
|
176
|
+
DB.create_table :test do
|
177
|
+
String :zipcode, :size => 10
|
178
|
+
enum :system, :elements => ['mac', 'linux', 'windows']
|
179
|
+
end
|
180
|
+
|
181
|
+
== Aliasing
|
182
|
+
|
183
|
+
DB[:items].select(:name.as(:item_name))
|
184
|
+
DB[:items].select(:name___item_name)
|
185
|
+
DB[:items___items_table].select(:items_table__name___item_name)
|
186
|
+
# => "SELECT items_table.name AS item_name FROM items AS items_table"
|
187
|
+
|
188
|
+
== Transactions
|
189
|
+
|
190
|
+
DB.transaction do
|
191
|
+
dataset << {:first_name => 'Inigo', :last_name => 'Montoya'}
|
192
|
+
dataset << {:first_name => 'Farm', :last_name => 'Boy'}
|
193
|
+
end # Either both are inserted or neither are inserted
|
194
|
+
|
195
|
+
Database#transaction is re-entrant:
|
196
|
+
|
197
|
+
DB.transaction do # BEGIN issued only here
|
198
|
+
DB.transaction
|
199
|
+
dataset << {:first_name => 'Inigo', :last_name => 'Montoya'}
|
200
|
+
end
|
201
|
+
end # COMMIT issued only here
|
202
|
+
|
203
|
+
Transactions are aborted if an error is raised:
|
204
|
+
|
205
|
+
DB.transaction do
|
206
|
+
raise "some error occurred"
|
207
|
+
end # ROLLBACK issued and the error is re-raised
|
208
|
+
|
209
|
+
Transactions can also be aborted by raising Sequel::Error::Rollback:
|
210
|
+
|
211
|
+
DB.transaction do
|
212
|
+
raise(Sequel::Error::Rollback) if something_bad_happened
|
213
|
+
end # ROLLBACK issued and no error raised
|
214
|
+
|
215
|
+
Miscellaneous:
|
216
|
+
|
217
|
+
dataset.sql #=> "SELECT * FROM items"
|
218
|
+
dataset.delete_sql #=> "DELETE FROM items"
|
219
|
+
dataset.where(:name => 'sequel').exists #=> "EXISTS ( SELECT 1 FROM items WHERE name = 'sequel' )"
|
220
|
+
dataset.print #=> pretty table print to $stdout
|
221
|
+
dataset.columns #=> array of columns in the result set, does a SELECT
|
222
|
+
DB.schema(:items) => [[:id, {:type=>:integer, ...}], [:name, {:type=>:string, ...}], ...]
|
223
|
+
# Works on PostgreSQL, MySQL, SQLite, and JDBC
|
@@ -0,0 +1,158 @@
|
|
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
|
+
=== Specifying SQL functions
|
18
|
+
|
19
|
+
Sequel also allows you to specify functions by using the Symbol#sql_function method (and the Symbol#[] method on ruby 1.8):
|
20
|
+
|
21
|
+
items.literal(:avg.sql_function(:price)) #=> "avg(price)"
|
22
|
+
|
23
|
+
== Filtering using a hash
|
24
|
+
|
25
|
+
If you just need to compare records against values, you can supply a hash:
|
26
|
+
|
27
|
+
items.filter(:category => 'ruby').sql
|
28
|
+
#=> "SELECT * FROM items WHERE (category = 'ruby')"
|
29
|
+
|
30
|
+
Sequel can check for null values:
|
31
|
+
|
32
|
+
items.filter(:category => nil).sql
|
33
|
+
#=> "SELECT * FROM items WHERE (category IS NULL)"
|
34
|
+
|
35
|
+
Or compare two columns:
|
36
|
+
|
37
|
+
items.filter(:x => :some_table__y).sql
|
38
|
+
#=> "SELECT * FROM items WHERE (x = some_table.y)"
|
39
|
+
|
40
|
+
And also compare against multiple values:
|
41
|
+
|
42
|
+
items.filter(:category => ['ruby', 'perl']).sql
|
43
|
+
#=> "SELECT * FROM items WHERE (category IN ('ruby', 'perl'))"
|
44
|
+
|
45
|
+
Ranges (both inclusive and exclusive) can also be used:
|
46
|
+
|
47
|
+
items.filter(:price => 100..200).sql
|
48
|
+
#=> "SELECT * FROM items WHERE (price >= 100 AND price <= 200)"
|
49
|
+
|
50
|
+
items.filter(:price => 100...200).sql
|
51
|
+
#=> "SELECT * FROM items WHERE (price >= 100 AND price < 200)"
|
52
|
+
|
53
|
+
== Filtering using expressions
|
54
|
+
|
55
|
+
Sequel allows you to use ruby expressions directly in the call to filter, without using a block:
|
56
|
+
|
57
|
+
items.filter(:price * 2 < 50).sql
|
58
|
+
#=> "SELECT * FROM items WHERE ((price * 2) < 50)
|
59
|
+
|
60
|
+
This works for the standard inequality and arithmetic operators:
|
61
|
+
|
62
|
+
items.filter(:price + 100 < 200).sql
|
63
|
+
#=> "SELECT * FROM items WHERE ((price + 100) < 200)
|
64
|
+
|
65
|
+
items.filter(:price - 100 > 200).sql
|
66
|
+
#=> "SELECT * FROM items WHERE ((price - 100) > 200)
|
67
|
+
|
68
|
+
items.filter(:price * 100 <= 200).sql
|
69
|
+
#=> "SELECT * FROM items WHERE ((price * 100) <= 200)
|
70
|
+
|
71
|
+
items.filter(:price / 100 >= 200).sql
|
72
|
+
#=> "SELECT * FROM items WHERE ((price / 100) >= 200)
|
73
|
+
|
74
|
+
You use the overloaded bitwise and (&) and or (|) operators to combine expressions:
|
75
|
+
|
76
|
+
items.filter((:price + 100 < 200) & (:price * 100 <= 200)).sql
|
77
|
+
#=> "SELECT * FROM items WHERE (((price + 100) < 200) AND ((price * 100) <= 200))
|
78
|
+
|
79
|
+
items.filter((:price - 100 > 200) | (:price / 100 >= 200)).sql
|
80
|
+
#=> "SELECT * FROM items WHERE (((price - 100) > 200) OR ((price / 100) >= 200))
|
81
|
+
|
82
|
+
To filter by equality, you use the standard hash, which can be combined with other operators:
|
83
|
+
|
84
|
+
items.filter({:category => 'ruby'} & (:price + 100 < 200)).sql
|
85
|
+
#=> "SELECT * FROM items WHERE ((category = 'ruby') AND ((price + 100) < 200))"
|
86
|
+
|
87
|
+
This works with other hash values, such as arrays and ranges:
|
88
|
+
|
89
|
+
items.filter({:category => ['ruby', 'other']} | (:price - 100 > 200)).sql
|
90
|
+
#=> "SELECT * FROM items WHERE ((category IN ('ruby', 'other')) OR ((price - 100) <= 200))"
|
91
|
+
|
92
|
+
items.filter({:price => (100..200)} & :active).sql
|
93
|
+
#=> "SELECT * FROM items WHERE ((price >= 100 AND price <= 200) AND active)"
|
94
|
+
|
95
|
+
=== Negating conditions
|
96
|
+
|
97
|
+
You can use the negation operator (~) in most cases:
|
98
|
+
|
99
|
+
items.filter(~{:category => 'ruby'}).sql
|
100
|
+
#=> "SELECT * FROM items WHERE (category != 'ruby')"
|
101
|
+
|
102
|
+
items.filter {~:active}.sql
|
103
|
+
#=> "SELECT * FROM items WHERE NOT active"
|
104
|
+
|
105
|
+
items.filter(~(:price / 100 >= 200)).sql
|
106
|
+
#=> "SELECT * FROM items WHERE ((price / 100) < 200)
|
107
|
+
|
108
|
+
=== Comparing against column references
|
109
|
+
|
110
|
+
You can also compare against other columns:
|
111
|
+
|
112
|
+
items.filter{|o| o.credit > :debit}.sql
|
113
|
+
#=> "SELECT * FROM items WHERE (credit > debit)
|
114
|
+
|
115
|
+
Or against SQL functions:
|
116
|
+
|
117
|
+
items.filter{|o| :price - 100 < o.max(:price)}.sql
|
118
|
+
#=> "SELECT * FROM items WHERE ((price - 100) < max(price))"
|
119
|
+
|
120
|
+
== String search functions
|
121
|
+
|
122
|
+
You can search SQL strings using the #like method:
|
123
|
+
|
124
|
+
items.filter(:name.like('Acme%')).sql
|
125
|
+
#=> "SELECT * FROM items WHERE (name LIKE 'Acme%')"
|
126
|
+
|
127
|
+
You can specify a Regexp as a like argument, but this will probably only work
|
128
|
+
on PostgreSQL and MySQL:
|
129
|
+
|
130
|
+
items.filter(:name.like(/Acme.*/)).sql
|
131
|
+
#=> "SELECT * FROM items WHERE (name ~ 'Acme.*')"
|
132
|
+
|
133
|
+
Like can also take more than one argument:
|
134
|
+
|
135
|
+
items.filter(:name.like('Acme%', /Beta.*/)).sql
|
136
|
+
#=> "SELECT * FROM items WHERE ((name LIKE 'Acme%') OR (name ~ 'Beta.*'))"
|
137
|
+
|
138
|
+
== String concatenation
|
139
|
+
|
140
|
+
You can concatenate SQL strings using Array#sql_string_join:
|
141
|
+
|
142
|
+
items.filter([:name, :comment].sql_string_join.like('%acme%')).sql
|
143
|
+
#=> "SELECT * FROM items WHERE ((name || comment) LIKE 'Acme%')"
|
144
|
+
|
145
|
+
Array#sql_string_join also takes a join argument:
|
146
|
+
|
147
|
+
items.filter([:name, :comment].sql_string_join(' ').like('%acme%')).sql
|
148
|
+
#=> "SELECT * FROM items WHERE ((name || ' ' || comment) LIKE 'Acme%')"
|
149
|
+
|
150
|
+
== Filtering using sub-queries
|
151
|
+
|
152
|
+
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:
|
153
|
+
|
154
|
+
refs = consumer_refs.filter(:logged_in => true).select(:consumer_id)
|
155
|
+
consumers.filter(:id => refs).sql
|
156
|
+
#=> "SELECT * FROM consumers WHERE (id IN (SELECT consumer_id FROM consumer_refs WHERE (logged_in = 't')))"
|
157
|
+
|
158
|
+
Note that if you compare against a sub-query, you must select a single column in the sub-query.
|
@@ -0,0 +1,104 @@
|
|
1
|
+
= Prepared Statements and Bound Variables
|
2
|
+
|
3
|
+
Starting with version 2.4.0, Sequel has support for prepared statements and
|
4
|
+
bound variables. No matter which database you are using, the Sequel prepared
|
5
|
+
statement/bound variable API remains exactly the same. There is native support
|
6
|
+
for prepared statements/bound variables on the following databases:
|
7
|
+
|
8
|
+
* PostgreSQL (using the pg driver, requires type specifiers)
|
9
|
+
* MySQL (prepared statements only, as the ruby mysql driver doesn't support
|
10
|
+
bound variables)
|
11
|
+
* SQLite (a new native prepared statement is used for each call, though)
|
12
|
+
* JDBC (using the postgresql, mysql, or sqlite databases, and possibly others)
|
13
|
+
|
14
|
+
Support on other databases is emulated via the usual string interpolation.
|
15
|
+
|
16
|
+
== Placeholders
|
17
|
+
|
18
|
+
Generally, when using prepared statements (and certainly when using bound
|
19
|
+
variables), you need to put placeholders in your SQL to indicate where you
|
20
|
+
want your bound arguments to appear. Database support and syntax vary
|
21
|
+
significantly for placeholders (e.g. :name, $1, ?). Sequel abstracts all of
|
22
|
+
that and allows you to specify placeholders by using the :$name format for
|
23
|
+
placeholders, e.g.:
|
24
|
+
|
25
|
+
ds = DB[:items].filter(:name=>:$name)
|
26
|
+
|
27
|
+
== Bound Variables
|
28
|
+
|
29
|
+
Using bound variables for this query is simple:
|
30
|
+
|
31
|
+
ds.call(:select, :name=>'Jim')
|
32
|
+
|
33
|
+
This will do the equivalent of selecting records that have the name 'Jim'. It
|
34
|
+
returns all records, and can take a block that is passed to Dataset#all.
|
35
|
+
|
36
|
+
Deleting or returning the first record works similarly:
|
37
|
+
|
38
|
+
ds.call(:first, :name=>'Jim') # First record with name 'Jim'
|
39
|
+
ds.call(:delete, :name=>'Jim') # Delete records with name 'Jim'
|
40
|
+
|
41
|
+
For inserting/updating records, you should also specify a value hash, which
|
42
|
+
may itself contain placeholders:
|
43
|
+
|
44
|
+
# Insert record with 'Jim', note that the previous filter is ignored
|
45
|
+
ds.call(:insert, {:name=>'Jim'}, :name=>:$name)
|
46
|
+
# Change name to 'Bob' for all records with name of 'Jim'
|
47
|
+
ds.call(:update, {:name=>'Jim', :new_name=>'Bob'}, :name=>$:new_name)
|
48
|
+
|
49
|
+
== Prepared Statements
|
50
|
+
|
51
|
+
Prepared statement support is similar to bound variable support, but you
|
52
|
+
use Dataset#prepare with a name, and Dataset#call later with the values:
|
53
|
+
|
54
|
+
ds = DB[:items].filter(:name=>:$name)
|
55
|
+
ps = ds.prepare(:select, :select_by_name)
|
56
|
+
ps.call(:name=>'Jim')
|
57
|
+
DB.call(:select_by_name, :name=>'Jim') # same as above
|
58
|
+
|
59
|
+
The Dataset#prepare method returns a prepared statement, and also stores a
|
60
|
+
copy of the prepared statement in the database for later use. For insert
|
61
|
+
and update queries, the hash to insert/update is passed to prepare:
|
62
|
+
|
63
|
+
ps1 = DB[:items].prepare(:insert, :insert_with_name, :name=>:$name)
|
64
|
+
ps1.call(:name=>'Jim')
|
65
|
+
DB.call(:insert_with_name, :name=>'Jim') # same as above
|
66
|
+
ds = DB[:items].filter(:name=>:$name)
|
67
|
+
ps2 = ds.prepare(:update, :update_name, :name=>:$new_name)
|
68
|
+
ps2.call(:name=>'Jim', :new_name=>'Bob')
|
69
|
+
DB.call(:update_name, :name=>'Jim', :new_name=>'Bob') # same as above
|
70
|
+
|
71
|
+
== Database support
|
72
|
+
|
73
|
+
=== PostgreSQL
|
74
|
+
|
75
|
+
If you are using the ruby-postgres or postgres-pr driver, PostgreSQL uses the
|
76
|
+
default emulated support. If you are using ruby-pg, there is native support,
|
77
|
+
but it requires type specifiers most of the time. This is easy if you have
|
78
|
+
direct control over the SQL string, but since Sequel abstracts that, the types
|
79
|
+
have to be specified another way. This is done by adding a __* suffix to the
|
80
|
+
placeholder symbol (e.g. :$name__text, which will be compiled to "$1::text"
|
81
|
+
in the SQL). Prepared statements are always server side.
|
82
|
+
|
83
|
+
=== SQLite
|
84
|
+
|
85
|
+
SQLite supports bound variables and prepared statements exactly the same, since
|
86
|
+
a new native prepared statement is created and executed for each call.
|
87
|
+
|
88
|
+
=== MySQL
|
89
|
+
|
90
|
+
The MySQL ruby driver does not support bound variables, so the the bound
|
91
|
+
variable methods fall back to string interpolation. It uses server side
|
92
|
+
prepared statements.
|
93
|
+
|
94
|
+
=== JDBC
|
95
|
+
|
96
|
+
JDBC supports both prepared statements and bound variables. Whether these
|
97
|
+
are server side or client side depends on the JDBC driver. For PostgreSQL
|
98
|
+
over JDBC, you can add the prepareThreshold=N parameter to the connection
|
99
|
+
string, which will use a server side prepared statement after N calls to
|
100
|
+
the prepared statement.
|
101
|
+
|
102
|
+
=== All Others
|
103
|
+
|
104
|
+
Support is emulated using interpolation.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
=== New code organization
|
2
|
+
|
3
|
+
Sequel is now divided into two parts: sequel_core and sequel_model.
|
4
|
+
These two parts are distributed as two separate gems. The sequel gem
|
5
|
+
bundles sequel_core and sequel_model together. If you don't use
|
6
|
+
Sequel::Model in your code, you can just install and use sequel_core.
|
7
|
+
|
8
|
+
=== New model hooks implementation
|
9
|
+
|
10
|
+
The hooks implementation have been rewritten from scratch, is much
|
11
|
+
more robust and offers a few new features:
|
12
|
+
|
13
|
+
* More ways to define hooks: hooks can now be defined by supplying a
|
14
|
+
block or a method name, or by overriding the hook instance method.
|
15
|
+
|
16
|
+
* Inheritable hooks: Hooks can now be inherited, which means that you
|
17
|
+
can define general hooks in a model superclass, and use them in
|
18
|
+
subclasses. You can also define global hooks on Sequel::Model that
|
19
|
+
will be invoked for all model classes.
|
20
|
+
|
21
|
+
* Hook chains can be broken by returning false from within the hook.
|
22
|
+
|
23
|
+
* New after_initialize hook, invoked after instance initialization.
|
24
|
+
|
25
|
+
* The hook invocation order can no longer be changed. Hooks are
|
26
|
+
invoked in order of definition, from the top of the class hierarchy
|
27
|
+
(that is, from Sequel::Model) down to the specific class.
|
28
|
+
|
29
|
+
=== Miscellanea
|
30
|
+
|
31
|
+
* Removed deprecated adapter stubs, and all other deprecations in both
|
32
|
+
sequel_core and sequel_model.
|
33
|
+
|
34
|
+
* Fixed String#to_time to raise error correctly for invalid time
|
35
|
+
stamps.
|
36
|
+
|
37
|
+
* Fixed error behavior when parse_tree or ruby2ruby are not available.
|
38
|
+
|
@@ -0,0 +1,143 @@
|
|
1
|
+
=== DRY Sequel models
|
2
|
+
|
3
|
+
With the new Sequel release you no longer need to explicitly specify
|
4
|
+
the table
|
5
|
+
name for each model class, assuming your model name is the singular of
|
6
|
+
the
|
7
|
+
table name (just like in ActiveRecord or DataMapper):
|
8
|
+
|
9
|
+
class UglyBug < Sequel::Model
|
10
|
+
end
|
11
|
+
|
12
|
+
UglyBug.table_name #=> :ugly_bugs
|
13
|
+
|
14
|
+
=== New model validations and support for virtual attributes
|
15
|
+
|
16
|
+
Sequel model now include validation functionality which largly follows
|
17
|
+
the
|
18
|
+
validations offered in ActiveRecord. Validations can be checked
|
19
|
+
anytime by
|
20
|
+
calling Model#valid?, with validation errors accessible through
|
21
|
+
Model#errors:
|
22
|
+
|
23
|
+
class Item < Sequel::Model
|
24
|
+
validates_presence_of :name
|
25
|
+
end
|
26
|
+
|
27
|
+
my_item = Item.new
|
28
|
+
my_item.valid? #=> false
|
29
|
+
my_item.errors.full_messages #=> ["name is not present"]
|
30
|
+
|
31
|
+
The Model#save method has been changed to check for validity before
|
32
|
+
saving. If
|
33
|
+
the model instance is not valid, the #save method returns false
|
34
|
+
without saving
|
35
|
+
the instance. You can also bypass the validity test by calling
|
36
|
+
Model#save!
|
37
|
+
instead.
|
38
|
+
|
39
|
+
Model classes also now support virtual attributes, letting you assign
|
40
|
+
values to
|
41
|
+
any attribute (virtual or persistent) at initialization time:
|
42
|
+
|
43
|
+
class User < Sequel::Model
|
44
|
+
attr_accessor :password
|
45
|
+
end
|
46
|
+
|
47
|
+
u = User.new(:password => 'blah', ...)
|
48
|
+
u.password #=> 'blah'
|
49
|
+
|
50
|
+
Also, virtual attributes can be validated just like persistent
|
51
|
+
attributes.
|
52
|
+
|
53
|
+
=== Other changes (long list!)
|
54
|
+
|
55
|
+
* Added Model#reload as alias to Model#refresh.
|
56
|
+
|
57
|
+
* Changed Model.create to accept a block (#126).
|
58
|
+
|
59
|
+
* Fixed Model#initialize to accept nil values (#115).
|
60
|
+
|
61
|
+
* Added Model#update_with_params method with support for virtual
|
62
|
+
attributes and auto-filtering of unrelated parameters, and changed
|
63
|
+
Model.create_with_params to support virtual attributes (#128).
|
64
|
+
|
65
|
+
* Fixed Model.dataset to correctly set the dataset if using implicit
|
66
|
+
naming or inheriting the superclass dataset (thanks celldee).
|
67
|
+
|
68
|
+
* Finalized support for virtual attributes.
|
69
|
+
|
70
|
+
* Fixed Model#set to work with string keys (#143).
|
71
|
+
|
72
|
+
* Fixed Model.create to correctly initialize instances marked as new
|
73
|
+
(#135).
|
74
|
+
|
75
|
+
* Fixed Model#initialize to convert string keys into symbol keys. This
|
76
|
+
also fixes problem with validating objects initialized with string
|
77
|
+
keys (#136).
|
78
|
+
|
79
|
+
* Added Dataset#table_exists? convenience method.
|
80
|
+
|
81
|
+
* Changed Dataset#group_and_count to accept multiple columns (#134).
|
82
|
+
|
83
|
+
* Added Dataset#select_all method.
|
84
|
+
|
85
|
+
* Added Dataset#select_more, Dataset#order_more methods (#129).
|
86
|
+
|
87
|
+
* Fixed Dataset#count to work correctly for grouped datasets (#144).
|
88
|
+
|
89
|
+
* Fixed joining datasets using aliased tables (#140).
|
90
|
+
|
91
|
+
* Added support for UNSIGNED constraint, used in MySQL? (#127).
|
92
|
+
|
93
|
+
* Implemented constraint definitions inside Database#create_table.
|
94
|
+
|
95
|
+
* Enhanced Database.connect to accept options with string keys, so it
|
96
|
+
can now accept options loaded from YAML files. Database.connect also
|
97
|
+
automatically converts :username option into :user for compatibility
|
98
|
+
with existing YAML configuration files for AR and DataMapper.
|
99
|
+
|
100
|
+
* Changed ODBC::Database to support connection using driver and
|
101
|
+
database name, also added support for untitled columns in
|
102
|
+
ODBC::Dataset (thanks Leonid Borisenko).
|
103
|
+
|
104
|
+
* Changed MySQL adapter to support specifying socket option.
|
105
|
+
|
106
|
+
* Fixed MySQL adapter to correctly format foreign key definitions
|
107
|
+
(#123).
|
108
|
+
|
109
|
+
* Changed MySQL::Dataset to allow HAVING clause on ungrouped datasets,
|
110
|
+
and put HAVING clause before ORDER BY clause (#133).
|
111
|
+
|
112
|
+
* Changed mysql adapter to default to localhost if :host option is not
|
113
|
+
specified (#114).
|
114
|
+
|
115
|
+
* Added String#to_date. Updated mysql adapter to use String#to_date
|
116
|
+
for mysql date types (thanks drfreeze).
|
117
|
+
|
118
|
+
* Fixed postgres adapter to define PGconn#async_exec as alias to #exec
|
119
|
+
if not defined (for pure-ruby postgres driver).
|
120
|
+
|
121
|
+
* Changed postgres adapter to quote column references using double
|
122
|
+
quotes.
|
123
|
+
|
124
|
+
* Applied patch for oracle adapter: fix behavior of limit and offset,
|
125
|
+
transactions, #table_exists?, #tables and additional specs (thanks
|
126
|
+
Liming Lian #122).
|
127
|
+
|
128
|
+
* Added support additional field types in postgresql adapter (#146).
|
129
|
+
|
130
|
+
* Added support for date field types in postgresql adapter (#145).
|
131
|
+
|
132
|
+
* Added support for limiting and paginating datasets with fixed SQL,
|
133
|
+
e.g. using Database#fetch.
|
134
|
+
|
135
|
+
* Added new Dataset#from_self method that returns a dataset selecting
|
136
|
+
from the original dataset.
|
137
|
+
|
138
|
+
* Allow for additional filters on a grouped dataset (#119 and #120)
|
139
|
+
|
140
|
+
* Refactored Sequelizer to use Proc#to_sexp (method provided by r2r).
|
141
|
+
|
142
|
+
* Fixed bin/sequel to require sequel_model if available.
|
143
|
+
|