Fix COPY FROM and add tests (#522)

* Fix COPY FROM and add tests

* E

* fmt
This commit is contained in:
Mostafa Abdelraouf
2023-07-21 01:06:01 -05:00
committed by GitHub
parent 19cb8a3022
commit 2a8f3653a6
3 changed files with 132 additions and 3 deletions

View File

@@ -1260,7 +1260,7 @@ where
// Release server back to the pool if we are in transaction mode. // Release server back to the pool if we are in transaction mode.
// If we are in session mode, we keep the server until the client disconnects. // If we are in session mode, we keep the server until the client disconnects.
if self.transaction_mode { if self.transaction_mode && !server.in_copy_mode() {
self.stats.idle(); self.stats.idle();
break; break;
@@ -1410,7 +1410,7 @@ where
// Release server back to the pool if we are in transaction mode. // Release server back to the pool if we are in transaction mode.
// If we are in session mode, we keep the server until the client disconnects. // If we are in session mode, we keep the server until the client disconnects.
if self.transaction_mode { if self.transaction_mode && !server.in_copy_mode() {
break; break;
} }
} }

View File

@@ -170,6 +170,9 @@ pub struct Server {
/// Is there more data for the client to read. /// Is there more data for the client to read.
data_available: bool, data_available: bool,
/// Is the server in copy-in or copy-out modes
in_copy_mode: bool,
/// Is the server broken? We'll remote it from the pool if so. /// Is the server broken? We'll remote it from the pool if so.
bad: bool, bad: bool,
@@ -677,6 +680,7 @@ impl Server {
process_id, process_id,
secret_key, secret_key,
in_transaction: false, in_transaction: false,
in_copy_mode: false,
data_available: false, data_available: false,
bad: false, bad: false,
cleanup_state: CleanupState::new(), cleanup_state: CleanupState::new(),
@@ -828,8 +832,19 @@ impl Server {
break; break;
} }
// ErrorResponse
'E' => {
if self.in_copy_mode {
self.in_copy_mode = false;
}
}
// CommandComplete // CommandComplete
'C' => { 'C' => {
if self.in_copy_mode {
self.in_copy_mode = false;
}
let mut command_tag = String::new(); let mut command_tag = String::new();
match message.reader().read_to_string(&mut command_tag) { match message.reader().read_to_string(&mut command_tag) {
Ok(_) => { Ok(_) => {
@@ -873,10 +888,14 @@ impl Server {
} }
// CopyInResponse: copy is starting from client to server. // CopyInResponse: copy is starting from client to server.
'G' => break, 'G' => {
self.in_copy_mode = true;
break;
}
// CopyOutResponse: copy is starting from the server to the client. // CopyOutResponse: copy is starting from the server to the client.
'H' => { 'H' => {
self.in_copy_mode = true;
self.data_available = true; self.data_available = true;
break; break;
} }
@@ -1030,6 +1049,10 @@ impl Server {
self.in_transaction self.in_transaction
} }
pub fn in_copy_mode(&self) -> bool {
self.in_copy_mode
}
/// We don't buffer all of server responses, e.g. COPY OUT produces too much data. /// We don't buffer all of server responses, e.g. COPY OUT produces too much data.
/// The client is responsible to call `self.recv()` while this method returns true. /// The client is responsible to call `self.recv()` while this method returns true.
pub fn is_data_available(&self) -> bool { pub fn is_data_available(&self) -> bool {
@@ -1129,6 +1152,10 @@ impl Server {
self.cleanup_state.reset(); self.cleanup_state.reset();
} }
if self.in_copy_mode() {
warn!("Server returned while still in copy-mode");
}
Ok(()) Ok(())
} }

102
tests/ruby/copy_spec.rb Normal file
View File

@@ -0,0 +1,102 @@
# frozen_string_literal: true
require_relative 'spec_helper'
describe "COPY Handling" do
let(:processes) { Helpers::Pgcat.single_instance_setup("sharded_db", 5) }
before do
new_configs = processes.pgcat.current_config
# Allow connections in the pool to expire faster
new_configs["general"]["idle_timeout"] = 5
processes.pgcat.update_config(new_configs)
# We need to kill the old process that was using the default configs
processes.pgcat.stop
processes.pgcat.start
processes.pgcat.wait_until_ready
end
before do
processes.all_databases.first.with_connection do |conn|
conn.async_exec "CREATE TABLE copy_test_table (a TEXT,b TEXT,c TEXT,d TEXT)"
end
end
after do
processes.all_databases.first.with_connection do |conn|
conn.async_exec "DROP TABLE copy_test_table;"
end
end
after do
processes.all_databases.map(&:reset)
processes.pgcat.shutdown
end
describe "COPY FROM" do
context "within transaction" do
it "finishes within alloted time" do
conn = PG.connect(processes.pgcat.connection_string("sharded_db", "sharding_user"))
Timeout.timeout(3) do
conn.async_exec("BEGIN")
conn.copy_data "COPY copy_test_table FROM STDIN CSV" do
sleep 0.5
conn.put_copy_data "some,data,to,copy\n"
conn.put_copy_data "more,data,to,copy\n"
end
conn.async_exec("COMMIT")
end
res = conn.async_exec("SELECT * FROM copy_test_table").to_a
expect(res).to eq([
{"a"=>"some", "b"=>"data", "c"=>"to", "d"=>"copy"},
{"a"=>"more", "b"=>"data", "c"=>"to", "d"=>"copy"}
])
end
end
context "outside transaction" do
it "finishes within alloted time" do
conn = PG.connect(processes.pgcat.connection_string("sharded_db", "sharding_user"))
Timeout.timeout(3) do
conn.copy_data "COPY copy_test_table FROM STDIN CSV" do
sleep 0.5
conn.put_copy_data "some,data,to,copy\n"
conn.put_copy_data "more,data,to,copy\n"
end
end
res = conn.async_exec("SELECT * FROM copy_test_table").to_a
expect(res).to eq([
{"a"=>"some", "b"=>"data", "c"=>"to", "d"=>"copy"},
{"a"=>"more", "b"=>"data", "c"=>"to", "d"=>"copy"}
])
end
end
end
describe "COPY TO" do
before do
conn = PG.connect(processes.pgcat.connection_string("sharded_db", "sharding_user"))
conn.async_exec("BEGIN")
conn.copy_data "COPY copy_test_table FROM STDIN CSV" do
conn.put_copy_data "some,data,to,copy\n"
conn.put_copy_data "more,data,to,copy\n"
end
conn.async_exec("COMMIT")
conn.close
end
it "works" do
res = []
conn = PG.connect(processes.pgcat.connection_string("sharded_db", "sharding_user"))
conn.copy_data "COPY copy_test_table TO STDOUT CSV" do
while row=conn.get_copy_data
res << row
end
end
expect(res).to eq(["some,data,to,copy\n", "more,data,to,copy\n"])
end
end
end