norikra 0.0.13-java → 0.0.14-java

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.
@@ -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
- [ {:f => props[1].child.name, :t => props[0].child.name} ]
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", "("] #### now()
142
- # ["LIB_FUNCTION", "hoge", "length", "("] # #### hoge.length()
143
- # ["LIB_FUNCTION", "hoge", "substr", "0", "("] #### hoge.substr(0)
144
- # ["LIB_FUNCTION", "substr", "10", "0", "("] #### substr(10,0)
145
- # ["LIB_FUNCTION", "hoge", "substr", "0", "8", "("] #### hoge.substr(0,8)
146
- # ["LIB_FUNCTION", "max", ["EVENT_PROP_EXPR", ["EVENT_PROP_SIMPLE", "size"]], "("] #### max(size)
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 (simple 'fieldname.funcname()' or fully qualified 'target.fieldname.funcname()')
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('.', 2)
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
 
@@ -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)
@@ -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.typedef_manager.dump,
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)
@@ -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
@@ -1,238 +1,41 @@
1
- require 'digest'
2
1
  require 'json'
3
2
 
4
3
  require 'norikra/error'
5
4
 
6
- # Norikra::Field, Norikra::FieldSet, Norikra::Typedef
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
- @baseset = FieldSet.new(fields, false) # all fields are required
230
- @fields = @baseset.fields.dup
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
- @fields[fieldname] = Field.new(fieldname, type, optional)
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[f.name] && fields[f.name].type == f.type} &&
276
- fields.values.reduce(true){|r,f| r && (@fields[f.name].nil? || @fields[f.name].type == f.type)}
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
- @fields[fieldname] = field.dup(true) unless @fields[fieldname]
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 refer(data)
349
- field_names_key = FieldSet.field_names_key(data, self)
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 = FieldSet.simple_guess(data)
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 format(data)
366
- # all keys of data should be already known at #format (before #format, do #refer)
367
- ret = {}
368
- data.each do |key, value|
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
- ret
251
+ fields
372
252
  end
373
253
 
374
- def dump
254
+ def dump # to cli display
375
255
  fields = {}
376
- @fields.map{|key,field|
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