norikra 1.1.2-java → 1.2.0-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.
@@ -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