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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Changes.md +4 -0
- data/Gemfile +4 -0
- data/README.md +3 -6
- data/lib/norikra/cli.rb +54 -54
- data/lib/norikra/engine.rb +91 -44
- data/lib/norikra/field.rb +12 -7
- data/lib/norikra/fieldset.rb +26 -13
- data/lib/norikra/listener.rb +192 -61
- data/lib/norikra/logger.rb +34 -10
- data/lib/norikra/output_pool.rb +1 -1
- data/lib/norikra/query/ast.rb +240 -20
- data/lib/norikra/query.rb +103 -46
- data/lib/norikra/rpc/handler.rb +3 -3
- data/lib/norikra/rpc/http.rb +1 -1
- data/lib/norikra/server.rb +34 -28
- data/lib/norikra/stats.rb +3 -3
- data/lib/norikra/target.rb +1 -1
- data/lib/norikra/typedef.rb +22 -17
- data/lib/norikra/typedef_manager.rb +9 -6
- data/lib/norikra/udf.rb +6 -2
- data/lib/norikra/version.rb +1 -1
- data/lib/norikra/webui/api.rb +3 -3
- data/lib/norikra/webui/handler.rb +6 -6
- data/norikra.gemspec +0 -1
- data/script/{spec_server_pry → pry} +0 -24
- data/spec/field_spec.rb +14 -0
- data/spec/fieldset_spec.rb +111 -0
- data/spec/listener_spec.rb +64 -26
- data/spec/query_spec.rb +171 -25
- data/spec/spec_helper.rb +0 -46
- data/spec/typedef_manager_spec.rb +15 -9
- data/spec/typedef_spec.rb +11 -11
- metadata +3 -19
- data/lib/norikra/rpc/error.rb +0 -0
- data/lib/norikra/rubyudf.rb +0 -49
data/lib/norikra/server.rb
CHANGED
@@ -19,28 +19,28 @@ module Norikra
|
|
19
19
|
attr_accessor :running
|
20
20
|
|
21
21
|
MICRO_PREDEFINED = {
|
22
|
-
:
|
23
|
-
|
24
|
-
:
|
25
|
-
:
|
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
|
-
:
|
29
|
-
|
30
|
-
:
|
31
|
-
:
|
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
|
-
:
|
35
|
-
|
36
|
-
:
|
37
|
-
:
|
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
|
-
:
|
41
|
-
|
42
|
-
:
|
43
|
-
:
|
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
|
-
:
|
122
|
-
:
|
123
|
-
:
|
121
|
+
engine: @engine,
|
122
|
+
host: @host, port: @port,
|
123
|
+
threads: @thread_conf[:rpc][:threads]
|
124
124
|
)
|
125
125
|
@webserver = Norikra::WebUI::HTTP.new(
|
126
|
-
:
|
127
|
-
:
|
128
|
-
:
|
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(:
|
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", :
|
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", :
|
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", :
|
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
|
-
:
|
12
|
-
:
|
13
|
-
:
|
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)
|
data/lib/norikra/target.rb
CHANGED
data/lib/norikra/typedef.rb
CHANGED
@@ -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
|
-
|
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", :
|
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", :
|
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
|
-
|
177
|
-
|
178
|
-
|
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,
|
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,
|
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,
|
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] = {:
|
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) #
|
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) #
|
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!", :
|
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", :
|
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
|
data/lib/norikra/version.rb
CHANGED
data/lib/norikra/webui/api.rb
CHANGED
@@ -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", :
|
33
|
+
debug "WebAPI", handler: handler.to_s, args: args
|
34
34
|
else
|
35
|
-
trace "WebAPI", :
|
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(:
|
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, :
|
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
|
30
|
+
debug("WebUI"){ { handler: handler.to_s, args: args } }
|
31
31
|
else
|
32
|
-
trace
|
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, :
|
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], :
|
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(:
|
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
@@ -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
|
data/spec/fieldset_spec.rb
CHANGED
@@ -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
|