sequel_core 1.5.1 → 2.0.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 +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
|