safrano 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/lib/multipart.rb +92 -91
- data/lib/odata/attribute.rb +15 -6
- data/lib/odata/collection.rb +109 -106
- data/lib/odata/collection_filter.rb +14 -485
- data/lib/odata/collection_order.rb +15 -22
- data/lib/odata/entity.rb +31 -41
- data/lib/odata/filter/error.rb +53 -0
- data/lib/odata/filter/parse.rb +171 -0
- data/lib/odata/filter/sequel.rb +208 -0
- data/lib/odata/filter/token.rb +59 -0
- data/lib/odata/filter/tree.rb +368 -0
- data/lib/odata/relations.rb +36 -0
- data/lib/odata/url_parameters.rb +58 -0
- data/lib/odata/walker.rb +55 -42
- data/lib/request.rb +1 -3
- data/lib/safrano.rb +17 -0
- data/lib/safrano_core.rb +30 -7
- data/lib/sequel/plugins/join_by_paths.rb +239 -0
- data/lib/sequel_join_by_paths.rb +5 -0
- data/lib/service.rb +84 -112
- metadata +11 -3
@@ -1,7 +1,8 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
1
|
require 'odata/error.rb'
|
4
2
|
|
3
|
+
require_relative 'filter/parse.rb'
|
4
|
+
require_relative 'filter/sequel.rb'
|
5
|
+
|
5
6
|
# a few helper method
|
6
7
|
class String
|
7
8
|
MASK_RGX = /'((?:[^']|(?:\'{2}))*)'/.freeze
|
@@ -31,502 +32,30 @@ class String
|
|
31
32
|
"'$#{cnt}'"
|
32
33
|
end
|
33
34
|
yield tmpstr
|
34
|
-
# tmpstr2.gsub(UNMASK_RGX) do |_m|
|
35
|
-
# k = Regexp.last_match(2).to_s
|
36
|
-
# "'#{Regexp.last_match(1)}#{repl[k]}#{Regexp.last_match(3)}'"
|
37
|
-
# end
|
38
35
|
end
|
39
36
|
end
|
40
37
|
|
41
38
|
# filter base class and subclass in our OData namespace
|
42
39
|
module OData
|
43
|
-
#
|
44
|
-
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
48
|
-
|
49
|
-
def get_qualified_fn(dtcx)
|
50
|
-
seqtn = if dtcx.respond_to? :table_name
|
51
|
-
@assoc ? @assoc.to_sym : dtcx.table_name
|
52
|
-
else
|
53
|
-
@assoc ? @assoc.to_sym : dtcx.model.table_name
|
54
|
-
end
|
55
|
-
Sequel[seqtn][@fn.to_sym]
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# base class for filtering
|
60
|
-
class Filter
|
61
|
-
attr_reader :assoc
|
62
|
-
attr_reader :assocs
|
63
|
-
QUO = /'((?:[^']|(?:\'{2}))*)'/.freeze
|
64
|
-
NOTQ = /((?:[^'\s\)])*)/.freeze
|
65
|
-
|
66
|
-
def initialize(matx)
|
67
|
-
@matx = matx
|
68
|
-
@assocs = Set.new
|
69
|
-
end
|
70
|
-
|
71
|
-
class << self
|
72
|
-
attr_reader :regexp
|
73
|
-
end
|
74
|
-
|
75
|
-
# input : the filter string
|
76
|
-
# returns a filter object that should have a apply_to(cx) method
|
77
|
-
def self.new_by_parse(filterstr, dtset = nil)
|
78
|
-
# handle complex flat expressions (flat = without relationships)
|
79
|
-
# with Sequel
|
80
|
-
ComplexFilter.new(filterstr, dtset)
|
81
|
-
end
|
82
|
-
|
83
|
-
# try to handle a simple expression like < fieldname EQ value >
|
84
|
-
# or < substringof(a,b) >
|
85
|
-
# NO parenthesis and no AND/OR handled here
|
86
|
-
def self.new_full_match(filterstr)
|
87
|
-
full_reg = Regexp.new(/\A#{regexp}\z/)
|
88
|
-
# this needs to return nil, not false, or some testcase will fail
|
89
|
-
return nil unless (matx = filterstr.match(full_reg))
|
90
|
-
|
91
|
-
# actually it's self.new(matx)
|
92
|
-
new(matx)
|
93
|
-
end
|
94
|
-
|
95
|
-
def apply_to_dataset(dtcx)
|
96
|
-
dtcx
|
97
|
-
end
|
98
|
-
|
99
|
-
def apply_associations(dtcx)
|
100
|
-
@assocs.each { |aj| dtcx = dtcx.association_join(aj) }
|
101
|
-
dtcx
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
# well...
|
106
|
-
class EqualFilter < Filter
|
107
|
-
include FilterWithAssoc
|
108
|
-
EQL = '[eE][qQ]|[LlgGNn][eETt]'.freeze
|
109
|
-
|
110
|
-
def self.qualified_regexp(paths_rx)
|
111
|
-
/\s*(#{paths_rx})\s+(#{EQL})\s+(?:#{QUO}|#{NOTQ})\s*/
|
112
|
-
end
|
113
|
-
|
114
|
-
def initialize(matx)
|
115
|
-
super(matx)
|
116
|
-
@fn = matx[1]
|
117
|
-
get_assoc if @fn
|
118
|
-
|
119
|
-
@val = matx[3] ? matx[3].gsub("''", "'") : matx[4]
|
120
|
-
|
121
|
-
@op = matx[2].downcase
|
122
|
-
end
|
123
|
-
|
124
|
-
# ugly hack... but working
|
125
|
-
def gen_sql(dtcx)
|
126
|
-
# handle ambiguous column names (joins) by qualifiying the names
|
127
|
-
# seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
|
128
|
-
# handle ambiguous column names (joins) by qualifiying the names
|
129
|
-
# for the main table as well
|
130
|
-
# TODO: find a better way to differentiate between the two types
|
131
|
-
# of dtcx : Model or Dataset
|
132
|
-
|
133
|
-
# DONE: same handling for order and substring and everywhere where
|
134
|
-
# qualified fieldnames could be needed (uses attrib path regexp)
|
135
|
-
x = apply_to_dataset(dtcx)
|
136
|
-
# ugly ugly hack :-(
|
137
|
-
y = x.unordered.sql.sub(x.unfiltered.unordered.sql + ' WHERE', '')
|
138
|
-
y
|
139
|
-
end
|
140
|
-
|
141
|
-
def apply_op_to_dataset(dtcx, lefval, rightval)
|
142
|
-
case @op
|
143
|
-
when 'eq'
|
144
|
-
dtcx.where(lefval => rightval)
|
145
|
-
when 'ne'
|
146
|
-
dtcx.exclude(lefval => rightval)
|
147
|
-
when 'le'
|
148
|
-
dtcx.where { lefval <= rightval }
|
149
|
-
when 'ge'
|
150
|
-
dtcx.where { lefval >= rightval }
|
151
|
-
when 'lt'
|
152
|
-
dtcx.where { lefval < rightval }
|
153
|
-
when 'gt'
|
154
|
-
dtcx.where { lefval > rightval }
|
155
|
-
else
|
156
|
-
raise OData::FilterParseError
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
def apply_to_dataset(dtcx)
|
161
|
-
seqfn = get_qualified_fn(dtcx)
|
162
|
-
# using @val in Procs below does not work reliably. Using a local var
|
163
|
-
# seems to work better
|
164
|
-
argval = @val
|
165
|
-
|
166
|
-
apply_op_to_dataset(dtcx, seqfn, argval)
|
167
|
-
end
|
168
|
-
|
169
|
-
def apply_to_object(inp)
|
170
|
-
case @op
|
171
|
-
when 'eq'
|
172
|
-
get_value(inp) == @val
|
173
|
-
when 'ne'
|
174
|
-
get_value(inp) != @val
|
175
|
-
else
|
176
|
-
raise OData::FilterParseError
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
class Func2a_EqualFilter < EqualFilter
|
182
|
-
FUNC = 'concat'.freeze
|
183
|
-
attr_reader :funcname
|
184
|
-
|
185
|
-
# like for concat(Name, 'xyz') EQ 'blablux'
|
186
|
-
def self.qualified_regexp(pathsrx)
|
187
|
-
/\s*(#{FUNC})\((#{pathsrx}),\s*(?:#{QUO})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
188
|
-
end
|
189
|
-
|
190
|
-
def initialize(matx)
|
191
|
-
# super(matx)
|
192
|
-
@funcname = matx[1]
|
193
|
-
@fn = matx[2]
|
194
|
-
get_assoc if @fn
|
195
|
-
@farg = matx[3].gsub("''", "'")
|
196
|
-
@op = matx[4].downcase
|
197
|
-
|
198
|
-
@val = matx[5].gsub("''", "'")
|
199
|
-
end
|
200
|
-
|
201
|
-
def apply_to_dataset(dtcx)
|
202
|
-
seqfn = get_qualified_fn(dtcx)
|
203
|
-
# using @val in Procs below does not work reliably. Using a local var
|
204
|
-
# seems to work better
|
205
|
-
argval = @val
|
206
|
-
seqfunc = case @funcname
|
207
|
-
when 'concat'
|
208
|
-
Sequel.join([seqfn, @farg])
|
209
|
-
else
|
210
|
-
raise OData::FilterParseError
|
211
|
-
end
|
212
|
-
apply_op_to_dataset(dtcx, seqfunc, argval)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
class Func2b_EqualFilter < EqualFilter
|
216
|
-
FUNC = 'concat'.freeze
|
217
|
-
attr_reader :funcname
|
218
|
-
|
219
|
-
# like for concat('xyz', Name) EQ 'blablux'
|
220
|
-
def self.qualified_regexp(pathsrx)
|
221
|
-
/\s*(#{FUNC})\((?:#{QUO}),\s*(#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
222
|
-
end
|
223
|
-
|
224
|
-
def initialize(matx)
|
225
|
-
# super(matx)
|
226
|
-
@funcname = matx[1]
|
227
|
-
@fn = matx[3]
|
228
|
-
get_assoc if @fn
|
229
|
-
@farg = matx[2].gsub("''", "'")
|
230
|
-
@op = matx[4].downcase
|
231
|
-
|
232
|
-
@val = matx[5].gsub("''", "'")
|
233
|
-
end
|
234
|
-
|
235
|
-
def apply_to_dataset(dtcx)
|
236
|
-
seqfn = get_qualified_fn(dtcx)
|
237
|
-
# using @val in Procs below does not work reliably. Using a local var
|
238
|
-
# seems to work better
|
239
|
-
argval = @val
|
240
|
-
seqfunc = case @funcname
|
241
|
-
when 'concat'
|
242
|
-
Sequel.join([@farg, seqfn])
|
243
|
-
else
|
244
|
-
raise OData::FilterParseError
|
245
|
-
end
|
246
|
-
apply_op_to_dataset(dtcx, seqfunc, argval)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
class Func2c_EqualFilter < EqualFilter
|
251
|
-
FUNC = 'concat'.freeze
|
252
|
-
attr_reader :funcname
|
253
|
-
|
254
|
-
# like for concat(first_ame, last_name) EQ 'blablux'
|
255
|
-
def self.qualified_regexp(pathsrx)
|
256
|
-
/\s*(#{FUNC})\((#{pathsrx}),\s*(#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO})\s*/
|
257
|
-
end
|
258
|
-
|
259
|
-
def get_assoc
|
260
|
-
@assoc, @fn = @fn.split('/') if @fn.include?('/')
|
261
|
-
assoc1, @fn1 = @fn1.split('/') if @fn1.include?('/')
|
262
|
-
return unless assoc1 != @assoc
|
263
|
-
|
264
|
-
# TODO... handle this
|
265
|
-
raise OData::ServerError
|
266
|
-
end
|
267
|
-
|
268
|
-
def get_qualified_fn1(dtcx)
|
269
|
-
seqtn = if dtcx.respond_to? :table_name
|
270
|
-
@assoc ? @assoc.to_sym : dtcx.table_name
|
271
|
-
else
|
272
|
-
@assoc ? @assoc.to_sym : dtcx.model.table_name
|
273
|
-
end
|
274
|
-
Sequel[seqtn][@fn1.to_sym]
|
275
|
-
end
|
276
|
-
|
277
|
-
def initialize(matx)
|
278
|
-
# super(matx)
|
279
|
-
@funcname = matx[1]
|
280
|
-
@fn = matx[2]
|
281
|
-
@fn1 = matx[3]
|
282
|
-
get_assoc if @fn
|
283
|
-
|
284
|
-
@op = matx[4].downcase
|
285
|
-
|
286
|
-
@val = matx[5].gsub("''", "'")
|
287
|
-
end
|
288
|
-
|
289
|
-
def apply_to_dataset(dtcx)
|
290
|
-
seqfn = get_qualified_fn(dtcx)
|
291
|
-
seqfn1 = get_qualified_fn1(dtcx)
|
292
|
-
# using @val in Procs below does not work reliably. Using a local var
|
293
|
-
# seems to work better
|
294
|
-
argval = @val
|
295
|
-
seqfunc = case @funcname
|
296
|
-
when 'concat'
|
297
|
-
Sequel.join([seqfn, seqfn1])
|
298
|
-
else
|
299
|
-
raise OData::FilterParseError
|
300
|
-
end
|
301
|
-
apply_op_to_dataset(dtcx, seqfunc, argval)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
|
305
|
-
# equality expressions with functions having 1 parameter
|
306
|
-
# like for length(name) eq 2
|
307
|
-
class FuncEqualFilter < EqualFilter
|
308
|
-
FUNC = 'length|trim|tolower|toupper'.freeze
|
309
|
-
|
310
|
-
attr_reader :funcname
|
311
|
-
|
312
|
-
def self.qualified_regexp(pathsrx)
|
313
|
-
/\s*(#{FUNC})\((#{pathsrx})\)\s+(#{EQL})\s+(?:#{QUO}|#{NOTQ})\s*/
|
314
|
-
end
|
315
|
-
|
316
|
-
def initialize(matx)
|
317
|
-
super(matx)
|
318
|
-
@funcname = matx[1]
|
319
|
-
@fn = matx[2]
|
320
|
-
get_assoc if @fn
|
321
|
-
|
322
|
-
@op = matx[3].downcase
|
323
|
-
|
324
|
-
@val = matx[4] ? matx[4].gsub("''", "'") : matx[5]
|
325
|
-
end
|
326
|
-
|
327
|
-
def apply_to_dataset(dtcx)
|
328
|
-
seqfn = get_qualified_fn(dtcx)
|
329
|
-
# using @val in Procs below does not work reliably. Using a local var
|
330
|
-
# seems to work better
|
331
|
-
argval = @val
|
332
|
-
seqfunc = case @funcname
|
333
|
-
when 'length'
|
334
|
-
argval = Sequel.lit(@val)
|
335
|
-
Sequel.char_length(seqfn)
|
336
|
-
when 'trim'
|
337
|
-
Sequel.trim(seqfn)
|
338
|
-
when 'toupper'
|
339
|
-
Sequel.function(:upper, seqfn)
|
340
|
-
when 'tolower'
|
341
|
-
Sequel.function(:lower, seqfn)
|
342
|
-
else
|
343
|
-
raise OData::FilterParseError
|
344
|
-
end
|
345
|
-
|
346
|
-
apply_op_to_dataset(dtcx, seqfunc, argval)
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
# keyword based
|
351
|
-
class FilterWithKeyword < Filter
|
352
|
-
attr_reader :keyword
|
353
|
-
end
|
354
|
-
|
355
|
-
# for substringof('value', field)
|
356
|
-
class SubstringOfFilterSig1 < FilterWithKeyword
|
357
|
-
include FilterWithAssoc
|
358
|
-
|
359
|
-
def self.qualified_regexp(paths_rx)
|
360
|
-
# Note: QUOTED_RGX captures the string content without
|
361
|
-
# start and end quote (no need to un-quote)
|
362
|
-
# NOTQ_RGX captures an not-quoted value argument
|
363
|
-
# (like for numbers)
|
364
|
-
/(substringof)\((?:#{QUO}|#{NOTQ}),\s*(#{paths_rx})\)/
|
365
|
-
end
|
366
|
-
|
367
|
-
def initialize(matx)
|
368
|
-
super(matx)
|
369
|
-
|
370
|
-
@fn = matx[4]
|
371
|
-
get_assoc if @fn
|
372
|
-
# @val = matx[2].strip_single_quote
|
373
|
-
@val = matx[2] ? matx[2].gsub("''", "'") : matx[3]
|
374
|
-
@keyword = matx[1]
|
375
|
-
end
|
376
|
-
|
377
|
-
def whcl(dtcx)
|
378
|
-
# handle ambiguous column names (joins) by qualifiying the names
|
379
|
-
# seqfn = @assoc ? Sequel[@assoc.to_sym][@fn.to_sym] : @fn.to_sym
|
380
|
-
seqfn = get_qualified_fn(dtcx)
|
381
|
-
Sequel.like(seqfn, "%#{@val}%")
|
382
|
-
end
|
383
|
-
|
384
|
-
def gen_sql(dtcx)
|
385
|
-
sql_ = ''
|
386
|
-
dt = dtcx.respond_to?(:dataset) ? dtcx.dataset : dtcx
|
387
|
-
dt.literal_append(sql_, whcl(dt))
|
388
|
-
sql_
|
389
|
-
end
|
390
|
-
|
391
|
-
def apply_to_dataset(dtcx)
|
392
|
-
dtcx.where(whcl(dtcx))
|
393
|
-
end
|
394
|
-
end
|
395
|
-
# for substringof(field,'value')
|
396
|
-
class SubstringOfFilterSig2 < FilterWithKeyword
|
397
|
-
include FilterWithAssoc
|
398
|
-
|
399
|
-
def initialize(matx)
|
400
|
-
super(matx)
|
401
|
-
@fn = matx[2]
|
402
|
-
get_assoc if @fn
|
403
|
-
@val = matx[3]
|
404
|
-
@keyword = matx[1]
|
405
|
-
|
406
|
-
raise FilterParseError
|
407
|
-
end
|
408
|
-
|
409
|
-
def apply_to_dataset(dtcx)
|
410
|
-
dtcx
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
# Filter by start/end with
|
415
|
-
class StartOrEndsWithFilter < FilterWithKeyword
|
416
|
-
include FilterWithAssoc
|
417
|
-
# DONE: better handle quotes vs not-quotes. Unblanced quotes are now handled
|
418
|
-
# example : startswith(year, 190')
|
419
|
-
|
420
|
-
def self.qualified_regexp(paths)
|
421
|
-
/(endswith|startswith)\((#{paths}),\s*(?:#{QUO}|#{NOTQ})\s*\)/
|
422
|
-
end
|
423
|
-
|
424
|
-
def initialize(matx)
|
425
|
-
super(matx)
|
426
|
-
# DONE: the regexp should only match on known field names...
|
427
|
-
# --> use qualified_regexp(attr_paths_rgx)
|
428
|
-
@fn = matx[2]
|
429
|
-
get_assoc if @fn
|
430
|
-
# double quotes need to be unescaped
|
431
|
-
@val = matx[3] ? matx[3].gsub("''", "'") : matx[4]
|
432
|
-
@keyword = matx[1]
|
433
|
-
end
|
434
|
-
|
435
|
-
def whcl(dtcx)
|
436
|
-
seqfn = get_qualified_fn(dtcx)
|
437
|
-
case @keyword
|
438
|
-
when 'startswith'
|
439
|
-
Sequel.like(seqfn, "#{@val}%")
|
440
|
-
when 'endswith'
|
441
|
-
Sequel.like(seqfn, "%#{@val}")
|
442
|
-
else
|
443
|
-
raise FilterParseError
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
def gen_sql(dtcx)
|
448
|
-
sql_ = ''
|
449
|
-
dt = dtcx.respond_to?(:dataset) ? dtcx.dataset : dtcx
|
450
|
-
dt.literal_append(sql_, whcl(dt))
|
451
|
-
# puts "in gen_sql : sql_ == " , sql_
|
452
|
-
sql_
|
40
|
+
# should handle everything by parsing
|
41
|
+
class FilterByParse
|
42
|
+
def initialize(filterstr, jh)
|
43
|
+
@filterstr = filterstr.dup
|
44
|
+
@ast = OData::Filter::Parser.new(@filterstr).build
|
45
|
+
@jh = jh
|
453
46
|
end
|
454
47
|
|
455
48
|
def apply_to_dataset(dtcx)
|
456
|
-
|
49
|
+
filtexpr = @ast.sequel_expr(@jh)
|
50
|
+
dtcx = @jh.dataset(dtcx).where(filtexpr).select_all(@jh.start_model.table_name)
|
457
51
|
end
|
458
|
-
end
|
459
52
|
|
460
|
-
|
461
|
-
|
462
|
-
SubstringOfFilterSig2,
|
463
|
-
StartOrEndsWithFilter].freeze
|
464
|
-
def Filter.new_by_simple_full_match(filterstr)
|
465
|
-
new_filt_obj = nil
|
466
|
-
OData::FILTER_CLASSES.find do |fklas|
|
467
|
-
new_filt_obj = fklas.new_full_match(filterstr)
|
468
|
-
end
|
469
|
-
new_filt_obj
|
470
|
-
end
|
471
|
-
|
472
|
-
# handles some combinations
|
473
|
-
class ComplexFilter < Filter
|
474
|
-
# list of active Subfilter classes. The ordering is important
|
475
|
-
SUBFILTERS = [EqualFilter,
|
476
|
-
FuncEqualFilter,
|
477
|
-
Func2a_EqualFilter,
|
478
|
-
Func2b_EqualFilter,
|
479
|
-
Func2c_EqualFilter,
|
480
|
-
SubstringOfFilterSig1,
|
481
|
-
StartOrEndsWithFilter].freeze
|
482
|
-
# for counting number of AND OR's
|
483
|
-
ANDORRGX = /(AND|OR\s+)/i.freeze
|
484
|
-
|
485
|
-
# for detecting consecutive AND OR
|
486
|
-
ANDORERRRGX = /(AND|OR)\s+[\(\)]*(AND|OR)/i.freeze
|
487
|
-
|
488
|
-
def initialize(filterstr, dtset)
|
489
|
-
super(nil)
|
490
|
-
@filterstr = filterstr.dup
|
491
|
-
|
492
|
-
@attrib_paths_url_regexp = if dtset.is_a? Sequel::Model::ClassMethods
|
493
|
-
dtset.attrib_paths_url_regexp.dup
|
494
|
-
else
|
495
|
-
dtset.model.attrib_paths_url_regexp.dup
|
496
|
-
end
|
497
|
-
|
498
|
-
@active_subfs = []
|
499
|
-
@osql = filterstr.dup
|
500
|
-
@dt = dtset
|
501
|
-
|
502
|
-
md = nil
|
503
|
-
@osql.with_mask_quoted_substrings! do |s|
|
504
|
-
unless (@and_or_err = ANDORERRRGX.match(s))
|
505
|
-
md = ANDORRGX.match(s)
|
506
|
-
|
507
|
-
@expected_count = md.nil? ? 1 : md.size
|
508
|
-
SUBFILTERS.each { |klass| init_subfilter(klass) }
|
509
|
-
end
|
510
|
-
end
|
53
|
+
def sequel_expr
|
54
|
+
@ast.sequel_expr(@jh)
|
511
55
|
end
|
512
56
|
|
513
57
|
def parse_error?
|
514
|
-
@
|
515
|
-
end
|
516
|
-
|
517
|
-
def init_subfilter(subfiltclass)
|
518
|
-
rgx = subfiltclass.qualified_regexp(@attrib_paths_url_regexp)
|
519
|
-
@osql.gsub!(rgx) do |_mx|
|
520
|
-
subf = subfiltclass.new(Regexp.last_match)
|
521
|
-
@active_subfs << subf
|
522
|
-
@assocs.add(subf.assoc.to_sym) if subf.assoc
|
523
|
-
subf.gen_sql(@dt)
|
524
|
-
end
|
525
|
-
end
|
526
|
-
|
527
|
-
def apply_to_dataset(dtcx)
|
528
|
-
# new Sequel 5.6 requires -- lit --
|
529
|
-
dtcx.where(Sequel.lit(@osql))
|
58
|
+
@ast.kind_of? StandardError
|
530
59
|
end
|
531
60
|
end
|
532
61
|
end
|