safrano 0.4.3 → 0.5.1

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Dir/iter.rb +18 -0
  3. data/lib/core_ext/Hash/transform.rb +21 -0
  4. data/lib/core_ext/Integer/edm.rb +13 -0
  5. data/lib/core_ext/REXML/Document/output.rb +16 -0
  6. data/lib/core_ext/String/convert.rb +25 -0
  7. data/lib/core_ext/String/edm.rb +13 -0
  8. data/lib/core_ext/dir.rb +3 -0
  9. data/lib/core_ext/hash.rb +3 -0
  10. data/lib/core_ext/integer.rb +3 -0
  11. data/lib/core_ext/rexml.rb +3 -0
  12. data/lib/core_ext/string.rb +5 -0
  13. data/lib/odata/attribute.rb +8 -4
  14. data/lib/odata/batch.rb +9 -7
  15. data/lib/odata/collection.rb +139 -642
  16. data/lib/odata/collection_filter.rb +18 -42
  17. data/lib/odata/collection_media.rb +111 -54
  18. data/lib/odata/collection_order.rb +5 -2
  19. data/lib/odata/common_logger.rb +2 -0
  20. data/lib/odata/complex_type.rb +196 -0
  21. data/lib/odata/edm/primitive_types.rb +184 -0
  22. data/lib/odata/entity.rb +78 -123
  23. data/lib/odata/error.rb +170 -37
  24. data/lib/odata/expand.rb +20 -17
  25. data/lib/odata/filter/base.rb +9 -1
  26. data/lib/odata/filter/error.rb +43 -27
  27. data/lib/odata/filter/parse.rb +39 -25
  28. data/lib/odata/filter/sequel.rb +112 -56
  29. data/lib/odata/filter/sequel_function_adapter.rb +50 -49
  30. data/lib/odata/filter/token.rb +21 -18
  31. data/lib/odata/filter/tree.rb +78 -44
  32. data/lib/odata/function_import.rb +168 -0
  33. data/lib/odata/model_ext.rb +641 -0
  34. data/lib/odata/navigation_attribute.rb +9 -24
  35. data/lib/odata/relations.rb +5 -5
  36. data/lib/odata/select.rb +17 -5
  37. data/lib/odata/transition.rb +71 -0
  38. data/lib/odata/url_parameters.rb +100 -24
  39. data/lib/odata/walker.rb +18 -10
  40. data/lib/safrano.rb +18 -38
  41. data/lib/safrano/contract.rb +141 -0
  42. data/lib/safrano/core.rb +24 -106
  43. data/lib/safrano/core_ext.rb +13 -0
  44. data/lib/safrano/deprecation.rb +73 -0
  45. data/lib/safrano/multipart.rb +29 -24
  46. data/lib/safrano/rack_app.rb +62 -63
  47. data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -1
  48. data/lib/safrano/request.rb +96 -38
  49. data/lib/safrano/response.rb +4 -2
  50. data/lib/safrano/sequel_join_by_paths.rb +2 -2
  51. data/lib/safrano/service.rb +156 -110
  52. data/lib/safrano/version.rb +3 -1
  53. data/lib/sequel/plugins/join_by_paths.rb +6 -19
  54. metadata +30 -11
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './token.rb'
4
- require_relative './tree.rb'
5
- require_relative './error.rb'
3
+ require_relative './token'
4
+ require_relative './tree'
5
+ require_relative './error'
6
6
 
7
- # top level OData namespace
8
- module OData
7
+ # top level Safrano namespace
8
+ module Safrano
9
9
  # for handling $filter
10
10
  module Filter
11
11
  # Parser for $filter input
12
12
  class Parser
13
13
  include Token
14
14
  attr_reader :cursor
15
+ attr_reader :error
16
+
15
17
  def initialize(input)
16
18
  @tree = RootTree.new
17
19
  @cursor = @tree
@@ -20,10 +22,14 @@ module OData
20
22
  @binop_stack = []
21
23
  end
22
24
 
25
+ def server_error
26
+ (@error = ::Safrano::ServerError)
27
+ end
28
+
23
29
  def grow_at_cursor(child)
24
- raise 'unknown BroGrammingError' if @cursor.nil?
30
+ return server_error if @cursor.nil?
25
31
 
26
- @cursor.attach(child)
32
+ @cursor.attach(child).tap_error { |err| return (@error = err) }
27
33
  @cursor = child
28
34
  end
29
35
 
@@ -42,7 +48,7 @@ module OData
42
48
  def insert_before_cursor(node)
43
49
  left = detach_cursor
44
50
  grow_at_cursor(node)
45
- @cursor.attach(left)
51
+ @cursor.attach(left).tap_error { |err| return (@error = err) }
46
52
  end
47
53
 
48
54
  def invalid_separator_error(tok, typ)
@@ -58,12 +64,7 @@ module OData
58
64
  end
59
65
 
60
66
  def with_accepted(tok, typ)
61
- acc, err = @cursor.accept?(tok, typ)
62
- if acc
63
- yield
64
- else
65
- @error = err
66
- end
67
+ (err = @cursor.accept?(tok, typ)) ? (@error = err) : yield
67
68
  end
68
69
 
69
70
  def build
@@ -71,15 +72,19 @@ module OData
71
72
  case typ
72
73
  when :FuncTree
73
74
  with_accepted(tok, typ) { grow_at_cursor(FuncTree.new(tok)) }
75
+
74
76
  when :Delimiter
75
77
  case tok
76
78
  when '('
77
79
  with_accepted(tok, typ) do
78
80
  grow_at_cursor(IdentityFuncTree.new) unless @cursor.is_a? FuncTree
79
- openarg = ArgTree.new('(')
80
- @stack << openarg
81
- grow_at_cursor(openarg)
81
+ unless @error
82
+ openarg = ArgTree.new('(')
83
+ @stack << openarg
84
+ grow_at_cursor(openarg)
85
+ end
82
86
  end
87
+
83
88
  when ')'
84
89
  break invalid_closing_delimiter_error(tok, typ) unless (@cursor = @stack.pop)
85
90
 
@@ -123,6 +128,7 @@ module OData
123
128
  end
124
129
  insert_before_cursor(binoptr)
125
130
  end
131
+
126
132
  when :BinopArithm
127
133
  with_accepted(tok, typ) do
128
134
  binoptr = BinopArithm.new(tok)
@@ -145,6 +151,12 @@ module OData
145
151
  grow_at_cursor(Literal.new(tok))
146
152
  end
147
153
 
154
+ when :NullLiteral
155
+ with_accepted(tok, typ) do
156
+ @cursor.update_state(tok, typ)
157
+ grow_at_cursor(NullLiteral.new(tok))
158
+ end
159
+
148
160
  when :Qualit
149
161
  with_accepted(tok, typ) do
150
162
  @cursor.update_state(tok, typ)
@@ -162,19 +174,21 @@ module OData
162
174
  @cursor.update_state(tok, typ)
163
175
  grow_at_cursor(FPNumber.new(tok))
164
176
  end
177
+
165
178
  when :unmatchedQuote
166
179
  break unmatched_quote_error(tok, typ)
180
+
181
+ when :space
182
+ with_accepted(tok, typ) do
183
+ @cursor.update_state(tok, typ)
184
+ end
167
185
  else
168
- raise 'Severe Error'
186
+ server_error
169
187
  end
170
- break if @error
171
- end
172
- begin
173
- @tree.check_types unless @error
174
- rescue ErrorInvalidArgumentType => e
175
- @error = e
188
+ break(@error) if @error
176
189
  end
177
- @error || @tree
190
+ (@error = @tree.check_types) unless @error
191
+ @error ? @error : Contract.valid(@tree)
178
192
  end
179
193
  end
180
194
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './base.rb'
4
- require_relative './sequel_function_adapter.rb'
3
+ require_relative './base'
4
+ require_relative './sequel_function_adapter'
5
5
 
6
- module OData
6
+ module Safrano
7
7
  module Filter
8
8
  # Base class for Leaves, Trees, RootTrees etc
9
9
  # class Node
@@ -16,8 +16,9 @@ module OData
16
16
  # RootTrees have childrens but no parent
17
17
  class RootTree
18
18
  def apply_to_dataset(dtcx, jh)
19
- filtexpr = @children.first.leuqes(jh)
20
- jh.dataset(dtcx).where(filtexpr).select_all(jh.start_model.table_name)
19
+ @children.first.leuqes(jh).if_valid do |filtexpr|
20
+ jh.dataset(dtcx).where(filtexpr).select_all(jh.start_model.table_name)
21
+ end
21
22
  end
22
23
 
23
24
  def sequel_expr(jh)
@@ -36,62 +37,102 @@ module OData
36
37
  def leuqes(jh)
37
38
  case @value
38
39
  when :startswith
39
- Sequel.like(args[0].leuqes(jh),
40
- args[1].leuqes_starts_like(jh))
40
+ Contract.collect_result!(args[0].leuqes(jh),
41
+ args[1].leuqes_starts_like(jh)) do |l0, l1|
42
+ Sequel.like(l0, l1)
43
+ end
44
+
41
45
  when :endswith
42
- Sequel.like(args[0].leuqes(jh),
43
- args[1].leuqes_ends_like(jh))
46
+ Contract.collect_result!(args[0].leuqes(jh),
47
+ args[1].leuqes_ends_like(jh)) do |l0, l1|
48
+ Sequel.like(l0, l1)
49
+ end
50
+
44
51
  when :substringof
45
52
 
46
53
  # there are multiple possible argument types (but all should return edm.string)
47
54
  if args[0].is_a?(QString)
48
55
  # substringof('Rhum', name) -->
49
56
  # name contains substr 'Rhum'
50
- Sequel.like(args[1].leuqes(jh),
51
- args[0].leuqes_substringof_sig1(jh))
57
+ Contract.collect_result!(args[1].leuqes(jh),
58
+ args[0].leuqes_substringof_sig1(jh)) do |l1, l0|
59
+ Sequel.like(l1, l0)
60
+ end
61
+
52
62
  # special non standard (ui5 client) case ?
53
63
  elsif args[0].is_a?(Literal) && args[1].is_a?(Literal)
54
- Sequel.like(args[1].leuqes(jh),
55
- args[0].leuqes_substringof_sig1(jh))
64
+ Contract.collect_result!(args[1].leuqes(jh),
65
+ args[0].leuqes_substringof_sig1(jh)) do |l1, l0|
66
+ Sequel.like(l1, l0)
67
+ end
68
+
56
69
  elsif args[1].is_a?(QString)
57
70
  substringof_sig2(jh) # adapter specific
58
71
  else
59
72
  # TODO... actually not supported?
60
- raise OData::Filter::Parser::ErrorFunctionArgumentType
73
+ raise Safrano::Filter::Parser::ErrorFunctionArgumentType
61
74
  end
62
75
  when :concat
63
- Sequel.join([args[0].leuqes(jh),
64
- args[1].leuqes(jh)])
76
+ Contract.collect_result!(args[0].leuqes(jh),
77
+ args[1].leuqes(jh)) do |l0, l1|
78
+ Sequel.join([l0, l1])
79
+ end
80
+
65
81
  when :length
66
- Sequel.char_length(args.first.leuqes(jh))
82
+ args.first.leuqes(jh)
83
+ .map_result! { |l| Sequel.char_length(l) }
84
+
67
85
  when :trim
68
- Sequel.trim(args.first.leuqes(jh))
86
+ args.first.leuqes(jh)
87
+ .map_result! { |l| Sequel.trim(l) }
88
+
69
89
  when :toupper
70
- Sequel.function(:upper, args.first.leuqes(jh))
90
+ args.first.leuqes(jh)
91
+ .map_result! { |l| Sequel.function(:upper, l) }
92
+
71
93
  when :tolower
72
- Sequel.function(:lower, args.first.leuqes(jh))
94
+ args.first.leuqes(jh)
95
+ .map_result! { |l| Sequel.function(:lower, l) }
96
+
73
97
  # all datetime funcs are adapter specific (because sqlite does not have extract)
74
98
  when :year
75
- year(jh)
99
+ args.first.leuqes(jh)
100
+ .map_result! { |l| year(l) }
101
+
76
102
  when :month
77
- month(jh)
103
+ args.first.leuqes(jh)
104
+ .map_result! { |l| month(l) }
105
+
78
106
  when :second
79
- second(jh)
107
+ args.first.leuqes(jh)
108
+ .map_result! { |l| second(l) }
109
+
80
110
  when :minute
81
- minute(jh)
111
+ args.first.leuqes(jh)
112
+ .map_result! { |l| minute(l) }
113
+
82
114
  when :hour
83
- hour(jh)
115
+ args.first.leuqes(jh)
116
+ .map_result! { |l| hour(l) }
117
+
84
118
  when :day
85
- day(jh)
119
+ args.first.leuqes(jh)
120
+ .map_result! { |l| day(l) }
121
+
86
122
  # math functions
87
123
  when :round
88
- Sequel.function(:round, args.first.leuqes(jh))
124
+ args.first.leuqes(jh)
125
+ .map_result! { |l| Sequel.function(:round, l) }
126
+
89
127
  when :floor
90
- floor(jh)
128
+ args.first.leuqes(jh)
129
+ .if_valid { |l| floor(l) }
130
+
91
131
  when :ceiling
92
- ceiling(jh)
132
+ args.first.leuqes(jh)
133
+ .if_valid { |l| ceiling(l) }
93
134
  else
94
- raise OData::FilterParseError
135
+ Safrano::FilterParseError
95
136
  end
96
137
  end
97
138
  end
@@ -110,9 +151,9 @@ module OData
110
151
  def leuqes(jh)
111
152
  case @value
112
153
  when :not
113
- Sequel.~(@children.first.leuqes(jh))
154
+ @children.first.leuqes(jh).map_result! { |l| Sequel.~(l) }
114
155
  else
115
- raise OData::FilterParseError
156
+ Safrano::FilterParseError
116
157
  end
117
158
  end
118
159
  end
@@ -138,12 +179,19 @@ module OData
138
179
  when :and
139
180
  :AND
140
181
  else
141
- raise OData::FilterParseError
182
+ return Safrano::FilterParseError
142
183
  end
143
-
144
- Sequel::SQL::BooleanExpression.new(leuqes_op,
145
- @children[0].leuqes(jh),
146
- @children[1].leuqes(jh))
184
+ Contract.collect_result!(@children[0].leuqes(jh),
185
+ @children[1].leuqes(jh)) do |c0, c1|
186
+ if c1 == NullLiteral::LEUQES
187
+ if @value == :eq
188
+ leuqes_op = :IS
189
+ elsif @value == :ne
190
+ leuqes_op = :'IS NOT'
191
+ end
192
+ end
193
+ Sequel::SQL::BooleanExpression.new(leuqes_op, c0, c1)
194
+ end
147
195
  end
148
196
  end
149
197
 
@@ -162,12 +210,12 @@ module OData
162
210
  when :mod
163
211
  :%
164
212
  else
165
- raise OData::FilterParseError
213
+ return Safrano::FilterParseError
166
214
  end
167
-
168
- Sequel::SQL::NumericExpression.new(leuqes_op,
169
- @children[0].leuqes(jh),
170
- @children[1].leuqes(jh))
215
+ Contract.collect_result!(@children[0].leuqes(jh),
216
+ @children[1].leuqes(jh)) do |c0, c1|
217
+ Sequel::SQL::NumericExpression.new(leuqes_op, c0, c1)
218
+ end
171
219
  end
172
220
  end
173
221
 
@@ -178,42 +226,42 @@ module OData
178
226
  # Numbers (floating point, ints, dec)
179
227
  class FPNumber
180
228
  def leuqes(_jh)
181
- Sequel.lit(@value)
229
+ success Sequel.lit(@value)
182
230
  end
183
231
 
184
232
  def leuqes_starts_like(_jh)
185
- "#{@value}%"
233
+ success "#{@value}%"
186
234
  end
187
235
 
188
236
  def leuqes_ends_like(_jh)
189
- "%#{@value}"
237
+ success "%#{@value}"
190
238
  end
191
239
 
192
240
  def leuqes_substringof_sig1(_jh)
193
- "%#{@value}%"
241
+ success "%#{@value}%"
194
242
  end
195
243
  end
196
244
 
197
245
  # Literals are unquoted words
198
246
  class Literal
199
247
  def leuqes(jh)
200
- raise OData::Filter::Parser::ErrorWrongColumnName unless jh.start_model.db_schema.key?(@value.to_sym)
248
+ return Safrano::FilterParseErrorWrongColumnName unless jh.start_model.db_schema.key?(@value.to_sym)
201
249
 
202
- Sequel[jh.start_model.table_name][@value.to_sym]
250
+ success Sequel[jh.start_model.table_name][@value.to_sym]
203
251
  end
204
252
 
205
253
  # non stantard extensions to support things like
206
254
  # substringof(Rhum, name) ????
207
255
  def leuqes_starts_like(_jh)
208
- "#{@value}%"
256
+ success "#{@value}%"
209
257
  end
210
258
 
211
259
  def leuqes_ends_like(_jh)
212
- "%#{@value}"
260
+ success "%#{@value}"
213
261
  end
214
262
 
215
263
  def leuqes_substringof_sig1(_jh)
216
- "%#{@value}%"
264
+ success "%#{@value}%"
217
265
  end
218
266
 
219
267
  def as_string
@@ -221,31 +269,39 @@ module OData
221
269
  end
222
270
  end
223
271
 
272
+ # Null
273
+ class NullLiteral
274
+ def leuqes(jh)
275
+ # Sequel's representation of NULL
276
+ success LEUQES
277
+ end
278
+ end
279
+
224
280
  # Qualit (qualified lits) are words separated by /
225
281
  class Qualit
226
282
  def leuqes(jh)
227
283
  jh.add(@path)
228
284
  talias = jh.start_model.get_alias_sym(@path)
229
- Sequel[talias][@attrib.to_sym]
285
+ success Sequel[talias][@attrib.to_sym]
230
286
  end
231
287
  end
232
288
 
233
289
  # Quoted Strings
234
290
  class QString
235
291
  def leuqes(_jh)
236
- @value
292
+ success @value
237
293
  end
238
294
 
239
295
  def leuqes_starts_like(_jh)
240
- "#{@value}%"
296
+ success "#{@value}%"
241
297
  end
242
298
 
243
299
  def leuqes_ends_like(_jh)
244
- "%#{@value}"
300
+ success "%#{@value}"
245
301
  end
246
302
 
247
303
  def leuqes_substringof_sig1(_jh)
248
- "%#{@value}%"
304
+ success "%#{@value}%"
249
305
  end
250
306
  end
251
307
  end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './tree.rb'
4
- require_relative './sequel.rb'
3
+ require_relative './tree'
4
+ require_relative './sequel'
5
5
 
6
- module OData
6
+ module Safrano
7
7
  module Filter
8
8
  # sqlite adapter specific function handler
9
9
  module FuncTreeSqlite
@@ -11,10 +11,11 @@ module OData
11
11
  # substringof(name, '__Route du Rhum__') -->
12
12
  # '__Route du Rhum__' contains name as a substring
13
13
  # sqlite uses instr()
14
-
15
- substr_func = Sequel.function(:instr, args[1].leuqes(jh), args[0].leuqes(jh))
16
-
17
- Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
14
+ Contract.collect_result!(args[1].leuqes(jh),
15
+ args[0].leuqes(jh)) do |l1, l0|
16
+ substr_func = Sequel.function(:instr, l1, l0)
17
+ Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
18
+ end
18
19
  end
19
20
  # %d day of month: 00
20
21
  # %f fractional seconds: SS.SSS
@@ -31,77 +32,73 @@ module OData
31
32
  # %% %
32
33
 
33
34
  # sqlite does not have extract but recommends to use strftime
34
- def year(jh)
35
- Sequel.function(:strftime, '%Y', args.first.leuqes(jh)).cast(:integer)
35
+ def year(lq)
36
+ Sequel.function(:strftime, '%Y', lq).cast(:integer)
36
37
  end
37
38
 
38
- def month(jh)
39
- Sequel.function(:strftime, '%m', args.first.leuqes(jh)).cast(:integer)
39
+ def month(lq)
40
+ Sequel.function(:strftime, '%m', lq).cast(:integer)
40
41
  end
41
42
 
42
- def second(jh)
43
- Sequel.function(:strftime, '%S', args.first.leuqes(jh)).cast(:integer)
43
+ def second(lq)
44
+ Sequel.function(:strftime, '%S', lq).cast(:integer)
44
45
  end
45
46
 
46
- def minute(jh)
47
- Sequel.function(:strftime, '%M', args.first.leuqes(jh)).cast(:integer)
47
+ def minute(lq)
48
+ Sequel.function(:strftime, '%M', lq).cast(:integer)
48
49
  end
49
50
 
50
- def hour(jh)
51
- Sequel.function(:strftime, '%H', args.first.leuqes(jh)).cast(:integer)
51
+ def hour(lq)
52
+ Sequel.function(:strftime, '%H', lq).cast(:integer)
52
53
  end
53
54
 
54
- def day(jh)
55
- Sequel.function(:strftime, '%d', args.first.leuqes(jh)).cast(:integer)
55
+ def day(lq)
56
+ Sequel.function(:strftime, '%d', lq).cast(:integer)
56
57
  end
57
58
 
58
- def floor(jh)
59
- raise OData::Filter::FunctionNotImplemented, "$filter function 'floor' is not implemented in sqlite adapter"
59
+ def floor(_lq)
60
+ Safrano::FilterFunctionNotImplementedError.new("$filter function 'floor' is not implemented in sqlite adapter")
60
61
  end
61
62
 
62
- def ceiling(jh)
63
- raise OData::Filter::FunctionNotImplemented, "$filter function 'ceiling' is not implemented in sqlite adapter"
63
+ def ceiling(_lq)
64
+ Safrano::FilterFunctionNotImplementedError.new("$filter function 'ceiling' is not implemented in sqlite adapter")
64
65
  end
65
66
  end
66
67
  # re-useable module with math floor/ceil functions for those adapters having these SQL funcs
67
68
  module MathFloorCeilFuncTree
68
- def floor(jh)
69
- Sequel.function(:floor, args.first.leuqes(jh))
69
+ def floor(lq)
70
+ success Sequel.function(:floor, lq)
70
71
  end
71
72
 
72
- def ceiling(jh)
73
- Sequel.function(:ceil, args.first.leuqes(jh))
73
+ def ceiling(lq)
74
+ success Sequel.function(:ceil, lq)
74
75
  end
75
76
  end
76
77
 
77
78
  # re-useable module with Datetime functions with extract()
78
79
  module DateTimeFuncTreeExtract
79
- def year(jh)
80
- args.first.leuqes(jh).extract(:year)
81
- end
82
-
83
- def year(jh)
84
- args.first.leuqes(jh).extract(:year)
80
+ def year(lq)
81
+ lq.extract(:year)
85
82
  end
86
83
 
87
- def month(jh)
88
- args.first.leuqes(jh).extract(:month)
84
+ def month(lq)
85
+ lq.extract(:month)
89
86
  end
90
87
 
91
- def second(jh)
92
- args.first.leuqes(jh).extract(:second)
88
+ def second(lq)
89
+ lq.extract(:second)
93
90
  end
94
91
 
95
- def minute(jh)
96
- args.first.leuqes(jh).extract(:minute)
92
+ def minute(lq)
93
+ lq.extract(:minute)
97
94
  end
98
95
 
99
- def hour(jh)
100
- args.first.leuqes(jh).extract(:hour)
96
+ def hour(lq)
97
+ lq.extract(:hour)
101
98
  end
102
99
 
103
- def day(jh)
104
- args.first.leuqes(jh).extract(:day)
100
+ def day(lq)
101
+ lq.extract(:day)
105
102
  end
106
103
  end
107
104
 
@@ -111,9 +108,11 @@ module OData
111
108
  # substringof(name, '__Route du Rhum__') -->
112
109
  # '__Route du Rhum__' contains name as a substring
113
110
  # postgres does not know instr() but has strpos
114
- substr_func = Sequel.function(:strpos, args[1].leuqes(jh), args[0].leuqes(jh))
115
-
116
- Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
111
+ Contract.collect_result!(args[1].leuqes(jh),
112
+ args[0].leuqes(jh)) do |l1, l0|
113
+ substr_func = Sequel.function(:strpos, l1, l0)
114
+ Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
115
+ end
117
116
  end
118
117
 
119
118
  # postgres uses extract()
@@ -132,9 +131,11 @@ module OData
132
131
  # substringof(name, '__Route du Rhum__') -->
133
132
  # '__Route du Rhum__' contains name as a substring
134
133
  # instr() seems to be the most common substring func
135
- substr_func = Sequel.function(:instr, args[1].leuqes(jh), args[0].leuqes(jh))
136
-
137
- Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
134
+ Contract.collect_result!(args[1].leuqes(jh),
135
+ args[0].leuqes(jh)) do |l1, l0|
136
+ substr_func = Sequel.function(:instr, l1, l0)
137
+ Sequel::SQL::BooleanExpression.new(:>, substr_func, 0)
138
+ end
138
139
  end
139
140
 
140
141
  # XYZ uses extract() ?