Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Very very basic where conditions, grouping and aggregation completed.…
… Some specs added but more still needed.
- Loading branch information
Brad Seefeld
committed
Jun 6, 2011
0 parents
commit 9f9f696
Showing
15 changed files
with
506 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
*.gem | ||
.bundle | ||
Gemfile.lock | ||
pkg/* | ||
.rvmrc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
source "http://rubygems.org" | ||
|
||
# Specify your gem's dependencies in rgviz_table.gemspec | ||
gemspec | ||
|
||
gem "rgviz" | ||
|
||
group :test, :development do | ||
gem "rspec-rails" | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
== Rgviz DataTable | ||
|
||
An extension to the {rgviz}{} library to provide a non-activerecord way to connect data to Google Visualization clients. | ||
|
||
# Sample code | ||
rows = [] | ||
|
||
CSV.each_line(file, "r") do |row| | ||
rows.add(row) | ||
end | ||
|
||
query = Rgviz::Parser.parse(params[:tq]) | ||
result_rows = Rgviz::QueryExecutor.execute(rows, query) | ||
|
||
# result_rows now has the results of the query. | ||
|
||
== TODO | ||
|
||
In order of needed: | ||
|
||
* Implement ordering statements | ||
* Implement select statements | ||
* Implement labels | ||
* Refactor where parsing so that parens and OR statements are supported | ||
* Implement pivots |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
require "bundler" | ||
Bundler.setup | ||
require "rspec/core/rake_task" | ||
|
||
Bundler::GemHelper.install_tasks | ||
RSpec::Core::RakeTask.new |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
require "rgviz" | ||
require "rgviz/data_table/query_executor" | ||
require "rgviz/data_table/column_value_filter" | ||
require "rgviz/data_table/column" | ||
require "rgviz/data_table/sum_column" | ||
|
||
module Rgviz | ||
module DataTable | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
module Rgviz | ||
module DataTable | ||
class Column | ||
|
||
def self.factory(statement) | ||
col = nil | ||
if m = statement.match(/sum\((.*)\)/i) | ||
col = Rgviz::DataTable::SumColumn.new(m[1], statement) | ||
end | ||
|
||
unless col | ||
col = Rgviz::DataTable::Column.new(statement) | ||
end | ||
col | ||
end | ||
|
||
def initialize(col_name, label = nil) | ||
@column = col_name | ||
@label = label | ||
@label ||= col_name | ||
end | ||
|
||
def column | ||
@column | ||
end | ||
|
||
def label | ||
@label | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
require "rgviz/data_table/comparison_filter" | ||
|
||
module Rgviz | ||
module DataTable | ||
class ColumnValueFilter < ComparisonFilter | ||
|
||
def initialize(column, value, operator = Rgviz::DataTable::ComparisonFilter::EQUALS) | ||
super(operator) | ||
|
||
@column = column | ||
@value = value | ||
@is_casted = false | ||
end | ||
|
||
def match?(row) | ||
val = row[@column] | ||
|
||
cast_type(val) | ||
|
||
super(val, @value) | ||
end | ||
|
||
def column | ||
@column | ||
end | ||
|
||
def value | ||
@value | ||
end | ||
|
||
## | ||
# Cast the raw type to the complex type if needed. | ||
def cast_type(complex) | ||
return if @is_casted | ||
|
||
if complex.is_a? Integer and @value.respond_to? :to_i | ||
@value = @value.to_i | ||
elsif complex.is_a? Float and @value.respond_to? :to_f | ||
@value = @value.to_f | ||
elsif complex.is_a? Time | ||
@value = Time.parse(@value) | ||
end | ||
@is_casted = true | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
module Rgviz | ||
module DataTable | ||
|
||
## | ||
# A base filter class that handles the operator logic of the filter. | ||
class ComparisonFilter | ||
|
||
EQUALS = "=" | ||
NOT_EQUALS = "!=" | ||
LESS_THAN = "<" | ||
LESS_THAN_OR_EQUALS = "<=" | ||
GREATER_THAN = ">" | ||
GREATER_THAN_OR_EQUALS = ">=" | ||
|
||
def self.operators | ||
[EQUALS, NOT_EQUALS, LESS_THAN, LESS_THAN_OR_EQUALS, GREATER_THAN, GREATER_THAN_OR_EQUALS] | ||
end | ||
|
||
## | ||
# Initialize the filter with an operator type. | ||
# | ||
# @param operator [String] The operator type. | ||
def initialize(operator) | ||
@operator = operator | ||
end | ||
|
||
## | ||
# Determine if the two given values match based on the operator. | ||
# | ||
# @param left [Object] The left hand side of the comparison | ||
# @param right [Object] The right hand side of the comparison | ||
# @return [Boolean] True if they are a match. | ||
def match?(left, right) | ||
return case operator | ||
when EQUALS | ||
left == right | ||
when NOT_EQUALS | ||
left != right | ||
when LESS_THAN | ||
left < right | ||
when LESS_THAN_OR_EQUALS | ||
left <= right | ||
when GREATER_THAN | ||
left > right | ||
when GREATER_THAN_OR_EQUALS | ||
left >= right | ||
else | ||
false | ||
end | ||
end | ||
|
||
## | ||
# Fetch the operator for this filter. | ||
# | ||
# @return [String] The operator type | ||
def operator | ||
@operator | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module Rgviz | ||
module DataTable | ||
class MaxColumn | ||
|
||
def evaluate(rows) | ||
max = nil | ||
rows.each do |row| | ||
if max.nil? or max < row[column] | ||
max = row[column] | ||
end | ||
end | ||
max | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
module Rgviz | ||
module DataTable | ||
class QueryExecutor | ||
|
||
## | ||
# Execute a query against a collection of rows. | ||
# | ||
# @param rows [Array] An array of row data (Hashes). | ||
# @param query [String|Rgviz::Query] The query to execute. | ||
# @return [Array] A new data table that has been filtered by the query. | ||
def self.execute(rows, query) | ||
|
||
if rows.nil? or rows.empty? | ||
return [] | ||
end | ||
|
||
if query.is_a? String | ||
query = Rgviz::Parser.parse(query) | ||
end | ||
|
||
# Convert all hash keys to strings. | ||
string_keys = [] | ||
rows.each do |row| | ||
temp = {} | ||
row.each_pair do |key, value| | ||
temp[key.to_s] = value | ||
end | ||
string_keys << temp | ||
end | ||
|
||
# Get the select part of the statement | ||
selects = parse_select(query.select) | ||
|
||
# Filter the data. | ||
rows = execute_where(string_keys, query.where) | ||
|
||
# Group the data and perform any aggregation. | ||
rows = execute_grouping(rows, query.group_by, selects) | ||
|
||
# Perform ordering | ||
|
||
# Perform selects | ||
|
||
rows | ||
end | ||
|
||
## | ||
# | ||
def self.execute_grouping(rows, raw_group, selects) | ||
|
||
groups = parse_group(raw_group) | ||
return rows if groups.empty? | ||
|
||
rows = group(rows, groups, selects) | ||
rows | ||
end | ||
|
||
def self.parse_select(select) | ||
selects = [] | ||
select.to_s.split(",").each do |select| | ||
selects << Rgviz::DataTable::Column.factory(select) | ||
end | ||
selects | ||
end | ||
|
||
def self.group(rows, groups, selects) | ||
|
||
if groups.empty? | ||
row = rows.first | ||
selects.each do |select| | ||
if select.respond_to? :evaluate | ||
row[select.label] = select.evaluate(rows) | ||
end | ||
end | ||
return [row] | ||
end | ||
|
||
group = groups.shift | ||
|
||
buckets = {} | ||
rows.each do |row| | ||
buckets[row[group]] ||= [] | ||
buckets[row[group]] << row | ||
end | ||
|
||
rows = [] | ||
buckets.each_key do |bucket| | ||
rows.concat(group(buckets[bucket], groups, selects)) | ||
end | ||
rows | ||
end | ||
|
||
## | ||
# | ||
# | ||
# @param table [] | ||
# @param where [Rgviz::Where] The where clause part of the query. | ||
def self.execute_where(rows, where) | ||
return rows unless where | ||
|
||
filters = parse_where(where) | ||
|
||
filters.each do |filter| | ||
filtered_rows = [] | ||
rows.each do |row| | ||
if filter.match?(row) | ||
filtered_rows << row | ||
end | ||
end | ||
rows = filtered_rows | ||
end | ||
rows | ||
end | ||
|
||
def self.parse_group(group) | ||
return [] unless group | ||
groups = group.to_s.split(",") | ||
groups.each do |group| | ||
group.strip! | ||
end | ||
groups | ||
end | ||
|
||
def self.parse_where(where) | ||
|
||
filters = [] | ||
|
||
# TODO: First break into groups by parenthesis. | ||
|
||
# this is very naive... | ||
ands = where.to_s.split(/(\sand\s)/i) | ||
count = 0 | ||
ands.each do |raw| | ||
if count % 2 == 0 | ||
index = 0 | ||
operator = nil | ||
Rgviz::DataTable::ComparisonFilter.operators.each do |op| | ||
operator = op if raw.include?(op) | ||
end | ||
if operator | ||
parts = raw.split(operator) | ||
filters << Rgviz::DataTable::ColumnValueFilter.new(parts[0].strip, parts[1].strip, operator) | ||
end | ||
end | ||
count += 1 | ||
end | ||
filters | ||
end | ||
end | ||
end | ||
end |
Oops, something went wrong.