sequel 2.9.0 → 2.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/{README → README.rdoc} +85 -57
- data/Rakefile +10 -5
- data/bin/sequel +7 -16
- data/doc/advanced_associations.rdoc +5 -17
- data/doc/cheat_sheet.rdoc +18 -20
- data/doc/dataset_filtering.rdoc +8 -32
- data/doc/schema.rdoc +20 -0
- data/lib/sequel_core.rb +35 -1
- data/lib/sequel_core/adapters/ado.rb +1 -1
- data/lib/sequel_core/adapters/db2.rb +2 -2
- data/lib/sequel_core/adapters/dbi.rb +2 -11
- data/lib/sequel_core/adapters/do.rb +205 -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/firebird.rb +298 -0
- data/lib/sequel_core/adapters/informix.rb +10 -1
- data/lib/sequel_core/adapters/jdbc.rb +78 -19
- data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +10 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +7 -3
- data/lib/sequel_core/adapters/mysql.rb +53 -77
- data/lib/sequel_core/adapters/odbc.rb +1 -1
- data/lib/sequel_core/adapters/openbase.rb +1 -1
- data/lib/sequel_core/adapters/oracle.rb +2 -2
- data/lib/sequel_core/adapters/postgres.rb +16 -14
- data/lib/sequel_core/adapters/shared/mysql.rb +44 -9
- data/lib/sequel_core/adapters/shared/oracle.rb +4 -5
- data/lib/sequel_core/adapters/shared/postgres.rb +86 -82
- data/lib/sequel_core/adapters/shared/sqlite.rb +39 -24
- data/lib/sequel_core/adapters/sqlite.rb +11 -1
- data/lib/sequel_core/connection_pool.rb +10 -2
- data/lib/sequel_core/core_sql.rb +13 -3
- data/lib/sequel_core/database.rb +131 -30
- data/lib/sequel_core/database/schema.rb +5 -5
- data/lib/sequel_core/dataset.rb +31 -6
- data/lib/sequel_core/dataset/convenience.rb +11 -11
- data/lib/sequel_core/dataset/query.rb +2 -2
- data/lib/sequel_core/dataset/sql.rb +6 -6
- data/lib/sequel_core/exceptions.rb +4 -0
- data/lib/sequel_core/migration.rb +4 -4
- data/lib/sequel_core/schema/generator.rb +19 -3
- data/lib/sequel_core/schema/sql.rb +24 -20
- data/lib/sequel_core/sql.rb +13 -16
- data/lib/sequel_core/version.rb +11 -0
- data/lib/sequel_model.rb +2 -0
- data/lib/sequel_model/base.rb +2 -2
- data/lib/sequel_model/hooks.rb +46 -7
- data/lib/sequel_model/record.rb +11 -9
- data/lib/sequel_model/schema.rb +1 -1
- data/lib/sequel_model/validations.rb +72 -61
- data/spec/adapters/firebird_spec.rb +371 -0
- data/spec/adapters/mysql_spec.rb +118 -62
- data/spec/adapters/oracle_spec.rb +5 -5
- data/spec/adapters/postgres_spec.rb +33 -18
- data/spec/adapters/sqlite_spec.rb +2 -2
- data/spec/integration/dataset_test.rb +3 -3
- data/spec/integration/schema_test.rb +55 -5
- data/spec/integration/spec_helper.rb +11 -0
- data/spec/integration/type_test.rb +59 -16
- data/spec/sequel_core/connection_pool_spec.rb +14 -0
- data/spec/sequel_core/core_sql_spec.rb +24 -14
- data/spec/sequel_core/database_spec.rb +96 -11
- data/spec/sequel_core/dataset_spec.rb +97 -37
- data/spec/sequel_core/expression_filters_spec.rb +51 -40
- data/spec/sequel_core/object_graph_spec.rb +2 -2
- data/spec/sequel_core/schema_generator_spec.rb +31 -6
- data/spec/sequel_core/schema_spec.rb +25 -9
- data/spec/sequel_core/spec_helper.rb +4 -1
- data/spec/sequel_core/version_spec.rb +7 -0
- data/spec/sequel_model/associations_spec.rb +1 -1
- data/spec/sequel_model/hooks_spec.rb +68 -13
- data/spec/sequel_model/model_spec.rb +4 -4
- data/spec/sequel_model/record_spec.rb +22 -0
- data/spec/sequel_model/spec_helper.rb +2 -1
- data/spec/sequel_model/validations_spec.rb +107 -7
- metadata +15 -5
@@ -16,7 +16,7 @@ a different block when eager loading via Dataset#eager. Association blocks are
|
|
16
16
|
useful for things like:
|
17
17
|
|
18
18
|
Artist.one_to_many :gold_albums, :class=>:Album do |ds|
|
19
|
-
ds.filter
|
19
|
+
ds.filter{|o| o.copies_sold > 500000}
|
20
20
|
end
|
21
21
|
|
22
22
|
There are a whole bunch of options for changing how the association is eagerly
|
@@ -124,7 +124,7 @@ a swiss army chainsaw.
|
|
124
124
|
Sequel supports the same callbacks that ActiveRecord does: :before_add,
|
125
125
|
:before_remove, :after_add, and :after_remove. It also supports a
|
126
126
|
callback that ActiveRecord does not, :after_load, which is called
|
127
|
-
after the association has been loaded
|
127
|
+
after the association has been loaded.
|
128
128
|
|
129
129
|
Each of these options can be a Symbol specifying an instance method
|
130
130
|
that takes one argument (the associated object), or a Proc that takes
|
@@ -150,7 +150,7 @@ otherwise modified:
|
|
150
150
|
class Author < Sequel::Model
|
151
151
|
one_to_many :authorships
|
152
152
|
end
|
153
|
-
Author.first.authorships_dataset.filter
|
153
|
+
Author.first.authorships_dataset.filter{|o| o.number < 10}.first
|
154
154
|
|
155
155
|
You can extend a dataset with a module easily with :extend:
|
156
156
|
|
@@ -180,18 +180,6 @@ model object, you'll have to use a closure:
|
|
180
180
|
end
|
181
181
|
Author.first.authorships_dataset.find_or_create_by_name('Bob')
|
182
182
|
|
183
|
-
You can cheat if you want to:
|
184
|
-
|
185
|
-
module FindOrCreate
|
186
|
-
def find_or_create(vals)
|
187
|
-
# Exploits the fact that Sequel filters are ruby objects that
|
188
|
-
# can be introspected.
|
189
|
-
author_id = @opts[:where].args[1]
|
190
|
-
first(vals) || \
|
191
|
-
@opts[:models][nil].create(vals.merge(:author_id=>author_id))
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
183
|
===has_many :through associations
|
196
184
|
|
197
185
|
many_to_many handles the usual case of a has_many :through with a belongs_to in
|
@@ -310,7 +298,7 @@ Sequel::Model:
|
|
310
298
|
Firm.find(:first).invoices
|
311
299
|
|
312
300
|
It is significantly more code in Sequel Model, but quite a bit of it is setting
|
313
|
-
the intermediate
|
301
|
+
the intermediate associated record (the client) and the reciprocal association
|
314
302
|
in the associations cache for each object, which ActiveRecord won't do for you.
|
315
303
|
The reason you would want to do this is that then firm.invoices.first.firm or
|
316
304
|
firm.invoices.first.client doesn't do another query to get the firm/client.
|
@@ -551,7 +539,7 @@ node.children. You can even eager load the relationship up to a certain depth:
|
|
551
539
|
# Eager load three generations of generations of children for a given node
|
552
540
|
Node.filter(:id=>1).eager(:children=>{:children=>:children}).all.first
|
553
541
|
# Load parents and grandparents for a group of nodes
|
554
|
-
Node.filter
|
542
|
+
Node.filter{|o| o.id < 10}.eager(:parent=>:parent).all
|
555
543
|
|
556
544
|
What if you want to get all ancestors up to the root node, or all descendents,
|
557
545
|
without knowing the depth of the tree?
|
data/doc/cheat_sheet.rdoc
CHANGED
@@ -29,7 +29,7 @@ Without a filename argument, the sqlite adapter will setup a new sqlite database
|
|
29
29
|
DB.fetch("SELECT name FROM users") do |row|
|
30
30
|
p r[:name]
|
31
31
|
end
|
32
|
-
dataset = DB["SELECT age FROM users"]
|
32
|
+
dataset = DB["SELECT age FROM users WHERE name = ?", name]
|
33
33
|
dataset.print
|
34
34
|
dataset.map(:age)
|
35
35
|
|
@@ -70,17 +70,17 @@ Without a filename argument, the sqlite adapter will setup a new sqlite database
|
|
70
70
|
dataset.map {|r| r[:name]}
|
71
71
|
dataset.map(:name) # same effect as above
|
72
72
|
|
73
|
-
dataset.inject
|
73
|
+
dataset.inject(0){|sum, r| sum + r[:value]}
|
74
74
|
|
75
75
|
== Filtering (see also doc/dataset_filtering.rdoc)
|
76
76
|
|
77
77
|
dataset.filter(:name => 'abc')
|
78
78
|
dataset.filter('name = ?', 'abc')
|
79
|
-
dataset.filter
|
80
|
-
dataset.exclude
|
79
|
+
dataset.filter{|o| o.value > 100}
|
80
|
+
dataset.exclude{|o| o.value <= 100}
|
81
81
|
|
82
82
|
dataset.filter(:value => 50..100)
|
83
|
-
dataset.where(
|
83
|
+
dataset.where{|o| (o.value >= 50) & (o.value <= 100)}
|
84
84
|
|
85
85
|
dataset.where('value IN ?', [50,75,100])
|
86
86
|
|
@@ -91,11 +91,9 @@ Without a filename argument, the sqlite adapter will setup a new sqlite database
|
|
91
91
|
# Filter using a subquery
|
92
92
|
dataset.filter('price > ?', dataset.select('AVG(price) + 100'))
|
93
93
|
|
94
|
-
=== Advanced filtering using ruby expressions
|
94
|
+
=== Advanced filtering using ruby expressions
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
DB[:items].filter(:price < 100).sql
|
96
|
+
DB[:items].filter{|o| o.price < 100}.sql
|
99
97
|
#=> "SELECT * FROM items WHERE (price < 100)"
|
100
98
|
|
101
99
|
DB[:items].filter(:name.like('AL%')).sql
|
@@ -103,7 +101,7 @@ Available as of Sequel 2.0:
|
|
103
101
|
|
104
102
|
There's support for nested expressions with AND, OR and NOT:
|
105
103
|
|
106
|
-
DB[:items].filter(
|
104
|
+
DB[:items].filter{|o| (o.x > 5) & (o.y > 10)}.sql
|
107
105
|
#=> "SELECT * FROM items WHERE ((x > 5) AND (y > 10))"
|
108
106
|
|
109
107
|
DB[:items].filter({:x => 1, :y => 2}.sql_or & ~{:z => 3}).sql
|
@@ -114,8 +112,8 @@ You can use arithmetic operators and specify SQL functions:
|
|
114
112
|
DB[:items].filter((:x + :y) > :z).sql
|
115
113
|
#=> "SELECT * FROM items WHERE ((x + y) > z)"
|
116
114
|
|
117
|
-
DB[:items].filter
|
118
|
-
#=> "SELECT * FROM items WHERE (price <
|
115
|
+
DB[:items].filter{|o| :price - 100 < o.AVG(:price)}.sql
|
116
|
+
#=> "SELECT * FROM items WHERE ((price - 100) < AVG(price))"
|
119
117
|
|
120
118
|
== Ordering
|
121
119
|
|
@@ -151,23 +149,24 @@ You can use arithmetic operators and specify SQL functions:
|
|
151
149
|
dataset.avg(:price)
|
152
150
|
dataset.sum(:stock)
|
153
151
|
|
154
|
-
dataset.group(:category).select(:category, :AVG
|
152
|
+
dataset.group(:category).select(:category, :AVG.sql_function(:price))
|
155
153
|
|
156
154
|
== SQL Functions / Literals
|
157
155
|
|
158
|
-
dataset.update(:updated_at => :NOW
|
156
|
+
dataset.update(:updated_at => :NOW.sql_function)
|
159
157
|
dataset.update(:updated_at => 'NOW()'.lit)
|
160
158
|
|
161
159
|
dataset.update(:updated_at => "DateValue('1/1/2001')".lit)
|
162
|
-
dataset.update(:updated_at => :DateValue
|
160
|
+
dataset.update(:updated_at => :DateValue.sql_function('1/1/2001'))
|
163
161
|
|
164
162
|
== Schema Manipulation
|
165
163
|
|
166
164
|
DB.create_table :items do
|
167
165
|
primary_key :id
|
168
|
-
|
166
|
+
String :name, :unique => true, :null => false
|
169
167
|
boolean :active, :default => true
|
170
168
|
foreign_key :category_id, :categories
|
169
|
+
Time :created_at
|
171
170
|
|
172
171
|
index :grade
|
173
172
|
end
|
@@ -175,14 +174,13 @@ You can use arithmetic operators and specify SQL functions:
|
|
175
174
|
DB.drop_table :items
|
176
175
|
|
177
176
|
DB.create_table :test do
|
178
|
-
|
177
|
+
String :zipcode, :size => 10
|
179
178
|
enum :system, :elements => ['mac', 'linux', 'windows']
|
180
179
|
end
|
181
180
|
|
182
181
|
== Aliasing
|
183
182
|
|
184
183
|
DB[:items].select(:name.as(:item_name))
|
185
|
-
DB[:items].select(:name => :item_name)
|
186
184
|
DB[:items].select(:name___item_name)
|
187
185
|
DB[:items___items_table].select(:items_table__name___item_name)
|
188
186
|
# => "SELECT items_table.name AS item_name FROM items AS items_table"
|
@@ -221,5 +219,5 @@ Miscellaneous:
|
|
221
219
|
dataset.where(:name => 'sequel').exists #=> "EXISTS ( SELECT 1 FROM items WHERE name = 'sequel' )"
|
222
220
|
dataset.print #=> pretty table print to $stdout
|
223
221
|
dataset.columns #=> array of columns in the result set, does a SELECT
|
224
|
-
DB.
|
225
|
-
|
222
|
+
DB.schema(:items) => [[:id, {:type=>:integer, ...}], [:name, {:type=>:string, ...}], ...]
|
223
|
+
# Works on PostgreSQL, MySQL, SQLite, and JDBC
|
data/doc/dataset_filtering.rdoc
CHANGED
@@ -14,35 +14,11 @@ In order to prevent SQL injection, you can replace literal values with question
|
|
14
14
|
items.filter('category = ?', 'ruby').sql
|
15
15
|
#=> "SELECT * FROM items WHERE category = 'ruby'"
|
16
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
17
|
=== Specifying SQL functions
|
42
18
|
|
43
|
-
Sequel also allows you to specify functions by using the Symbol#[] method:
|
19
|
+
Sequel also allows you to specify functions by using the Symbol#sql_function method (and the Symbol#[] method on ruby 1.8):
|
44
20
|
|
45
|
-
items.literal(:avg
|
21
|
+
items.literal(:avg.sql_function(:price)) #=> "avg(price)"
|
46
22
|
|
47
23
|
== Filtering using a hash
|
48
24
|
|
@@ -76,10 +52,10 @@ Ranges (both inclusive and exclusive) can also be used:
|
|
76
52
|
|
77
53
|
== Filtering using expressions
|
78
54
|
|
79
|
-
|
55
|
+
Sequel allows you to use ruby expressions directly in the call to filter, without using a block:
|
80
56
|
|
81
|
-
items.filter(:price <
|
82
|
-
#=> "SELECT * FROM items WHERE (price <
|
57
|
+
items.filter(:price * 2 < 50).sql
|
58
|
+
#=> "SELECT * FROM items WHERE ((price * 2) < 50)
|
83
59
|
|
84
60
|
This works for the standard inequality and arithmetic operators:
|
85
61
|
|
@@ -133,13 +109,13 @@ You can use the negation operator (~) in most cases:
|
|
133
109
|
|
134
110
|
You can also compare against other columns:
|
135
111
|
|
136
|
-
items.filter
|
112
|
+
items.filter{|o| o.credit > :debit}.sql
|
137
113
|
#=> "SELECT * FROM items WHERE (credit > debit)
|
138
114
|
|
139
115
|
Or against SQL functions:
|
140
116
|
|
141
|
-
items.filter
|
142
|
-
#=> "SELECT * FROM items WHERE (price <
|
117
|
+
items.filter{|o| :price - 100 < o.max(:price)}.sql
|
118
|
+
#=> "SELECT * FROM items WHERE ((price - 100) < max(price))"
|
143
119
|
|
144
120
|
== String search functions
|
145
121
|
|
data/doc/schema.rdoc
CHANGED
@@ -7,3 +7,23 @@ The recommended way to set up schema modifications in Sequel is through migratio
|
|
7
7
|
The format of the individual migration files themselves is explained in the Sequel::Migration documentation. Each migration file contains a single migration class. The migration class acts a proxy for the related database (given on the command line if the sequel command line tool is used, or by the db argument to Sequel::Migration#apply if the API is used). The methods that can be used inside Sequel::Migration#up or Sequel::Migration#down are just Sequel::Database instance methods, such as create_table, drop_table, and alter_table. Most database methods that alter the schema take regular arguments, but create_table and alter_table take a block. The methods you can use inside the create_table block are documented in Sequel::Schema::Generator, and the methods you can use inside the alter_table block are documented in Sequel::Schema::AlterTableGenerator.
|
8
8
|
|
9
9
|
Migrations are not required, you can just call the schema modification methods directly on the database object. This is often done in test code and examples. However, it is recommended that you use the migration framework unless the database schema will not be changing in the future, as it provides a way to easily handle modifications to existing database schema.
|
10
|
+
|
11
|
+
Also, new in Sequel 2.10 is the ability to have database independent migrations using ruby classes as types. When you use a ruby class as a type, Sequel translates it to the most comparable type in the database you are using. Here's an example using all supported types:
|
12
|
+
|
13
|
+
DB.create_table(:cats) do
|
14
|
+
primary_key :id, :type=>Integer # integer
|
15
|
+
String :a # varchar(255)
|
16
|
+
column :b, File # blob
|
17
|
+
Fixnum :c # integer
|
18
|
+
foreign_key :d, :other_table, :type=>Bignum # bigint
|
19
|
+
Float :e # double precision
|
20
|
+
BigDecimal :f # numeric
|
21
|
+
Date :g # date
|
22
|
+
DateTime :h # timestamp
|
23
|
+
Time :i # timestamp
|
24
|
+
Numeric :j # numeric
|
25
|
+
TrueClass :k # boolean
|
26
|
+
FalseClass :l # boolean
|
27
|
+
end
|
28
|
+
|
29
|
+
Basically, if you use one of the ruby classes above, it will translate into a database specific type. If you use a lowercase method, symbol, or string to specify the type, Sequel won't attempt to translate it.
|
data/lib/sequel_core.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
require f
|
3
3
|
end
|
4
4
|
%w"core_ext sql core_sql connection_pool exceptions pretty_table
|
5
|
-
dataset migration schema database object_graph".each do |f|
|
5
|
+
dataset migration schema database object_graph version".each do |f|
|
6
6
|
require "sequel_core/#{f}"
|
7
7
|
end
|
8
8
|
|
@@ -69,6 +69,37 @@ module Sequel
|
|
69
69
|
end
|
70
70
|
metaalias :open, :connect
|
71
71
|
|
72
|
+
# Set the method to call on identifiers going into the database. This affects
|
73
|
+
# the literalization of identifiers by calling this method on them before they are input.
|
74
|
+
# Sequel upcases identifiers in all SQL strings for most databases, so to turn that off:
|
75
|
+
#
|
76
|
+
# Sequel.identifier_input_method = nil
|
77
|
+
#
|
78
|
+
# to downcase instead:
|
79
|
+
#
|
80
|
+
# Sequel.identifier_input_method = :downcase
|
81
|
+
#
|
82
|
+
# Other string methods work as well.
|
83
|
+
def self.identifier_input_method=(value)
|
84
|
+
Database.identifier_input_method = value
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set the method to call on identifiers coming out of the database. This affects
|
88
|
+
# the literalization of identifiers by calling this method on them when they are
|
89
|
+
# retrieved from the database. Sequel downcases identifiers retrieved for most
|
90
|
+
# databases, so to turn that off:
|
91
|
+
#
|
92
|
+
# Sequel.identifier_output_method = nil
|
93
|
+
#
|
94
|
+
# to upcase instead:
|
95
|
+
#
|
96
|
+
# Sequel.identifier_output_method = :upcase
|
97
|
+
#
|
98
|
+
# Other string methods work as well.
|
99
|
+
def self.identifier_output_method=(value)
|
100
|
+
Database.identifier_output_method = value
|
101
|
+
end
|
102
|
+
|
72
103
|
# Set whether to quote identifiers for all databases by default. By default,
|
73
104
|
# Sequel quotes identifiers in all SQL strings, so to turn that off:
|
74
105
|
#
|
@@ -92,6 +123,9 @@ module Sequel
|
|
92
123
|
# lower case (MySQL, PostgreSQL, and SQLite).
|
93
124
|
#
|
94
125
|
# Sequel.upcase_identifiers = false
|
126
|
+
#
|
127
|
+
# This will set the indentifier_input_method to :upcase if value is true
|
128
|
+
# or nil if value is false.
|
95
129
|
def self.upcase_identifiers=(value)
|
96
130
|
Database.upcase_identifiers = value
|
97
131
|
end
|
@@ -89,7 +89,7 @@ module Sequel
|
|
89
89
|
def fetch_rows(sql)
|
90
90
|
execute(sql) do |sth|
|
91
91
|
@column_info = get_column_info(sth)
|
92
|
-
@columns = @column_info.map {|c| c[:name]}
|
92
|
+
@columns = @column_info.map {|c| output_identifier(c[:name])}
|
93
93
|
while (rc = SQLFetch(@handle)) != SQL_NO_DATA_FOUND
|
94
94
|
@db.check_error(rc, "Could not fetch row")
|
95
95
|
yield hash_row(sth)
|
@@ -118,7 +118,7 @@ module Sequel
|
|
118
118
|
rc, v = SQLGetData(sth, i+1, c[:db2_type], c[:precision])
|
119
119
|
@db.check_error(rc, "Could not get data")
|
120
120
|
|
121
|
-
|
121
|
+
row[output_identifier(c[:name])] = convert_type(v)
|
122
122
|
end
|
123
123
|
row
|
124
124
|
end
|
@@ -3,8 +3,6 @@ require 'dbi'
|
|
3
3
|
module Sequel
|
4
4
|
module DBI
|
5
5
|
class Database < Sequel::Database
|
6
|
-
attr_writer :lowercase
|
7
|
-
|
8
6
|
set_adapter_scheme :dbi
|
9
7
|
|
10
8
|
DBI_ADAPTERS = {
|
@@ -69,11 +67,6 @@ module Sequel
|
|
69
67
|
synchronize(opts[:server]){|conn| conn.do(sql)}
|
70
68
|
end
|
71
69
|
alias_method :execute_dui, :do
|
72
|
-
|
73
|
-
# Converts all column names to lowercase
|
74
|
-
def lowercase
|
75
|
-
@lowercase ||= false
|
76
|
-
end
|
77
70
|
|
78
71
|
private
|
79
72
|
|
@@ -97,10 +90,8 @@ module Sequel
|
|
97
90
|
def fetch_rows(sql, &block)
|
98
91
|
execute(sql) do |s|
|
99
92
|
begin
|
100
|
-
@columns = s.column_names.map
|
101
|
-
|
102
|
-
end
|
103
|
-
s.fetch {|r| yield hash_row(s, r)}
|
93
|
+
@columns = s.column_names.map{|c| output_identifier(c)}
|
94
|
+
s.fetch{|r| yield hash_row(s, r)}
|
104
95
|
ensure
|
105
96
|
s.finish rescue nil
|
106
97
|
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'data_objects'
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
# Module holding the DataObjects support for Sequel. DataObjects is a
|
5
|
+
# ruby library with a standard API for accessing databases.
|
6
|
+
#
|
7
|
+
# The DataObjects adapter currently supports PostgreSQL, MySQL, and
|
8
|
+
# SQLite:
|
9
|
+
#
|
10
|
+
# * Sequel.connect('do:sqlite3::memory:')
|
11
|
+
# * Sequel.connect('do:postgres://user:password@host/database')
|
12
|
+
# * Sequel.connect('do:mysql://user:password@host/database')
|
13
|
+
module DataObjects
|
14
|
+
# Contains procs keyed on sub adapter type that extend the
|
15
|
+
# given database object so it supports the correct database type.
|
16
|
+
DATABASE_SETUP = {:postgres=>proc do |db|
|
17
|
+
require 'do_postgres'
|
18
|
+
require 'sequel_core/adapters/do/postgres'
|
19
|
+
db.converted_exceptions << PostgresError
|
20
|
+
db.extend(Sequel::DataObjects::Postgres::DatabaseMethods)
|
21
|
+
end,
|
22
|
+
:mysql=>proc do |db|
|
23
|
+
require 'do_mysql'
|
24
|
+
require 'sequel_core/adapters/do/mysql'
|
25
|
+
db.converted_exceptions << MysqlError
|
26
|
+
db.extend(Sequel::DataObjects::MySQL::DatabaseMethods)
|
27
|
+
end,
|
28
|
+
:sqlite3=>proc do |db|
|
29
|
+
require 'do_sqlite3'
|
30
|
+
require 'sequel_core/adapters/do/sqlite'
|
31
|
+
db.converted_exceptions << Sqlite3Error
|
32
|
+
db.extend(Sequel::DataObjects::SQLite::DatabaseMethods)
|
33
|
+
end
|
34
|
+
}
|
35
|
+
|
36
|
+
# DataObjects uses it's own internal connection pooling in addition to the
|
37
|
+
# pooling that Sequel uses. You should make sure that you don't set
|
38
|
+
# the connection pool size to more than 8 for a
|
39
|
+
# Sequel::DataObjects::Database object, or hack DataObjects (or Extlib) to
|
40
|
+
# use a pool size at least as large as the pool size being used by Sequel.
|
41
|
+
class Database < Sequel::Database
|
42
|
+
set_adapter_scheme :do
|
43
|
+
|
44
|
+
# Convert the given exceptions to Sequel:Errors, necessary
|
45
|
+
# because DO raises errors specific to database types in
|
46
|
+
# certain cases.
|
47
|
+
attr_accessor :converted_exceptions
|
48
|
+
|
49
|
+
# Call the DATABASE_SETUP proc directly after initialization,
|
50
|
+
# so the object always uses sub adapter specific code. Also,
|
51
|
+
# raise an error immediately if the connection doesn't have a
|
52
|
+
# uri, since DataObjects requires one.
|
53
|
+
def initialize(opts)
|
54
|
+
@opts = opts
|
55
|
+
@converted_exceptions = []
|
56
|
+
raise(Error, "No connection string specified") unless uri
|
57
|
+
if prok = DATABASE_SETUP[subadapter.to_sym]
|
58
|
+
prok.call(self)
|
59
|
+
end
|
60
|
+
super(opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Setup a DataObjects::Connection to the database.
|
64
|
+
def connect(server)
|
65
|
+
setup_connection(::DataObjects::Connection.new(uri(server_opts(server))))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return a Sequel::DataObjects::Dataset object for this database.
|
69
|
+
def dataset(opts = nil)
|
70
|
+
DataObjects::Dataset.new(self, opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Execute the given SQL. If a block is given, the DataObjects::Reader
|
74
|
+
# created is yielded to it. A block should not be provided unless a
|
75
|
+
# a SELECT statement is being used (or something else that returns rows).
|
76
|
+
# Otherwise, the return value is the insert id if opts[:type] is :insert,
|
77
|
+
# or the number of affected rows, otherwise.
|
78
|
+
def execute(sql, opts={})
|
79
|
+
log_info(sql)
|
80
|
+
synchronize(opts[:server]) do |conn|
|
81
|
+
begin
|
82
|
+
command = conn.create_command(sql)
|
83
|
+
res = block_given? ? command.execute_reader : command.execute_non_query
|
84
|
+
rescue Exception => e
|
85
|
+
raise_error(e, :classes=>@converted_exceptions)
|
86
|
+
end
|
87
|
+
if block_given?
|
88
|
+
begin
|
89
|
+
yield(res)
|
90
|
+
ensure
|
91
|
+
res.close if res
|
92
|
+
end
|
93
|
+
elsif opts[:type] == :insert
|
94
|
+
res.insert_id
|
95
|
+
else
|
96
|
+
res.affected_rows
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Execute the SQL on the this database, returning the number of affected
|
102
|
+
# rows.
|
103
|
+
def execute_dui(sql, opts={})
|
104
|
+
execute(sql, opts)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Execute the SQL on this database, returning the primary key of the
|
108
|
+
# table being inserted to.
|
109
|
+
def execute_insert(sql, opts={})
|
110
|
+
execute(sql, opts.merge(:type=>:insert))
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return the subadapter type for this database, i.e. sqlite3 for
|
114
|
+
# do:sqlite3::memory:.
|
115
|
+
def subadapter
|
116
|
+
uri.split(":").first
|
117
|
+
end
|
118
|
+
|
119
|
+
# Use DataObject's transaction support for transactions. This
|
120
|
+
# only supports single level transactions, and it always prepares
|
121
|
+
# transactions and commits them immediately after. It's wasteful,
|
122
|
+
# but required by DataObject's API.
|
123
|
+
def transaction(server=nil)
|
124
|
+
th = Thread.current
|
125
|
+
synchronize(server) do |conn|
|
126
|
+
return yield(conn) if @transactions.include?(th)
|
127
|
+
t = ::DataObjects::Transaction.create_for_uri(uri)
|
128
|
+
t.instance_variable_get(:@connection).close
|
129
|
+
t.instance_variable_set(:@connection, conn)
|
130
|
+
begin
|
131
|
+
log_info("Transaction.begin")
|
132
|
+
t.begin
|
133
|
+
@transactions << th
|
134
|
+
yield(conn)
|
135
|
+
rescue Exception => e
|
136
|
+
log_info("Transaction.rollback")
|
137
|
+
t.rollback
|
138
|
+
transaction_error(e)
|
139
|
+
ensure
|
140
|
+
unless e
|
141
|
+
log_info("Transaction.commit")
|
142
|
+
t.prepare
|
143
|
+
t.commit
|
144
|
+
end
|
145
|
+
@transactions.delete(th)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the DataObjects URI for the Sequel URI, removing the do:
|
151
|
+
# prefix.
|
152
|
+
def uri(opts={})
|
153
|
+
opts = @opts.merge(opts)
|
154
|
+
(opts[:uri] || opts[:url]).sub(/\Ado:/, '')
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
# Close the given database connection.
|
160
|
+
def disconnect_connection(c)
|
161
|
+
c.close
|
162
|
+
end
|
163
|
+
|
164
|
+
# Allow extending the given connection when it is first created.
|
165
|
+
# By default, just returns the connection.
|
166
|
+
def setup_connection(conn)
|
167
|
+
conn
|
168
|
+
end
|
169
|
+
|
170
|
+
# The DataObjects adapter should convert exceptions by default.
|
171
|
+
def connection_pool_default_options
|
172
|
+
super.merge(:pool_convert_exceptions=>false)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Dataset class for Sequel::DataObjects::Database objects.
|
177
|
+
class Dataset < Sequel::Dataset
|
178
|
+
# Handle the usual time class overrides.
|
179
|
+
def literal(v)
|
180
|
+
case v
|
181
|
+
when Time
|
182
|
+
literal(v.iso8601)
|
183
|
+
when Date, DateTime
|
184
|
+
literal(v.to_s)
|
185
|
+
else
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Execute the SQL on the database and yield the rows as hashes
|
191
|
+
# with symbol keys.
|
192
|
+
def fetch_rows(sql)
|
193
|
+
execute(sql) do |reader|
|
194
|
+
cols = @columns = reader.fields.map{|f| output_identifier(f)}
|
195
|
+
while(reader.next!) do
|
196
|
+
h = {}
|
197
|
+
cols.zip(reader.values).each{|k, v| h[k] = v}
|
198
|
+
yield h
|
199
|
+
end
|
200
|
+
end
|
201
|
+
self
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|