sequel 0.0.19 → 0.0.20
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -0
- data/README +8 -0
- data/Rakefile +1 -1
- data/lib/sequel.rb +1 -0
- data/lib/sequel/connection_pool.rb +66 -26
- data/lib/sequel/database.rb +7 -14
- data/lib/sequel/dataset.rb +40 -20
- data/lib/sequel/expressions.rb +70 -0
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/postgres.rb +7 -2
- metadata +12 -11
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
*0.0.20*
|
2
|
+
|
3
|
+
* Refactored Dataset where clause construction to use expressions.
|
4
|
+
|
5
|
+
* Implemented Proc expressions (adapted from a great idea by Sam Smoot.)
|
6
|
+
|
7
|
+
* Fixed Model#map.
|
8
|
+
|
9
|
+
* Documentation for ConnectionPool.
|
10
|
+
|
11
|
+
* Specs for Database.
|
12
|
+
|
1
13
|
*0.0.19*
|
2
14
|
|
3
15
|
* More specs for Dataset.
|
data/README
CHANGED
@@ -161,6 +161,14 @@ Or lists of values:
|
|
161
161
|
|
162
162
|
my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux'])
|
163
163
|
|
164
|
+
Sequel now also accepts expressions as closures:
|
165
|
+
|
166
|
+
my_posts = posts.filter {category == ['ruby', 'postgres', 'linux']}
|
167
|
+
|
168
|
+
Which also lets you do stuff like:
|
169
|
+
|
170
|
+
my_posts = posts.filter {stamp > 1.month.ago}
|
171
|
+
|
164
172
|
Some adapters (like postgresql) will also let you specify Regexps:
|
165
173
|
|
166
174
|
my_posts = posts.filter(:category => /ruby/i)
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "sequel"
|
9
|
-
VERS = "0.0.
|
9
|
+
VERS = "0.0.20"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
11
|
RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
|
12
12
|
"--opname", "index.html",
|
data/lib/sequel.rb
CHANGED
@@ -1,11 +1,30 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
3
|
module Sequel
|
4
|
+
# A ConnectionPool manages access to database connections by keeping
|
5
|
+
# multiple connections and giving threads exclusive access to each
|
6
|
+
# connection.
|
4
7
|
class ConnectionPool
|
5
|
-
attr_reader :
|
8
|
+
attr_reader :mutex
|
9
|
+
|
10
|
+
# The maximum number of connections.
|
11
|
+
attr_reader :max_size
|
12
|
+
|
13
|
+
# The proc used to create a new connection.
|
6
14
|
attr_accessor :connection_proc
|
15
|
+
|
7
16
|
attr_reader :available_connections, :allocated, :created_count
|
8
17
|
|
18
|
+
# Constructs a new pool with a maximum size. If a block is supplied, it
|
19
|
+
# is used to create new connections as they are needed.
|
20
|
+
#
|
21
|
+
# pool = ConnectionPool.new(10) {MyConnection.new(opts)}
|
22
|
+
#
|
23
|
+
# The connection creation proc can be changed at any time by assigning a
|
24
|
+
# Proc to pool#connection_proc.
|
25
|
+
#
|
26
|
+
# pool = ConnectionPool.new(10)
|
27
|
+
# pool.connection_proc = proc {MyConnection.new(opts)}
|
9
28
|
def initialize(max_size = 4, &block)
|
10
29
|
@max_size = max_size
|
11
30
|
@mutex = Mutex.new
|
@@ -16,10 +35,23 @@ module Sequel
|
|
16
35
|
@created_count = 0
|
17
36
|
end
|
18
37
|
|
38
|
+
# Returns the number of created connections.
|
19
39
|
def size
|
20
40
|
@created_count
|
21
41
|
end
|
22
42
|
|
43
|
+
# Assigns a connection to the current thread, yielding the connection
|
44
|
+
# to the supplied block.
|
45
|
+
#
|
46
|
+
# pool.hold {|conn| conn.execute('DROP TABLE posts;')}
|
47
|
+
#
|
48
|
+
# Pool#hold is re-entrant, meaning it can be called recursively in
|
49
|
+
# the same thread without blocking.
|
50
|
+
#
|
51
|
+
# If no connection is available, Pool#hold will block until a connection
|
52
|
+
# is available.
|
53
|
+
#
|
54
|
+
# Errors raised inside a hold call are wrapped in a SequelConnectionError.
|
23
55
|
def hold
|
24
56
|
t = Thread.current
|
25
57
|
if (conn = owned_connection(t))
|
@@ -37,34 +69,42 @@ module Sequel
|
|
37
69
|
raise SequelConnectionError.new(e)
|
38
70
|
end
|
39
71
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
72
|
+
private
|
73
|
+
# Returns the connection owned by the supplied thread, if any.
|
74
|
+
def owned_connection(thread)
|
75
|
+
@mutex.synchronize {@allocated[thread]}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Assigns a connection to the supplied thread, if one is available.
|
79
|
+
def acquire(thread)
|
80
|
+
@mutex.synchronize do
|
81
|
+
if conn = available
|
82
|
+
@allocated[thread] = conn
|
83
|
+
end
|
48
84
|
end
|
49
85
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
def make_new
|
57
|
-
if @created_count < @max_size
|
58
|
-
@created_count += 1
|
59
|
-
@connection_proc.call
|
86
|
+
|
87
|
+
# Returns an available connection. If no connection is available,
|
88
|
+
# tries to create a new connection.
|
89
|
+
def available
|
90
|
+
@available_connections.pop || make_new
|
60
91
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
@
|
66
|
-
|
92
|
+
|
93
|
+
# Creates a new connection if the size of the pool is less than the
|
94
|
+
# maximum size.
|
95
|
+
def make_new
|
96
|
+
if @created_count < @max_size
|
97
|
+
@created_count += 1
|
98
|
+
@connection_proc.call
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Releases the connection assigned to the supplied thread.
|
103
|
+
def release(thread)
|
104
|
+
@mutex.synchronize do
|
105
|
+
@available_connections << @allocated[thread]
|
106
|
+
@allocated.delete(thread)
|
107
|
+
end
|
67
108
|
end
|
68
109
|
end
|
69
|
-
end
|
70
110
|
end
|
data/lib/sequel/database.rb
CHANGED
@@ -8,15 +8,15 @@ module Sequel
|
|
8
8
|
# The Database class is meant to be subclassed by database adapters in order
|
9
9
|
# to provide the functionality needed for executing queries.
|
10
10
|
class Database
|
11
|
-
attr_reader :opts, :pool
|
11
|
+
attr_reader :opts, :pool, :logger
|
12
12
|
|
13
13
|
# Constructs a new instance of a database connection with the specified
|
14
14
|
# options hash.
|
15
15
|
#
|
16
16
|
# Sequel::Database is an abstract class that is not useful by itself.
|
17
|
-
def initialize(opts = {})
|
17
|
+
def initialize(opts = {}, &block)
|
18
18
|
@opts = opts
|
19
|
-
@pool = ConnectionPool.new(@opts[:max_connections] || 4)
|
19
|
+
@pool = ConnectionPool.new(@opts[:max_connections] || 4, &block)
|
20
20
|
@logger = opts[:logger]
|
21
21
|
end
|
22
22
|
|
@@ -36,6 +36,7 @@ module Sequel
|
|
36
36
|
def execute(sql)
|
37
37
|
raise NotImplementedError
|
38
38
|
end
|
39
|
+
alias_method :<<, :execute
|
39
40
|
|
40
41
|
# Acquires a database connection, yielding it to the passed block.
|
41
42
|
def synchronize(&block)
|
@@ -44,19 +45,10 @@ module Sequel
|
|
44
45
|
|
45
46
|
# Returns true if there is a database connection
|
46
47
|
def test_connection
|
47
|
-
@pool.hold {|conn|}
|
48
|
+
@pool.hold {|conn|}
|
48
49
|
true
|
49
50
|
end
|
50
51
|
|
51
|
-
# call-seq:
|
52
|
-
# db.execute(sql)
|
53
|
-
# db << sql
|
54
|
-
#
|
55
|
-
# Executes an sql query.
|
56
|
-
def <<(sql)
|
57
|
-
execute(sql)
|
58
|
-
end
|
59
|
-
|
60
52
|
# Creates a table. The easiest way to use this method is to provide a
|
61
53
|
# block:
|
62
54
|
# DB.create_table :posts do
|
@@ -78,7 +70,7 @@ module Sequel
|
|
78
70
|
# Drops a table.
|
79
71
|
def drop_table(*names)
|
80
72
|
transaction do
|
81
|
-
names.
|
73
|
+
execute(names.map {|n| Schema.drop_table_sql(n)}.join)
|
82
74
|
end
|
83
75
|
end
|
84
76
|
|
@@ -153,6 +145,7 @@ module Sequel
|
|
153
145
|
# call-seq:
|
154
146
|
# Sequel::Database.connect(conn_string)
|
155
147
|
# Sequel.connect(conn_string)
|
148
|
+
# Sequel.open(conn_string)
|
156
149
|
#
|
157
150
|
# Creates a new database object based on the supplied connection string.
|
158
151
|
# The specified scheme determines the database class used, and the rest
|
data/lib/sequel/dataset.rb
CHANGED
@@ -112,23 +112,19 @@ module Sequel
|
|
112
112
|
raise SequelError, "can't express #{v.inspect}:#{v.class} as a SQL literal"
|
113
113
|
end
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
AND_SEPARATOR = " AND ".freeze
|
117
|
-
|
117
|
+
EQUAL_EXPR = "(%s = %s)".freeze
|
118
118
|
IN_EXPR = "(%s IN (%s))".freeze
|
119
|
-
# BETWEEN_EXPR = "(%s BETWEEN %s AND %s)".freeze
|
120
119
|
INCLUSIVE_RANGE_EXPR = "(%s >= %s AND %s <= %s)".freeze
|
121
120
|
EXCLUSIVE_RANGE_EXPR = "(%s >= %s AND %s < %s)".freeze
|
122
121
|
NULL_EXPR = "(%s IS NULL)".freeze
|
123
122
|
|
124
|
-
|
125
|
-
def where_condition(left, right)
|
126
|
-
left = field_name(left)
|
123
|
+
def format_eq_expression(left, right)
|
127
124
|
case right
|
128
125
|
when Range:
|
129
126
|
(right.exclude_end? ? EXCLUSIVE_RANGE_EXPR : INCLUSIVE_RANGE_EXPR) %
|
130
127
|
[left, literal(right.begin), left, literal(right.end)]
|
131
|
-
# BETWEEN_EXPR % [field_name(left), literal(right.begin), literal(right.end)]
|
132
128
|
when Array:
|
133
129
|
IN_EXPR % [left, literal(right)]
|
134
130
|
when NilClass:
|
@@ -136,7 +132,25 @@ module Sequel
|
|
136
132
|
when Dataset:
|
137
133
|
IN_EXPR % [left, right.sql]
|
138
134
|
else
|
139
|
-
|
135
|
+
EQUAL_EXPR % [left, literal(right)]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def format_expression(left, op, right)
|
140
|
+
left = field_name(left)
|
141
|
+
case op
|
142
|
+
when :eql:
|
143
|
+
format_eq_expression(left, right)
|
144
|
+
when :not:
|
145
|
+
"NOT #{format_eq_expression(left, right)}"
|
146
|
+
when :lt:
|
147
|
+
"(#{left} < #{literal(right)})"
|
148
|
+
when :lte:
|
149
|
+
"(#{left} <= #{literal(right)})"
|
150
|
+
when :gt:
|
151
|
+
"(#{left} > #{literal(right)})"
|
152
|
+
when :gte:
|
153
|
+
"(#{left} >= #{literal(right)})"
|
140
154
|
end
|
141
155
|
end
|
142
156
|
|
@@ -146,10 +160,14 @@ module Sequel
|
|
146
160
|
case where
|
147
161
|
when Hash:
|
148
162
|
parenthesize = false if where.size == 1
|
149
|
-
fmt = where.map {|
|
163
|
+
fmt = where.map {|i| format_expression(i[0], :eql, i[1])}.
|
164
|
+
join(AND_SEPARATOR)
|
150
165
|
when Array:
|
151
166
|
fmt = where.shift
|
152
167
|
fmt.gsub!('?') {|i| literal(where.shift)}
|
168
|
+
when Proc:
|
169
|
+
fmt = where.to_expressions.map {|e| format_expression(e.left, e.op, e.right)}.
|
170
|
+
join(AND_SEPARATOR)
|
153
171
|
else
|
154
172
|
fmt = where
|
155
173
|
end
|
@@ -159,7 +177,7 @@ module Sequel
|
|
159
177
|
# Formats a join condition.
|
160
178
|
def join_cond_list(cond, join_table)
|
161
179
|
cond.map do |kv|
|
162
|
-
|
180
|
+
EQUAL_EXPR % [
|
163
181
|
qualified_field_name(kv[0], join_table),
|
164
182
|
qualified_field_name(kv[1], @opts[:from])]
|
165
183
|
end.join(AND_SEPARATOR)
|
@@ -216,48 +234,50 @@ module Sequel
|
|
216
234
|
# Returns a copy of the dataset with the given conditions imposed upon it.
|
217
235
|
# If the query has been grouped, then the conditions are imposed in the
|
218
236
|
# HAVING clause. If not, then they are imposed in the WHERE clause.
|
219
|
-
def filter(*cond)
|
237
|
+
def filter(*cond, &block)
|
220
238
|
clause = (@opts[:group] ? :having : :where)
|
221
239
|
cond = cond.first if cond.size == 1
|
222
240
|
parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
|
223
241
|
if @opts[clause]
|
224
|
-
cond = AND_WHERE % [where_list(@opts[clause]), where_list(cond, parenthesize)]
|
242
|
+
cond = AND_WHERE % [where_list(@opts[clause]), where_list(block || cond, parenthesize)]
|
243
|
+
dup_merge(clause => cond)
|
244
|
+
else
|
245
|
+
dup_merge(clause => where_list(block || cond))
|
225
246
|
end
|
226
|
-
dup_merge(clause => where_list(cond))
|
227
247
|
end
|
228
248
|
|
229
249
|
NOT_WHERE = "NOT %s".freeze
|
230
250
|
AND_NOT_WHERE = "%s AND NOT %s".freeze
|
231
251
|
|
232
|
-
def exclude(*cond)
|
252
|
+
def exclude(*cond, &block)
|
233
253
|
clause = (@opts[:group] ? :having : :where)
|
234
254
|
cond = cond.first if cond.size == 1
|
235
255
|
parenthesize = !(cond.is_a?(Hash) || cond.is_a?(Array))
|
236
256
|
if @opts[clause]
|
237
|
-
cond = AND_NOT_WHERE % [where_list(@opts[clause]), where_list(cond, parenthesize)]
|
257
|
+
cond = AND_NOT_WHERE % [where_list(@opts[clause]), where_list(block || cond, parenthesize)]
|
238
258
|
else
|
239
|
-
cond = NOT_WHERE % where_list(cond, true)
|
259
|
+
cond = NOT_WHERE % where_list(block || cond, true)
|
240
260
|
end
|
241
261
|
dup_merge(clause => cond)
|
242
262
|
end
|
243
263
|
|
244
264
|
# Returns a copy of the dataset with the where conditions changed. Raises
|
245
265
|
# if the dataset has been grouped. See also #filter
|
246
|
-
def where(*cond)
|
266
|
+
def where(*cond, &block)
|
247
267
|
if @opts[:group]
|
248
268
|
raise SequelError, "Can't specify a WHERE clause once the dataset has been grouped"
|
249
269
|
else
|
250
|
-
filter(*cond)
|
270
|
+
filter(*cond, &block)
|
251
271
|
end
|
252
272
|
end
|
253
273
|
|
254
274
|
# Returns a copy of the dataset with the having conditions changed. Raises
|
255
275
|
# if the dataset has not been grouped. See also #filter
|
256
|
-
def having(*cond)
|
276
|
+
def having(*cond, &block)
|
257
277
|
unless @opts[:group]
|
258
278
|
raise SequelError, "Can only specify a HAVING clause on a grouped dataset"
|
259
279
|
else
|
260
|
-
filter(*cond)
|
280
|
+
filter(*cond, &block)
|
261
281
|
end
|
262
282
|
end
|
263
283
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Based on great work by Sam Smoot
|
2
|
+
# http://substantiality.net/archives/2007/4/16/datamapper-part-xiv-finder-block
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
class Dataset
|
6
|
+
class BlankSlate #:nodoc:
|
7
|
+
instance_methods.each { |m| undef_method m unless m =~ /^(__|instance_eval)/ }
|
8
|
+
end
|
9
|
+
|
10
|
+
class Expression < BlankSlate
|
11
|
+
attr_reader :left, :right
|
12
|
+
attr_accessor :op
|
13
|
+
|
14
|
+
def initialize(left)
|
15
|
+
@left = left
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(sym, *right)
|
19
|
+
@op = case sym
|
20
|
+
when :==, :===, :in: :eql
|
21
|
+
when :=~: :like
|
22
|
+
when :"<=>": :not
|
23
|
+
when :<: :lt
|
24
|
+
when :<=: :lte
|
25
|
+
when :>: :gt
|
26
|
+
when :>=: :gte
|
27
|
+
else sym
|
28
|
+
end
|
29
|
+
@right = right.first
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def nil?
|
34
|
+
@op = :eql
|
35
|
+
@right = nil
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ExpressionCompiler < BlankSlate
|
41
|
+
def initialize(&block)
|
42
|
+
@block = block
|
43
|
+
@expressions = []
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(sym, *args)
|
47
|
+
expr = Expression.new(sym)
|
48
|
+
@expressions << expr
|
49
|
+
expr
|
50
|
+
end
|
51
|
+
|
52
|
+
def SUM(sym)
|
53
|
+
expr = Expression.new(sym.SUM)
|
54
|
+
@expressions << expr
|
55
|
+
expr
|
56
|
+
end
|
57
|
+
|
58
|
+
def __to_a__
|
59
|
+
instance_eval(&@block)
|
60
|
+
@expressions
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Proc
|
67
|
+
def to_expressions
|
68
|
+
Sequel::Dataset::ExpressionCompiler.new(&self).__to_a__
|
69
|
+
end
|
70
|
+
end
|
data/lib/sequel/model.rb
CHANGED
@@ -174,7 +174,7 @@ module Sequel
|
|
174
174
|
def self.order(*arg); dataset.order(*arg); end
|
175
175
|
def self.first(*arg); dataset.first(*arg); end
|
176
176
|
def self.count; dataset.count; end
|
177
|
-
def self.map(
|
177
|
+
def self.map(*arg, &block); dataset.map(*arg, &block); end
|
178
178
|
def self.hash_column(column); dataset.hash_column(primary_key, column); end
|
179
179
|
def self.join(*args); dataset.join(*args); end
|
180
180
|
def self.lock(mode, &block); dataset.lock(mode, &block); end
|
data/lib/sequel/postgres.rb
CHANGED
@@ -226,8 +226,8 @@ module Sequel
|
|
226
226
|
|
227
227
|
LIKE = '%s ~ %s'.freeze
|
228
228
|
LIKE_CI = '%s ~* %s'.freeze
|
229
|
-
|
230
|
-
def
|
229
|
+
|
230
|
+
def format_eq_expression(left, right)
|
231
231
|
case right
|
232
232
|
when Regexp:
|
233
233
|
(right.casefold? ? LIKE_CI : LIKE) %
|
@@ -236,6 +236,11 @@ module Sequel
|
|
236
236
|
super
|
237
237
|
end
|
238
238
|
end
|
239
|
+
|
240
|
+
def format_expression(left, op, right)
|
241
|
+
op = :eql if op == :like
|
242
|
+
super
|
243
|
+
end
|
239
244
|
|
240
245
|
def each(opts = nil, &block)
|
241
246
|
query_each(select_sql(opts), true, &block)
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: sequel
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-04-
|
6
|
+
version: 0.0.20
|
7
|
+
date: 2007-04-18 00:00:00 +03:00
|
8
8
|
summary: Concise ORM for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -35,18 +35,19 @@ files:
|
|
35
35
|
- bin/sequel
|
36
36
|
- doc/rdoc
|
37
37
|
- lib/sequel
|
38
|
-
- lib/sequel
|
39
|
-
- lib/sequel/
|
40
|
-
- lib/sequel/
|
41
|
-
- lib/sequel/sqlite.rb
|
38
|
+
- lib/sequel.rb
|
39
|
+
- lib/sequel/connection_pool.rb
|
40
|
+
- lib/sequel/core_ext.rb
|
42
41
|
- lib/sequel/database.rb
|
43
42
|
- lib/sequel/dataset.rb
|
43
|
+
- lib/sequel/error.rb
|
44
|
+
- lib/sequel/expressions.rb
|
45
|
+
- lib/sequel/model.rb
|
44
46
|
- lib/sequel/mysql.rb
|
45
47
|
- lib/sequel/postgres.rb
|
46
|
-
- lib/sequel/
|
47
|
-
- lib/sequel/
|
48
|
-
- lib/sequel/
|
49
|
-
- lib/sequel.rb
|
48
|
+
- lib/sequel/pretty_table.rb
|
49
|
+
- lib/sequel/schema.rb
|
50
|
+
- lib/sequel/sqlite.rb
|
50
51
|
- CHANGELOG
|
51
52
|
test_files: []
|
52
53
|
|