safrano 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +9 -7
- data/lib/odata/collection.rb +140 -591
- data/lib/odata/collection_filter.rb +18 -42
- data/lib/odata/collection_media.rb +111 -54
- data/lib/odata/collection_order.rb +5 -2
- data/lib/odata/common_logger.rb +2 -0
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +123 -172
- data/lib/odata/error.rb +183 -32
- data/lib/odata/expand.rb +20 -17
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +41 -25
- data/lib/odata/filter/sequel.rb +133 -62
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +106 -52
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +13 -26
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +17 -5
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +100 -24
- data/lib/odata/walker.rb +20 -10
- data/lib/safrano.rb +18 -38
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +23 -107
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +29 -33
- data/lib/safrano/rack_app.rb +66 -65
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +96 -45
- data/lib/safrano/response.rb +4 -2
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +240 -130
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +6 -19
- metadata +32 -11
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './tree'
|
4
|
+
require_relative './sequel'
|
5
|
+
|
6
|
+
module Safrano
|
7
|
+
module Filter
|
8
|
+
# sqlite adapter specific function handler
|
9
|
+
module FuncTreeSqlite
|
10
|
+
def substringof_sig2(jh)
|
11
|
+
# substringof(name, '__Route du Rhum__') -->
|
12
|
+
# '__Route du Rhum__' contains name as a substring
|
13
|
+
# sqlite uses instr()
|
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
|
19
|
+
end
|
20
|
+
# %d day of month: 00
|
21
|
+
# %f fractional seconds: SS.SSS
|
22
|
+
# %H hour: 00-24
|
23
|
+
# %j day of year: 001-366
|
24
|
+
# %J Julian day number
|
25
|
+
# %m month: 01-12
|
26
|
+
# %M minute: 00-59
|
27
|
+
# %s seconds since 1970-01-01
|
28
|
+
# %S seconds: 00-59
|
29
|
+
# %w day of week 0-6 with Sunday==0
|
30
|
+
# %W week of year: 00-53
|
31
|
+
# %Y year: 0000-9999
|
32
|
+
# %% %
|
33
|
+
|
34
|
+
# sqlite does not have extract but recommends to use strftime
|
35
|
+
def year(lq)
|
36
|
+
Sequel.function(:strftime, '%Y', lq).cast(:integer)
|
37
|
+
end
|
38
|
+
|
39
|
+
def month(lq)
|
40
|
+
Sequel.function(:strftime, '%m', lq).cast(:integer)
|
41
|
+
end
|
42
|
+
|
43
|
+
def second(lq)
|
44
|
+
Sequel.function(:strftime, '%S', lq).cast(:integer)
|
45
|
+
end
|
46
|
+
|
47
|
+
def minute(lq)
|
48
|
+
Sequel.function(:strftime, '%M', lq).cast(:integer)
|
49
|
+
end
|
50
|
+
|
51
|
+
def hour(lq)
|
52
|
+
Sequel.function(:strftime, '%H', lq).cast(:integer)
|
53
|
+
end
|
54
|
+
|
55
|
+
def day(lq)
|
56
|
+
Sequel.function(:strftime, '%d', lq).cast(:integer)
|
57
|
+
end
|
58
|
+
|
59
|
+
def floor(_lq)
|
60
|
+
Safrano::FilterFunctionNotImplementedError.new("$filter function 'floor' is not implemented in sqlite adapter")
|
61
|
+
end
|
62
|
+
|
63
|
+
def ceiling(_lq)
|
64
|
+
Safrano::FilterFunctionNotImplementedError.new("$filter function 'ceiling' is not implemented in sqlite adapter")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
# re-useable module with math floor/ceil functions for those adapters having these SQL funcs
|
68
|
+
module MathFloorCeilFuncTree
|
69
|
+
def floor(lq)
|
70
|
+
success Sequel.function(:floor, lq)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ceiling(lq)
|
74
|
+
success Sequel.function(:ceil, lq)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# re-useable module with Datetime functions with extract()
|
79
|
+
module DateTimeFuncTreeExtract
|
80
|
+
def year(lq)
|
81
|
+
lq.extract(:year)
|
82
|
+
end
|
83
|
+
|
84
|
+
def month(lq)
|
85
|
+
lq.extract(:month)
|
86
|
+
end
|
87
|
+
|
88
|
+
def second(lq)
|
89
|
+
lq.extract(:second)
|
90
|
+
end
|
91
|
+
|
92
|
+
def minute(lq)
|
93
|
+
lq.extract(:minute)
|
94
|
+
end
|
95
|
+
|
96
|
+
def hour(lq)
|
97
|
+
lq.extract(:hour)
|
98
|
+
end
|
99
|
+
|
100
|
+
def day(lq)
|
101
|
+
lq.extract(:day)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# postgresql adapter specific function handler
|
106
|
+
module FuncTreePostgres
|
107
|
+
def substringof_sig2(jh)
|
108
|
+
# substringof(name, '__Route du Rhum__') -->
|
109
|
+
# '__Route du Rhum__' contains name as a substring
|
110
|
+
# postgres does not know instr() but has strpos
|
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
|
116
|
+
end
|
117
|
+
|
118
|
+
# postgres uses extract()
|
119
|
+
include DateTimeFuncTreeExtract
|
120
|
+
|
121
|
+
# postgres has floor/ceil funcs
|
122
|
+
include MathFloorCeilFuncTree
|
123
|
+
end
|
124
|
+
|
125
|
+
# default adapter function handler for all others... try to use the most common version
|
126
|
+
# :substring --> instr because here is seems Postgres is special
|
127
|
+
# datetime funcs --> exctract, here sqlite is special(uses format)
|
128
|
+
# note: we dont test this, provided as an example/template, might work eg for mysql
|
129
|
+
module FuncTreeDefault
|
130
|
+
def substringof_sig2(jh)
|
131
|
+
# substringof(name, '__Route du Rhum__') -->
|
132
|
+
# '__Route du Rhum__' contains name as a substring
|
133
|
+
# instr() seems to be the most common substring func
|
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
|
139
|
+
end
|
140
|
+
|
141
|
+
# XYZ uses extract() ?
|
142
|
+
include DateTimeFuncTreeExtract
|
143
|
+
|
144
|
+
# ... assume SQL
|
145
|
+
include MathFloorCeilFuncTree
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/lib/odata/filter/token.rb
CHANGED
@@ -1,19 +1,24 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Safrano
|
3
4
|
module Filter
|
4
5
|
class Parser
|
5
6
|
# Input tokenizer
|
6
7
|
module Token
|
7
8
|
FUNCNAMES = %w[concat substringof endswith startswith length indexof
|
8
|
-
replace substring trim toupper tolower
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
replace substring trim toupper tolower
|
10
|
+
day hour minute month second year
|
11
|
+
round floor ceiling].freeze
|
12
|
+
FUNCRGX = FUNCNAMES.join('|')
|
13
|
+
NULLRGX = 'null|NULL|Null'
|
14
|
+
QSTRINGRGX = /'((?:[^']|(?:'{2}))*)'/.freeze
|
15
|
+
BINOBOOL = '[eE][qQ]|[LlgGNn][eETt]|[aA][nN][dD]|[oO][rR]'
|
16
|
+
BINOARITHM = '[aA][dD][dD]|[sS][uU][bB]|[mM][uU][lL]|[dD][iI][vV]|[mM][oO][dD]'
|
17
|
+
NOTRGX = 'not|NOT|Not'
|
18
|
+
FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?'
|
19
|
+
QUALITRGX = '\w+(?:\/\w+)+'
|
20
|
+
RGX = /(#{FUNCRGX})|(#{NULLRGX})|([\(\),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{FPRGX})|(#{QUALITRGX})|(\w+)|(')/.freeze
|
21
|
+
|
17
22
|
def each_typed_token(inp)
|
18
23
|
typ = nil
|
19
24
|
|
@@ -30,27 +35,29 @@ module OData
|
|
30
35
|
when 0
|
31
36
|
:FuncTree
|
32
37
|
when 1
|
38
|
+
:NullLiteral
|
39
|
+
when 2
|
33
40
|
case found
|
34
41
|
when '(', ')'
|
35
42
|
:Delimiter
|
36
43
|
when ','
|
37
44
|
:Separator
|
38
45
|
end
|
39
|
-
when 2
|
40
|
-
:BinopBool
|
41
46
|
when 3
|
42
|
-
:
|
47
|
+
:BinopBool
|
43
48
|
when 4
|
44
|
-
:
|
49
|
+
:BinopArithm
|
45
50
|
when 5
|
46
|
-
:
|
51
|
+
:UnopTree
|
47
52
|
when 6
|
48
|
-
:
|
53
|
+
:QString
|
49
54
|
when 7
|
50
|
-
:
|
55
|
+
:FPNumber
|
51
56
|
when 8
|
52
|
-
:
|
57
|
+
:Qualit
|
53
58
|
when 9
|
59
|
+
:Literal
|
60
|
+
when 10
|
54
61
|
:unmatchedQuote
|
55
62
|
end
|
56
63
|
yield found, typ
|
data/lib/odata/filter/tree.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative './base'
|
4
|
+
require_relative './error'
|
5
|
+
|
6
|
+
module Safrano
|
4
7
|
module Filter
|
5
8
|
# Base class for Leaves, Trees, RootTrees etc
|
6
9
|
class Node
|
7
10
|
attr_reader :value
|
11
|
+
|
8
12
|
def initialize(val, &block)
|
9
13
|
@value = val
|
10
14
|
instance_eval(&block) if block_given?
|
@@ -16,19 +20,26 @@ module OData
|
|
16
20
|
end
|
17
21
|
|
18
22
|
# Leaves are Nodes with a parent but no children
|
19
|
-
class Leave
|
23
|
+
class Leave
|
20
24
|
attr_accessor :parent
|
25
|
+
|
26
|
+
# nil is considered as accepted, otherwise non-nil=the error
|
21
27
|
def accept?(tok, typ)
|
22
|
-
|
28
|
+
Parser::ErrorInvalidToken(tok, typ)
|
23
29
|
end
|
24
30
|
|
25
31
|
def check_types; end
|
32
|
+
|
33
|
+
def attach(_child)
|
34
|
+
Safrano::Filter::Parser::ErrorLeaveChild
|
35
|
+
end
|
26
36
|
end
|
27
37
|
|
28
38
|
# RootTrees have childrens but no parent
|
29
|
-
class RootTree
|
39
|
+
class RootTree
|
30
40
|
attr_reader :children
|
31
41
|
attr_accessor :state
|
42
|
+
|
32
43
|
def initialize(val: :root, &block)
|
33
44
|
@children = []
|
34
45
|
super(val, &block)
|
@@ -37,6 +48,7 @@ module OData
|
|
37
48
|
def attach(child)
|
38
49
|
child.parent = self
|
39
50
|
@children << child
|
51
|
+
Contract::OK
|
40
52
|
end
|
41
53
|
|
42
54
|
def detach(child)
|
@@ -50,28 +62,32 @@ module OData
|
|
50
62
|
|
51
63
|
def update_state(tok, typ) end
|
52
64
|
|
65
|
+
# nil is considered as accepted, otherwise non-nil=the error
|
53
66
|
def accept?(tok, typ)
|
54
67
|
case typ
|
55
|
-
when :Literal, :Qualit, :QString, :FuncTree, :ArgTree,
|
56
|
-
|
68
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :ArgTree,
|
69
|
+
:UnopTree, :FPNumber
|
70
|
+
nil
|
57
71
|
when :Delimiter
|
58
72
|
if tok == '('
|
59
|
-
|
73
|
+
nil
|
60
74
|
else
|
61
|
-
|
75
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
62
76
|
end
|
63
77
|
else
|
64
|
-
|
78
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
82
|
def check_types
|
69
|
-
|
83
|
+
err = nil
|
84
|
+
@children.find { |c| (err = c.check_types) }
|
85
|
+
err
|
70
86
|
end
|
71
87
|
end
|
72
88
|
|
73
89
|
# Tree's have Parent and children
|
74
|
-
class Tree
|
90
|
+
class Tree
|
75
91
|
attr_accessor :parent
|
76
92
|
|
77
93
|
def initialize(val)
|
@@ -80,7 +96,7 @@ module OData
|
|
80
96
|
end
|
81
97
|
|
82
98
|
# For functions... should have a single child---> the argument list
|
83
|
-
class FuncTree
|
99
|
+
class FuncTree
|
84
100
|
def initialize(val)
|
85
101
|
super(val.downcase.to_sym)
|
86
102
|
end
|
@@ -117,10 +133,11 @@ module OData
|
|
117
133
|
end
|
118
134
|
end
|
119
135
|
|
136
|
+
# nil is considered as accepted, otherwise non-nil=the error
|
120
137
|
def accept?(tok, typ)
|
121
138
|
case typ
|
122
139
|
when :BinopBool, :BinopArithm
|
123
|
-
|
140
|
+
nil
|
124
141
|
else
|
125
142
|
super(tok, typ)
|
126
143
|
end
|
@@ -131,9 +148,9 @@ module OData
|
|
131
148
|
when :length
|
132
149
|
argtyp = args.first.edm_type
|
133
150
|
if (argtyp != :any) && (argtyp != :string)
|
134
|
-
|
135
|
-
|
136
|
-
|
151
|
+
return Parser::ErrorInvalidArgumentType.new(self,
|
152
|
+
expected: :string,
|
153
|
+
actual: argtyp)
|
137
154
|
end
|
138
155
|
end
|
139
156
|
super
|
@@ -143,24 +160,44 @@ module OData
|
|
143
160
|
# Indentity Func to use as "parent" func of parenthesis expressions
|
144
161
|
# --> allow to handle generically parenthesis always as argument of
|
145
162
|
# some function
|
146
|
-
class IdentityFuncTree
|
163
|
+
class IdentityFuncTree
|
147
164
|
def initialize
|
148
165
|
super(:__indentity)
|
149
166
|
end
|
150
167
|
|
151
168
|
# we can have parenthesis with one expression inside everywhere
|
152
169
|
# only in FuncTree this is redefined for the function's arity
|
170
|
+
# Note: if you change this method, please also update arity_full_monkey?
|
171
|
+
# see below
|
153
172
|
def arity_full?(cursize)
|
154
173
|
cursize >= 1
|
155
174
|
end
|
156
175
|
|
176
|
+
# this is for testing only.
|
177
|
+
# see 99_threadsafe_tc.rb
|
178
|
+
# there we will monkey patch arity_full? by adding some sleeping
|
179
|
+
# to easily slow down a given test-thread (while the other one runs normaly)
|
180
|
+
#
|
181
|
+
# The rule is to keep this method here exactly same as the original
|
182
|
+
# "productive" one
|
183
|
+
#
|
184
|
+
# With this trick we can test threadsafeness without touching
|
185
|
+
# "productive" code
|
186
|
+
def arity_full_monkey?(cursize)
|
187
|
+
cursize >= 1
|
188
|
+
end
|
189
|
+
|
157
190
|
def edm_type
|
158
191
|
@children.first.edm_type
|
159
192
|
end
|
193
|
+
|
194
|
+
def ==(other)
|
195
|
+
@children == other.children
|
196
|
+
end
|
160
197
|
end
|
161
198
|
|
162
199
|
# unary op eg. NOT
|
163
|
-
class UnopTree
|
200
|
+
class UnopTree
|
164
201
|
def initialize(val)
|
165
202
|
super(val.downcase.to_sym)
|
166
203
|
end
|
@@ -187,7 +224,7 @@ module OData
|
|
187
224
|
end
|
188
225
|
|
189
226
|
# Bin ops
|
190
|
-
class BinopTree
|
227
|
+
class BinopTree
|
191
228
|
def initialize(val)
|
192
229
|
@state = :open
|
193
230
|
super(val.downcase.to_sym)
|
@@ -195,13 +232,13 @@ module OData
|
|
195
232
|
|
196
233
|
def update_state(_tok, typ)
|
197
234
|
case typ
|
198
|
-
when :Literal, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm, :UnopTree, :FPNumber
|
235
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm, :UnopTree, :FPNumber
|
199
236
|
@state = :closed
|
200
237
|
end
|
201
238
|
end
|
202
239
|
end
|
203
240
|
|
204
|
-
class BinopBool
|
241
|
+
class BinopBool
|
205
242
|
# reference:
|
206
243
|
# OData v4 par 5.1.1.9 Operator Precedence
|
207
244
|
def precedence
|
@@ -224,7 +261,7 @@ module OData
|
|
224
261
|
end
|
225
262
|
end
|
226
263
|
|
227
|
-
class BinopArithm
|
264
|
+
class BinopArithm
|
228
265
|
# reference:
|
229
266
|
# OData v4 par 5.1.1.9 Operator Precedence
|
230
267
|
def precedence
|
@@ -245,8 +282,9 @@ module OData
|
|
245
282
|
end
|
246
283
|
|
247
284
|
# Arguments or lists
|
248
|
-
class ArgTree
|
285
|
+
class ArgTree
|
249
286
|
attr_reader :type
|
287
|
+
|
250
288
|
def initialize(val)
|
251
289
|
@type = :expression
|
252
290
|
@state = :open
|
@@ -259,45 +297,48 @@ module OData
|
|
259
297
|
@state = :closed
|
260
298
|
when :Separator
|
261
299
|
@state = :sep
|
262
|
-
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
300
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
|
263
301
|
@state = :val
|
264
302
|
end
|
265
303
|
end
|
266
304
|
|
305
|
+
# nil is considered as accepted, otherwise non-nil=the error
|
267
306
|
def accept?(tok, typ)
|
268
307
|
case typ
|
269
308
|
when :Delimiter
|
270
309
|
if @value == '(' && tok == ')' && @state != :closed
|
271
|
-
if @parent.
|
272
|
-
|
310
|
+
if (@parent.class == IdentityFuncTree) or
|
311
|
+
(@parent.arity_full?(@children.size))
|
312
|
+
|
313
|
+
nil
|
273
314
|
else
|
274
|
-
|
315
|
+
Parser::ErrorInvalidArity.new(tok, typ, self)
|
275
316
|
end
|
276
317
|
else
|
277
|
-
|
318
|
+
if @value == '(' && tok == '(' && @state == :open
|
319
|
+
nil
|
320
|
+
else
|
321
|
+
Parser::ErrorUnmatchedClose.new(tok, typ, self)
|
322
|
+
end
|
278
323
|
end
|
279
324
|
when :Separator
|
280
325
|
if @value == '(' && tok == ',' && @state == :val
|
281
|
-
|
326
|
+
nil
|
282
327
|
elsif @state == :sep
|
283
|
-
|
284
|
-
else
|
285
|
-
true
|
328
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
286
329
|
end
|
287
|
-
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
330
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
|
288
331
|
if (@state == :open) || (@state == :sep)
|
289
332
|
if @parent.arity_full?(@children.size)
|
290
|
-
|
291
|
-
else
|
292
|
-
true
|
333
|
+
Parser::ErrorInvalidArity.new(tok, typ, self)
|
293
334
|
end
|
294
335
|
else
|
295
|
-
|
336
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
296
337
|
end
|
297
338
|
when :BinopBool, :BinopArithm
|
298
|
-
|
339
|
+
nil
|
299
340
|
else
|
300
|
-
|
341
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
301
342
|
end
|
302
343
|
end
|
303
344
|
|
@@ -307,13 +348,13 @@ module OData
|
|
307
348
|
end
|
308
349
|
|
309
350
|
# Numbers (floating point, ints, dec)
|
310
|
-
class FPNumber
|
351
|
+
class FPNumber
|
311
352
|
def accept?(tok, typ)
|
312
353
|
case typ
|
313
354
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
314
|
-
|
355
|
+
nil
|
315
356
|
else
|
316
|
-
|
357
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
317
358
|
end
|
318
359
|
end
|
319
360
|
|
@@ -324,27 +365,40 @@ module OData
|
|
324
365
|
end
|
325
366
|
|
326
367
|
# Literals are unquoted words without /
|
327
|
-
class Literal
|
368
|
+
class Literal
|
328
369
|
def accept?(tok, typ)
|
329
370
|
case typ
|
330
371
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
331
|
-
|
372
|
+
nil
|
332
373
|
else
|
333
|
-
|
374
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
334
375
|
end
|
335
376
|
end
|
336
377
|
|
337
378
|
def edm_type
|
338
379
|
:any
|
339
380
|
end
|
381
|
+
|
382
|
+
# error, Literal are leaves
|
383
|
+
# when the child is a IdentityFuncTree then this looks like
|
384
|
+
# an attempt to use a unknown function, eg. ceil(Total)
|
385
|
+
# instead of ceiling(Total)
|
386
|
+
def attach(child)
|
387
|
+
if child.is_a? Safrano::Filter::IdentityFuncTree
|
388
|
+
Safrano::FilterUnknownFunctionError.new(value)
|
389
|
+
else
|
390
|
+
super
|
391
|
+
end
|
392
|
+
end
|
340
393
|
end
|
341
394
|
|
342
395
|
# Qualit (qualified lits) are words separated by /
|
343
396
|
# path/path/path/attrib
|
344
|
-
class Qualit
|
397
|
+
class Qualit
|
345
398
|
REGEXP = /((?:\w+\/)+)(\w+)/.freeze
|
346
399
|
attr_reader :path
|
347
400
|
attr_reader :attrib
|
401
|
+
|
348
402
|
def initialize(val)
|
349
403
|
super(val)
|
350
404
|
# split into path + attrib
|
@@ -356,9 +410,9 @@ module OData
|
|
356
410
|
end
|
357
411
|
|
358
412
|
# Quoted Strings
|
359
|
-
class QString
|
360
|
-
DBL_QO = "''"
|
361
|
-
SI_QO = "'"
|
413
|
+
class QString
|
414
|
+
DBL_QO = "''"
|
415
|
+
SI_QO = "'"
|
362
416
|
def initialize(val)
|
363
417
|
# unescape double quotes
|
364
418
|
super(val.gsub(DBL_QO, SI_QO))
|
@@ -367,9 +421,9 @@ module OData
|
|
367
421
|
def accept?(tok, typ)
|
368
422
|
case typ
|
369
423
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
370
|
-
|
424
|
+
nil
|
371
425
|
else
|
372
|
-
|
426
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
373
427
|
end
|
374
428
|
end
|
375
429
|
|