OSDN Git Service

Regular updates
[twpd/master.git] / sequel.md
1 ---
2 title: Sequel
3 category: Ruby libraries
4 ---
5
6 ### Open a database
7
8     require 'rubygems'
9     require 'sequel'
10
11     DB = Sequel.sqlite('my_blog.db')
12     DB = Sequel.connect('postgres://user:password@localhost/my_db')
13     DB = Sequel.postgres('my_db', :user => 'user', :password => 'password', :host => 'localhost')
14     DB = Sequel.ado('mydb')
15
16 ### Open an SQLite memory database
17
18 Without a filename argument, the sqlite adapter will setup a new sqlite database in memory.
19
20     DB = Sequel.sqlite
21
22 ### Logging SQL statements
23
24     require 'logger'
25     DB = Sequel.sqlite '', :loggers => [Logger.new($stdout)]
26     # or
27     DB.loggers << Logger.new(...)
28
29 ### Using raw SQL
30
31     DB.run "CREATE TABLE users (name VARCHAR(255) NOT NULL, age INT(3) NOT NULL)"
32     dataset = DB["SELECT age FROM users WHERE name = ?", name]
33     dataset.map(:age)
34     DB.fetch("SELECT name FROM users") do |row|
35       p row[:name]
36     end
37
38 ### Create a dataset
39
40     dataset = DB[:items]
41     dataset = DB.from(:items)
42
43 ### Most dataset methods are chainable
44
45     dataset = DB[:managers].where(:salary => 5000..10000).order(:name, :department)
46
47 ### Insert rows
48
49     dataset.insert(:name => 'Sharon', :grade => 50)
50
51 ### Retrieve rows
52
53     dataset.each{|r| p r}
54     dataset.all # => [{...}, {...}, ...]
55     dataset.first # => {...}
56
57 ### Update/Delete rows
58
59     dataset.filter(~:active).delete
60     dataset.filter('price < ?', 100).update(:active => true)
61
62 ### Datasets are Enumerable
63
64     dataset.map{|r| r[:name]}
65     dataset.map(:name) # same as above
66
67     dataset.inject(0){|sum, r| sum + r[:value]}
68     dataset.sum(:value) # same as above
69
70 ### Filtering (see also doc/dataset_filtering.rdoc)
71
72 #### Equality
73
74     dataset.filter(:name => 'abc')
75     dataset.filter('name = ?', 'abc')
76
77 #### Inequality
78
79     dataset.filter{value > 100}
80     dataset.exclude{value <= 100}
81
82 #### Inclusion
83
84     dataset.filter(:value => 50..100)
85     dataset.where{(value >= 50) & (value <= 100)}
86
87     dataset.where('value IN ?', [50,75,100])
88     dataset.where(:value=>[50,75,100])
89
90     dataset.where(:id=>other_dataset.select(:other_id))
91
92 #### Subselects as scalar values
93
94     dataset.where('price > (SELECT avg(price) + 100 FROM table)')
95     dataset.filter{price > dataset.select(avg(price) + 100)}
96
97 #### LIKE/Regexp
98
99     DB[:items].filter(:name.like('AL%'))
100     DB[:items].filter(:name => /^AL/)
101
102 #### AND/OR/NOT
103
104     DB[:items].filter{(x > 5) & (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 #### Mathematical operators
111
112     DB[:items].filter((:x + :y) > :z).sql 
113     # SELECT * FROM items WHERE ((x + y) > z)
114
115     DB[:items].filter{price - 100 < 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 ### Limit/Offset
125
126     dataset.limit(30) # LIMIT 30
127     dataset.limit(30, 10) # LIMIT 30 OFFSET 10
128
129 ### Joins
130
131     DB[:items].left_outer_join(:categories, :id => :category_id).sql 
132     # SELECT * FROM items LEFT OUTER JOIN categories ON categories.id = items.category_id
133
134     DB[:items].join(:categories, :id => :category_id).join(:groups, :id => :items__group_id) 
135     # SELECT * FROM items INNER JOIN categories ON categories.id = items.category_id INNER JOIN groups ON groups.id = items.group_id
136         
137 ### Aggregate functions methods
138
139     dataset.count #=> record count
140     dataset.max(:price)
141     dataset.min(:price)
142     dataset.avg(:price)
143     dataset.sum(:stock)
144
145     dataset.group_and_count(:category)
146     dataset.group(:category).select(:category, :AVG.sql_function(:price))
147
148 ### SQL Functions / Literals
149
150     dataset.update(:updated_at => :NOW.sql_function)
151     dataset.update(:updated_at => 'NOW()'.lit)
152
153     dataset.update(:updated_at => "DateValue('1/1/2001')".lit)
154     dataset.update(:updated_at => :DateValue.sql_function('1/1/2001'))
155
156 ### Schema Manipulation
157
158     DB.create_table :items do
159       primary_key :id
160       String :name, :unique => true, :null => false
161       TrueClass :active, :default => true
162       foreign_key :category_id, :categories
163       DateTime :created_at
164       
165       index :created_at
166     end
167
168     DB.drop_table :items
169
170     DB.create_table :test do
171       String :zipcode
172       enum :system, :elements => ['mac', 'linux', 'windows']
173     end
174
175 ### Aliasing
176
177     DB[:items].select(:name.as(:item_name))
178     DB[:items].select(:name___item_name)
179     DB[:items___items_table].select(:items_table__name___item_name)
180     # SELECT items_table.name AS item_name FROM items AS items_table
181
182 ### Transactions
183
184     DB.transaction do
185       dataset.insert(:first_name => 'Inigo', :last_name => 'Montoya')
186       dataset.insert(:first_name => 'Farm', :last_name => 'Boy')
187     end # Either both are inserted or neither are inserted
188
189 Database#transaction is re-entrant:
190
191     DB.transaction do # BEGIN issued only here
192       DB.transaction
193         dataset << {:first_name => 'Inigo', :last_name => 'Montoya'}
194       end
195     end # COMMIT issued only here
196
197 Transactions are aborted if an error is raised:
198
199     DB.transaction do
200       raise "some error occurred"
201     end # ROLLBACK issued and the error is re-raised
202
203 Transactions can also be aborted by raising Sequel::Rollback:
204
205     DB.transaction do
206       raise(Sequel::Rollback) if something_bad_happened
207     end # ROLLBACK issued and no error raised
208
209 Savepoints can be used if the database supports it:
210
211     DB.transaction do
212       dataset << {:first_name => 'Farm', :last_name => 'Boy'} # Inserted
213       DB.transaction(:savepoint=>true) # This savepoint is rolled back
214         dataset << {:first_name => 'Inigo', :last_name => 'Montoya'} # Not inserted
215         raise(Sequel::Rollback) if something_bad_happened
216       end
217       dataset << {:first_name => 'Prince', :last_name => 'Humperdink'} # Inserted
218     end
219
220 ### Miscellaneous:
221
222     dataset.sql # "SELECT * FROM items"
223     dataset.delete_sql # "DELETE FROM items"
224     dataset.where(:name => 'sequel').exists # "EXISTS ( SELECT * FROM items WHERE name = 'sequel' )"
225     dataset.columns #=> array of columns in the result set, does a SELECT
226     DB.schema(:items) => [[:id, {:type=>:integer, ...}], [:name, {:type=>:string, ...}], ...]
227
228 ----------------------------------------------------------------------------------------------------------------------------------------------------------------
229
230 ### Documents
231
232     http://sequel.rubyforge.org/rdoc/files/doc/association_basics_rdoc.html
233     http://sequel.rubyforge.org/rdoc/classes/Sequel/Schema/Generator.html
234     http://sequel.rubyforge.org/rdoc/files/doc/validations_rdoc.html
235     http://sequel.rubyforge.org/rdoc/classes/Sequel/Model.html
236
237 ### Alter table
238
239     database.alter_table :deals do
240       add_column :name, String
241       drop_column :column_name
242       rename_column :from, :to
243
244       add_constraint :valid_name, :name.like('A%')
245       drop_constraint :constraint
246
247       add_full_text_index :body
248       add_spacial_index [columns]
249
250       add_index :price
251       drop_index :index
252
253       add_foreign_key :artist_id, :table
254       add_primary_key :id
255       add_unique_constraint [columns]
256       set_column_allow_null :foo, false
257       set_column_default :title, ''
258
259       set_column_type :price, 'char(10)'
260     end
261
262 ### Model associations
263
264     class Deal < Sequel::Model
265
266       # Us (left) <=> Them (right)
267       many_to_many  :images,
268         left_id:    :deal_id,
269         right_id:   :image_id,
270         join_table: :image_links
271
272       one_to_many   :files,
273         key:        :deal_id,
274         class:      :DataFile,
275
276       many_to_one   :parent, class: self
277       one_to_many   :children, key: :parent_id, class: self
278
279       one_to_many :gold_albums, class: :Album do |ds|
280         ds.filter { copies_sold > 50000 }
281       end
282
283 Provided by many_to_many
284
285     Deal[1].images
286     Deal[1].add_image
287     Deal[1].remove_image
288     Deal[1].remove_all_images
289
290 ### Validations
291
292       def validate
293         super
294         errors.add(:name, 'cannot be empty') if !name || name.empty?
295
296         validates_presence [:title, :site]
297         validates_unique :name
298         validates_format /\Ahttps?:\/\//, :website, :message=>'is not a valid URL'
299         validates_includes %w(a b c), :type
300         validates_integer :rating
301         validates_numeric :number
302         validates_type String, [:title, :description]
303
304         validates_integer :rating  if new?
305
306         # options: :message =>, :allow_nil =>, :allow_blank =>,
307         #          :allow_missing =>,
308
309         validates_exact_length 17, :isbn
310         validates_min_length 3, :name
311         validates_max_length 100, :name
312         validates_length_range 3..100, :name
313         
314         # Setter override
315         def filename=(name)
316           @values[:filename] = name
317         end
318       end
319     end
320
321     deal.errors
322
323 ### Model stuff
324
325     deal = Deal[1]
326     deal.changed_columns
327     deal.destroy  # Calls hooks
328     deal.delete   # No hooks
329     deal.exists?
330     deal.new?
331     deal.hash  # Only uniques
332     deal.keys  #=> [:id, :name]
333     deal.modified!
334     deal.modified?
335
336     deal.lock!
337
338 ### Callbacks
339
340     before_create
341     after_create
342
343     before_validation
344     after_validation
345     before_save
346     before_update
347     UPDATE QUERY
348     after_update
349     after_save
350
351     before_destroy
352     DELETE QUERY
353     after_destroy
354
355 ### Schema
356
357     class Deal < Sequel::Model
358       set_schema do
359         primary_key :id
360         primary_key [:id, :title]
361         String :name, primary_key: true
362         
363         String  :title
364         Numeric :price
365         DateTime :expires
366
367         unique :whatever
368         check(:price) { num > 0 }
369
370         foreign_key :artist_id
371         String :artist_name, key: :id
372
373         index :title
374         index [:artist_id, :name]
375         full_text_index :title
376
377         # String, Integer, Fixnum, Bignum, Float, Numeric, BigDecimal,
378         # Date, DateTime, Time, File, TrueClass, FalseClass
379       end
380     end
381
382 ### Unrestrict primary key
383
384     Category.create id: 'travel'   # error
385     Category.unrestrict_primary_key
386     Category.create id: 'travel'   # ok