require_relative "../../test_helper" module Unit module Connection class TestQuery < MiniTest::Test class Connection < SimpleConnection include Clickhouse::Connection::Query include Clickhouse::Connection::Logger end describe Clickhouse::Connection::Query do before do @connection = Connection.new @connection.stubs(:parse_stats) @connection.stubs(:write_log) end describe "#execute" do it "sends a POST request" do @connection.expects(:post).with("sql", nil).returns("") assert_equal true, @connection.execute("sql") end describe "when server returns a non-empty body" do it "returns the body of the response" do @connection.expects(:post).with("sql", "body").returns("Ok.") assert_equal "Ok.", @connection.execute("sql", "body") end end end describe "#query" do it "sends a GET request requesting a TSV response including names and types" do @connection.expects(:get).with("sql FORMAT JSONCompact") @connection.stubs(:parse_data) assert_equal [], @connection.query("sql").to_a end end describe "#databases" do it "sends a 'SHOW DATABASES' query" do @connection.expects(:get).with("SHOW DATABASES FORMAT JSONCompact") @connection.stubs(:parse_data).returns([]) assert_equal [], @connection.databases end end describe "#tables" do it "sends a 'SHOW TABLES' query" do @connection.expects(:get).with("SHOW TABLES FORMAT JSONCompact") @connection.stubs(:parse_data).returns([]) @connection.tables end end describe "#create_table" do it "sends a 'CREATE TABLE' query" do sql = <<-SQL CREATE TABLE logs_test ( id UInt8, price Float32, name String, date Date, time DateTime, hex_id FixedString(8) ) ENGINE = MergeTree(date, 8192) SQL @connection.expects(:post).with(sql.strip, nil).returns("") @connection.create_table("logs_test") do |t| t.uint8 :id t.float32 :price t.string :name t.date :date t.date_time :time t.fixed_string :hex_id, 8 t.engine "MergeTree(date, 8192)" end end end describe "#describe_table" do it "sends a 'DESCRIBE TABLE <name>' query" do @connection.expects(:get).with("DESCRIBE TABLE logs FORMAT JSONCompact") @connection.stubs(:parse_data) @connection.describe_table("logs") end end describe "#rename_table" do describe "when passing an array with an even number of names" do it "sends a POST request containing a RENAME TABLE statement" do @connection.expects(:post).with("RENAME TABLE foo TO bar, baz TO qux", nil).returns("").twice assert_equal true, @connection.rename_table("foo", "bar", "baz", "qux") assert_equal true, @connection.rename_table(["foo", "bar"], ["baz", "qux"]) end end describe "when passing an array with an odd number of names" do it "raises an Clickhouse::InvalidQueryError" do assert_raises Clickhouse::InvalidQueryError do @connection.rename_table "foo" end assert_raises Clickhouse::InvalidQueryError do @connection.rename_table ["foo"] end end end describe "when passing a hash" do it "sends a POST request containing a RENAME TABLE statement" do @connection.expects(:post).with("RENAME TABLE foo TO bar, baz TO qux", nil).returns("") assert_equal true, @connection.rename_table(:foo => "bar", :baz => "qux") end end end describe "#drop_table" do it "sends a POST request containing a 'DROP TABLE' statement" do @connection.expects(:post).with("DROP TABLE logs", nil).returns("") assert_equal true, @connection.drop_table("logs") end end describe "#insert_rows" do before do @csv = <<-CSV id,first_name,last_name 12345,Paul,Engel 67890,Bruce,Wayne CSV @csv.gsub!(/^\s+/, "") end describe "when using hashes" do it "sends a POST request containing a 'INSERT INTO' statement using CSV" do @connection.expects(:post).with("INSERT INTO logs FORMAT CSVWithNames", @csv).returns("") assert_equal true, @connection.insert_rows("logs") { |rows| rows << {:id => 12345, :first_name => "Paul", :last_name => "Engel"} rows << {:id => 67890, :first_name => "Bruce", :last_name => "Wayne"} } end end describe "when using arrays" do it "sends a POST request containing a 'INSERT INTO' statement using CSV" do @connection.expects(:post).with("INSERT INTO logs FORMAT CSVWithNames", @csv).returns("") assert_equal true, @connection.insert_rows("logs", :names => %w(id first_name last_name)) { |rows| rows << [12345, "Paul", "Engel"] rows << [67890, "Bruce", "Wayne"] } end end end describe "#select_rows" do it "sends a GET request and parses the result set" do body = <<-JAVASCRIPT { "meta": [ {"name": "year", "type": "UInt16"}, {"name": "name", "type": "String"} ], "data": [ [1982, "Paul"], [1947, "Anna"] ] } JAVASCRIPT @connection.expects(:to_select_query).with(options = {:from => "logs"}).returns("") @connection.expects(:get).returns(JSON.parse(body)) assert_equal [ [1982, "Paul"], [1947, "Anna"] ], @connection.select_rows(options).to_a end end describe "#select_row" do it "returns an empty array" do @connection.expects(:select_rows).returns([["Paul", "Engel"], ["Bruce", "Wayne"]]) assert_equal ["Paul", "Engel"], @connection.select_row({}) end end describe "#select_values" do describe "when empty result set" do it "returns an empty array" do @connection.expects(:to_select_query).returns("") @connection.expects(:get).returns(stub(:body => "")) @connection.stubs(:parse_data).returns([]) assert_equal [], @connection.select_values({}) end end describe "when getting data" do it "returns every first value of every row" do body = <<-JAVASCRIPT { "meta": [ {"name": "year", "type": "UInt16"}, {"name": "name", "type": "String"} ], "data": [ [1982, "Paul"], [1947, "Anna"] ] } JAVASCRIPT @connection.expects(:to_select_query).returns("") @connection.expects(:get).returns(JSON.parse(body)) assert_equal [ 1982, 1947 ], @connection.select_values({}) end end end describe "#select_value" do describe "when empty result set" do it "returns nil" do @connection.expects(:select_values).with(options = {:foo => "bar"}).returns([]) assert_nil @connection.select_value(options) end end describe "when getting data" do it "returns the first value of the first row" do @connection.expects(:select_values).with(options = {:foo => "bar"}).returns([1982]) assert_equal 1982, @connection.select_value(options) end end end describe "#count" do it "returns the first value of the first row" do @connection.expects(:select_value).with(:select => "COUNT(*)", :from => "logs").returns(1982) assert_equal 1982, @connection.count(:from => "logs") end end describe "#to_select_query" do describe "when passing :from option" do it "generates a simple 'SELECT * FROM <table>' query" do query = <<-SQL SELECT * FROM logs SQL options = { :from => "logs" } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when passing :from and :select option" do describe "when passing a single column" do it "respects the single column in the SELECT statement" do query = <<-SQL SELECT MIN(date) FROM logs SQL options = { :select => "MIN(date)", :from => "logs" } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when passing multiple columns" do it "only includes the passed columns in the SELECT statement" do query = <<-SQL SELECT MIN(date), MAX(date) FROM logs SQL options = { :select => ["MIN(date)", "MAX(date)"], :from => "logs" } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when filtering on value is empty" do it "uses the empty() function in the WHERE statement" do query = <<-SQL SELECT * FROM logs WHERE empty(parent_id) SQL options = { :from => "logs", :where => { :parent_id => :empty } } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when filtering on value is within a certain range" do it "includes the range in the WHERE statement" do query = <<-SQL SELECT * FROM logs WHERE code >= 6 AND code <= 10 SQL options = { :from => "logs", :where => { :code => 6..10 } } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when filtering on value in array" do it "uses an IN operator in the WHERE statement" do query = <<-SQL SELECT * FROM logs WHERE code IN (6, 7, 8, 9, 10) SQL options = { :from => "logs", :where => { :code => [6, 7, 8, 9, 10] } } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when filtering using backticks" do it "uses the specified SQL as is" do query = <<-SQL SELECT * FROM logs WHERE id != 'cb5a67d2932911e6' SQL options = { :from => "logs", :where => { :id => "`!= 'cb5a67d2932911e6'`" } } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when filtering on a string" do it "uses a single quoted string" do query = <<-SQL SELECT * FROM logs WHERE id = 'cb5a67d2932911e6' SQL options = { :from => "logs", :where => { :id => "cb5a67d2932911e6" } } assert_query(query, @connection.send(:to_select_query, options)) end end describe "when using all options" do it "generates the complex query" do query = <<-SQL SELECT date, COUNT(id), groupUniqArray(severity), SUM(clicks) FROM logs WHERE date >= '2016-08-01' AND hidden = 0 GROUP BY date HAVING MIN(severity) = 2 ORDER BY MIN(time) DESC LIMIT 120, 60 SQL options = { :select => ["date", "COUNT(id)", "groupUniqArray(severity)", "SUM(clicks)"], :from => "logs", :where => { :date => "`>= '2016-08-01'`", :hidden => 0 }, :group => "date", :having => { "MIN(severity)" => 2 }, :order => "MIN(time) DESC", :limit => 60, :offset => 120 } assert_query(query, @connection.send(:to_select_query, options)) end end end end end end end end