omf_oml 0.9.3

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.
@@ -0,0 +1,64 @@
1
+
2
+ require 'omf_oml'
3
+ require 'omf_oml/schema'
4
+ require 'omf_oml/tuple'
5
+
6
+ module OMF::OML
7
+
8
+ # This class represents a single vector from an OML measurement stream.
9
+ # It provides various methods to access the vectors elements.
10
+ #
11
+ # NOTE: Do not store the vector itself, but make a copy as the instance may be
12
+ # reused over various rows by the sender.
13
+ #
14
+ class OmlTuple < Tuple
15
+
16
+ # Return the elements of the vector as an array
17
+ # def to_a(include_index_ts = false)
18
+ # res = []
19
+ # r = @raw
20
+ # if include_index_ts
21
+ # res << @vprocs[:oml_ts].call(r)
22
+ # res << @vprocs[:oml_seq_no].call(r)
23
+ # end
24
+ # @schema.each do |col|
25
+ # res << @vprocs[col[:name]].call(r)
26
+ # end
27
+ # res
28
+ # end
29
+
30
+ def ts
31
+ @raw[0].to_f
32
+ end
33
+
34
+ def seq_no
35
+ @raw[1].to_i
36
+ end
37
+
38
+ # Note: This method assumes that the first two elements in each OML tuple,
39
+ # 'oml_ts' and 'oml_seq_no' are not defined in the associated schema.
40
+ #
41
+ def process_schema(schema)
42
+ i = 0
43
+ @vprocs = {}
44
+ schema.each_column do |col| #
45
+ name = col[:name] || raise("Ill-formed schema '#{schema}'")
46
+ type = col[:type] || raise("Ill-formed schema '#{schema}'")
47
+ j = i + 2; # need to create a locally scoped variable for the following lambdas
48
+ @vprocs[name] = @vprocs[i] = case type
49
+ when :string
50
+ lambda do |r| r[j] end
51
+ when :float
52
+ lambda do |r| r[j].to_f end
53
+ when :integer
54
+ lambda do |r| r[j].to_i end
55
+ else raise "Unrecognized OML type '#{type}' (#{col.inspect})"
56
+ end
57
+ i += 1
58
+ end
59
+ @vprocs[:oml_ts] = lambda do |r| r[0].to_f end
60
+ @vprocs[:oml_seq_no] = lambda do |r| r[1].to_i end
61
+ end
62
+
63
+ end # OmlTuple
64
+ end # OMF::OML
@@ -0,0 +1,200 @@
1
+
2
+ require 'omf_common/lobject'
3
+ require 'omf_oml'
4
+
5
+ module OMF::OML
6
+
7
+ # This class represents the schema of an OML measurement stream.
8
+ #
9
+ class OmlSchema < OMF::Common::LObject
10
+
11
+ CLASS2TYPE = {
12
+ TrueClass => 'boolean',
13
+ FalseClass => 'boolean',
14
+ String => 'string',
15
+ Symbol => 'string',
16
+ Fixnum => 'decimal',
17
+ Float => 'double',
18
+ Time => 'dateTime'
19
+ }
20
+
21
+ # Map various type definitions (all lower case) into a single one
22
+ ANY2TYPE = {
23
+ 'integer' => :integer,
24
+ 'int' => :integer,
25
+ 'int32' => :integer,
26
+ 'int64' => :integer,
27
+ 'bigint' => :integer,
28
+ 'unsigned integer' => :integer,
29
+ 'float' => :float,
30
+ 'real' => :float,
31
+ 'double' => :float,
32
+ 'text' => :string,
33
+ 'string' => :string,
34
+ 'date' => :date,
35
+ 'dateTime'.downcase => :dateTime, # should be 'datetime' but we downcase the string for comparison
36
+ 'key' => :key,
37
+ }
38
+
39
+ def self.create(schema_description)
40
+ if schema_description.kind_of? self
41
+ return schema_description
42
+ end
43
+ return self.new(schema_description)
44
+ end
45
+
46
+ # Return the col name at a specific index
47
+ #
48
+ def name_at(index)
49
+ @schema[index][:name]
50
+ end
51
+
52
+ # Return the col index for column named +name+
53
+ #
54
+ def index_for_col(name)
55
+ name = name.to_sym
56
+ @schema.each_with_index do |col, i|
57
+ return i if col[:name] == name
58
+ end
59
+ raise "Unknonw column '#{name}'"
60
+ end
61
+
62
+ # Return the column names as an array
63
+ #
64
+ def names
65
+ @schema.collect do |col| col[:name] end
66
+ end
67
+
68
+ # Return the col type at a specific index
69
+ #
70
+ def type_at(index)
71
+ @schema[index][:type]
72
+ end
73
+
74
+ def columns
75
+ @schema
76
+ end
77
+
78
+ def insert_column_at(index, col)
79
+ if col.kind_of?(Symbol) || col.kind_of?(String)
80
+ col = [col]
81
+ end
82
+ if col.kind_of? Array
83
+ # should be [name, type]
84
+ if col.length == 1
85
+ col = {:name => col[0].to_sym,
86
+ :type => :string,
87
+ :title => col[0].to_s.split('_').collect {|s| s.capitalize}.join(' ')}
88
+ elsif col.length == 2
89
+ col = {:name => col[0].to_sym,
90
+ :type => col[1].to_sym,
91
+ :title => col[0].to_s.split('_').collect {|s| s.capitalize}.join(' ')}
92
+ elsif col.length == 3
93
+ col = {:name => col[0].to_sym, :type => col[1].to_sym, :title => col[2]}
94
+ else
95
+ throw "Simple column schema should consist of [name, type, title] array, but found '#{col.inspect}'"
96
+ end
97
+ elsif col.kind_of? Hash
98
+ # ensure there is a :title property
99
+ unless col[:title]
100
+ col[:title] = col[:name].to_s.split('_').collect {|s| s.capitalize}.join(' ')
101
+ end
102
+ end
103
+
104
+ # should normalize type
105
+ if type = col[:type]
106
+ unless type = ANY2TYPE[type.to_s.downcase]
107
+ warn "Unknown type definition '#{col[:type]}', default to 'string'"
108
+ type = :string
109
+ end
110
+ else
111
+ warn "Missing type definition in '#{col[:name]}', default to 'string'"
112
+ type = :string
113
+ end
114
+ col[:type] = type
115
+
116
+ col[:type_conversion] = case type
117
+ when :string
118
+ lambda do |r| r end
119
+ when :key
120
+ lambda do |r| r end
121
+ when :integer
122
+ lambda do |r| r.to_i end
123
+ when :float
124
+ lambda do |r| r.to_f end
125
+ when :date
126
+ lambda do |r| Date.parse(r) end
127
+ when :dateTime
128
+ lambda do |r| Time.parse(r) end
129
+ else raise "Unrecognized Schema type '#{type}'"
130
+ end
131
+
132
+ @schema.insert(index, col)
133
+ end
134
+
135
+ def each_column(&block)
136
+ @schema.each do |c|
137
+ block.call(c)
138
+ end
139
+ end
140
+
141
+ # Translate a record described in a hash of 'col_name => value'
142
+ # to a row array
143
+ #
144
+ # hrow - Hash describing a row
145
+ # set_nil_when_missing - If true, set any columns not described in hrow to nil
146
+ #
147
+ def hash_to_row(hrow, set_nil_when_missing = false)
148
+ r = @schema.collect do |cdescr|
149
+ cname = cdescr[:name]
150
+ next nil if cname == :__id__
151
+ unless hrow.key? cname
152
+ next nil if set_nil_when_missing
153
+ raise "Missing record element '#{cname}' in record '#{hrow}'"
154
+ end
155
+ cdescr[:type_conversion].call(hrow[cname])
156
+ end
157
+ r.shift # remove __id__ columns
158
+ #puts "#{r.inspect} -- #{@schema.map {|c| c[:name]}.inspect}"
159
+ r
160
+ end
161
+
162
+ # Cast each element in 'row' into its proper type according to this schema
163
+ #
164
+ def cast_row(raw_row)
165
+ unless raw_row.length == @schema.length
166
+ raise "Row needs to have same size as schema (#{raw_row.inspect})"
167
+ end
168
+ # This can be done more elegantly in 1.9
169
+ row = []
170
+ raw_row.each_with_index do |el, i|
171
+ row << @schema[i][:type_conversion].call(el)
172
+ end
173
+ row
174
+ end
175
+
176
+ def describe
177
+ @schema.map {|c| {:name => c[:name], :type => c[:type], :title => c[:title] }}
178
+ end
179
+
180
+ def to_json(*opt)
181
+ describe.to_json(*opt)
182
+ end
183
+
184
+ protected
185
+
186
+ # schema_description - Array containing [name, type*] for every column in table
187
+ # TODO: define format of TYPE
188
+ #
189
+ def initialize(schema_description)
190
+ # check if columns are described by hashes or 2-arrays
191
+ @schema = []
192
+ schema_description.each_with_index do |cdesc, i|
193
+ insert_column_at(i, cdesc)
194
+ end
195
+ debug "schema: '#{describe.inspect}'"
196
+
197
+ end
198
+ end # OmlSchema
199
+
200
+ end
@@ -0,0 +1,412 @@
1
+ require 'rubygems'
2
+ require 'rexml/document'
3
+ require 'time'
4
+ require 'logger'
5
+ require 'sequel'
6
+
7
+ require 'omf_common/lobject'
8
+ require 'omf_oml'
9
+
10
+ # module OMF::OML
11
+ # module Sequel; end
12
+ # end
13
+
14
+ module OMF::OML::Sequel
15
+ module Server
16
+
17
+ class Query < OMF::Common::LObject
18
+
19
+ def self.parse(xmls, repoFactory = RepositoryFactory.new, logger = Logger.new(STDOUT))
20
+ if xmls.kind_of? String
21
+ doc = REXML::Document.new(xmls)
22
+ root = doc.root
23
+ else
24
+ root = xmls
25
+ end
26
+ unless root.name == 'query'
27
+ raise "XML fragment needs to start with 'query' but does start with '#{root.name}"
28
+ end
29
+ q = self.new(root, repoFactory, logger)
30
+ q.relation
31
+ end
32
+
33
+ def initialize(queryEl, repoFactory, logger)
34
+ @queryEl = queryEl
35
+ @repoFactory = repoFactory || RepositoryFactory.new
36
+ @logger = logger || Logger.new(STDOUT)
37
+ @tables = {}
38
+ @lastRel = nil
39
+ @offset = 0
40
+ @limit = 0
41
+ queryEl.children.each do |el|
42
+ @lastRel = parse_el(el, @lastRel)
43
+ end
44
+ if @limit > 0
45
+ @lastRel = @lastRel.limit(@limit, @offset)
46
+ end
47
+ end
48
+
49
+ def each(&block)
50
+ # sel_mgr = relation
51
+ # unless sel_mgr.kind_of? SelectionManager
52
+ # raise "Can only be called on SELECT statement"
53
+ # end
54
+ # puts sel_mgr.engine
55
+ relation.each(&block)
56
+ end
57
+
58
+ def relation
59
+ raise "No query defined, yet" unless @lastRel
60
+ @lastRel
61
+ end
62
+
63
+ # Requested format for result. Default is 'xml'
64
+ def rformat
65
+ @queryEl.attributes['rformat'] || 'xml'
66
+ end
67
+
68
+ def parse_el(el, lastRel)
69
+ if (el.kind_of? REXML::Text)
70
+ # skip
71
+ return lastRel
72
+ end
73
+ args = parse_args(el)
74
+ @logger.debug "CHILD #{el.name}"
75
+ # keep the last table for this level to be used
76
+ # to create proper columns.
77
+ # NOTE: This is not fool-proof but we need columns
78
+ # to later resolve the column type.
79
+ #
80
+ name = el.name.downcase
81
+ if lastRel.nil?
82
+ case name
83
+ when /repository/
84
+ lastRel = repo = parse_repository(el)
85
+ @tables = repo.tables
86
+ @logger.debug "Created repository: #{lastRel}"
87
+ else
88
+ raise "Need to start with 'table' declaration, but does with '#{name}'"
89
+ end
90
+ elsif name == 'table'
91
+ lastRel = parse_table(el)
92
+ elsif name == 'project'
93
+ # turn all arguments into proper columns
94
+ # cols = convert_to_cols(args)
95
+ lastRel = lastRel.select(*args)
96
+ # elsif lastRel.kind_of?(::Arel::Table) && name == 'as'
97
+ # # keep track of all created tables
98
+ # lastRel = lastRel.alias(*args)
99
+ # @repository.add_table(args[0], lastRel)
100
+ elsif name == 'skip'
101
+ @offset = args[0].to_i
102
+ elsif name == 'take'
103
+ @limit = args[0].to_i
104
+ else
105
+ @logger.debug "Sending '#{name}' to #{lastRel.class}"
106
+ lastRel = lastRel.send(name, *args)
107
+ end
108
+ @logger.debug "lastRel for <#{el}> is #{lastRel.class}"
109
+ lastRel
110
+ end
111
+
112
+ def parse_repository(el)
113
+ @repository = @repoFactory.create_from_xml(el, @logger)
114
+ end
115
+
116
+
117
+ # Return the arguments defined in @parentEl as array
118
+ def parse_args(parentEl)
119
+ args = []
120
+ parentEl.children.each do |el|
121
+ next if (el.kind_of? REXML::Text)
122
+ unless el.name == 'arg'
123
+ raise "Expected argument definition but got element '#{el.name}"
124
+ end
125
+ args << parse_arg(el)
126
+ end
127
+ args
128
+ end
129
+
130
+ # Return the arguments defined in @parentEl as array
131
+ def parse_arg(pel)
132
+ res = nil
133
+ #col = nil
134
+ pel.children.each do |el|
135
+ if (el.kind_of? REXML::Text)
136
+ val = el.value
137
+ next if val.strip.empty? # skip text between els
138
+ return parse_arg_primitive(pel, val)
139
+ else
140
+ name = el.name.downcase
141
+ case name
142
+ when /col/
143
+ res = parse_column(el)
144
+ when /eq/
145
+ if res.nil?
146
+ raise "Missing 'col' definiton before 'eq'."
147
+ end
148
+ p = parse_args(el)
149
+ unless p.length == 1
150
+ raise "'eq' can only hnadle 1 argument, but is '#{p.inspect}'"
151
+ end
152
+ res = {res => p[0]}
153
+ else
154
+ raise "Need to be 'col' declaration, but is '#{name}'"
155
+ end
156
+ end
157
+ end
158
+ res
159
+ end
160
+
161
+ def parse_arg_primitive(pel, value)
162
+ type = pel.attributes['type'] || 'string'
163
+ case type
164
+ when /string/
165
+ value
166
+ when /boolean/
167
+ value.downcase == 'true' || value == '1'
168
+ when /decimal/
169
+ value.to_i
170
+ when /double/
171
+ value.to_f
172
+ when /dateTime/
173
+ Time.xmlschema
174
+ else
175
+ raise "Unknown arg type '#{type}"
176
+ end
177
+ end
178
+
179
+ def convert_to_cols(args)
180
+ args.collect do |arg|
181
+ if arg.kind_of? String
182
+ table = @repository.get_first_table()
183
+ raise "Unknown table for column '#{arg}'" unless table
184
+ table[arg]
185
+ else
186
+ arg
187
+ end
188
+ end
189
+ end
190
+
191
+ # <col name='oml_sender_id' table='iperf_TCP_Info'/>
192
+ def parse_column(el)
193
+ unless colName = el.attributes['name']
194
+ raise "Missing 'name' attribute for 'col' element"
195
+ end
196
+ col = colName
197
+ unless tblName = el.attributes['table']
198
+ raise "Missing 'table' attribute for col '#{colName}'"
199
+ end
200
+ unless @tables.member?(tblName.to_sym)
201
+ raise "Unknown table name '#{tblName}' (#{el})"
202
+ end
203
+ col = "#{tblName}__#{colName}"
204
+
205
+ if colAlias = el.attributes['alias']
206
+ col = "#{col}___#{colAlias}"
207
+ end
208
+ col.to_sym
209
+ end
210
+
211
+ def parse_table(el)
212
+ unless name = el.attributes['tname']
213
+ raise "Missing 'tname' attribute for 'table' element"
214
+ end
215
+ if talias = el.attributes['talias']
216
+ name = "#{name}___#{talias}"
217
+ @tables << talias.to_sym
218
+ end
219
+
220
+ @repository[name.to_sym]
221
+ end
222
+
223
+ end # Query
224
+
225
+ class RepositoryFactory < OMF::Common::LObject
226
+
227
+ def initialize(opts = {})
228
+ @opts = opts
229
+ end
230
+
231
+ def create_from_xml(el, logger)
232
+ name = el ? el.attributes['name'] : nil
233
+ raise "<repository> is missing attribute 'name'" unless name
234
+ create(name, logger)
235
+ end
236
+
237
+ def create(database, logger = Logger.new(STDOUT))
238
+ opts = @opts.dup
239
+ if pre = opts[:database_prefix]
240
+ database = pre + database
241
+ opts.delete(:database_prefix)
242
+ end
243
+ if post = opts[:database_postfix]
244
+ database = database + post
245
+ opts.delete(:database_postfix)
246
+ end
247
+ opts[:database] = database
248
+ ::Sequel.connect(opts)
249
+ end
250
+
251
+ end # RepositoryFactory
252
+ end # Server
253
+ end
254
+
255
+ module Sequel
256
+ class Dataset
257
+ CLASS2TYPE = {
258
+ TrueClass => 'boolean',
259
+ FalseClass => 'boolean',
260
+ String => 'string',
261
+ Symbol => 'string',
262
+ Fixnum => 'decimal',
263
+ Float => 'double',
264
+ Time => 'dateTime'
265
+ }
266
+
267
+ def row_description(row)
268
+ n = naked
269
+ cols = n.columns
270
+ descr = {}
271
+ cols.collect do |cn|
272
+ cv = row[cn]
273
+ descr[cn] = CLASS2TYPE[cv.class]
274
+ end
275
+ descr
276
+ end
277
+
278
+ def schema_for_row(row)
279
+ n = naked
280
+ cols = n.columns
281
+ descr = {}
282
+ cols.collect do |cn|
283
+ cv = row[cn]
284
+ {:name => cn, :type => CLASS2TYPE[cv.class]}
285
+ end
286
+ end
287
+ end
288
+ end
289
+
290
+
291
+
292
+ def test_sequel_server()
293
+
294
+ tests = []
295
+
296
+ tests << %{
297
+ <query>
298
+ <repository name='test'/>
299
+ <table tname='iperf_TCP_Info'/>
300
+ <project>
301
+ <arg><col name='Bandwidth_avg' table='iperf_TCP_Info'/></arg>
302
+ </project>
303
+ </query>
304
+ }
305
+
306
+ tests << %{
307
+ <query>
308
+ <repository name='test'/>
309
+ <table tname='iperf_TCP_Info' talias='t'/>
310
+ <project>
311
+ <arg>
312
+ <col name='oml_sender_id' alias='foo' table='t'/>
313
+ </arg>
314
+ <arg>
315
+ <col name='oml_ts_server' table='t' alias='goo'/>
316
+ </arg>
317
+ <arg><col name='Bandwidth_avg' table='t'/></arg>
318
+ </project>
319
+ <where>
320
+ <arg>
321
+ <col name='oml_sender_id' table='t'/>
322
+ <eq>
323
+ <arg type='decimal'> 2</arg>
324
+ </eq>
325
+ </arg>
326
+ </where>
327
+ </query>
328
+ }
329
+
330
+ # mc = repo[:mediacontent]
331
+ # mc2 = mc.alias
332
+ # accessed = mc2.where(mc2[:status].eq('Accessed')).project(:oml_ts_server, :name)
333
+ # q = mc.project(:name).join(accessed).on(mc[:name].eq(mc2[:name]))
334
+
335
+ # tests << %{
336
+ # <query>
337
+ # <repository name='prefetching_4'/>
338
+ # <table tname='mediacontent'/>
339
+ # <project>
340
+ # <arg type='string'>name</arg>
341
+ # </project>
342
+ # <join>
343
+ # <arg>
344
+ # <table tname='mediacontent' talias='mediacontent1'/>
345
+ # <where>
346
+ # <arg>
347
+ # <col name='status' table='mediacontent' talias='mediacontent1'/>
348
+ # <eq>
349
+ # <arg type='string'>Accessed</arg>
350
+ # </eq>
351
+ # </arg>
352
+ # </where>
353
+ # <project>
354
+ # <arg type='string'>oml_ts_server</arg>
355
+ # <arg type='string'>name</arg>
356
+ # </project>
357
+ # </arg>
358
+ # </join>
359
+ # <on>
360
+ # <arg>
361
+ # <col name='name' table='mediacontent'/>
362
+ # <eq>
363
+ # <arg>
364
+ # <col name='name' table='mediacontent' talias='mediacontent1'/>
365
+ # </arg>
366
+ # </eq>
367
+ # </arg>
368
+ # </on>
369
+ # </query>
370
+ # }
371
+
372
+ factory = OMF::OML::Sequel::Server::RepositoryFactory.new(
373
+ :adapter => 'sqlite',
374
+ :database_prefix => '/Users/max/src/omf_mytestbed_net/omf-common/test/',
375
+ :database_postfix => '.sq3'
376
+ )
377
+
378
+ repo = factory.create('test')
379
+ puts repo.tables
380
+
381
+ tests.each do |t|
382
+ ds = OMF::OML::Sequel::Server::Query.parse(t, factory)
383
+ puts ds.inspect
384
+ puts ds.columns.inspect
385
+ puts ds.first.inspect
386
+ end
387
+
388
+ first = true
389
+ types = []
390
+ ds = OMF::OML::Sequel::Server::Query.parse(tests[1], factory).limit(10)
391
+ ds.each do |r|
392
+ if (first)
393
+ puts ds.row_description(r).inspect
394
+ puts ds.schema_for_row(r).inspect
395
+ #puts (ds.schema_for_row(r).methods - Object.new.methods).sort
396
+ # cols = ds.columns
397
+ # #cols.collect do |c|
398
+ # cols.each do |c|
399
+ # puts "#{c} : #{OML::Sequel::XML::Server::Query::CLASS2TYPE[r[c].class]}"
400
+ # end
401
+ first = false
402
+ # puts types.inspect
403
+ end
404
+ puts r.inspect
405
+ end
406
+ puts "QUERY: done"
407
+ end
408
+
409
+ if $0 == __FILE__
410
+ test_sequel_server
411
+ end
412
+