Skip to content

Commit

Permalink
Change Yql::QueryBuilder
Browse files Browse the repository at this point in the history
  * #find_all to optionally take a hash parameter with
    key limit for limiting the number of records returned by YQL

  * #describe_table to call the right class method

  * add specs for other methods in Yql::QueryBuilder
  • Loading branch information
nas committed Jul 1, 2010
1 parent ff53dcc commit 3aeb237
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 49 deletions.
102 changes: 53 additions & 49 deletions lib/yql/query_builder.rb
@@ -1,11 +1,11 @@
module Yql

class QueryBuilder

attr_accessor :table, :conditions, :limit, :truncate, :sanitize_field, :select,
:sort_field, :current_pipe_command_types, :sort_descending,
:per_page, :current_page

def initialize(table, args = {})
@select = args[:select]
@table = table
Expand All @@ -22,38 +22,42 @@ def initialize(table, args = {})
@current_pipe_command_types = []
@per_page = args[:per_page]
end

def find
self.limit = 1
"#{construct_query}"
end

def find_all
self.limit = nil
construct_query
end


def self.show_tables
"show tables;"
end

def self.describe_table(table)
"desc #{table};"
end

def describe_table
Yql::QueryBuilder.describle_table(table)
Yql::QueryBuilder.describe_table(table)
end

def find
self.limit = 1
"#{construct_query}"
end

# Can be optionally passed a limit for limiting the number of records returned
# object.find_all(:limit => 10)
# #=> will return only 10 records
#
def find_all(args = {})
self.limit = args[:limit] || nil
construct_query
end

def to_s
construct_query
end

def limit
return unless @limit
"limit #{@limit}"
end

# Conditions can either be provided as hash or plane string
def conditions
if @conditions.kind_of?(String)
Expand All @@ -69,63 +73,63 @@ def conditions
end
return "where #{cond}"
end

%w{sort tail truncate reverse unique sanitize}.each do |method|
self.send(:define_method, "#{method}=") do |param|
instance_variable_set("@#{method}", param)
current_pipe_command_types << method unless current_pipe_command_types.include?(method)
end
end

# Cption can be piped
# Sorts the result set according to the specified field (column) in the result set.
# Option can be piped
# Sorts the result set according to the specified field (column) in the result set.
def sort
return unless @sort_field
return "sort(field='#{@sort_field}')" unless @sort_descending
return "sort(field='#{@sort_field}', descending='true')"
end
# Cption can be piped
# Gets the last count items

# Option can be piped
# Gets the last count items
def tail
return unless @tail
"tail(count=#{@tail})"
end
# Cption can be piped

# Option can be piped
# Gets the first count items (rows)
def truncate
return unless @truncate
"truncate(count=#{@truncate})"
end
# Cption can be piped

# Option can be piped
# Reverses the order of the rows
def reverse
return unless @reverse
"reverse()"
end
# Cption can be piped

# Option can be piped
# Removes items (rows) with duplicate values in the specified field (column)
def unique
return unless @unique
"unique(field='#{@unique}')"
end
# Cption can be piped

# Option can be piped
# Sanitizes the output for HTML-safe rendering. To sanitize all returned fields, omit the field parameter.
def sanitize
return unless @sanitize
return "sanitize()" unless @sanitize_field
"sanitize(field='#{@sanitize_field}')"
end
# Its always advisable to order the pipe when there are more than one

# Its always advisable to order the pipe when there are more than one
# pipe commands available else unexpected results might get returned
# reorder_pipe_command {:from => 1, :to => 0}
# reorder_pipe_command {:from => 1, :to => 0}
# the values in the hash are the element numbers
#
#
def reorder_pipe_command(args)
return if current_pipe_command_types.empty?
if args[:from].nil? or args[:to].nil?
Expand All @@ -135,52 +139,52 @@ def reorder_pipe_command(args)
if element > current_pipe_command_types.size-1
raise Yql::Error, "Not able to move pipe commands. Wrong element numbers. Please try again"
end
end
end
element_to_be_inserted_at = args[:from] < args[:to] ? args[:to]+1 : args[:to]
element_to_be_removed = args[:from] < args[:to] ? args[:from] : args[:from]+1
current_pipe_command_types.insert(element_to_be_inserted_at, current_pipe_command_types.at(args[:from]))
current_pipe_command_types.delete_at(element_to_be_removed)
end


# Remove a command that will be piped to the yql query
def remove_pipe_command(command)
current_pipe_command_types.delete(command)
end

private

def pipe_commands
return if current_pipe_command_types.empty?
'| ' + current_pipe_command_types.map{|c| self.send(c)}.join(' | ')
end

def build_select_query
[select_statement, conditions, limit, pipe_commands].compact.join(' ')
end

def construct_query
return build_select_query unless @use
return [@use, build_select_query].join('; ')
end

def select_statement
select = "select #{column_select} from #{table}"
return select unless per_page
with_pagination(select)
end

def with_pagination(select)
self.current_page ||= 1
offset = (current_page - 1) * per_page
last_record = current_page * per_page
"#{select}(#{offset},#{last_record})"
end

def column_select
@select ? @select : '*'
end

end

end
154 changes: 154 additions & 0 deletions spec/yql/query_builder_spec.rb
Expand Up @@ -18,4 +18,158 @@

end

describe "#describe_table" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
end

it "should describe the current table using class method" do
Yql::QueryBuilder.should_receive(:describe_table).with('yql.table.name')
@query_builder.describe_table
end

it "should return the result from the class method" do
Yql::QueryBuilder.stub!(:describe_table).and_return('table description')
@query_builder.describe_table.should eql('table description')
end

end

describe "#find" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
@query_builder.stub!(:construct_query).and_return('The yql query')
end

it "should set the limit of records to 1 by default" do
@query_builder.should_receive(:limit=).with(1)
@query_builder.find
end

it "should construct the query" do
@query_builder.should_receive(:construct_query)
@query_builder.find
end

it "should return the constructed query" do
@query_builder.find.should eql('The yql query')
end

end

describe "#find_all" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
@query_builder.stub!(:construct_query).and_return('The yql query')
end

it "should set the limit of records to nil" do
@query_builder.should_receive(:limit=).with(nil)
@query_builder.find_all
end

it "should set the with provided hash" do
@query_builder.should_receive(:limit=).with(10)
@query_builder.find_all({:limit => 10})
end

it "should construct the query" do
@query_builder.should_receive(:construct_query)
@query_builder.find_all
end

it "should return the constructed query" do
@query_builder.find_all.should eql('The yql query')
end

end

describe "#to_s" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
@query_builder.stub!(:construct_query).and_return('The yql query')
end

it "should construct the query" do
@query_builder.should_receive(:construct_query)
@query_builder.to_s
end

it "should return the constructed query" do
@query_builder.to_s.should eql('The yql query')
end

end

describe "#limit" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
end

it "should return nil if @limit instance is not set" do
@query_builder.limit.should be_nil
end

it "should return the limit text with limit number" do
@query_builder.limit = 3
@query_builder.limit.should eql('limit 3')
end

end

describe "#conditions" do

before(:each) do
@query_builder = Yql::QueryBuilder.new('yql.table.name')
end

context "when condition is set as string" do

it "should return the condition string with where clause" do
@query_builder.conditions = "some sql like yql conditions"
@query_builder.conditions.should eql('where some sql like yql conditions')
end

end

context "when condition is passed as a hash" do

before(:each) do
@query_builder.conditions = {:attr1 => 'val1', :attr2 => 'val2', :attr3 => 4, :attr4 => '5'}
end

it "should 'where' text in the beginning of the return value" do
@query_builder.conditions.should match(/^where attr/)
# @query_builder.conditions.should match("where key1='val1' and key2='val2' and key3=4 and key4='5'")
end

it "should have the attr1 key value pair" do
@query_builder.conditions.should match(/attr1='val1'/)
end

it "should have the attr2 key value pair" do
@query_builder.conditions.should match(/attr2='val2'/)
end

it "should have the attr3 key value pair as integer" do
@query_builder.conditions.should match(/attr3=4/)
end

it "should have the attr3 key value pair integer as string" do
@query_builder.conditions.should match(/attr4='5'/)
end

it "should have 'and' only thrice in the returned value as there are 4 key values passed to condition" do
@query_builder.conditions.scan('and').size.should eql(3)
end

end

end

end

0 comments on commit 3aeb237

Please sign in to comment.