safrano 0.4.0 → 0.4.5
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/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 +15 -13
- data/lib/odata/collection.rb +144 -535
- data/lib/odata/collection_filter.rb +47 -40
- data/lib/odata/collection_media.rb +145 -74
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +36 -34
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +151 -197
- data/lib/odata/error.rb +175 -32
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +44 -36
- data/lib/odata/filter/sequel.rb +136 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +113 -63
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +637 -0
- data/lib/odata/navigation_attribute.rb +44 -61
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +54 -0
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +19 -11
- data/lib/safrano.rb +17 -37
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +29 -104
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +39 -43
- data/lib/safrano/rack_app.rb +68 -67
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +102 -51
- data/lib/safrano/response.rb +5 -3
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +264 -220
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +34 -12
@@ -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
|
@@ -238,15 +275,16 @@ module OData
|
|
238
275
|
end
|
239
276
|
end
|
240
277
|
|
241
|
-
# TODO different num types?
|
278
|
+
# TODO: different num types?
|
242
279
|
def edm_type
|
243
280
|
:any
|
244
281
|
end
|
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,47 +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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
[false, Parser::ErrorInvalidToken.new(tok, typ, self)]
|
285
|
-
else
|
286
|
-
true
|
287
|
-
end
|
326
|
+
nil
|
327
|
+
elsif @state == :sep
|
328
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
288
329
|
end
|
289
|
-
when :Literal, :Qualit, :QString, :FuncTree, :FPNumber
|
330
|
+
when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
|
290
331
|
if (@state == :open) || (@state == :sep)
|
291
332
|
if @parent.arity_full?(@children.size)
|
292
|
-
|
293
|
-
else
|
294
|
-
true
|
333
|
+
Parser::ErrorInvalidArity.new(tok, typ, self)
|
295
334
|
end
|
296
335
|
else
|
297
|
-
|
336
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
298
337
|
end
|
299
338
|
when :BinopBool, :BinopArithm
|
300
|
-
|
339
|
+
nil
|
301
340
|
else
|
302
|
-
|
341
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
303
342
|
end
|
304
343
|
end
|
305
344
|
|
@@ -309,13 +348,13 @@ module OData
|
|
309
348
|
end
|
310
349
|
|
311
350
|
# Numbers (floating point, ints, dec)
|
312
|
-
class FPNumber
|
351
|
+
class FPNumber
|
313
352
|
def accept?(tok, typ)
|
314
353
|
case typ
|
315
354
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
316
|
-
|
355
|
+
nil
|
317
356
|
else
|
318
|
-
|
357
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
319
358
|
end
|
320
359
|
end
|
321
360
|
|
@@ -326,54 +365,65 @@ module OData
|
|
326
365
|
end
|
327
366
|
|
328
367
|
# Literals are unquoted words without /
|
329
|
-
class Literal
|
368
|
+
class Literal
|
330
369
|
def accept?(tok, typ)
|
331
370
|
case typ
|
332
371
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
333
|
-
|
372
|
+
nil
|
334
373
|
else
|
335
|
-
|
374
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
336
375
|
end
|
337
376
|
end
|
338
377
|
|
339
378
|
def edm_type
|
340
379
|
:any
|
341
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
|
342
393
|
end
|
343
394
|
|
344
395
|
# Qualit (qualified lits) are words separated by /
|
345
396
|
# path/path/path/attrib
|
346
|
-
class Qualit
|
397
|
+
class Qualit
|
347
398
|
REGEXP = /((?:\w+\/)+)(\w+)/.freeze
|
348
399
|
attr_reader :path
|
349
400
|
attr_reader :attrib
|
401
|
+
|
350
402
|
def initialize(val)
|
351
403
|
super(val)
|
352
404
|
# split into path + attrib
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
raise Parser::Error.new(self, Qualit)
|
358
|
-
end
|
405
|
+
raise Parser::Error.new(self, Qualit) unless (md = REGEXP.match(val))
|
406
|
+
|
407
|
+
@path = md[1].chomp('/')
|
408
|
+
@attrib = md[2]
|
359
409
|
end
|
360
410
|
end
|
361
411
|
|
362
412
|
# Quoted Strings
|
363
|
-
class QString
|
364
|
-
|
365
|
-
|
413
|
+
class QString
|
414
|
+
DBL_QO = "''"
|
415
|
+
SI_QO = "'"
|
366
416
|
def initialize(val)
|
367
417
|
# unescape double quotes
|
368
|
-
super(val.gsub(
|
418
|
+
super(val.gsub(DBL_QO, SI_QO))
|
369
419
|
end
|
370
420
|
|
371
421
|
def accept?(tok, typ)
|
372
422
|
case typ
|
373
423
|
when :Delimiter, :Separator, :BinopBool, :BinopArithm
|
374
|
-
|
424
|
+
nil
|
375
425
|
else
|
376
|
-
|
426
|
+
Parser::ErrorInvalidToken.new(tok, typ, self)
|
377
427
|
end
|
378
428
|
end
|
379
429
|
|