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 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