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 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.19"
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
@@ -5,6 +5,7 @@ require File.join(dir, 'database')
5
5
  require File.join(dir, 'connection_pool')
6
6
  require File.join(dir, 'schema')
7
7
  require File.join(dir, 'pretty_table')
8
+ require File.join(dir, 'expressions')
8
9
  require File.join(dir, 'dataset')
9
10
  require File.join(dir, 'model')
10
11
 
@@ -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 :max_size, :mutex
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
- def owned_connection(thread)
41
- @mutex.synchronize {@allocated[thread]}
42
- end
43
-
44
- def acquire(thread)
45
- @mutex.synchronize do
46
- if conn = available
47
- @allocated[thread] = conn
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
- end
51
-
52
- def available
53
- @available_connections.pop || make_new
54
- end
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
- end
62
-
63
- def release(thread)
64
- @mutex.synchronize do
65
- @available_connections << @allocated[thread]
66
- @allocated.delete(thread)
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
@@ -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|} if @pool
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.each {|n| execute Schema.drop_table_sql(n)}
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
@@ -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
- EQUAL_COND = "(%s = %s)".freeze
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
- # Formats an equality condition SQL expression.
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
- EQUAL_COND % [left, literal(right)]
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 {|kv| where_condition(*kv)}.join(AND_SEPARATOR)
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
- EQUAL_COND % [
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(column); dataset.map(column); end
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
@@ -226,8 +226,8 @@ module Sequel
226
226
 
227
227
  LIKE = '%s ~ %s'.freeze
228
228
  LIKE_CI = '%s ~* %s'.freeze
229
-
230
- def where_condition(left, right)
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
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.19
7
- date: 2007-04-16 00:00:00 +03:00
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/pretty_table.rb
39
- - lib/sequel/model.rb
40
- - lib/sequel/schema.rb
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/connection_pool.rb
47
- - lib/sequel/core_ext.rb
48
- - lib/sequel/error.rb
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