sequel 0.1.9.12 → 0.2.0
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 +52 -0
- data/README +3 -3
- data/Rakefile +3 -1
- data/lib/sequel.rb +1 -1
- data/lib/sequel/connection_pool.rb +2 -1
- data/lib/sequel/core_ext.rb +22 -20
- data/lib/sequel/database.rb +6 -2
- data/lib/sequel/dataset.rb +6 -4
- data/lib/sequel/dataset/{dataset_convenience.rb → convenience.rb} +0 -0
- data/lib/sequel/dataset/sequelizer.rb +246 -0
- data/lib/sequel/dataset/{dataset_sql.rb → sql.rb} +13 -81
- data/lib/sequel/model.rb +4 -4
- data/lib/sequel/mysql.rb +14 -2
- data/lib/sequel/postgres.rb +5 -7
- data/lib/sequel/schema/schema_generator.rb +1 -2
- data/spec/adapters/mysql_spec.rb +7 -0
- data/spec/adapters/postgres_spec.rb +87 -0
- data/spec/adapters/sqlite_spec.rb +2 -2
- data/spec/connection_pool_spec.rb +2 -2
- data/spec/core_ext_spec.rb +43 -2
- data/spec/database_spec.rb +6 -0
- data/spec/dataset_spec.rb +29 -30
- data/spec/sequelizer_spec.rb +250 -0
- metadata +48 -29
- data/spec/expressions_spec.rb +0 -151
data/CHANGELOG
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
=== 0.2.0 (2007-09-02)
|
2
|
+
|
3
|
+
* Fixed Model.drop_table (thanks Duane Johnson.)
|
4
|
+
|
5
|
+
* Dataset#each can now return rows for arbitrary SQL by specifying :sql option.
|
6
|
+
|
7
|
+
* Added spec for postgres adapter.
|
8
|
+
|
9
|
+
* Fixed Model.method_missing to work with new SQL generation.
|
10
|
+
|
11
|
+
* Fixed #compare_expr to support regexps.
|
12
|
+
|
13
|
+
* Fixed postgres, mysql adapters to support regexps.
|
14
|
+
|
15
|
+
* More specs for block filters. Updated README.
|
16
|
+
|
17
|
+
* Added support for globals and $X macros in block filters.
|
18
|
+
|
19
|
+
* Fixed Sequelizer to not fail if ParseTree or Ruby2Ruby gems are missing.
|
20
|
+
|
21
|
+
* Renamed String#expr into String#lit (#expr should be deprecated in future versions).
|
22
|
+
|
23
|
+
* Renamed Sequel::ExpressionString into LiteralString.
|
24
|
+
|
25
|
+
* Fixed Symbol#[] to return an ExpressionString, so as not to be literalized.
|
26
|
+
|
27
|
+
* Renamed Dataset::Expressions to Dataset::Sequelizer.
|
28
|
+
|
29
|
+
* Renamed Expressions#format_re_expression to match_expr.
|
30
|
+
|
31
|
+
* Renamed Expressions#format_eq_expression to compare_expr.
|
32
|
+
|
33
|
+
* Added support for Regexp in MySQL adapter.
|
34
|
+
|
35
|
+
* Refactored Regexp expressions into a separate #format_re_expression method.
|
36
|
+
|
37
|
+
* Added support for arithmetic in proc filters.
|
38
|
+
|
39
|
+
* Added support for nested proc expressions, more specs.
|
40
|
+
|
41
|
+
* Added support for SQL function using symbols, e.g. :sum[:x].
|
42
|
+
|
43
|
+
* Fixed deadlock bug in ConnectionPool.
|
44
|
+
|
45
|
+
* Removed deprecated old expressions.rb.
|
46
|
+
|
47
|
+
* Rewrote Proc filter feature using ParseTree.
|
48
|
+
|
49
|
+
* Added support for additional functions on columns using Symbol#method_missing.
|
50
|
+
|
51
|
+
* Added support for supplying filter block to DB#[] method, to allow stuff like DB[:nodes] {:path =~ /^icex1/}.
|
52
|
+
|
1
53
|
=== 0.1.9.12 (2007-08-26)
|
2
54
|
|
3
55
|
* Added spec for PrettyTable.
|
data/README
CHANGED
@@ -163,13 +163,13 @@ Or lists of values:
|
|
163
163
|
|
164
164
|
my_posts = posts.filter(:category => ['ruby', 'postgres', 'linux'])
|
165
165
|
|
166
|
-
Sequel now also accepts expressions as closures:
|
166
|
+
Sequel now also accepts expressions as closures, AKA block filters:
|
167
167
|
|
168
|
-
my_posts = posts.filter {category == ['ruby', 'postgres', 'linux']}
|
168
|
+
my_posts = posts.filter {:category == ['ruby', 'postgres', 'linux']}
|
169
169
|
|
170
170
|
Which also lets you do stuff like:
|
171
171
|
|
172
|
-
my_posts = posts.filter {stamp > 1.month.ago}
|
172
|
+
my_posts = posts.filter {:stamp > 1.month.ago}
|
173
173
|
|
174
174
|
Some adapters (like postgresql) will also let you specify Regexps:
|
175
175
|
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "sequel"
|
9
|
-
VERS = "0.
|
9
|
+
VERS = "0.2.0"
|
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",
|
@@ -44,6 +44,8 @@ spec = Gem::Specification.new do |s|
|
|
44
44
|
s.executables = ['sequel']
|
45
45
|
|
46
46
|
s.add_dependency('metaid')
|
47
|
+
s.add_dependency('ParseTree')
|
48
|
+
s.add_dependency('ruby2ruby')
|
47
49
|
|
48
50
|
s.required_ruby_version = '>= 1.8.4'
|
49
51
|
|
data/lib/sequel.rb
CHANGED
data/lib/sequel/core_ext.rb
CHANGED
@@ -18,10 +18,10 @@ class Array
|
|
18
18
|
end
|
19
19
|
|
20
20
|
module Sequel
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
class
|
21
|
+
# LiteralString is used to represent literal SQL expressions. An
|
22
|
+
# LiteralString is copied verbatim into an SQL statement. Instances of
|
23
|
+
# LiteralString can be created by calling String#expr.
|
24
|
+
class LiteralString < ::String
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -39,19 +39,21 @@ class String
|
|
39
39
|
to_sql.split(';').map {|s| s.strip}
|
40
40
|
end
|
41
41
|
|
42
|
-
# Converts a string into an
|
42
|
+
# Converts a string into an LiteralString, in order to override string
|
43
43
|
# literalization, e.g.:
|
44
44
|
#
|
45
45
|
# DB[:items].filter(:abc => 'def').sql #=>
|
46
46
|
# "SELECT * FROM items WHERE (abc = 'def')"
|
47
47
|
#
|
48
|
-
# DB[:items].filter(:abc => 'def'.
|
48
|
+
# DB[:items].filter(:abc => 'def'.lit).sql #=>
|
49
49
|
# "SELECT * FROM items WHERE (abc = def)"
|
50
50
|
#
|
51
|
-
def
|
52
|
-
Sequel::
|
51
|
+
def lit
|
52
|
+
Sequel::LiteralString.new(self)
|
53
53
|
end
|
54
54
|
|
55
|
+
alias_method :expr, :lit
|
56
|
+
|
55
57
|
# Converts a string into a Time object.
|
56
58
|
def to_time
|
57
59
|
Time.parse(self)
|
@@ -93,18 +95,6 @@ module FieldCompositionMethods
|
|
93
95
|
s
|
94
96
|
end
|
95
97
|
end
|
96
|
-
|
97
|
-
# Formats a min(x) expression.
|
98
|
-
def MIN; "min(#{to_field_name})"; end
|
99
|
-
|
100
|
-
# Formats a max(x) expression.
|
101
|
-
def MAX; "max(#{to_field_name})"; end
|
102
|
-
|
103
|
-
# Formats a sum(x) expression.
|
104
|
-
def SUM; "sum(#{to_field_name})"; end
|
105
|
-
|
106
|
-
# Formats an avg(x) expression.
|
107
|
-
def AVG; "avg(#{to_field_name})"; end
|
108
98
|
end
|
109
99
|
|
110
100
|
class String
|
@@ -139,6 +129,18 @@ class Symbol
|
|
139
129
|
s
|
140
130
|
end
|
141
131
|
end
|
132
|
+
|
133
|
+
# Converts missing method calls into functions on columns, if the
|
134
|
+
# method name is made of all upper case letters.
|
135
|
+
def method_missing(sym)
|
136
|
+
((s = sym.to_s) =~ /^([A-Z]+)$/) ? \
|
137
|
+
"#{s.downcase}(#{to_field_name})" : super
|
138
|
+
end
|
139
|
+
|
140
|
+
# Formats an SQL function with optional parameters
|
141
|
+
def [](*args)
|
142
|
+
"#{to_s}(#{args.join(', ')})".lit
|
143
|
+
end
|
142
144
|
end
|
143
145
|
|
144
146
|
|
data/lib/sequel/database.rb
CHANGED
@@ -63,8 +63,12 @@ module Sequel
|
|
63
63
|
Sequel::Dataset.new(self)
|
64
64
|
end
|
65
65
|
|
66
|
-
# Returns a new dataset with the from method invoked.
|
67
|
-
|
66
|
+
# Returns a new dataset with the from method invoked. If a block is given,
|
67
|
+
# it is used as a filter on the dataset.
|
68
|
+
def from(*args, &block)
|
69
|
+
ds = dataset.from(*args)
|
70
|
+
block ? ds.filter(&block) : ds
|
71
|
+
end
|
68
72
|
|
69
73
|
# Returns a new dataset with the select method invoked.
|
70
74
|
def select(*args); dataset.select(*args); end
|
data/lib/sequel/dataset.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'date'
|
3
3
|
|
4
|
-
require File.join(File.dirname(__FILE__), 'dataset/
|
5
|
-
require File.join(File.dirname(__FILE__), 'dataset/
|
4
|
+
require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
|
5
|
+
require File.join(File.dirname(__FILE__), 'dataset/sql')
|
6
|
+
require File.join(File.dirname(__FILE__), 'dataset/convenience')
|
6
7
|
|
7
8
|
module Sequel
|
8
9
|
# A Dataset represents a view of a the data in a database, constrained by
|
@@ -63,8 +64,9 @@ module Sequel
|
|
63
64
|
# end
|
64
65
|
class Dataset
|
65
66
|
include Enumerable
|
66
|
-
include
|
67
|
-
include
|
67
|
+
include Sequelizer
|
68
|
+
include SQL
|
69
|
+
include Convenience
|
68
70
|
|
69
71
|
attr_reader :db
|
70
72
|
attr_accessor :opts
|
File without changes
|
@@ -0,0 +1,246 @@
|
|
1
|
+
class Sequel::Dataset
|
2
|
+
# The Sequelizer module includes methods for translating Ruby expressions
|
3
|
+
# into SQL expressions, making it possible to specify dataset filters using
|
4
|
+
# blocks, e.g.:
|
5
|
+
#
|
6
|
+
# DB[:items].filter {:price < 100}
|
7
|
+
# DB[:items].filter {:category == 'ruby' && :date < 3.days.ago}
|
8
|
+
#
|
9
|
+
# Block filters can refer to literals, variables, constants, arguments,
|
10
|
+
# instance variables or anything else in order to create parameterized
|
11
|
+
# queries. Block filters can also refer to other dataset objects as
|
12
|
+
# sub-queries. Block filters are pretty much limitless!
|
13
|
+
#
|
14
|
+
# Block filters are based on ParseTree. If you do not have the ParseTree
|
15
|
+
# gem installed, block filters will raise an error.
|
16
|
+
#
|
17
|
+
# To enable full block filter support make sure you have both ParseTree and
|
18
|
+
# Ruby2Ruby installed:
|
19
|
+
#
|
20
|
+
# sudo gem install parsetree
|
21
|
+
# sudo gem install ruby2ruby
|
22
|
+
module Sequelizer
|
23
|
+
# Formats an comparison expression involving a left value and a right
|
24
|
+
# value. Comparison expressions differ according to the class of the right
|
25
|
+
# value. The stock implementation supports Range (inclusive and exclusive),
|
26
|
+
# Array (as a list of values to compare against), Dataset (as a subquery to
|
27
|
+
# compare against), or a regular value.
|
28
|
+
#
|
29
|
+
# dataset.compare_expr('id', 1..20) #=>
|
30
|
+
# "(id >= 1 AND id <= 20)"
|
31
|
+
# dataset.compare_expr('id', [3,6,10]) #=>
|
32
|
+
# "(id IN (3, 6, 10))"
|
33
|
+
# dataset.compare_expr('id', DB[:items].select(:id)) #=>
|
34
|
+
# "(id IN (SELECT id FROM items))"
|
35
|
+
# dataset.compare_expr('id', nil) #=>
|
36
|
+
# "(id IS NULL)"
|
37
|
+
# dataset.compare_expr('id', 3) #=>
|
38
|
+
# "(id = 3)"
|
39
|
+
def compare_expr(l, r)
|
40
|
+
case r
|
41
|
+
when Range:
|
42
|
+
r.exclude_end? ? \
|
43
|
+
"(#{l} >= #{literal(r.begin)} AND #{l} < #{literal(r.end)})" : \
|
44
|
+
"(#{l} >= #{literal(r.begin)} AND #{l} <= #{literal(r.end)})"
|
45
|
+
when Array:
|
46
|
+
"(#{literal(l)} IN (#{literal(r)}))"
|
47
|
+
when Sequel::Dataset:
|
48
|
+
"(#{literal(l)} IN (#{r.sql}))"
|
49
|
+
when NilClass:
|
50
|
+
"(#{literal(l)} IS NULL)"
|
51
|
+
when Regexp:
|
52
|
+
match_expr(l, r)
|
53
|
+
else
|
54
|
+
"(#{literal(l)} = #{literal(r)})"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Formats a string matching expression. The stock implementation supports
|
59
|
+
# matching against strings only using the LIKE operator. Specific adapters
|
60
|
+
# can override this method to provide support for regular expressions.
|
61
|
+
def match_expr(l, r)
|
62
|
+
case r
|
63
|
+
when String:
|
64
|
+
"(#{literal(l)} LIKE #{literal(r)})"
|
65
|
+
else
|
66
|
+
raise SequelError, "Unsupported match pattern class (#{r.class})."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Evaluates a method call. This method is used to evaluate Ruby expressions
|
71
|
+
# referring to indirect values, e.g.:
|
72
|
+
#
|
73
|
+
# dataset.filter {:category => category.to_s}
|
74
|
+
# dataset.filter {:x > y[0..3]}
|
75
|
+
#
|
76
|
+
# This method depends on the Ruby2Ruby gem. If you do not have Ruby2Ruby
|
77
|
+
# installed, this method will raise an error.
|
78
|
+
def ext_expr(e, b)
|
79
|
+
eval(RubyToRuby.new.process(e), b)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Translates a method call parse-tree to SQL expression. The following
|
83
|
+
# operators are recognized and translated to SQL expressions: >, <, >=, <=,
|
84
|
+
# ==, =~, +, -, *, /, %:
|
85
|
+
#
|
86
|
+
# :x == 1 #=> "(x = 1)"
|
87
|
+
# (:x + 100) < 200 #=> "((x + 100) < 200)"
|
88
|
+
#
|
89
|
+
# The in, in?, nil and nil? method calls are intercepted and passed to
|
90
|
+
# #compare_expr.
|
91
|
+
#
|
92
|
+
# :x.in [1, 2, 3] #=> "(x IN (1, 2, 3))"
|
93
|
+
# :x.in?(DB[:y].select(:z)) #=> "(x IN (SELECT z FROM y))"
|
94
|
+
# :x.nil? #=> "(x IS NULL)"
|
95
|
+
#
|
96
|
+
# The like and like? method calls are intercepted and passed to #match_expr.
|
97
|
+
#
|
98
|
+
# :x.like? 'ABC%' #=> "(x LIKE 'ABC%')"
|
99
|
+
#
|
100
|
+
# The method also supports SQL functions by invoking Symbol#[]:
|
101
|
+
#
|
102
|
+
# :avg[:x] #=> "avg(x)"
|
103
|
+
# :substring[:x, 5] #=> "substring(x, 5)"
|
104
|
+
#
|
105
|
+
# All other method calls are evaulated as normal Ruby code.
|
106
|
+
def call_expr(e, b)
|
107
|
+
case op = e[2]
|
108
|
+
when :>, :<, :>=, :<=
|
109
|
+
l = pt_expr(e[1], b)
|
110
|
+
r = pt_expr(e[3][1], b)
|
111
|
+
"(#{l} #{op} #{r})"
|
112
|
+
when :==
|
113
|
+
l = eval_expr(e[1], b)
|
114
|
+
r = eval_expr(e[3][1], b)
|
115
|
+
compare_expr(l, r)
|
116
|
+
when :=~
|
117
|
+
l = eval_expr(e[1], b)
|
118
|
+
r = eval_expr(e[3][1], b)
|
119
|
+
match_expr(l, r)
|
120
|
+
when :+, :-, :*, :/, :%
|
121
|
+
l = pt_expr(e[1], b)
|
122
|
+
r = pt_expr(e[3][1], b)
|
123
|
+
"(#{l} #{op} #{r})"
|
124
|
+
when :in, :in?
|
125
|
+
# in/in? operators are supported using two forms:
|
126
|
+
# :x.in([1, 2, 3])
|
127
|
+
# :x.in(1, 2, 3) # variable arity
|
128
|
+
l = eval_expr(e[1], b)
|
129
|
+
r = eval_expr((e[3].size == 2) ? e[3][1] : e[3], b)
|
130
|
+
compare_expr(l, r)
|
131
|
+
when :nil, :nil?
|
132
|
+
l = eval_expr(e[1], b)
|
133
|
+
compare_expr(l, nil)
|
134
|
+
when :like, :like?
|
135
|
+
l = eval_expr(e[1], b)
|
136
|
+
r = eval_expr(e[3][1], b)
|
137
|
+
match_expr(l, r)
|
138
|
+
else
|
139
|
+
if (op == :[]) && (e[1][0] == :lit) && (Symbol === e[1][1])
|
140
|
+
# SQL Functions, e.g.: :sum[:x]
|
141
|
+
e[1][1][*pt_expr(e[3], b)]
|
142
|
+
else
|
143
|
+
# external code
|
144
|
+
ext_expr(e, b)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Evaluates a parse-tree into an SQL expression.
|
150
|
+
def eval_expr(e, b)
|
151
|
+
case e[0]
|
152
|
+
when :call # method call
|
153
|
+
call_expr(e, b)
|
154
|
+
when :ivar, :cvar, :dvar, :vcall, :const, :gvar # local ref
|
155
|
+
eval(e[1].to_s, b)
|
156
|
+
when :nth_ref:
|
157
|
+
eval("$#{e[1]}", b)
|
158
|
+
when :lvar: # local context
|
159
|
+
if e[1] == :block
|
160
|
+
pr = eval(e[1].to_s, b)
|
161
|
+
"#{proc_to_sql(pr)}"
|
162
|
+
else
|
163
|
+
eval(e[1].to_s, b)
|
164
|
+
end
|
165
|
+
when :lit, :str # literal
|
166
|
+
e[1]
|
167
|
+
when :dot2 # inclusive range
|
168
|
+
eval_expr(e[1], b)..eval_expr(e[2], b)
|
169
|
+
when :dot3 # exclusive range
|
170
|
+
eval_expr(e[1], b)...eval_expr(e[2], b)
|
171
|
+
when :colon2 # qualified constant ref
|
172
|
+
eval_expr(e[1], b).const_get(e[2])
|
173
|
+
when :false: false
|
174
|
+
when :true: true
|
175
|
+
when :nil: nil
|
176
|
+
when :array
|
177
|
+
# array
|
178
|
+
e[1..-1].map {|i| eval_expr(i, b)}
|
179
|
+
when :match3
|
180
|
+
# =~/!~ operator
|
181
|
+
l = eval_expr(e[2], b)
|
182
|
+
r = eval_expr(e[1], b)
|
183
|
+
compare_expr(l, r)
|
184
|
+
when :iter
|
185
|
+
eval_expr(e[3], b)
|
186
|
+
when :dasgn, :dasgn_curr
|
187
|
+
# assignment
|
188
|
+
l = e[1]
|
189
|
+
r = eval_expr(e[2], b)
|
190
|
+
raise SequelError, "Invalid expression #{l} = #{r}. Did you mean :#{l} == #{r}?"
|
191
|
+
else
|
192
|
+
raise SequelError, "Invalid expression tree: #{e.inspect}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def pt_expr(e, b)
|
197
|
+
case e[0]
|
198
|
+
when :not # negation: !x, (x != y), (x !~ y)
|
199
|
+
if (e[1][0] == :lit) && (Symbol === e[1][1])
|
200
|
+
# translate (!:x) into (x = 'f')
|
201
|
+
compare_expr(e[1][1], false)
|
202
|
+
else
|
203
|
+
"(NOT #{pt_expr(e[1], b)})"
|
204
|
+
end
|
205
|
+
when :block, :and # block of statements, x && y
|
206
|
+
"(#{e[1..-1].map {|i| pt_expr(i, b)}.join(" AND ")})"
|
207
|
+
when :or # x || y
|
208
|
+
"(#{pt_expr(e[1], b)} OR #{pt_expr(e[2], b)})"
|
209
|
+
when :call, :vcall, :iter # method calls, blocks
|
210
|
+
eval_expr(e, b)
|
211
|
+
else # literals
|
212
|
+
if e == [:lvar, :block]
|
213
|
+
eval_expr(e, b)
|
214
|
+
else
|
215
|
+
literal(eval_expr(e, b))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Translates a Ruby block into an SQL expression.
|
221
|
+
def proc_to_sql(proc)
|
222
|
+
c = Class.new {define_method(:m, &proc)}
|
223
|
+
pt_expr(ParseTree.translate(c, :m)[2][2], proc.binding)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
begin
|
229
|
+
require 'parse_tree'
|
230
|
+
rescue LoadError
|
231
|
+
module Sequel::Dataset::Sequelizer
|
232
|
+
def proc_to_sql(proc)
|
233
|
+
raise SequelError, "You must have the ParseTree gem installed in order to use block filters."
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
begin
|
239
|
+
require 'ruby2ruby'
|
240
|
+
rescue LoadError
|
241
|
+
module Sequel::Dataset::Sequelizer
|
242
|
+
def ext_expr(e)
|
243
|
+
raise SequelError, "You must have the Ruby2Ruby gem installed in order to use this block filter."
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|