norikra 0.0.13-java → 0.0.14-java
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/norikra/engine.rb +101 -80
- data/lib/norikra/field.rb +204 -0
- data/lib/norikra/fieldset.rb +167 -0
- data/lib/norikra/logger.rb +37 -22
- data/lib/norikra/query.rb +152 -14
- data/lib/norikra/query/ast.rb +65 -22
- data/lib/norikra/rpc/handler.rb +10 -3
- data/lib/norikra/server.rb +8 -2
- data/lib/norikra/target.rb +24 -0
- data/lib/norikra/typedef.rb +117 -234
- data/lib/norikra/typedef_manager.rb +22 -16
- data/lib/norikra/version.rb +1 -1
- data/norikra.gemspec +1 -1
- data/script/spec_server_pry +31 -0
- data/spec/field_spec.rb +131 -2
- data/spec/fieldset_spec.rb +92 -23
- data/spec/query_spec.rb +204 -29
- data/spec/spec_helper.rb +39 -3
- data/spec/target_spec.rb +18 -0
- data/spec/typedef_manager_spec.rb +35 -12
- data/spec/typedef_spec.rb +235 -24
- metadata +6 -4
data/lib/norikra/query/ast.rb
CHANGED
@@ -52,6 +52,33 @@ module Norikra
|
|
52
52
|
# "double"],
|
53
53
|
# "10.5"]]]]
|
54
54
|
|
55
|
+
### SELECT count(*) AS cnt
|
56
|
+
### FROM TestTable.win:time_batch(10 sec)
|
57
|
+
### WHERE params.$$path.$1="/" AND size.$0.bytes > 100 and opts.num.seq.length() > 0
|
58
|
+
|
59
|
+
# ["EPL_EXPR",
|
60
|
+
# ["SELECTION_EXPR", ["SELECTION_ELEMENT_EXPR", "count", "cnt"]],
|
61
|
+
# ["STREAM_EXPR",
|
62
|
+
# ["EVENT_FILTER_EXPR", "TestTable"],
|
63
|
+
# ["VIEW_EXPR", "win", "time_batch", ["TIME_PERIOD", ["SECOND_PART", "10"]]]],
|
64
|
+
# ["WHERE_EXPR",
|
65
|
+
# ["EVAL_AND_EXPR",
|
66
|
+
# ["EVAL_EQUALS_EXPR",
|
67
|
+
# ["EVENT_PROP_EXPR",
|
68
|
+
# ["EVENT_PROP_SIMPLE", "params"],
|
69
|
+
# ["EVENT_PROP_SIMPLE", "$$path"],
|
70
|
+
# ["EVENT_PROP_SIMPLE", "$1"]],
|
71
|
+
# "\"/\""],
|
72
|
+
# [">",
|
73
|
+
# ["EVENT_PROP_EXPR",
|
74
|
+
# ["EVENT_PROP_SIMPLE", "size"],
|
75
|
+
# ["EVENT_PROP_SIMPLE", "$0"],
|
76
|
+
# ["EVENT_PROP_SIMPLE", "bytes"]],
|
77
|
+
# "100"],
|
78
|
+
# [">",
|
79
|
+
# ["LIB_FUNC_CHAIN", ["LIB_FUNCTION", "opts.num.seq", "length", "("]],
|
80
|
+
# "0"]]]]
|
81
|
+
|
55
82
|
def astnode(tree)
|
56
83
|
children = if tree.children
|
57
84
|
tree.children.map{|c| astnode(c)}
|
@@ -114,23 +141,28 @@ module Norikra
|
|
114
141
|
result
|
115
142
|
end
|
116
143
|
|
117
|
-
def fields(default_target=nil)
|
118
|
-
@children.map{|c| c.nodetype?(:subquery) ? [] : c.fields(default_target)}.reduce(&:+) || []
|
144
|
+
def fields(default_target=nil, known_targets_aliases=[])
|
145
|
+
@children.map{|c| c.nodetype?(:subquery) ? [] : c.fields(default_target, known_targets_aliases)}.reduce(&:+) || []
|
119
146
|
end
|
120
147
|
end
|
121
148
|
|
122
149
|
class ASTEventPropNode < ASTNode # EVENT_PROP_EXPR
|
123
|
-
# ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "bbb"]]
|
124
|
-
# ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "fraud"], ["EVENT_PROP_SIMPLE", "aaa"]]
|
150
|
+
# "bbb" => ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "bbb"]]
|
151
|
+
# "fraud.aaa" => ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "fraud"], ["EVENT_PROP_SIMPLE", "aaa"]]
|
152
|
+
# "size.$0.bytes" => ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "size"], ["EVENT_PROP_SIMPLE", "$0"], ["EVENT_PROP_SIMPLE", "bytes"]]
|
125
153
|
|
126
154
|
def nodetype?(*sym)
|
127
155
|
sym.include?(:prop) || sym.include?(:property)
|
128
156
|
end
|
129
157
|
|
130
|
-
def fields(default_target=nil)
|
158
|
+
def fields(default_target=nil, known_targets_aliases=[])
|
131
159
|
props = self.listup('EVENT_PROP_SIMPLE')
|
132
|
-
if props.size > 1 # alias.fieldname
|
133
|
-
|
160
|
+
if props.size > 1 # alias.fieldname or container_fieldname.key.$1
|
161
|
+
if known_targets_aliases.include?(props[0].child.name)
|
162
|
+
[ {:f => props[1..-1].map{|n| n.child.name}.join("."), :t => props[0].child.name} ]
|
163
|
+
else
|
164
|
+
[ {:f => props.map{|n| n.child.name}.join("."), :t => default_target} ]
|
165
|
+
end
|
134
166
|
else # fieldname (default target)
|
135
167
|
[ {:f => props[0].child.name, :t => default_target } ]
|
136
168
|
end
|
@@ -138,42 +170,53 @@ module Norikra
|
|
138
170
|
end
|
139
171
|
|
140
172
|
class ASTLibFunctionNode < ASTNode # LIB_FUNCTION
|
141
|
-
# ["LIB_FUNCTION", "now", "("]
|
142
|
-
# ["LIB_FUNCTION", "hoge", "length", "("]
|
143
|
-
# ["LIB_FUNCTION", "hoge", "substr", "0", "("]
|
144
|
-
# ["LIB_FUNCTION", "substr", "10", "0", "("]
|
145
|
-
# ["LIB_FUNCTION", "hoge", "substr", "0", "8", "("]
|
146
|
-
|
173
|
+
# "now()" => ["LIB_FUNCTION", "now", "("]
|
174
|
+
# "hoge.length()" => ["LIB_FUNCTION", "hoge", "length", "("]
|
175
|
+
# "hoge.substr(0)" => ["LIB_FUNCTION", "hoge", "substr", "0", "("]
|
176
|
+
# "substr(10,0)" => ["LIB_FUNCTION", "substr", "10", "0", "("]
|
177
|
+
# "hoge.substr(0,8)" => ["LIB_FUNCTION", "hoge", "substr", "0", "8", "("]
|
178
|
+
|
179
|
+
# "opts.num.$0.length()" => ["LIB_FUNCTION", "opts.num.$0", "length", "("]
|
180
|
+
|
181
|
+
# "max(size)" => ["LIB_FUNCTION", "max", ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "size"]], "("]
|
147
182
|
|
148
183
|
def nodetype?(*sym)
|
149
184
|
sym.include?(:lib) || sym.include?(:libfunc)
|
150
185
|
end
|
151
186
|
|
152
|
-
def fields(default_target=nil)
|
187
|
+
def fields(default_target=nil, known_targets_aliases=[])
|
153
188
|
if @children.size <= 2
|
154
189
|
# single function like 'now()', function-name and "("
|
155
190
|
[]
|
156
191
|
|
157
192
|
elsif @children[1].nodetype?(:prop, :lib, :subquery)
|
158
193
|
# first element should be func name if second element is property, library call or subqueries
|
159
|
-
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target)}.reduce(&:+) || []
|
194
|
+
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target, known_targets_aliases)}.reduce(&:+) || []
|
160
195
|
|
161
196
|
elsif @children[1].name =~ /^(-)?\d+(\.\d+)$/ || @children[1].name =~ /^'[^']*'$/ || @children[1].name =~ /^"[^"]*"$/
|
162
197
|
# first element should be func name if secod element is number/string literal
|
163
|
-
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target)}.reduce(&:+) || []
|
198
|
+
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target, known_targets_aliases)}.reduce(&:+) || []
|
164
199
|
|
165
200
|
elsif Norikra::Query.imported_java_class?(@children[0].name)
|
166
201
|
# Java imported class name (ex: 'Math.abs(-1)')
|
167
|
-
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target)}.reduce(&:+) || []
|
202
|
+
self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target, known_targets_aliases)}.reduce(&:+) || []
|
168
203
|
|
169
204
|
else
|
170
|
-
# first element may be property
|
205
|
+
# first element may be property
|
206
|
+
# * simple 'fieldname.funcname()'
|
207
|
+
# * fully qualified 'target.fieldname.funcname()'
|
208
|
+
# * simple/fully-qualified container field access 'fieldname.key.$0.funcname()' or 'target.fieldname.$1.funcname()'
|
171
209
|
target,fieldname = if @children[0].name.include?('.')
|
172
|
-
@children[0].name.split('.'
|
210
|
+
parts = @children[0].name.split('.')
|
211
|
+
if known_targets_aliases.include?(parts[0])
|
212
|
+
[ parts[0], parts[1..-1].join(".") ]
|
213
|
+
else
|
214
|
+
[ default_target, @children[0].name ]
|
215
|
+
end
|
173
216
|
else
|
174
217
|
[default_target,@children[0].name]
|
175
218
|
end
|
176
|
-
children_list = self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target)}.reduce(&:+) || []
|
219
|
+
children_list = self.listup('EVENT_PROP_EXPR').map{|c| c.fields(default_target, known_targets_aliases)}.reduce(&:+) || []
|
177
220
|
[{:f => fieldname, :t => target}] + children_list
|
178
221
|
end
|
179
222
|
end
|
@@ -202,9 +245,9 @@ module Norikra
|
|
202
245
|
@children.last.children.size < 1 ? @children.last.name : nil
|
203
246
|
end
|
204
247
|
|
205
|
-
def fields(default_target=nil)
|
248
|
+
def fields(default_target=nil, known_targets_aliases=[])
|
206
249
|
this_target = self.target
|
207
|
-
self.listup('EVENT_PROP_EXPR').map{|p| p.fields(this_target)}.reduce(&:+) || []
|
250
|
+
self.listup('EVENT_PROP_EXPR').map{|p| p.fields(this_target,known_targets_aliases)}.reduce(&:+) || []
|
208
251
|
end
|
209
252
|
end
|
210
253
|
|
data/lib/norikra/rpc/handler.rb
CHANGED
@@ -32,13 +32,13 @@ class Norikra::RPC::Handler
|
|
32
32
|
|
33
33
|
def targets
|
34
34
|
logging(:show, :targets){
|
35
|
-
@engine.targets
|
35
|
+
@engine.targets.map(&:to_hash)
|
36
36
|
}
|
37
37
|
end
|
38
38
|
|
39
|
-
def open(target, fields)
|
39
|
+
def open(target, fields, auto_field)
|
40
40
|
logging(:manage, :open, target, fields){
|
41
|
-
r = @engine.open(target, fields)
|
41
|
+
r = @engine.open(target, fields, auto_field)
|
42
42
|
!!r
|
43
43
|
}
|
44
44
|
end
|
@@ -50,6 +50,13 @@ class Norikra::RPC::Handler
|
|
50
50
|
}
|
51
51
|
end
|
52
52
|
|
53
|
+
def modify(target, auto_field)
|
54
|
+
logging(:manage, :modify, target, auto_field){
|
55
|
+
r = @engine.modify(target, auto_field)
|
56
|
+
!!r
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
53
60
|
def queries
|
54
61
|
logging(:show, :queries){
|
55
62
|
@engine.queries.map(&:to_hash)
|
data/lib/norikra/server.rb
CHANGED
@@ -108,7 +108,7 @@ module Norikra
|
|
108
108
|
info "loading from stats file"
|
109
109
|
if @stats.targets && @stats.targets.size > 0
|
110
110
|
@stats.targets.each do |target|
|
111
|
-
@engine.open(target[:name], target[:fields])
|
111
|
+
@engine.open(target[:name], target[:fields], target[:auto_field])
|
112
112
|
end
|
113
113
|
end
|
114
114
|
if @stats.queries && @stats.queries.size > 0
|
@@ -163,7 +163,13 @@ module Norikra
|
|
163
163
|
port: @port,
|
164
164
|
threads: @thread_conf,
|
165
165
|
log: @log_conf,
|
166
|
-
targets: @engine.
|
166
|
+
targets: @engine.targets.map{|t|
|
167
|
+
{
|
168
|
+
:name => t.name,
|
169
|
+
:fields => @engine.typedef_manager.dump_target(t.name),
|
170
|
+
:auto_field => t.auto_field
|
171
|
+
}
|
172
|
+
},
|
167
173
|
queries: @engine.queries.map{|q| {:name => q.name, :expression => q.expression}}
|
168
174
|
)
|
169
175
|
stats.dump(@stats_path)
|
data/lib/norikra/target.rb
CHANGED
@@ -1,7 +1,31 @@
|
|
1
1
|
module Norikra
|
2
2
|
class Target
|
3
|
+
attr_accessor :name, :fields, :auto_field
|
4
|
+
|
3
5
|
def self.valid?(target_name)
|
4
6
|
target_name =~ /^[a-zA-Z]([_a-zA-Z0-9]*[a-zA-Z0-9])?$/
|
5
7
|
end
|
8
|
+
|
9
|
+
def initialize(name, fields=[], auto_field=true)
|
10
|
+
@name = name
|
11
|
+
@fields = fields
|
12
|
+
@auto_field = !!auto_field
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@name
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{:name => @name, :auto_field => @auto_field}
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
self.class == other.class ? self.name == other.name : self.name == other.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def auto_field?
|
28
|
+
@auto_field
|
29
|
+
end
|
6
30
|
end
|
7
31
|
end
|
data/lib/norikra/typedef.rb
CHANGED
@@ -1,238 +1,41 @@
|
|
1
|
-
require 'digest'
|
2
1
|
require 'json'
|
3
2
|
|
4
3
|
require 'norikra/error'
|
5
4
|
|
6
|
-
|
5
|
+
require 'norikra/field'
|
6
|
+
require 'norikra/fieldset'
|
7
7
|
|
8
8
|
module Norikra
|
9
|
-
class Field
|
10
|
-
attr_accessor :name, :type, :optional
|
11
|
-
|
12
|
-
def initialize(name, type, optional=nil)
|
13
|
-
@name = name.to_s
|
14
|
-
@type = self.class.valid_type?(type)
|
15
|
-
@optional = optional
|
16
|
-
end
|
17
|
-
|
18
|
-
def to_hash(sym=false)
|
19
|
-
if sym
|
20
|
-
{name: @name, type: @type, optional: @optional}
|
21
|
-
else
|
22
|
-
{'name' => @name, 'type' => @type, 'optional' => @optional}
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def dup(optional=nil)
|
27
|
-
self.class.new(@name, @type, optional.nil? ? @optional : optional)
|
28
|
-
end
|
29
|
-
|
30
|
-
def ==(other)
|
31
|
-
self.name == other.name && self.type == other.type && self.optional == other.optional
|
32
|
-
end
|
33
|
-
|
34
|
-
def optional? # used outside of FieldSet
|
35
|
-
@optional
|
36
|
-
end
|
37
|
-
|
38
|
-
### esper types
|
39
|
-
### http://esper.codehaus.org/esper-4.9.0/doc/reference/en-US/html/epl_clauses.html#epl-syntax-datatype
|
40
|
-
# string A single character to an unlimited number of characters.
|
41
|
-
# boolean A boolean value.
|
42
|
-
# integer An integer value (4 byte).
|
43
|
-
# long A long value (8 byte). Use the "L" or "l" (lowercase L) suffix. # select 1L as field1, 1l as field2
|
44
|
-
# double A double-precision 64-bit IEEE 754 floating point. # select 1.67 as field1, 167e-2 as field2, 1.67d as field3
|
45
|
-
# float A single-precision 32-bit IEEE 754 floating point. Use the "f" suffix. # select 1.2f as field1, 1.2F as field2
|
46
|
-
# byte A 8-bit signed two's complement integer. # select 0x10 as field1
|
47
|
-
#
|
48
|
-
#### 'integer' in epser document IS WRONG.
|
49
|
-
#### If 'integer' specified, esper raises this exception:
|
50
|
-
### Exception: Nestable type configuration encountered an unexpected property type name 'integer' for property 'status',
|
51
|
-
### expected java.lang.Class or java.util.Map or the name of a previously-declared Map or ObjectArray type
|
52
|
-
#### Correct type name is 'int'. see and run 'junks/esper-test.rb'
|
53
|
-
def self.valid_type?(type)
|
54
|
-
case type.to_s.downcase
|
55
|
-
when 'string' then 'string'
|
56
|
-
when 'boolean' then 'boolean'
|
57
|
-
when 'int' then 'int'
|
58
|
-
when 'long' then 'long'
|
59
|
-
when 'float' then 'float'
|
60
|
-
when 'double' then 'double'
|
61
|
-
else
|
62
|
-
raise Norikra::ArgumentError, "invalid field type '#{type}'"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def format(value)
|
67
|
-
case @type
|
68
|
-
when 'string' then value.to_s
|
69
|
-
when 'boolean' then value =~ /^(true|false)$/i ? ($1.downcase == 'true') : (!!value)
|
70
|
-
when 'long','int' then value.to_i
|
71
|
-
when 'double','float' then value.to_f
|
72
|
-
else
|
73
|
-
raise RuntimeError, "unknown field type (in format), maybe BUG. name:#{@name},type:#{@type}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class FieldSet
|
79
|
-
attr_accessor :summary, :fields
|
80
|
-
attr_accessor :target, :level
|
81
|
-
|
82
|
-
def initialize(fields, default_optional=nil, rebounds=0)
|
83
|
-
@fields = {}
|
84
|
-
fields.keys.each do |key|
|
85
|
-
data = fields[key]
|
86
|
-
type,optional = if data.is_a?(Hash)
|
87
|
-
[data[:type].to_s, (data.has_key?(:optional) ? data[:optional] : default_optional)]
|
88
|
-
elsif data.is_a?(String) || data.is_a?(Symbol)
|
89
|
-
[data.to_s, default_optional]
|
90
|
-
else
|
91
|
-
raise ArgumentError, "FieldSet.new argument class unknown: #{fields.class}"
|
92
|
-
end
|
93
|
-
@fields[key.to_s] = Field.new(key.to_s, type, optional)
|
94
|
-
end
|
95
|
-
self.update_summary
|
96
|
-
|
97
|
-
@target = nil
|
98
|
-
@level = nil
|
99
|
-
@rebounds = rebounds
|
100
|
-
@event_type_name = nil
|
101
|
-
end
|
102
|
-
|
103
|
-
def dup
|
104
|
-
fields = Hash[@fields.map{|key,field| [key, {:type => field.type, :optional => field.optional}]}]
|
105
|
-
self.class.new(fields, nil, @rebounds)
|
106
|
-
end
|
107
|
-
|
108
|
-
def self.field_names_key(data, fieldset=nil)
|
109
|
-
if fieldset
|
110
|
-
keys = []
|
111
|
-
fieldset.fields.each do |key,field|
|
112
|
-
unless field.optional?
|
113
|
-
keys.push(key)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
data.keys.each do |key|
|
117
|
-
keys.push(key) unless keys.include?(key)
|
118
|
-
end
|
119
|
-
keys.sort.join(',')
|
120
|
-
else
|
121
|
-
data.keys.sort.join(',')
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def field_names_key
|
126
|
-
self.class.field_names_key(@fields)
|
127
|
-
end
|
128
|
-
|
129
|
-
def update_summary
|
130
|
-
@summary = @fields.keys.sort.map{|k| @fields[k].name + ':' + @fields[k].type}.join(',')
|
131
|
-
self
|
132
|
-
end
|
133
|
-
|
134
|
-
def update(fields, optional_flag)
|
135
|
-
fields.each do |field|
|
136
|
-
@fields[field.name] = field.dup(optional_flag)
|
137
|
-
end
|
138
|
-
self.update_summary
|
139
|
-
end
|
140
|
-
|
141
|
-
#TODO: have a bug?
|
142
|
-
def ==(other)
|
143
|
-
return false if self.class != other.class
|
144
|
-
self.summary == other.summary
|
145
|
-
end
|
146
|
-
|
147
|
-
def definition
|
148
|
-
d = {}
|
149
|
-
@fields.each do |key, field|
|
150
|
-
d[field.name] = field.type
|
151
|
-
end
|
152
|
-
d
|
153
|
-
end
|
154
|
-
|
155
|
-
def subset?(other) # self is subset of other (or not)
|
156
|
-
(self.fields.keys - other.fields.keys).size == 0
|
157
|
-
end
|
158
|
-
|
159
|
-
def event_type_name
|
160
|
-
@event_type_name.dup
|
161
|
-
end
|
162
|
-
|
163
|
-
def bind(target, level, update_type_name=false)
|
164
|
-
@target = target
|
165
|
-
@level = level
|
166
|
-
prefix = case level
|
167
|
-
when :base then 'b_'
|
168
|
-
when :query then 'q_'
|
169
|
-
when :data then 'e_' # event
|
170
|
-
else
|
171
|
-
raise ArgumentError, "unknown fieldset bind level: #{level}, for target #{target}"
|
172
|
-
end
|
173
|
-
@rebounds += 1 if update_type_name
|
174
|
-
|
175
|
-
@event_type_name = prefix + Digest::MD5.hexdigest([target, level.to_s, @rebounds.to_s, @summary].join("\t"))
|
176
|
-
self
|
177
|
-
end
|
178
|
-
|
179
|
-
def rebind(update_type_name)
|
180
|
-
self.dup.bind(@target, @level, update_type_name)
|
181
|
-
end
|
182
|
-
|
183
|
-
def self.simple_guess(data, optional=true)
|
184
|
-
mapping = Hash[
|
185
|
-
data.map{|key,value|
|
186
|
-
type = case value
|
187
|
-
when TrueClass,FalseClass then 'boolean'
|
188
|
-
when Integer then 'long'
|
189
|
-
when Float then 'double'
|
190
|
-
else
|
191
|
-
'string'
|
192
|
-
end
|
193
|
-
[key,type]
|
194
|
-
}
|
195
|
-
]
|
196
|
-
self.new(mapping, optional)
|
197
|
-
end
|
198
|
-
|
199
|
-
# def self.guess(data, optional=true)
|
200
|
-
# mapping = Hash[
|
201
|
-
# data.map{|key,value|
|
202
|
-
# sval = value.to_s
|
203
|
-
# type = case
|
204
|
-
# when val.is_a?(TrueClass) || val.is_a?(FalseClass) || sval =~ /^(?:true|false)$/i
|
205
|
-
# 'boolean'
|
206
|
-
# when val.is_a?(Integer) || sval =~ /^-?\d+[lL]?$/
|
207
|
-
# 'long'
|
208
|
-
# when val.is_a?(Float) || sval =~ /^-?\d+\.\d+(?:[eE]-?\d+|[dDfF])?$/
|
209
|
-
# 'double'
|
210
|
-
# else
|
211
|
-
# 'string'
|
212
|
-
# end
|
213
|
-
# [key,type]
|
214
|
-
# }
|
215
|
-
# ]
|
216
|
-
# self.new(mapping, optional)
|
217
|
-
# end
|
218
|
-
end
|
219
|
-
|
220
9
|
# Typedef is
|
221
|
-
# * known field list of target (and these are optional or not)
|
10
|
+
# * known field list of target (and these are optional or not), and container fields
|
222
11
|
# * known field-set list of a target
|
223
12
|
# * base set of a target
|
224
13
|
class Typedef
|
225
|
-
attr_accessor :fields, :baseset, :queryfieldsets, :datafieldsets
|
14
|
+
attr_accessor :fields, :container_fields, :waiting_fields ,:baseset, :queryfieldsets, :datafieldsets
|
226
15
|
|
227
16
|
def initialize(fields=nil)
|
17
|
+
@container_fields = {}
|
18
|
+
|
228
19
|
if fields && !fields.empty?
|
229
|
-
|
230
|
-
@fields =
|
20
|
+
given_set = FieldSet.new(fields, false) # default_optional=false, but chained access fields are always optional
|
21
|
+
@fields = given_set.fields.dup
|
22
|
+
base_fields = {}
|
23
|
+
@fields.values.each do |field|
|
24
|
+
if !field.optional?
|
25
|
+
base_fields[field.name] = field
|
26
|
+
elsif field.chained_access?
|
27
|
+
cname = field.container_name
|
28
|
+
@container_fields[cname] = Norikra::Field.new(cname, field.container_type, true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@baseset = FieldSet.new(base_fields, false)
|
231
32
|
else
|
232
33
|
@baseset = nil
|
233
34
|
@fields = {}
|
234
35
|
end
|
235
36
|
|
37
|
+
@waiting_fields = []
|
38
|
+
|
236
39
|
@queryfieldsets = []
|
237
40
|
@datafieldsets = []
|
238
41
|
|
@@ -242,6 +45,8 @@ module Norikra
|
|
242
45
|
end
|
243
46
|
|
244
47
|
def field_defined?(list)
|
48
|
+
# used for only queries: TypedefManager#ready?(query) and TypedefManager#generate_field_mapping(query)
|
49
|
+
# be not needed to think about containers
|
245
50
|
list.reduce(true){|r,f| r && @fields[f]}
|
246
51
|
end
|
247
52
|
|
@@ -256,6 +61,9 @@ module Norikra
|
|
256
61
|
set.fields[fieldname] = field.dup(false)
|
257
62
|
end
|
258
63
|
@baseset = set
|
64
|
+
@baseset.fields.each do |name,f|
|
65
|
+
@waiting_fields.delete(name) if @waiting_fields.include?(name)
|
66
|
+
end
|
259
67
|
@fields = @baseset.fields.merge(@fields)
|
260
68
|
end
|
261
69
|
end
|
@@ -264,7 +72,15 @@ module Norikra
|
|
264
72
|
fieldname = fieldname.to_s
|
265
73
|
@mutex.synchronize do
|
266
74
|
return false if @fields[fieldname]
|
267
|
-
|
75
|
+
field = Norikra::Field.new(fieldname, type, optional)
|
76
|
+
if @waiting_fields.include?(fieldname)
|
77
|
+
@waiting_fields.delete(fieldname)
|
78
|
+
end
|
79
|
+
@fields[fieldname] = field
|
80
|
+
if field.chained_access? && !@container_fields[field.container_name]
|
81
|
+
container = Norikra::Field.new(field.container_name, field.container_type, true)
|
82
|
+
@container_fields[field.container_name] = container
|
83
|
+
end
|
268
84
|
end
|
269
85
|
true
|
270
86
|
end
|
@@ -272,12 +88,13 @@ module Norikra
|
|
272
88
|
def consistent?(fieldset)
|
273
89
|
fields = fieldset.fields
|
274
90
|
@baseset.subset?(fieldset) &&
|
275
|
-
@fields.values.select{|f| !f.optional? }.reduce(true){|r,f| r && fields
|
276
|
-
fields.values.reduce(true){|r,f| r && (
|
91
|
+
@fields.values.select{|f| !f.optional? }.reduce(true){|r,f| r && fields.has_key?(f.name) && fields[f.name].type == f.type} &&
|
92
|
+
fields.values.reduce(true){|r,f| r && (!@fields.has_key?(f.name) || @fields[f.name].type == f.type)}
|
277
93
|
end
|
278
94
|
|
279
95
|
def push(level, fieldset)
|
280
96
|
unless self.consistent?(fieldset)
|
97
|
+
warn "fieldset mismatch", :self => self, :with => fieldset
|
281
98
|
raise Norikra::ArgumentError, "field definition mismatch with already defined fields"
|
282
99
|
end
|
283
100
|
|
@@ -292,6 +109,7 @@ module Norikra
|
|
292
109
|
@queryfieldsets.push(fieldset)
|
293
110
|
|
294
111
|
fieldset.fields.each do |fieldname,field|
|
112
|
+
@waiting_fields.delete(fieldname) if @waiting_fields.include?(fieldname)
|
295
113
|
@fields[fieldname] = field.dup(true) unless @fields[fieldname]
|
296
114
|
end
|
297
115
|
end
|
@@ -301,7 +119,15 @@ module Norikra
|
|
301
119
|
@set_map[fieldset.field_names_key] = fieldset
|
302
120
|
|
303
121
|
fieldset.fields.each do |fieldname,field|
|
304
|
-
@
|
122
|
+
if @waiting_fields.include?(fieldname)
|
123
|
+
@waiting_fields.delete(fieldname)
|
124
|
+
end
|
125
|
+
unless @fields[fieldname]
|
126
|
+
@fields[fieldname] = field.dup(true)
|
127
|
+
if field.chained_access? && !@container_fields[field.container_name]
|
128
|
+
@container_fields[field.container_name] = Norikra::Field.new(field.container_name, field.container_type, true)
|
129
|
+
end
|
130
|
+
end
|
305
131
|
end
|
306
132
|
end
|
307
133
|
else
|
@@ -329,6 +155,7 @@ module Norikra
|
|
329
155
|
|
330
156
|
def replace(level, old_fieldset, fieldset)
|
331
157
|
unless self.consistent?(fieldset)
|
158
|
+
warn "fieldset mismatch", :self => self, :with => fieldset
|
332
159
|
raise Norikra::ArgumentError, "field definition mismatch with already defined fields"
|
333
160
|
end
|
334
161
|
if level != :data
|
@@ -345,11 +172,65 @@ module Norikra
|
|
345
172
|
true
|
346
173
|
end
|
347
174
|
|
348
|
-
def
|
349
|
-
|
175
|
+
def simple_guess(data, optional=true, strict=false)
|
176
|
+
flatten_key_value_pairs = []
|
177
|
+
|
178
|
+
data.each do |key,value|
|
179
|
+
next if strict && !(@fields.has_key?(key) || @waiting_fields.include?(key) || value.is_a?(Hash) || value.is_a?(Array))
|
180
|
+
|
181
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
182
|
+
Norikra::FieldSet.leaves(value).map{|chain| [key] + chain}.each do |chain|
|
183
|
+
value = chain.pop
|
184
|
+
key = Norikra::Field.regulate_key_chain(chain).join('.')
|
185
|
+
next unless @fields.has_key?(key) || @waiting_fields.include?(key)
|
186
|
+
flatten_key_value_pairs.push([key, value])
|
187
|
+
end
|
188
|
+
else
|
189
|
+
flatten_key_value_pairs.push([key, value])
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
mapping = Hash[
|
194
|
+
flatten_key_value_pairs.map{|key,value|
|
195
|
+
type = case value
|
196
|
+
when TrueClass,FalseClass then 'boolean'
|
197
|
+
when Integer then 'long'
|
198
|
+
when Float then 'double'
|
199
|
+
else
|
200
|
+
'string'
|
201
|
+
end
|
202
|
+
[key,type]
|
203
|
+
}
|
204
|
+
]
|
205
|
+
|
206
|
+
FieldSet.new(mapping, optional)
|
207
|
+
end
|
208
|
+
|
209
|
+
# def self.guess(data, optional=true)
|
210
|
+
# mapping = Hash[
|
211
|
+
# data.map{|key,value|
|
212
|
+
# sval = value.to_s
|
213
|
+
# type = case
|
214
|
+
# when val.is_a?(TrueClass) || val.is_a?(FalseClass) || sval =~ /^(?:true|false)$/i
|
215
|
+
# 'boolean'
|
216
|
+
# when val.is_a?(Integer) || sval =~ /^-?\d+[lL]?$/
|
217
|
+
# 'long'
|
218
|
+
# when val.is_a?(Float) || sval =~ /^-?\d+\.\d+(?:[eE]-?\d+|[dDfF])?$/
|
219
|
+
# 'double'
|
220
|
+
# else
|
221
|
+
# 'string'
|
222
|
+
# end
|
223
|
+
# [key,type]
|
224
|
+
# }
|
225
|
+
# ]
|
226
|
+
# self.new(mapping, optional)
|
227
|
+
# end
|
228
|
+
|
229
|
+
def refer(data, strict=false)
|
230
|
+
field_names_key = FieldSet.field_names_key(data, self, strict, @waiting_fields)
|
350
231
|
return @set_map[field_names_key] if @set_map.has_key?(field_names_key)
|
351
232
|
|
352
|
-
guessed =
|
233
|
+
guessed = self.simple_guess(data, false, strict)
|
353
234
|
guessed_fields = guessed.fields
|
354
235
|
@fields.each do |key,field|
|
355
236
|
if guessed_fields.has_key?(key)
|
@@ -359,23 +240,25 @@ module Norikra
|
|
359
240
|
guessed_fields[key] = field unless field.optional?
|
360
241
|
end
|
361
242
|
end
|
362
|
-
guessed.update_summary
|
243
|
+
guessed.update_summary #=> guessed
|
363
244
|
end
|
364
245
|
|
365
|
-
def
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
ret[key] = @fields[key].format(value)
|
246
|
+
def dump_all
|
247
|
+
fields = {}
|
248
|
+
@fields.each do |key,field|
|
249
|
+
fields[field.name] = field.to_hash(true)
|
370
250
|
end
|
371
|
-
|
251
|
+
fields
|
372
252
|
end
|
373
253
|
|
374
|
-
def dump
|
254
|
+
def dump # to cli display
|
375
255
|
fields = {}
|
376
|
-
@fields.
|
256
|
+
@fields.each do |key,field|
|
257
|
+
fields[key.to_sym] = field.to_hash(true) unless field.chained_access?
|
258
|
+
end
|
259
|
+
@container_fields.each do |key, field|
|
377
260
|
fields[key.to_sym] = field.to_hash(true)
|
378
|
-
|
261
|
+
end
|
379
262
|
fields
|
380
263
|
end
|
381
264
|
end
|