norikra 1.1.2-java → 1.2.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,28 +19,28 @@ module Norikra
19
19
  attr_accessor :running
20
20
 
21
21
  MICRO_PREDEFINED = {
22
- :engine => { inbound: { threads: 0, capacity: 0 }, outbound: { threads: 0, capacity: 0 },
23
- route_exec: { threads: 0, capacity: 0 }, timer_exec: { threads: 0, capacity: 0 }, },
24
- :rpc => { threads: 2 }, # for desktop
25
- :web => { threads: 2 },
22
+ engine: { inbound: { threads: 0, capacity: 0 }, outbound: { threads: 0, capacity: 0 },
23
+ route_exec: { threads: 0, capacity: 0 }, timer_exec: { threads: 0, capacity: 0 }, },
24
+ rpc: { threads: 2 }, # for desktop
25
+ web: { threads: 2 },
26
26
  }
27
27
  SMALL_PREDEFINED = {
28
- :engine => { inbound: { threads: 2, capacity: 0 }, outbound: { threads: 2, capacity: 0 },
29
- route_exec: { threads: 2, capacity: 0 }, timer_exec: { threads: 2, capacity: 0 }, },
30
- :rpc => { threads: 9 }, # 4core HT
31
- :web => { threads: 9 },
28
+ engine: { inbound: { threads: 2, capacity: 0 }, outbound: { threads: 2, capacity: 0 },
29
+ route_exec: { threads: 2, capacity: 0 }, timer_exec: { threads: 2, capacity: 0 }, },
30
+ rpc: { threads: 9 }, # 4core HT
31
+ web: { threads: 9 },
32
32
  }
33
33
  MIDDLE_PREDEFINED = {
34
- :engine => { inbound: { threads: 4, capacity: 0 }, outbound: { threads: 4, capacity: 0 },
35
- route_exec: { threads: 4, capacity: 0 }, timer_exec: { threads: 4, capacity: 0 }, },
36
- :rpc => { threads: 17 }, # 4core HT 2CPU
37
- :web => { threads: 17 },
34
+ engine: { inbound: { threads: 4, capacity: 0 }, outbound: { threads: 4, capacity: 0 },
35
+ route_exec: { threads: 4, capacity: 0 }, timer_exec: { threads: 4, capacity: 0 }, },
36
+ rpc: { threads: 17 }, # 4core HT 2CPU
37
+ web: { threads: 17 },
38
38
  }
39
39
  LARGE_PREDEFINED = {
40
- :engine => { inbound: { threads: 8, capacity: 0 }, outbound: { threads: 8, capacity: 0 },
41
- route_exec: { threads: 8, capacity: 0 }, timer_exec: { threads: 8, capacity: 0 }, },
42
- :rpc => { threads: 49 }, # 6core HT 4CPU
43
- :web => { threads: 49 },
40
+ engine: { inbound: { threads: 8, capacity: 0 }, outbound: { threads: 8, capacity: 0 },
41
+ route_exec: { threads: 8, capacity: 0 }, timer_exec: { threads: 8, capacity: 0 }, },
42
+ rpc: { threads: 49 }, # 6core HT 4CPU
43
+ web: { threads: 49 },
44
44
  }
45
45
 
46
46
  def self.threading_configuration(conf)
@@ -118,14 +118,14 @@ module Norikra
118
118
  @engine = Norikra::Engine.new(@output_pool, @typedef_manager, {thread: @thread_conf[:engine]})
119
119
 
120
120
  @rpcserver = Norikra::RPC::HTTP.new(
121
- :engine => @engine,
122
- :host => @host, :port => @port,
123
- :threads => @thread_conf[:rpc][:threads]
121
+ engine: @engine,
122
+ host: @host, port: @port,
123
+ threads: @thread_conf[:rpc][:threads]
124
124
  )
125
125
  @webserver = Norikra::WebUI::HTTP.new(
126
- :engine => @engine,
127
- :host => @host, :port => @ui_port,
128
- :threads => @thread_conf[:web][:threads]
126
+ engine: @engine,
127
+ host: @host, port: @ui_port,
128
+ threads: @thread_conf[:web][:threads]
129
129
  )
130
130
  end
131
131
 
@@ -143,7 +143,7 @@ module Norikra
143
143
  end
144
144
  if @stats.queries && @stats.queries.size > 0
145
145
  @stats.queries.each do |query|
146
- @engine.register(Norikra::Query.new(:name => query[:name], :group => query[:group], :expression => query[:expression]))
146
+ @engine.register(Norikra::Query.new(name: query[:name], group: query[:group], expression: query[:expression]))
147
147
  end
148
148
  end
149
149
  end
@@ -201,21 +201,27 @@ module Norikra
201
201
  info "Loading UDF plugins"
202
202
  Norikra::UDF.listup.each do |mojule|
203
203
  if mojule.is_a?(Class)
204
- name = @engine.load(mojule)
205
- info "UDF loaded", :name => name
204
+ name = @engine.load(:udf, mojule)
205
+ info "UDF loaded", name: name
206
206
  elsif mojule.is_a?(Module) && mojule.respond_to?(:plugins)
207
207
  mojule.init if mojule.respond_to?(:init)
208
208
  mojule.plugins.each do |klass|
209
- name = @engine.load(klass)
210
- info "UDF loaded", :name => name
209
+ name = @engine.load(:udf, klass)
210
+ info "UDF loaded", name: name
211
211
  end
212
212
  end
213
213
  end
214
+
215
+ info "Loading Listener plugins"
216
+ Norikra::Listener.listup.each do |klass|
217
+ @engine.load(:listener, klass)
218
+ info "Listener loaded", name: klass
219
+ end
214
220
  end
215
221
 
216
222
  def dump_stats
217
223
  Norikra::Stats.generate(@engine).dump(@stats_path, @stats_secondary_path)
218
- info "Current status saved", :path => @stats_path
224
+ info "Current status saved", path: @stats_path
219
225
  end
220
226
  end
221
227
  end
data/lib/norikra/stats.rb CHANGED
@@ -8,9 +8,9 @@ module Norikra
8
8
  Norikra::Stats.new(
9
9
  targets: engine.targets.map{|t|
10
10
  {
11
- :name => t.name,
12
- :fields => engine.typedef_manager.dump_target(t.name),
13
- :auto_field => t.auto_field
11
+ name: t.name,
12
+ fields: engine.typedef_manager.dump_target(t.name),
13
+ auto_field: t.auto_field
14
14
  }
15
15
  },
16
16
  queries: engine.queries.map(&:dump)
@@ -18,7 +18,7 @@ module Norikra
18
18
  end
19
19
 
20
20
  def to_hash
21
- {:name => @name, :auto_field => @auto_field}
21
+ {name: @name, auto_field: @auto_field}
22
22
  end
23
23
 
24
24
  def ==(other)
@@ -10,6 +10,7 @@ module Norikra
10
10
  # * known field list of target (and these are optional or not), and container fields
11
11
  # * known field-set list of a target
12
12
  # * base set of a target
13
+
13
14
  class Typedef
14
15
  attr_accessor :fields, :container_fields, :waiting_fields ,:baseset, :queryfieldsets, :datafieldsets
15
16
 
@@ -39,7 +40,10 @@ module Norikra
39
40
  @queryfieldsets = []
40
41
  @datafieldsets = []
41
42
 
42
- @set_map = {} # FieldSet.field_names_key(data_fieldset, fieldset) => data_fieldset
43
+ # FieldSet.field_names_key(data_fieldset, fieldset) => data_fieldset
44
+ ### field_names_key is built by keys w/ data, without null fields
45
+ ### data_fieldset includes null fields
46
+ @set_map = {}
43
47
 
44
48
  @mutex = Mutex.new
45
49
  end
@@ -94,7 +98,7 @@ module Norikra
94
98
 
95
99
  def push(level, fieldset)
96
100
  unless self.consistent?(fieldset)
97
- warn "fieldset mismatch", :self => self, :with => fieldset
101
+ warn "fieldset mismatch", receiver: self, with: fieldset
98
102
  raise Norikra::ArgumentError, "field definition mismatch with already defined fields"
99
103
  end
100
104
 
@@ -131,7 +135,7 @@ module Norikra
131
135
  end
132
136
  end
133
137
  else
134
- raise ArgumentError, "unknown level #{level}"
138
+ raise ::ArgumentError, "unknown level #{level}"
135
139
  end
136
140
  end
137
141
  true
@@ -147,7 +151,7 @@ module Norikra
147
151
  when :data
148
152
  raise RuntimeError, "BUG: pop of data fieldset is nonsense"
149
153
  else
150
- raise ArgumentError, "unknown level #{level}"
154
+ raise ::ArgumentError, "unknown level #{level}"
151
155
  end
152
156
  end
153
157
  true
@@ -155,14 +159,14 @@ module Norikra
155
159
 
156
160
  def replace(level, old_fieldset, fieldset)
157
161
  unless self.consistent?(fieldset)
158
- warn "fieldset mismatch", :self => self, :with => fieldset
162
+ warn "fieldset mismatch", receiver: self, with: fieldset
159
163
  raise Norikra::ArgumentError, "field definition mismatch with already defined fields"
160
164
  end
161
165
  if level != :data
162
- raise ArgumentError, "invalid argument, fieldset replace should be called for :data"
166
+ raise ::ArgumentError, "invalid argument, fieldset replace should be called for :data"
163
167
  end
164
168
  if old_fieldset.field_names_key != fieldset.field_names_key
165
- raise ArgumentError, "try to replace different field name sets"
169
+ raise ::ArgumentError, "try to replace different field name sets"
166
170
  end
167
171
  @mutex.synchronize do
168
172
  @datafieldsets.delete(old_fieldset)
@@ -173,9 +177,11 @@ module Norikra
173
177
  end
174
178
 
175
179
  def simple_guess(data, opts={})
176
- unless opts.has_key?(:optional)
177
- opts[:optional] = true
178
- end
180
+ #### in typedef_manager
181
+ # guessed = @typedefs[target].simple_guess(event, strict: false, baseset: true)
182
+
183
+ #### in Typedef#refer
184
+ # guessed = self.simple_guess(data, strict: strict)
179
185
 
180
186
  flatten_key_value_pairs = []
181
187
 
@@ -199,24 +205,23 @@ module Norikra
199
205
  mapping = Hash[
200
206
  flatten_key_value_pairs.map{|key,value|
201
207
  type = case value
202
- when TrueClass,FalseClass then 'boolean'
203
- when Integer then 'long'
204
- when Float then 'double'
205
- else
206
- 'string'
208
+ when TrueClass,FalseClass then {type: 'boolean'}
209
+ when Integer then {type: 'long'}
210
+ when Float then {type: 'double'}
211
+ else {type: 'string'}
207
212
  end
208
213
  [key,type]
209
214
  }
210
215
  ]
211
216
 
212
- FieldSet.new(mapping, opts[:optional])
217
+ FieldSet.new(mapping, false) # in simple_guess, optional is always false
213
218
  end
214
219
 
215
220
  def refer(data, strict=false)
216
221
  field_names_key = FieldSet.field_names_key(data, self, strict, @waiting_fields)
217
222
  return @set_map[field_names_key] if @set_map.has_key?(field_names_key)
218
223
 
219
- guessed = self.simple_guess(data, optional: false, strict: strict)
224
+ guessed = self.simple_guess(data, strict: strict)
220
225
  guessed_fields = guessed.fields
221
226
  @fields.each do |key,field|
222
227
  if guessed_fields.has_key?(key)
@@ -68,9 +68,11 @@ module Norikra
68
68
 
69
69
  def generate_fieldset_mapping(query)
70
70
  fields_set = {}
71
+ nullables_set = {}
71
72
 
72
73
  query.targets.each do |target|
73
74
  fields_set[target] = query.fields(target)
75
+ nullables_set[target] = query.nullable_fields(target)
74
76
  end
75
77
  query.fields(nil).each do |field|
76
78
  assumed = query.targets.select{|t| @typedefs[t].field_defined?([field])}
@@ -78,11 +80,12 @@ module Norikra
78
80
  raise Norikra::ClientError, "cannot determine target for field '#{field}' in this query"
79
81
  end
80
82
  fields_set[assumed.first].push(field)
83
+ nullables_set[assumed.first].push(field) if query.nullable_fields(nil).include?(field)
81
84
  end
82
85
 
83
86
  mapping = {}
84
87
  fields_set.each do |target,fields|
85
- mapping[target] = generate_query_fieldset(target, fields.sort.uniq, query.name, query.group)
88
+ mapping[target] = generate_query_fieldset(target, fields.sort.uniq, nullables_set[target].sort.uniq, query.name, query.group)
86
89
  end
87
90
  mapping
88
91
  end
@@ -101,19 +104,19 @@ module Norikra
101
104
  end
102
105
 
103
106
  def generate_base_fieldset(target, event)
104
- guessed = @typedefs[target].simple_guess(event, optional: false, strict: false, baseset: true) # all fields are non-optional
107
+ guessed = @typedefs[target].simple_guess(event, strict: false, baseset: true) # all fields are non-optional
105
108
  guessed.update(@typedefs[target].fields, false)
106
109
  guessed
107
110
  end
108
111
 
109
- def generate_query_fieldset(target, field_name_list, query_name, query_group)
112
+ def generate_query_fieldset(target, field_name_list, nullable_list, query_name, query_group)
110
113
  # all fields of field_name_list should exists in definitions of typedef fields
111
114
  # for this premise, call 'bind_fieldset' for data fieldset before this method.
112
115
  required_fields = {}
113
116
  @mutex.synchronize do
114
117
  @typedefs[target].fields.each do |fieldname, field|
115
118
  if field_name_list.include?(fieldname) || !(field.optional?)
116
- required_fields[fieldname] = {:type => field.type, :optional => field.optional}
119
+ required_fields[fieldname] = {type: field.type, optional: field.optional, nullable: nullable_list.include?(fieldname)}
117
120
  end
118
121
  end
119
122
  end
@@ -124,7 +127,7 @@ module Norikra
124
127
  @typedefs[target].baseset
125
128
  end
126
129
 
127
- def subsets(target, fieldset) # for data fieldset
130
+ def subsets(target, fieldset) # manager.subsets(target, data_fieldset) #=> [query_fieldset]
128
131
  sets = []
129
132
  @mutex.synchronize do
130
133
  @typedefs[target].queryfieldsets.each do |set|
@@ -135,7 +138,7 @@ module Norikra
135
138
  sets
136
139
  end
137
140
 
138
- def supersets(target, fieldset) # for query fieldset
141
+ def supersets(target, fieldset) # manager.supersets(target, query_fieldset) #=> [data_fieldset]
139
142
  sets = []
140
143
  @mutex.synchronize do
141
144
  @typedefs[target].datafieldsets.each do |set|
data/lib/norikra/udf.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  require 'norikra/error'
2
2
  require 'rubygems'
3
3
 
4
+ require 'norikra/logger'
5
+ include Norikra::Log
6
+
4
7
  module Norikra
5
8
  module UDF
6
9
  #### esper-4.9.0/esper/doc/reference/html/extension.html#custom-singlerow-function
@@ -97,17 +100,18 @@ module Norikra
97
100
  plugins = Gem.find_latest_files('norikra/udf/*.rb')
98
101
  plugins.each do |plugin|
99
102
  begin
100
- debug "plugin file found!", :file => plugin
103
+ debug "plugin file found!", file: plugin
101
104
  rbpath = plugin.dup
102
105
  4.times do
103
106
  rbpath = File.dirname( rbpath )
104
107
  end
105
108
  files = Dir.entries( rbpath )
106
109
  gemname = files.select{|f| f=~ /\.gemspec$/ }.first.sub(/\.gemspec$/, '')
110
+ trace "Loading UDF gem", gemname: gemname, path: plugin
107
111
  require gemname
108
112
  load plugin
109
113
  rescue => e
110
- warn "Failed to load norikra UDF plugin", :plugin => plugin.to_s, :error_class => e.class, :error => e.message
114
+ warn "Failed to load norikra UDF plugin", plugin: plugin.to_s, error_class: e.class, error: e.message
111
115
  e.backtrace.each do |t|
112
116
  warn " " + t
113
117
  end
@@ -1,3 +1,3 @@
1
1
  module Norikra
2
- VERSION = "1.1.2"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -30,9 +30,9 @@ class Norikra::WebUI::API < Sinatra::Base
30
30
 
31
31
  def logging(type, handler, args=[], opts={})
32
32
  if type == :manage
33
- debug "WebAPI", :handler => handler.to_s, :args => args
33
+ debug "WebAPI", handler: handler.to_s, args: args
34
34
  else
35
- trace "WebAPI", :handler => handler.to_s, :args => args
35
+ trace "WebAPI", handler: handler.to_s, args: args
36
36
  end
37
37
 
38
38
  begin
@@ -111,7 +111,7 @@ class Norikra::WebUI::API < Sinatra::Base
111
111
  post '/register' do
112
112
  query_name, query_group, expression = args = parse_args(['query_name', 'query_group', 'expression'], request)
113
113
  logging(:manage, :register, args){
114
- r = engine.register(Norikra::Query.new(:name => query_name, :group => query_group, :expression => expression))
114
+ r = engine.register(Norikra::Query.new(name: query_name, group: query_group, expression: expression))
115
115
  json result: (!!r)
116
116
  }
117
117
  end
@@ -13,7 +13,7 @@ require 'erubis'
13
13
  class Norikra::WebUI::Handler < Sinatra::Base
14
14
  set :public_folder, File.absolute_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'public'))
15
15
  set :views, File.absolute_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'views'))
16
- set :erb, :escape_html => true
16
+ set :erb, escape_html: true
17
17
 
18
18
  enable :sessions
19
19
 
@@ -27,9 +27,9 @@ class Norikra::WebUI::Handler < Sinatra::Base
27
27
 
28
28
  def logging(type, handler, args=[], opts={})
29
29
  if type == :manage
30
- debug "WebUI", :handler => handler.to_s, :args => args
30
+ debug("WebUI"){ { handler: handler.to_s, args: args } }
31
31
  else
32
- trace "WebUI", :handler => handler.to_s, :args => args
32
+ trace("WebUI"){ { handler: handler.to_s, args: args } }
33
33
  end
34
34
 
35
35
  begin
@@ -78,7 +78,7 @@ class Norikra::WebUI::Handler < Sinatra::Base
78
78
  }
79
79
  }
80
80
 
81
- erb :index, :layout => :base, :locals => {
81
+ erb :index, layout: :base, locals: {
82
82
  input_data: input_data,
83
83
  stat: engine.statistics,
84
84
  queries: queries,
@@ -109,14 +109,14 @@ class Norikra::WebUI::Handler < Sinatra::Base
109
109
  redirect '/#query_add'
110
110
  }
111
111
 
112
- logging(:manage, :register, [query_name, query_group, expression], :on_error_hook => error_hook) do
112
+ logging(:manage, :register, [query_name, query_group, expression], on_error_hook: error_hook) do
113
113
  if query_name.nil? || query_name.empty?
114
114
  raise Norikra::ClientError, "Query name MUST NOT be blank"
115
115
  end
116
116
  if query_group.nil? || query_group.empty?
117
117
  query_group = nil
118
118
  end
119
- engine.register(Norikra::Query.new(:name => query_name, :group => query_group, :expression => expression))
119
+ engine.register(Norikra::Query.new(name: query_name, group: query_group, expression: expression))
120
120
  redirect '/#queries'
121
121
  end
122
122
  end
data/norikra.gemspec CHANGED
@@ -31,6 +31,5 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "bundler", "~> 1.3"
32
32
  spec.add_development_dependency "rake"
33
33
  spec.add_development_dependency "rspec"
34
- spec.add_development_dependency "spork"
35
34
  spec.add_development_dependency "pry"
36
35
  end
@@ -1,27 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'drb'
4
3
  require 'pry'
5
4
 
6
- begin
7
- begin
8
- DRb.start_service("druby://localhost:0")
9
- rescue SocketError, Errno::EADDRNOTAVAIL
10
- DRb.start_service("druby://:0")
11
- end
12
- $spec_server = DRbObject.new_with_uri("druby://127.0.0.1:8989")
13
- rescue DRb::DRbConnError
14
- err.puts "No DRb server is running. Running in local process instead ..."
15
- end
16
-
17
- def rspec(file=nil)
18
- if file
19
- $spec_server.run(["--color", "--format", "s", file], STDERR, STDOUT)
20
- else
21
- $spec_server.run(["--color", 'spec'], STDERR, STDOUT)
22
- end
23
- end
24
-
25
5
  $service = nil
26
6
  def service
27
7
  $service ||= com.espertech.esper.client.EPServiceProviderManager.getDefaultProvider
@@ -90,10 +70,6 @@ end
90
70
  puts <<DESC
91
71
 
92
72
  Example:
93
- > # execute 'spork' on other terminal
94
- > rspec 'spec/xxx_spec.rb'
95
- > rspec # for all tests
96
-
97
73
  > query = Norikra::Query.new(name:'test1', expression:'SELECT ...')
98
74
  >
99
75
  > query.ast.to_a # dump query AST
data/spec/field_spec.rb CHANGED
@@ -135,6 +135,20 @@ describe Norikra::Field do
135
135
  end
136
136
  end
137
137
 
138
+ context 'specified as nullable' do
139
+ describe '#dup' do
140
+ it 'saves original boolean value' do
141
+ f = Norikra::Field.new('foo', 'string', false, false) # non-nullable
142
+ expect(f.nullable?).to be_falsy
143
+ expect(f.dup.nullable?).to be_falsy
144
+
145
+ f = Norikra::Field.new('bar', 'int', false, true)
146
+ expect(f.nullable?).to be_truthy
147
+ expect(f.dup.nullable?).to be_truthy
148
+ end
149
+ end
150
+ end
151
+
138
152
  context 'defined as string field' do
139
153
  describe '#format' do
140
154
  it 'converts specified value as string' do
@@ -36,11 +36,44 @@ describe Norikra::FieldSet do
36
36
  set = Norikra::FieldSet.new({'x' => 'string', 'y' => 'long', 'a' => 'Boolean'})
37
37
  expect(set.summary).to eql('a:boolean,x:string,y:integer')
38
38
  end
39
+
40
+ it 'can set optional/nullable options for each fields' do
41
+ set = Norikra::FieldSet.new({
42
+ 'x' => {type: 'string'}, # optional: default_optional(==nil, falsy), nullable: false
43
+ 'y' => {type: 'long', optional: true, nullable: true},
44
+ })
45
+ expect(set.fields['x'].type).to eql('string')
46
+ expect(set.fields['x'].optional?).to be_falsy
47
+ expect(set.fields['x'].nullable?).to be_falsy
48
+
49
+ expect(set.fields['y'].type).to eql('integer')
50
+ expect(set.fields['y'].optional?).to be_truthy
51
+ expect(set.fields['y'].nullable?).to be_truthy
52
+ end
53
+
54
+ it 'sets summary with nullable informations, ordered by key names' do
55
+ set = Norikra::FieldSet.new({
56
+ 'x' => {type: 'string'}, # optional: default_optional(==nil, falsy), nullable: false
57
+ 'y' => {type: 'integer', optional: true, nullable: true},
58
+ 'z' => {type: 'boolean'}
59
+ })
60
+ expect(set.summary).to eql('x:string,y:integer:nullable,z:boolean')
61
+ end
39
62
  end
40
63
 
41
64
  context 'initialized with some fields' do
42
65
  set = Norikra::FieldSet.new({'x' => 'string', 'y' => 'long', 'a' => 'Boolean'})
43
66
  set2 = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'float', 'd' => 'bool', 'e' => 'integer'})
67
+ set3 = Norikra::FieldSet.new({
68
+ 'a' => 'string', 'b' => 'int', 'c' => 'float',
69
+ 'd' => {type:'bool', optional: true, nullable: true},
70
+ 'e' => {type:'long', optional: true, nullable: false}
71
+ })
72
+ set4 = Norikra::FieldSet.new({
73
+ 'a' => 'string', 'b' => 'int', 'c' => 'float',
74
+ 'd' => {type:'bool', optional: true, nullable: true},
75
+ 'e' => {type:'long', optional: true, nullable: true}
76
+ })
44
77
 
45
78
  q_set1 = Norikra::FieldSet.new({'x' => 'string', 'y' => 'long'}, nil, 0, ['set1', 'g1'])
46
79
  q_set2 = Norikra::FieldSet.new({'x' => 'string', 'y' => 'long'}, nil, 0, ['set2', nil])
@@ -63,6 +96,15 @@ describe Norikra::FieldSet do
63
96
  expect(q_set2 == q_set2.dup).to be_truthy
64
97
  expect(q_set3 == q_set3.dup).to be_truthy
65
98
  end
99
+
100
+ it 'make duplicated object with same type/optional/nullable specifications' do
101
+ set3d = set3.dup
102
+ ['a', 'b', 'c', 'd', 'e'].each do |f|
103
+ expect(set3d.fields[f].type).to eql(set3.fields[f].type)
104
+ expect(set3d.fields[f].optional).to eql(set3.fields[f].optional)
105
+ expect(set3d.fields[f].nullable).to eql(set3.fields[f].nullable)
106
+ end
107
+ end
66
108
  end
67
109
 
68
110
  describe '.leaves' do
@@ -175,6 +217,11 @@ describe Norikra::FieldSet do
175
217
  it 'returns comma-separeted sorted field names' do
176
218
  expect(set.field_names_key).to eql('a,x,y')
177
219
  end
220
+
221
+ it 'returns result w/o nullable fields' do
222
+ expect(set3.field_names_key).to eql('a,b,c,e')
223
+ expect(set4.field_names_key).to eql('a,b,c')
224
+ end
178
225
  end
179
226
 
180
227
  describe '#udpate_summary' do
@@ -192,6 +239,18 @@ describe Norikra::FieldSet do
192
239
 
193
240
  expect(x.summary).not_to eql(oldsummary)
194
241
  expect(x.summary).to eql('a:boolean,x:integer,y:integer')
242
+
243
+ x.fields['c'] = Norikra::Field.new('c', 'string', true, true) # optional, nullable
244
+
245
+ x.update_summary
246
+
247
+ expect(x.summary).to eql('a:boolean,c:string:nullable,x:integer,y:integer')
248
+
249
+ x.fields['b'] = Norikra::Field.new('b', 'float', true, false) # optional, non-nullable
250
+
251
+ x.update_summary
252
+
253
+ expect(x.summary).to eql('a:boolean,b:float,c:string:nullable,x:integer,y:integer')
195
254
  end
196
255
  end
197
256
 
@@ -219,6 +278,31 @@ describe Norikra::FieldSet do
219
278
  x.update([Norikra::Field.new('z', 'string')], true)
220
279
  expect(x.fields.size).to eql(4)
221
280
  expect(x.summary).to eql('a:boolean,x:string,y:integer,z:string')
281
+ x.update([Norikra::Field.new('b', 'string', true, true), Norikra::Field.new('c', 'integer', true, true)], true)
282
+ expect(x.fields.size).to eql(6)
283
+ expect(x.summary).to eql('a:boolean,b:string:nullable,c:integer:nullable,x:string,y:integer,z:string')
284
+ end
285
+ end
286
+
287
+ describe '#nullable_diff' do
288
+ it 'provides the list of nullable fields, missing in self' do
289
+ q1 = Norikra::FieldSet.new({
290
+ 'a' => 'string', 'd' => {type:'string', optional: false, nullable: true},
291
+ })
292
+ q2 = Norikra::FieldSet.new({
293
+ 'a' => 'string', 'b' => 'int',
294
+ 'd' => {type:'string', optional: false, nullable: true},
295
+ 'e' => {type:'int', optional: false, nullable: true},
296
+ })
297
+
298
+ s1 = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'bool'})
299
+ expect(s1.nullable_diff(q1).map(&:name).sort).to eql(['d'])
300
+
301
+ s2 = Norikra::FieldSet.new({'a' => 'string'})
302
+ expect(s2.nullable_diff(q1).map(&:name).sort).to eql(['d'])
303
+
304
+ s3 = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'bool', 'd' => 'string'})
305
+ expect(s3.nullable_diff(q2).map(&:name).sort).to eql(['e'])
222
306
  end
223
307
  end
224
308
 
@@ -240,6 +324,15 @@ describe Norikra::FieldSet do
240
324
  expect(d['c']).to eql('double')
241
325
  expect(d['d']).to eql('boolean')
242
326
  expect(d['e']).to eql('long')
327
+
328
+ d = set3.definition # nullable does not have any effects
329
+ expect(d).to be_instance_of(Hash)
330
+ expect(d.size).to eql(5)
331
+ expect(d['a']).to eql('string')
332
+ expect(d['b']).to eql('long')
333
+ expect(d['c']).to eql('double')
334
+ expect(d['d']).to eql('boolean')
335
+ expect(d['e']).to eql('long')
243
336
  end
244
337
  end
245
338
 
@@ -254,6 +347,24 @@ describe Norikra::FieldSet do
254
347
  other.update([Norikra::Field.new('z', 'double')], false)
255
348
  expect(set.subset?(other)).to be_truthy
256
349
  end
350
+
351
+ it 'returns true if other instance does not have fields marked as nullable' do
352
+ other = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'float', 'd' => 'bool', 'e' => 'int'})
353
+ expect(set3.subset?(other)).to be_truthy
354
+ expect(set4.subset?(other)).to be_truthy
355
+
356
+ other = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'float'})
357
+ expect(set3.subset?(other)).to be_falsy
358
+ expect(set4.subset?(other)).to be_truthy
359
+
360
+ other = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'float', 'd' => 'bool'})
361
+ expect(set3.subset?(other)).to be_falsy
362
+ expect(set4.subset?(other)).to be_truthy
363
+
364
+ other = Norikra::FieldSet.new({'a' => 'string', 'b' => 'int', 'c' => 'float', 'e' => 'int'})
365
+ expect(set3.subset?(other)).to be_truthy
366
+ expect(set4.subset?(other)).to be_truthy
367
+ end
257
368
  end
258
369
 
259
370
  describe '#bind' do