sequel 0.0.19 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|