rgviz 0.40 → 0.41

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.
Files changed (2) hide show
  1. data/lib/rgviz/memory_executor.rb +617 -0
  2. metadata +4 -3
@@ -0,0 +1,617 @@
1
+ module Rgviz
2
+ class MemoryExecutor
3
+ def initialize(query, rows, types)
4
+ @query = query
5
+ @rows = rows
6
+ @types = types
7
+ @types_to_indices = {}
8
+ i = 0
9
+ @types.each do |k, v|
10
+ @types_to_indices[k.to_s] = i
11
+ i += 1
12
+ end
13
+ @labels = {}
14
+ end
15
+
16
+ def execute(options = {})
17
+ @query = Parser.parse(@query, options) unless @query.kind_of?(Query)
18
+ @table = Table.new
19
+
20
+ process_labels
21
+
22
+ check_has_aggregation
23
+
24
+ generate_columns
25
+
26
+ if @has_aggregation
27
+ filter_and_group_rows
28
+ else
29
+ filter_rows
30
+ end
31
+
32
+ sort_rows
33
+ limit_rows
34
+ generate_rows
35
+
36
+ @table
37
+ end
38
+
39
+ private
40
+
41
+ def process_labels
42
+ return unless @query.labels
43
+
44
+ @query.labels.each do |label|
45
+ @labels[label.column] = label.label
46
+ end
47
+ end
48
+
49
+ def check_has_aggregation
50
+ @has_aggregation = @query.group_by || @query.pivot || (@query.select? && @query.select.columns.any?{|x| x.class == AggregateColumn})
51
+ end
52
+
53
+ def generate_columns
54
+ if @query.select?
55
+ # Select the specified columns
56
+ i = 0
57
+ @query.select.columns.each do |col|
58
+ @table.cols << (Column.new :id => column_id(col, i), :type => column_type(col), :label => column_label(col))
59
+ i += 1
60
+ end
61
+ else
62
+ # Select all columns
63
+ @table.cols = @types.map{|k, v| Column.new :id => k, :type => v, :label => k}
64
+ end
65
+ end
66
+
67
+ def filter_rows
68
+ return unless @query.where
69
+
70
+ @rows = @rows.reject{|row| row_is_filtered? row}
71
+ end
72
+
73
+ def row_is_filtered?(row)
74
+ visitor = EvalWhereVisitor.new @types_to_indices, row
75
+ @query.where.accept visitor
76
+ !visitor.value
77
+ end
78
+
79
+ def filter_and_group_rows
80
+ if !@query.group_by && !@query.pivot
81
+ @rows = [[nil, @rows]] if @has_aggregation
82
+ return
83
+ end
84
+
85
+ group_columns = []
86
+ group_columns += @query.group_by.columns if @query.group_by
87
+ group_columns += @query.pivot.columns if @query.pivot
88
+
89
+ groups = Hash.new{|h, k| h[k] = []}
90
+ @rows.each do |row|
91
+ next if @query.where && row_is_filtered?(row)
92
+
93
+ group = group_columns.map{|col| group_row row, col}
94
+ groups[group] << row
95
+ end
96
+ @rows = groups.to_a
97
+ end
98
+
99
+ def group_row(row, col)
100
+ visitor = EvalGroupVisitor.new @types_to_indices, row
101
+ col.accept visitor
102
+ [col, visitor.value]
103
+ end
104
+
105
+ def sort_rows
106
+ return unless @query.order_by
107
+
108
+ if @has_aggregation
109
+ sort_aggregated_rows
110
+ return
111
+ end
112
+
113
+ @rows.sort! do |row1, row2|
114
+ @sort = 0
115
+ @query.order_by.sorts.each do |sort|
116
+ val1 = eval_select sort.column, row1
117
+ val2 = eval_select sort.column, row2
118
+ @sort = sort.order == Sort::Asc ? val1 <=> val2 : val2 <=> val1
119
+ break unless @sort == 0
120
+ end
121
+ @sort
122
+ end
123
+ end
124
+
125
+ def sort_aggregated_rows
126
+ @rows.sort! do |row1, row2|
127
+ g1 = row1[0]
128
+ g2 = row2[0]
129
+ @sort = 0
130
+ @query.order_by.sorts.each do |sort|
131
+ g1sc = g1.select{|x| x[0] == sort.column}.first
132
+ if g1sc
133
+ g2sc = g2.select{|x| x[0] == sort.column}.first
134
+ @sort = sort.order == Sort::Asc ? g1sc[1] <=> g2sc[1] : g2sc[1] <=> g1sc[1]
135
+ break unless @sort == 0
136
+ else
137
+ raise "Order by column not found: #{sort.column}"
138
+ end
139
+ end
140
+ @sort
141
+ end
142
+ end
143
+
144
+ def limit_rows
145
+ return unless @query.limit
146
+
147
+ offset = (@query.offset ? (@query.offset - 1) : 0)
148
+ @rows = @rows[offset ... offset + @query.limit]
149
+ end
150
+
151
+ def generate_rows
152
+ if !@has_aggregation
153
+ @table.rows = @rows.map{|row| generate_row row}
154
+ return
155
+ end
156
+
157
+ rows = generate_aggregated_rows
158
+
159
+ if !@query.pivot
160
+ rows_to_table rows
161
+ return
162
+ end
163
+
164
+ uniq_pivots = pivot_rows rows
165
+ pivot_cols uniq_pivots
166
+ end
167
+
168
+ def pivot_rows(rows)
169
+ # This is grouping => pivot => [selections]
170
+ fin = if RUBY_VERSION.start_with?("1.8")
171
+ if defined? ActiveSupport::OrderedHash
172
+ ActiveSupport::OrderedHash.new
173
+ else
174
+ SimpleOrderedHash.new
175
+ end
176
+ else
177
+ Hash.new
178
+ end
179
+
180
+ # The uniq pivot values
181
+ uniq_pivots = []
182
+
183
+ selection_indices = []
184
+
185
+ i = 0
186
+ @query.select.columns.each do |col|
187
+ if !@query.group_by || !@query.group_by.columns.include?(col)
188
+ selection_indices << i
189
+ end
190
+ i += 1
191
+ end
192
+
193
+ # Fill fin and uniq_pivots
194
+ rows.each do |group, row|
195
+ grouped_by = []
196
+ pivots = []
197
+
198
+ # The grouping key of this result
199
+ grouped_by = group.select{|g| @query.group_by && @query.group_by.columns.include?(g[0])}
200
+
201
+ # The pivots of this result
202
+ pivots = group.select{|g| @query.pivot.columns.include?(g[0])}
203
+
204
+ # The selections of this result
205
+ selections = selection_indices.map{|i| row[i]}
206
+
207
+ uniq_pivots << pivots unless uniq_pivots.include? pivots
208
+
209
+ # Now put all this info into fin
210
+ fin[grouped_by] = {} unless fin[grouped_by]
211
+ fin[grouped_by][pivots] = selections
212
+ end
213
+
214
+ # Sort the uniq pivots so the results will be sorted for a human
215
+ uniq_pivots = uniq_pivots.sort_by{|a| a.map{|x| x[1]}.to_s}
216
+
217
+ # Create the rows
218
+ fin.each do |key, value|
219
+ row = Row.new
220
+ @table.rows << row
221
+
222
+ pivot_i = 0
223
+
224
+ @query.select.columns.each do |col|
225
+ if @query.group_by && @query.group_by.columns.include?(col)
226
+ matching_col = key.select{|x| x[0] == col}.first
227
+ row.c << Cell.new(:v => format_value(matching_col[1]))
228
+ else
229
+ uniq_pivots.each do |uniq_pivot|
230
+ u = value[uniq_pivot]
231
+ row.c << Cell.new(:v => u ? format_value(u[pivot_i]) : nil)
232
+ end
233
+ pivot_i += 1
234
+ end
235
+ end
236
+ end
237
+
238
+ uniq_pivots
239
+ end
240
+
241
+ def pivot_cols(uniq_pivots)
242
+ return unless uniq_pivots.length > 0
243
+
244
+ new_cols = []
245
+ i = 0
246
+ @query.select.columns.each do |col|
247
+ id = "c#{i}"
248
+ label = column_label col
249
+ type = column_type col
250
+
251
+ if @query.group_by && @query.group_by.columns.include?(col)
252
+ new_cols << Column.new(:id => id, :label => label, :type => type)
253
+ i += 1
254
+ else
255
+ uniq_pivots.each do |uniq_pivot|
256
+ new_cols << Column.new(:id => "c#{i}", :label => "#{uniq_pivot.map{|x| x[1]}.join ', '} #{label}", :type => type)
257
+ i += 1
258
+ end
259
+ end
260
+ end
261
+ @table.cols = new_cols
262
+ end
263
+
264
+ def rows_to_table(rows)
265
+ @table.rows = rows.map do |grouping, cols|
266
+ r = Row.new
267
+ r.c = cols.map do |value|
268
+ Cell.new :v => format_value(value)
269
+ end
270
+ r
271
+ end
272
+ end
273
+
274
+ def generate_aggregated_rows
275
+ @rows.map do |grouping, rows|
276
+ ag = []
277
+ rows_length = rows.length
278
+ row_i = 0
279
+ rows.each do |row|
280
+ compute_row_aggregation row, row_i, rows_length, ag
281
+ row_i += 1
282
+ end
283
+ [grouping, ag]
284
+ end
285
+ end
286
+
287
+ def generate_row(row)
288
+ r = Row.new
289
+ if @query.select?
290
+ r.c = @query.select.columns.map{|col| Cell.new :v => format_value(eval_select col, row)}
291
+ else
292
+ r.c = row.map{|v| Cell.new :v => format_value(v)}
293
+ end
294
+ r
295
+ end
296
+
297
+ def compute_row_aggregation(row, row_i, rows_length, ag)
298
+ raise "Must specify a select clause if group by or pivot specified" unless @query.select?
299
+
300
+ i = 0
301
+ @query.select.columns.each do |col|
302
+ ag[i] = eval_aggregated_select col, row, row_i, rows_length, ag[i]
303
+ i += 1
304
+ end
305
+ end
306
+
307
+ def format_value(v)
308
+ case v
309
+ when FalseClass, TrueClass, Integer, Float
310
+ v
311
+ when Date
312
+ v.strftime "%Y-%m-%d"
313
+ when Time
314
+ v.strftime "%Y-%m-%d %H:%M:%S"
315
+ else
316
+ v.to_s
317
+ end
318
+ end
319
+
320
+ def column_id(col, i)
321
+ case col
322
+ when IdColumn
323
+ col.name
324
+ else
325
+ "c#{i}"
326
+ end
327
+ end
328
+
329
+ def column_type(col)
330
+ case col
331
+ when IdColumn
332
+ i = 0
333
+ type = @types.select{|x| x[0].to_s == col.name}.first
334
+ raise "Unknown column #{col}" unless type
335
+ type[1]
336
+ when NumberColumn
337
+ :number
338
+ when StringColumn
339
+ :string
340
+ when BooleanColumn
341
+ :boolean
342
+ when DateColumn
343
+ :date
344
+ when DateTimeColumn
345
+ :datetime
346
+ when TimeOfDayColumn
347
+ :timeofday
348
+ when ScalarFunctionColumn
349
+ case col.function
350
+ when ScalarFunctionColumn::Now
351
+ :datetime
352
+ when ScalarFunctionColumn::ToDate
353
+ :date
354
+ when ScalarFunctionColumn::Upper, ScalarFunctionColumn::Lower, ScalarFunctionColumn::Concat
355
+ :string
356
+ else
357
+ :number
358
+ end
359
+ when AggregateColumn
360
+ :number
361
+ end
362
+ end
363
+
364
+ def column_label(col)
365
+ @labels[col] || col.to_s
366
+ end
367
+
368
+ def eval_select(col, row)
369
+ eval_aggregated_select col, row, nil, nil, nil
370
+ end
371
+
372
+ def eval_aggregated_select(col, row, row_i, rows_length, ag)
373
+ visitor = EvalSelectVisitor.new @types_to_indices, row, row_i, rows_length, ag
374
+ col.accept visitor
375
+ visitor.value
376
+ end
377
+
378
+ class EvalSelectVisitor < Visitor
379
+ attr_reader :value
380
+
381
+ def initialize(types_to_indices, row, row_i, rows_length, ag)
382
+ @types_to_indices = types_to_indices
383
+ @row = row
384
+ @row_i = row_i
385
+ @rows_length = rows_length
386
+ @row_i = row_i
387
+ @rows_length = rows_length
388
+ @ag = ag
389
+ end
390
+
391
+ def visit_id_column(col)
392
+ i = @types_to_indices[col.name]
393
+ raise "Unknown column #{col}" unless i
394
+ @value = @row[i]
395
+ end
396
+
397
+ def visit_number_column(col)
398
+ @value = col.value
399
+ end
400
+
401
+ def visit_string_column(col)
402
+ @value = col.value
403
+ end
404
+
405
+ def visit_boolean_column(col)
406
+ @value = col.value
407
+ end
408
+
409
+ def visit_date_column(col)
410
+ @value = col.value
411
+ end
412
+
413
+ def visit_date_time_column(node)
414
+ @value = node.value
415
+ end
416
+
417
+ def visit_time_of_day_column(node)
418
+ @value = node.value.strftime("%H:%M:%S")
419
+ end
420
+
421
+ def visit_scalar_function_column(node)
422
+ if node.arguments.length >= 1
423
+ node.arguments[0].accept self; val1 = @value
424
+ end
425
+ if node.arguments.length == 2
426
+ node.arguments[1].accept self; val2 = @value
427
+ end
428
+ case node.function
429
+ when ScalarFunctionColumn::Sum
430
+ @value = val1 + val2
431
+ when ScalarFunctionColumn::Difference
432
+ @value = val1 - val2
433
+ when ScalarFunctionColumn::Product
434
+ @value = val1 * val2
435
+ when ScalarFunctionColumn::Quotient
436
+ @value = val1 / val2
437
+ when ScalarFunctionColumn::Concat
438
+ @value = "#{val1}#{val2}"
439
+ when ScalarFunctionColumn::DateDiff
440
+ @value = (val1 - val2).to_i
441
+ when ScalarFunctionColumn::Year
442
+ @value = val1.year
443
+ when ScalarFunctionColumn::Month
444
+ @value = val1.month
445
+ when ScalarFunctionColumn::Day
446
+ @value = val1.day
447
+ when ScalarFunctionColumn::DayOfWeek
448
+ @value = val1.wday
449
+ when ScalarFunctionColumn::Hour
450
+ @value = val1.hour
451
+ when ScalarFunctionColumn::Minute
452
+ @value = val1.min
453
+ when ScalarFunctionColumn::Second
454
+ @value = val1.sec
455
+ when ScalarFunctionColumn::Quarter
456
+ @value = (val1.month / 3.0).ceil
457
+ when ScalarFunctionColumn::Millisecond
458
+ raise "Millisecond is not implemented"
459
+ when ScalarFunctionColumn::Lower
460
+ @value = val1.downcase
461
+ when ScalarFunctionColumn::Upper
462
+ @value = val1.upcase
463
+ when ScalarFunctionColumn::Now
464
+ @value = Time.now
465
+ when ScalarFunctionColumn::ToDate
466
+ case @value
467
+ when Date
468
+ @value = @value
469
+ when Time
470
+ @value = Date.civil @value.year, @value.month, @value.day
471
+ when Integer
472
+ seconds = @value / 1000
473
+ millis = @value % 1000
474
+ @value = Time.at seconds, millis
475
+ @value = Date.civil @value.year, @value.month, @value.day
476
+ end
477
+ end
478
+ false
479
+ end
480
+
481
+ def visit_aggregate_column(col)
482
+ case col.function
483
+ when AggregateColumn::Sum
484
+ col.argument.accept self
485
+ @value += @ag || 0
486
+ when AggregateColumn::Avg
487
+ col.argument.accept self
488
+ @value += @ag || 0
489
+ @value = @value / @rows_length.to_f if @row_i == @rows_length - 1
490
+ when AggregateColumn::Count
491
+ @value = (@ag || 0) + 1
492
+ when AggregateColumn::Max
493
+ col.argument.accept self
494
+ @value = @ag if @ag && @ag > @value
495
+ when AggregateColumn::Min
496
+ col.argument.accept self
497
+ @value = @ag if @ag && @ag < @value
498
+ end
499
+ false
500
+ end
501
+ end
502
+
503
+ class EvalWhereVisitor < EvalSelectVisitor
504
+ def initialize(types_to_indices, row)
505
+ @row = row
506
+ @types_to_indices = types_to_indices
507
+ end
508
+
509
+ def visit_aggregate_column(col)
510
+ raise "Aggregation function #{col.function} cannot be used in where clause"
511
+ end
512
+
513
+ def visit_binary_expression(node)
514
+ node.left.accept self; left = @value
515
+ node.right.accept self; right = @value
516
+ case node.operator
517
+ when BinaryExpression::Gt
518
+ @value = left > right
519
+ when BinaryExpression::Gte
520
+ @value = left >= right
521
+ when BinaryExpression::Lt
522
+ @value = left < right
523
+ when BinaryExpression::Lte
524
+ @value = left <= right
525
+ when BinaryExpression::Eq
526
+ @value = left == right
527
+ when BinaryExpression::Neq
528
+ @value = left != right
529
+ when BinaryExpression::Contains
530
+ @value = !!left[right]
531
+ when BinaryExpression::StartsWith
532
+ @value = left.start_with? right
533
+ when BinaryExpression::EndsWith
534
+ @value = left.end_with? right
535
+ when BinaryExpression::Like
536
+ right.gsub!('%', '.*')
537
+ right.gsub!('_', '.')
538
+ right = Regexp.new right
539
+ @value = !!(left =~ right)
540
+ when BinaryExpression::Matches
541
+ right = Regexp.new "^#{right}$"
542
+ @value = !!(left =~ right)
543
+ end
544
+ false
545
+ end
546
+
547
+ def visit_logical_expression(node)
548
+ values = []
549
+ node.operands.each do |op|
550
+ op.accept self
551
+ values << @value
552
+ end
553
+ case node.operator
554
+ when LogicalExpression::And
555
+ node.operands.each do |op|
556
+ op.accept self
557
+ break unless @value
558
+ end
559
+ when LogicalExpression::Or
560
+ node.operands.each do |op|
561
+ op.accept self
562
+ break if @value
563
+ end
564
+ end
565
+ false
566
+ end
567
+
568
+ def visit_unary_expression(node)
569
+ node.operand.accept self
570
+ case node.operator
571
+ when UnaryExpression::Not
572
+ @value = !@value
573
+ when UnaryExpression::IsNull
574
+ @value = @value.nil?
575
+ when UnaryExpression::IsNotNull
576
+ @value = !@value.nil?
577
+ end
578
+ false
579
+ end
580
+ end
581
+
582
+ class EvalGroupVisitor < EvalSelectVisitor
583
+ def initialize(types_to_indices, row)
584
+ @row = row
585
+ @types_to_indices = types_to_indices
586
+ end
587
+
588
+ def visit_aggregate_column(node)
589
+ raise "Can't use aggregation functions in group by"
590
+ end
591
+ end
592
+
593
+ class SimpleOrderedHash
594
+ def initialize
595
+ @keys = []
596
+ @hash = Hash.new
597
+ end
598
+
599
+ def [](key)
600
+ @hash[key]
601
+ end
602
+
603
+ def []=(key, value)
604
+ if !@keys.include?(key)
605
+ @keys << key
606
+ end
607
+ @hash[key] = value
608
+ end
609
+
610
+ def each
611
+ @keys.each do |key|
612
+ yield key, @hash[key]
613
+ end
614
+ end
615
+ end
616
+ end
617
+ end
metadata CHANGED
@@ -1,12 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rgviz
3
3
  version: !ruby/object:Gem::Version
4
- hash: 91
4
+ hash: 89
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 40
9
- version: "0.40"
8
+ - 41
9
+ version: "0.41"
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ary Borenszweig
@@ -31,6 +31,7 @@ files:
31
31
  - lib/rgviz/csv_renderer.rb
32
32
  - lib/rgviz/html_renderer.rb
33
33
  - lib/rgviz/lexer.rb
34
+ - lib/rgviz/memory_executor.rb
34
35
  - lib/rgviz/nodes.rb
35
36
  - lib/rgviz/parser.rb
36
37
  - lib/rgviz/token.rb