sequel_core 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +116 -0
- data/COPYING +19 -19
- data/README +83 -32
- data/Rakefile +9 -20
- data/bin/sequel +43 -112
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +257 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +4 -2
- data/lib/sequel_core/adapters/ado.rb +3 -1
- data/lib/sequel_core/adapters/db2.rb +4 -2
- data/lib/sequel_core/adapters/dbi.rb +127 -113
- data/lib/sequel_core/adapters/informix.rb +4 -2
- data/lib/sequel_core/adapters/jdbc.rb +5 -3
- data/lib/sequel_core/adapters/mysql.rb +112 -46
- data/lib/sequel_core/adapters/odbc.rb +5 -7
- data/lib/sequel_core/adapters/odbc_mssql.rb +12 -3
- data/lib/sequel_core/adapters/openbase.rb +3 -1
- data/lib/sequel_core/adapters/oracle.rb +11 -9
- data/lib/sequel_core/adapters/postgres.rb +261 -262
- data/lib/sequel_core/adapters/sqlite.rb +72 -22
- data/lib/sequel_core/connection_pool.rb +140 -73
- data/lib/sequel_core/core_ext.rb +201 -66
- data/lib/sequel_core/core_sql.rb +123 -153
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/database.rb +321 -338
- data/lib/sequel_core/dataset/callback.rb +11 -12
- data/lib/sequel_core/dataset/convenience.rb +213 -240
- data/lib/sequel_core/dataset/pagination.rb +58 -43
- data/lib/sequel_core/dataset/parse_tree_sequelizer.rb +331 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sequelizer.rb +41 -373
- data/lib/sequel_core/dataset/sql.rb +741 -632
- data/lib/sequel_core/dataset.rb +183 -168
- data/lib/sequel_core/deprecated.rb +1 -169
- data/lib/sequel_core/exceptions.rb +24 -19
- data/lib/sequel_core/migration.rb +44 -52
- data/lib/sequel_core/object_graph.rb +43 -42
- data/lib/sequel_core/pretty_table.rb +71 -76
- data/lib/sequel_core/schema/generator.rb +163 -105
- data/lib/sequel_core/schema/sql.rb +250 -93
- data/lib/sequel_core/schema.rb +2 -8
- data/lib/sequel_core/sql.rb +394 -0
- data/lib/sequel_core/worker.rb +37 -27
- data/lib/sequel_core.rb +99 -45
- data/spec/adapters/informix_spec.rb +0 -1
- data/spec/adapters/mysql_spec.rb +177 -124
- data/spec/adapters/oracle_spec.rb +0 -1
- data/spec/adapters/postgres_spec.rb +98 -58
- data/spec/adapters/sqlite_spec.rb +45 -4
- data/spec/blockless_filters_spec.rb +269 -0
- data/spec/connection_pool_spec.rb +21 -18
- data/spec/core_ext_spec.rb +169 -19
- data/spec/core_sql_spec.rb +56 -49
- data/spec/database_spec.rb +78 -17
- data/spec/dataset_spec.rb +300 -428
- data/spec/migration_spec.rb +1 -1
- data/spec/object_graph_spec.rb +5 -11
- data/spec/rcov.opts +1 -1
- data/spec/schema_generator_spec.rb +16 -4
- data/spec/schema_spec.rb +89 -10
- data/spec/sequelizer_spec.rb +56 -56
- data/spec/spec.opts +0 -5
- data/spec/spec_config.rb +7 -0
- data/spec/spec_config.rb.example +5 -5
- data/spec/spec_helper.rb +6 -0
- data/spec/worker_spec.rb +1 -1
- metadata +78 -63
@@ -0,0 +1,394 @@
|
|
1
|
+
# This file holds classes and modules under Sequel that are related to SQL
|
2
|
+
# creation.
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
# The SQL module holds classes whose instances represent SQL fragments.
|
6
|
+
# It also holds modules that are included in core ruby classes that
|
7
|
+
# make Sequel a friendly DSL.
|
8
|
+
module SQL
|
9
|
+
|
10
|
+
### Classes ###
|
11
|
+
|
12
|
+
# Base class for all SQL fragments
|
13
|
+
class Expression
|
14
|
+
# Returns self, because SQL::Expression already acts like
|
15
|
+
# LiteralString.
|
16
|
+
def lit
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Represents all columns in a given table, table.* in SQL
|
22
|
+
class ColumnAll < Expression
|
23
|
+
# The table containing the columns being selected
|
24
|
+
attr_reader :table
|
25
|
+
|
26
|
+
# Create an object with the given table
|
27
|
+
def initialize(table)
|
28
|
+
@table = table
|
29
|
+
end
|
30
|
+
|
31
|
+
# ColumnAll expressions are considered equivalent if they
|
32
|
+
# have the same class and string representation
|
33
|
+
def ==(x)
|
34
|
+
x.class == self.class && @table == x.table
|
35
|
+
end
|
36
|
+
|
37
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
38
|
+
# since it may be database dependent.
|
39
|
+
def to_s(ds)
|
40
|
+
ds.column_all_sql(self)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Represents a generic column expression, used for specifying order
|
45
|
+
# and aliasing of columns.
|
46
|
+
class ColumnExpr < Expression
|
47
|
+
# Created readers for the left, operator, and right expression.
|
48
|
+
attr_reader :l, :op, :r
|
49
|
+
|
50
|
+
# Sets the attributes for the object to those given.
|
51
|
+
# The right (r) is not required, it is used when aliasing.
|
52
|
+
# The left (l) usually specifies the column name, and the
|
53
|
+
# operator (op) is usually 'ASC', 'DESC', or 'AS'.
|
54
|
+
def initialize(l, op, r = nil)
|
55
|
+
@l, @op, @r = l, op, r
|
56
|
+
end
|
57
|
+
|
58
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
59
|
+
# since it may be database dependent.
|
60
|
+
def to_s(ds)
|
61
|
+
ds.column_expr_sql(self)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Represents a complex SQL expression, with a given operator and one
|
66
|
+
# or more attributes (which may also be ComplexExpressions, forming
|
67
|
+
# a tree). This class is the backbone of the blockless filter support in
|
68
|
+
# Sequel.
|
69
|
+
#
|
70
|
+
# Most ruby operators methods are defined via metaprogramming: +, -, /, *, <, >, <=,
|
71
|
+
# >=, & (AND), | (OR). This allows for a simple DSL after some core
|
72
|
+
# classes have been overloaded with ComplexExpressionMethods.
|
73
|
+
class ComplexExpression < Expression
|
74
|
+
# A hash of the opposite for each operator symbol, used for inverting
|
75
|
+
# objects.
|
76
|
+
OPERTATOR_INVERSIONS = {:AND => :OR, :OR => :AND, :< => :>=, :> => :<=,
|
77
|
+
:<= => :>, :>= => :<, :'=' => :'!=' , :'!=' => :'=', :LIKE => :'NOT LIKE',
|
78
|
+
:'NOT LIKE' => :LIKE, :~ => :'!~', :'!~' => :~, :IN => :'NOT IN',
|
79
|
+
:'NOT IN' => :IN, :IS => :'IS NOT', :'IS NOT' => :IS, :'~*' => :'!~*',
|
80
|
+
:'!~*' => :'~*'}
|
81
|
+
|
82
|
+
MATHEMATICAL_OPERATORS = [:+, :-, :/, :*]
|
83
|
+
INEQUALITY_OPERATORS = [:<, :>, :<=, :>=]
|
84
|
+
STRING_OPERATORS = [:'||']
|
85
|
+
SEARCH_OPERATORS = [:LIKE, :'NOT LIKE', :~, :'!~', :'~*', :'!~*']
|
86
|
+
INCLUSION_OPERATORS = [:IN, :'NOT IN']
|
87
|
+
BOOLEAN_OPERATORS = [:AND, :OR]
|
88
|
+
|
89
|
+
# Collection of all equality/inequality operator symbols
|
90
|
+
EQUALITY_OPERATORS = [:'=', :'!=', :IS, :'IS NOT', *INEQUALITY_OPERATORS]
|
91
|
+
|
92
|
+
# Operator symbols that do not work on boolean SQL input
|
93
|
+
NO_BOOLEAN_INPUT_OPERATORS = MATHEMATICAL_OPERATORS + INEQUALITY_OPERATORS + STRING_OPERATORS
|
94
|
+
|
95
|
+
# Operator symbols that result in boolean SQL output
|
96
|
+
BOOLEAN_RESULT_OPERATORS = BOOLEAN_OPERATORS + EQUALITY_OPERATORS + SEARCH_OPERATORS + INCLUSION_OPERATORS + [:NOT]
|
97
|
+
|
98
|
+
# Literal SQL booleans that are not allowed
|
99
|
+
BOOLEAN_LITERALS = [true, false, nil]
|
100
|
+
|
101
|
+
# Hash of ruby operator symbols to SQL operators, used for method creation
|
102
|
+
BOOLEAN_OPERATOR_METHODS = {:& => :AND, :| =>:OR}
|
103
|
+
|
104
|
+
# Operator symbols that take exactly two arguments
|
105
|
+
TWO_ARITY_OPERATORS = EQUALITY_OPERATORS + SEARCH_OPERATORS + INCLUSION_OPERATORS
|
106
|
+
|
107
|
+
# Operator symbols that take one or more arguments
|
108
|
+
N_ARITY_OPERATORS = MATHEMATICAL_OPERATORS + BOOLEAN_OPERATORS + STRING_OPERATORS
|
109
|
+
|
110
|
+
# An array of args for this object
|
111
|
+
attr_reader :args
|
112
|
+
|
113
|
+
# The operator symbol for this object
|
114
|
+
attr_reader :op
|
115
|
+
|
116
|
+
# Set the operator symbol and arguments for this object to the ones given.
|
117
|
+
# Convert all args that are hashes or arrays with all two pairs to ComplexExpressions.
|
118
|
+
# Raise an error if the operator doesn't allow boolean input and a boolean argument is given.
|
119
|
+
# Raise an error if the wrong number of arguments for a given operator is used.
|
120
|
+
def initialize(op, *args)
|
121
|
+
args.collect! do |a|
|
122
|
+
case a
|
123
|
+
when Hash
|
124
|
+
a.sql_expr
|
125
|
+
when Array
|
126
|
+
a.all_two_pairs? ? a.sql_expr : a
|
127
|
+
else
|
128
|
+
a
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if NO_BOOLEAN_INPUT_OPERATORS.include?(op)
|
132
|
+
args.each do |a|
|
133
|
+
if BOOLEAN_LITERALS.include?(a) || ((ComplexExpression === a) && BOOLEAN_RESULT_OPERATORS.include?(a.op))
|
134
|
+
raise(Sequel::Error, "cannot apply #{op} to a boolean expression")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
case op
|
139
|
+
when *N_ARITY_OPERATORS
|
140
|
+
raise(Sequel::Error, 'mathematical and boolean operators require at least 1 argument') unless args.length >= 1
|
141
|
+
when *TWO_ARITY_OPERATORS
|
142
|
+
raise(Sequel::Error, '(in)equality operators require precisely 2 arguments') unless args.length == 2
|
143
|
+
when :NOT
|
144
|
+
raise(Sequel::Error, 'the NOT operator requires a single argument') unless args.length == 1
|
145
|
+
else
|
146
|
+
raise(Sequel::Error, "invalid operator #{op}")
|
147
|
+
end
|
148
|
+
@op = op
|
149
|
+
@args = args
|
150
|
+
end
|
151
|
+
|
152
|
+
# Take pairs of values (e.g. a hash or array of arrays of two pairs)
|
153
|
+
# and converts it to a ComplexExpression. The operator and args
|
154
|
+
# used depends on the case of the right (2nd) argument:
|
155
|
+
#
|
156
|
+
# * 0..10 - left >= 0 AND left <= 10
|
157
|
+
# * [1,2] - left IN (1,2)
|
158
|
+
# * nil - left IS NULL
|
159
|
+
# * /as/ - left ~ 'as'
|
160
|
+
# * :blah - left = blah
|
161
|
+
# * 'blah' - left = 'blah'
|
162
|
+
#
|
163
|
+
# If multiple arguments are given, they are joined with the op given (AND
|
164
|
+
# by default, OR possible). If negate is set to true,
|
165
|
+
# all subexpressions are inverted before used. Therefore, the following
|
166
|
+
# expressions are equivalent:
|
167
|
+
#
|
168
|
+
# ~from_value_pairs(hash)
|
169
|
+
# from_value_pairs(hash, :OR, true)
|
170
|
+
def self.from_value_pairs(pairs, op=:AND, negate=false)
|
171
|
+
pairs = pairs.collect do |l,r|
|
172
|
+
ce = case r
|
173
|
+
when Range
|
174
|
+
new(:AND, new(:>=, l, r.begin), new(r.exclude_end? ? :< : :<=, l, r.end))
|
175
|
+
when Array, ::Sequel::Dataset
|
176
|
+
new(:IN, l, r)
|
177
|
+
when NilClass
|
178
|
+
new(:IS, l, r)
|
179
|
+
when Regexp
|
180
|
+
like(l, r)
|
181
|
+
else
|
182
|
+
new(:'=', l, r)
|
183
|
+
end
|
184
|
+
negate ? ~ce : ce
|
185
|
+
end
|
186
|
+
pairs.length == 1 ? pairs.at(0) : new(op, *pairs)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Creates a SQL pattern match exprssion. left (l) is the SQL string we
|
190
|
+
# are matching against, and ces are the patterns we are matching.
|
191
|
+
# The match succeeds if any of the patterns match (SQL OR). Patterns
|
192
|
+
# can be given as strings or regular expressions. Strings will cause
|
193
|
+
# the SQL LIKE operator to be used, and should be supported by most
|
194
|
+
# databases. Regular expressions will probably only work on MySQL
|
195
|
+
# and PostgreSQL, and SQL regular expression syntax is not fully compatible
|
196
|
+
# with ruby regular expression syntax, so be careful if using regular
|
197
|
+
# expressions.
|
198
|
+
def self.like(l, *ces)
|
199
|
+
ces.collect! do |ce|
|
200
|
+
op, expr = Regexp === ce ? [ce.casefold? ? :'~*' : :~, ce.source] : [:LIKE, ce.to_s]
|
201
|
+
new(op, l, expr)
|
202
|
+
end
|
203
|
+
ces.length == 1 ? ces.at(0) : new(:OR, *ces)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Invert the regular expression, if possible. If the expression cannot
|
207
|
+
# be inverted, raise an error. An inverted expression should match everything that the
|
208
|
+
# uninverted expression did not match, and vice-versa.
|
209
|
+
def ~
|
210
|
+
case @op
|
211
|
+
when *BOOLEAN_OPERATORS
|
212
|
+
self.class.new(OPERTATOR_INVERSIONS[@op], *@args.collect{|a| ComplexExpression === a ? ~a : ComplexExpression.new(:NOT, a)})
|
213
|
+
when *TWO_ARITY_OPERATORS
|
214
|
+
self.class.new(OPERTATOR_INVERSIONS[@op], *@args.dup)
|
215
|
+
when :NOT
|
216
|
+
@args.first
|
217
|
+
else
|
218
|
+
raise(Sequel::Error, "operator #{@op} cannot be inverted")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
223
|
+
# since it may be database dependent.
|
224
|
+
def to_s(ds)
|
225
|
+
ds.complex_expression_sql(@op, @args)
|
226
|
+
end
|
227
|
+
|
228
|
+
BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
229
|
+
define_method(m) do |ce|
|
230
|
+
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression") unless BOOLEAN_RESULT_OPERATORS.include?(@op)
|
231
|
+
super
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
(MATHEMATICAL_OPERATORS + INEQUALITY_OPERATORS).each do |o|
|
236
|
+
define_method(o) do |ce|
|
237
|
+
raise(Sequel::Error, "cannot apply #{o} to a boolean expression") unless NO_BOOLEAN_INPUT_OPERATORS.include?(@op)
|
238
|
+
super
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Represents an SQL function call.
|
244
|
+
class Function < Expression
|
245
|
+
# The array of arguments to pass to the function (may be blank)
|
246
|
+
attr_reader :args
|
247
|
+
|
248
|
+
# The SQL function to call
|
249
|
+
attr_reader :f
|
250
|
+
|
251
|
+
# Set the attributes to the given arguments
|
252
|
+
def initialize(f, *args)
|
253
|
+
@f, @args = f, args
|
254
|
+
end
|
255
|
+
|
256
|
+
# Functions are considered equivalent if they
|
257
|
+
# have the same class, function, and arguments.
|
258
|
+
def ==(x)
|
259
|
+
x.class == self.class && @f == x.f && @args == x.args
|
260
|
+
end
|
261
|
+
|
262
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
263
|
+
# since it may be database dependent.
|
264
|
+
def to_s(ds)
|
265
|
+
ds.function_sql(self)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Represents a qualified (column with table) reference. Used when
|
270
|
+
# joining tables to disambiguate columns.
|
271
|
+
class QualifiedColumnRef < Expression
|
272
|
+
# The table and column to reference
|
273
|
+
attr_reader :table, :column
|
274
|
+
|
275
|
+
# Set the attributes to the given arguments
|
276
|
+
def initialize(table, column)
|
277
|
+
@table, @column = table, column
|
278
|
+
end
|
279
|
+
|
280
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
281
|
+
# since it may be database dependent.
|
282
|
+
def to_s(ds)
|
283
|
+
ds.qualified_column_ref_sql(self)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Represents an SQL array access, with multiple possible arguments.
|
288
|
+
class Subscript < Expression
|
289
|
+
# The SQL array column
|
290
|
+
attr_reader :f
|
291
|
+
|
292
|
+
# The array of subscripts to use (should be an array of numbers)
|
293
|
+
attr_reader :sub
|
294
|
+
|
295
|
+
# Set the attributes to the given arguments
|
296
|
+
def initialize(f, sub)
|
297
|
+
@f, @sub = f, sub
|
298
|
+
end
|
299
|
+
|
300
|
+
# Create a new subscript appending the given subscript(s)
|
301
|
+
# the the current array of subscripts.
|
302
|
+
def |(sub)
|
303
|
+
Subscript.new(@f, @sub + Array(sub))
|
304
|
+
end
|
305
|
+
|
306
|
+
# Delegate the creation of the resulting SQL to the given dataset,
|
307
|
+
# since it may be database dependent.
|
308
|
+
def to_s(ds)
|
309
|
+
ds.subscript_sql(self)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
### Modules ###
|
314
|
+
|
315
|
+
# Module included in core classes giving them a simple and easy DSL
|
316
|
+
# for creation of ComplexExpressions.
|
317
|
+
#
|
318
|
+
# Most ruby operators methods are defined via metaprogramming: +, -, /, *, <, >, <=,
|
319
|
+
# >=, & (AND), | (OR).
|
320
|
+
module ComplexExpressionMethods
|
321
|
+
NO_BOOLEAN_INPUT_OPERATORS = ComplexExpression::NO_BOOLEAN_INPUT_OPERATORS
|
322
|
+
BOOLEAN_RESULT_OPERATORS = ComplexExpression::BOOLEAN_RESULT_OPERATORS
|
323
|
+
BOOLEAN_OPERATOR_METHODS = ComplexExpression::BOOLEAN_OPERATOR_METHODS
|
324
|
+
|
325
|
+
BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
326
|
+
define_method(m) do |ce|
|
327
|
+
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression") if (ComplexExpression === ce) && !BOOLEAN_RESULT_OPERATORS.include?(ce.op)
|
328
|
+
ComplexExpression.new(o, self, ce)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
(ComplexExpression::MATHEMATICAL_OPERATORS + ComplexExpression::INEQUALITY_OPERATORS).each do |o|
|
333
|
+
define_method(o) do |ce|
|
334
|
+
raise(Sequel::Error, "cannot apply #{o} to a boolean expression") if (ComplexExpression === ce) && !NO_BOOLEAN_INPUT_OPERATORS.include?(ce.op)
|
335
|
+
ComplexExpression.new(o, self, ce)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Create a new ComplexExpression with NOT, representing the inversion of whatever self represents.
|
340
|
+
def ~
|
341
|
+
ComplexExpression.new(:NOT, self)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Create a ComplexExpression pattern match of self with the given patterns.
|
345
|
+
def like(*ces)
|
346
|
+
ComplexExpression.like(self, *ces)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
# Holds methods that should be called on columns only.
|
351
|
+
module ColumnMethods
|
352
|
+
AS = 'AS'.freeze
|
353
|
+
DESC = 'DESC'.freeze
|
354
|
+
ASC = 'ASC'.freeze
|
355
|
+
|
356
|
+
# Create an SQL column alias of the receiving column to the given alias.
|
357
|
+
def as(a)
|
358
|
+
ColumnExpr.new(self, AS, a)
|
359
|
+
end
|
360
|
+
|
361
|
+
# Mark the receiving SQL column as sorting in a descending fashion.
|
362
|
+
def desc
|
363
|
+
ColumnExpr.new(self, DESC)
|
364
|
+
end
|
365
|
+
|
366
|
+
# Mark the receiving SQL column as sorting in an ascending fashion (generally a no-op).
|
367
|
+
def asc
|
368
|
+
ColumnExpr.new(self, ASC)
|
369
|
+
end
|
370
|
+
|
371
|
+
# Cast the reciever to the given SQL type
|
372
|
+
def cast_as(t)
|
373
|
+
t = t.to_s.lit if t.is_a?(Symbol)
|
374
|
+
Sequel::SQL::Function.new(:cast, self.as(t))
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
class Expression
|
379
|
+
# Include the modules in Expression, couldn't be done
|
380
|
+
# earlier due to cyclic dependencies.
|
381
|
+
include ColumnMethods
|
382
|
+
include ComplexExpressionMethods
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
# LiteralString is used to represent literal SQL expressions. An
|
387
|
+
# LiteralString is copied verbatim into an SQL statement. Instances of
|
388
|
+
# LiteralString can be created by calling String#lit.
|
389
|
+
# LiteralStrings can use all of the SQL::ColumnMethods and the
|
390
|
+
# SQL::ComplexExpressionMethods.
|
391
|
+
class LiteralString < ::String
|
392
|
+
include SQL::ComplexExpressionMethods
|
393
|
+
end
|
394
|
+
end
|
data/lib/sequel_core/worker.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
require "thread"
|
2
|
-
|
3
1
|
module Sequel
|
4
|
-
|
2
|
+
# A Worker is a thread that accepts jobs off a work queue and
|
3
|
+
# processes them in the background. It accepts an optional
|
4
|
+
# database where it wruns all of its work inside a transaction.
|
5
5
|
class Worker < Thread
|
6
|
-
|
7
6
|
attr_reader :queue
|
8
7
|
attr_reader :errors
|
9
8
|
|
9
|
+
# Setup the interal variables. If a database is given,
|
10
|
+
# run the thread inside a database transaction. Continue
|
11
|
+
# to work until #join is called.
|
10
12
|
def initialize(db = nil)
|
11
13
|
@queue = Queue.new
|
12
14
|
@errors = []
|
@@ -16,17 +18,8 @@ module Sequel
|
|
16
18
|
db ? super {db.transaction {t.work}} : super {t.work}
|
17
19
|
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
rescue Sequel::Error::WorkerStop # signals the worker thread to stop
|
22
|
-
ensure
|
23
|
-
raise Sequel::Error::Rollback if @transaction && !@errors.empty?
|
24
|
-
end
|
25
|
-
|
26
|
-
def busy?
|
27
|
-
@cur || !@queue.empty?
|
28
|
-
end
|
29
|
-
|
21
|
+
# Add a job to the queue, specified either as a proc argument
|
22
|
+
# or as a block.
|
30
23
|
def async(proc = nil, &block)
|
31
24
|
@queue << (proc || block)
|
32
25
|
self
|
@@ -34,25 +27,42 @@ module Sequel
|
|
34
27
|
alias_method :add, :async
|
35
28
|
alias_method :<<, :async
|
36
29
|
|
30
|
+
# Whether the worker is actively working and/or has work scheduled
|
31
|
+
def busy?
|
32
|
+
@cur || !@queue.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Wait until the worker is no longer busy and then stop working.
|
37
36
|
def join
|
38
|
-
while busy?
|
39
|
-
sleep 0.1
|
40
|
-
end
|
37
|
+
sleep(0.1) while busy?
|
41
38
|
self.raise Error::WorkerStop
|
42
39
|
super
|
43
40
|
end
|
44
41
|
|
42
|
+
# Continually get jobs from the work queue and process them.
|
43
|
+
def work
|
44
|
+
begin
|
45
|
+
loop {next_job}
|
46
|
+
rescue Sequel::Error::WorkerStop # signals the worker thread to stop
|
47
|
+
ensure
|
48
|
+
raise Sequel::Error::Rollback if @transaction && !@errors.empty?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
45
52
|
private
|
53
|
+
|
54
|
+
# Get the next job from the work queue and process it.
|
46
55
|
def next_job
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
56
|
+
begin
|
57
|
+
@cur = @queue.pop
|
58
|
+
@cur.call
|
59
|
+
rescue Error::WorkerStop => e
|
60
|
+
raise e
|
61
|
+
rescue Exception => e
|
62
|
+
@errors << e
|
63
|
+
ensure
|
64
|
+
@cur = nil
|
65
|
+
end
|
55
66
|
end
|
56
67
|
end
|
57
|
-
|
58
68
|
end
|
data/lib/sequel_core.rb
CHANGED
@@ -1,52 +1,106 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
|
1
|
+
%w'bigdecimal bigdecimal/util date enumerator thread time uri yaml'.each do |f|
|
2
|
+
require f
|
3
|
+
end
|
4
|
+
%w"core_ext sql core_sql connection_pool exceptions pretty_table
|
5
|
+
dataset migration schema database worker object_graph".each do |f|
|
6
|
+
require "sequel_core/#{f}"
|
7
|
+
end
|
4
8
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
# Top level module for Sequel
|
10
|
+
#
|
11
|
+
# There are some class methods that are added via metaprogramming, one for
|
12
|
+
# each supported adapter. For example:
|
13
|
+
#
|
14
|
+
# DB = Sequel.sqlite # Memory database
|
15
|
+
# DB = Sequel.sqlite('blog.db')
|
16
|
+
# DB = Sequel.postgres('database_name', :user=>'user', \
|
17
|
+
# :password=>'password', :host=>'host', :port=>5432, \
|
18
|
+
# :max_connections=>10)
|
19
|
+
#
|
20
|
+
# If a block is given to these meethods, it is passed the opened Database
|
21
|
+
# object, which is closed when the block exits. For example:
|
22
|
+
#
|
23
|
+
# Sequel.sqlite('blog.db'){|db| puts db.users.count}
|
24
|
+
module Sequel
|
25
|
+
# Creates a new database object based on the supplied connection string
|
26
|
+
# and optional arguments. The specified scheme determines the database
|
27
|
+
# class used, and the rest of the string specifies the connection options.
|
28
|
+
# For example:
|
29
|
+
#
|
30
|
+
# DB = Sequel.connect('sqlite:/') # Memory database
|
31
|
+
# DB = Sequel.connect('sqlite://blog.db') # ./blog.db
|
32
|
+
# DB = Sequel.connect('sqlite:///blog.db') # /blog.db
|
33
|
+
# DB = Sequel.connect('postgres://user:password@host:port/database_name')
|
34
|
+
# DB = Sequel.connect('sqlite:///blog.db', :max_connections=>10)
|
35
|
+
#
|
36
|
+
# If a block is given, it is passed the opened Database object, which is
|
37
|
+
# closed when the block exits. For example:
|
38
|
+
#
|
39
|
+
# Sequel.connect('sqlite://blog.db'){|db| puts db.users.count}
|
40
|
+
def self.connect(*args, &block)
|
41
|
+
Database.connect(*args, &block)
|
42
|
+
end
|
43
|
+
metaalias :open, :connect
|
44
|
+
|
45
|
+
# Set whether to quote identifiers for all databases by default. By default,
|
46
|
+
# Sequel quotes identifiers in all SQL strings, so to turn that off:
|
47
|
+
#
|
48
|
+
# Sequel.quote_identifiers = false
|
49
|
+
def self.quote_identifiers=(value)
|
50
|
+
Database.quote_identifiers = value
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set whether to set the single threaded mode for all databases by default. By default,
|
54
|
+
# Sequel uses a threadsafe connection pool, which isn't as fast as the
|
55
|
+
# single threaded connection pool. If your program will only have one thread,
|
56
|
+
# and speed is a priority, you may want to set this to true:
|
57
|
+
#
|
58
|
+
# Sequel.single_threaded = true
|
59
|
+
#
|
60
|
+
# Note that some database adapters (e.g. MySQL) have issues with single threaded mode if
|
61
|
+
# you try to perform more than one query simultaneously. For example, the
|
62
|
+
# following code will not work well in single threaded mode on MySQL:
|
63
|
+
#
|
64
|
+
# DB[:items].each{|i| DB[:nodes].filter(:item_id=>i[:id]).each{|n| puts "#{i} #{n}"}}
|
65
|
+
#
|
66
|
+
# Basically, you can't issue another query inside a call to Dataset#each in single
|
67
|
+
# threaded mode. There is a fairly easy fix, just use Dataset#all inside
|
68
|
+
# Dataset#each for the outer query:
|
69
|
+
#
|
70
|
+
# DB[:items].all{|i| DB[:nodes].filter(:item_id=>i[:id]).each{|n| puts "#{i} #{n}"}}
|
71
|
+
#
|
72
|
+
# Dataset#all gets all of the returned objects before calling the block, so the query
|
73
|
+
# isn't left open. Some of the adapters do this internally, and thus don't have a
|
74
|
+
# problem issuing queries inside of Dataset#each.
|
75
|
+
def self.single_threaded=(value)
|
76
|
+
Database.single_threaded = value
|
77
|
+
end
|
78
|
+
|
79
|
+
### Private Class Methods ###
|
11
80
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# The specified scheme determines the database class used, and the rest
|
23
|
-
# of the string specifies the connection options. For example:
|
24
|
-
# DB = Sequel.open 'sqlite:///blog.db'
|
25
|
-
def connect(*args)
|
26
|
-
Database.connect(*args)
|
27
|
-
end
|
28
|
-
alias_method :open, :connect
|
29
|
-
|
30
|
-
def single_threaded=(value)
|
31
|
-
Database.single_threaded = value
|
81
|
+
# Helper method that the database adapter class methods that are added to Sequel via
|
82
|
+
# metaprogramming use to parse arguments.
|
83
|
+
def self.adapter_method(adapter, *args, &block) # :nodoc:
|
84
|
+
raise(::Sequel::Error, "Wrong number of arguments, 0-2 arguments valid") if args.length > 2
|
85
|
+
opts = {:adapter=>adapter.to_sym}
|
86
|
+
opts[:database] = args.shift if args.length >= 1 && !(args[0].is_a?(Hash))
|
87
|
+
if Hash === (arg = args[0])
|
88
|
+
opts.merge!(arg)
|
89
|
+
elsif !arg.nil?
|
90
|
+
raise ::Sequel::Error, "Wrong format of arguments, either use (), (String), (Hash), or (String, Hash)"
|
32
91
|
end
|
92
|
+
connect(opts, &block)
|
93
|
+
end
|
33
94
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
opts[:database] = args.shift if args.length >= 1 && !(args[0].is_a?(Hash))
|
40
|
-
if Hash === (arg = args[0])
|
41
|
-
opts.merge!(arg)
|
42
|
-
elsif !arg.nil?
|
43
|
-
raise ::Sequel::Error, "Wrong format of arguments, either use (), (String), (Hash), or (String, Hash)"
|
44
|
-
end
|
45
|
-
::Sequel::Database.connect(opts)
|
46
|
-
end
|
47
|
-
end
|
95
|
+
# Method that adds a database adapter class method to Sequel that calls
|
96
|
+
# Sequel.adapter_method.
|
97
|
+
def self.def_adapter_method(*adapters) # :nodoc:
|
98
|
+
adapters.each do |adapter|
|
99
|
+
instance_eval("def #{adapter}(*args, &block); adapter_method('#{adapter}', *args, &block) end")
|
48
100
|
end
|
49
|
-
|
50
|
-
def_adapter_method(*Database::ADAPTERS)
|
51
101
|
end
|
102
|
+
metaprivate :adapter_method, :def_adapter_method
|
103
|
+
|
104
|
+
# Add the database adapter class methods to Sequel via metaprogramming
|
105
|
+
def_adapter_method(*Database::ADAPTERS)
|
52
106
|
end
|