rgviz 0.40 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
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