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.
- 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
|