diff --git a/lib/gooddata/cloud_resources/redshift/redshift_client.rb b/lib/gooddata/cloud_resources/redshift/redshift_client.rb index 09ddf2abd..89db2d0fa 100644 --- a/lib/gooddata/cloud_resources/redshift/redshift_client.rb +++ b/lib/gooddata/cloud_resources/redshift/redshift_client.rb @@ -57,9 +57,10 @@ def realize_query(query, _params) result = statement.get_result_set metadata = result.get_meta_data col_count = metadata.column_count + boolean_columns = boolean_column_indexes(metadata) CSV.open(filename, 'wb') do |csv| csv << Array(1..col_count).map { |i| metadata.get_column_name(i) } # build the header - csv << Array(1..col_count).map { |i| result.get_string(i)&.to_s } while result.next + csv << Array(1..col_count).map { |i| column_value(result, i, boolean_columns) } while result.next end end end @@ -98,6 +99,20 @@ def connect private + # Driver 2.x getString() returns BOOLEAN as "t"/"f"; keep "true"/"false" + def boolean_column_indexes(metadata) + Array(1..metadata.column_count).select do |i| + [java.sql.Types::BIT, java.sql.Types::BOOLEAN].include?(metadata.get_column_type(i)) + end + end + + def column_value(result, index, boolean_columns) + return result.get_string(index)&.to_s unless boolean_columns.include?(index) + + value = result.get_boolean(index) + result.was_null ? nil : value.to_s + end + def build_url(url, database) url_parts = url.split('?') url_path = url_parts[0].chomp('/') diff --git a/spec/unit/cloud_resources/redshift/redshift_client_spec.rb b/spec/unit/cloud_resources/redshift/redshift_client_spec.rb index 466d93770..1e1d86544 100644 --- a/spec/unit/cloud_resources/redshift/redshift_client_spec.rb +++ b/spec/unit/cloud_resources/redshift/redshift_client_spec.rb @@ -7,7 +7,7 @@ if RUBY_PLATFORM =~ /java/ require 'gooddata/cloud_resources/redshift/redshift_client' - describe GoodData::CloudResources::RedshiftClient do + shared_context 'redshift client with stubbed driver' do let(:driver) { double('driver') } let(:driver_class) { double('driver_class', new: driver) } let(:options) do @@ -32,6 +32,10 @@ allow(client).to receive_message_chain('com.amazon.redshift.jdbc42.Driver').and_return(driver_class) client end + end + + describe GoodData::CloudResources::RedshiftClient do + include_context 'redshift client with stubbed driver' def connect_properties received = nil @@ -67,4 +71,42 @@ def connect_properties end end end + + describe GoodData::CloudResources::RedshiftClient, '#realize_query' do + include_context 'redshift client with stubbed driver' + + let(:authentication) { { 'basic' => { 'userName' => 'basic_user', 'password' => 'secret' } } } + let(:metadata) { double('metadata', column_count: 3) } + let(:result) { double('result') } + let(:statement) { double('statement', execute: true, get_result_set: result) } + let(:connection) { double('connection', create_statement: statement, close: nil) } + + before do + # Other specs leave doubles in the global loggers + allow(GoodData).to receive(:gd_logger).and_return(double('gd_logger', info: nil)) + allow(GoodData).to receive(:logger).and_return(double('logger', info: nil)) + allow(driver).to receive(:connect).and_return(connection) + allow(result).to receive(:get_meta_data).and_return(metadata) + allow(metadata).to receive(:get_column_name) { |i| %w[name active deleted][i - 1] } + allow(metadata).to receive(:get_column_type) do |i| + [java.sql.Types::VARCHAR, java.sql.Types::BIT, java.sql.Types::BOOLEAN][i - 1] + end + allow(result).to receive(:next).and_return(true, true, false) + allow(result).to receive(:get_string).with(1).and_return('Alice', 'Bob') + # Driver 2.x getString() returns BOOLEAN as "t"/"f" + allow(result).to receive(:get_string).with(2).and_return('t', 'f') + allow(result).to receive(:get_boolean).with(2).and_return(true, false) + allow(result).to receive(:get_boolean).with(3).and_return(false, false) + allow(result).to receive(:was_null).and_return(false, false, false, true) + end + + it 'writes BOOLEAN columns as true/false and NULL as empty' do + Dir.mktmpdir do |dir| + Dir.chdir(dir) do + rows = CSV.read(subject.realize_query('SELECT 1', nil)) + expect(rows).to eq [%w[name active deleted], %w[Alice true false], ['Bob', 'false', nil]] + end + end + end + end end