From 6b715eaf6f72acb70bece5f78db70a0f0af4a8f6 Mon Sep 17 00:00:00 2001 From: Herwin Date: Wed, 26 Mar 2025 11:06:12 +0100 Subject: [PATCH 1/3] Expose send_query_params function --- lib/db/postgres/connection.rb | 6 ++++++ lib/db/postgres/native/connection.rb | 8 ++++++++ test/db/postgres/connection.rb | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/lib/db/postgres/connection.rb b/lib/db/postgres/connection.rb index e54af44..28bd6d0 100644 --- a/lib/db/postgres/connection.rb +++ b/lib/db/postgres/connection.rb @@ -101,6 +101,12 @@ def send_query(statement) @native.send_query(statement) end + def send_query_params(statement, *params) + @native.discard_results + + @native.send_query_params(statement, *params) + end + def next_result @native.next_result end diff --git a/lib/db/postgres/native/connection.rb b/lib/db/postgres/native/connection.rb index fa144e6..c30c40b 100644 --- a/lib/db/postgres/native/connection.rb +++ b/lib/db/postgres/native/connection.rb @@ -186,6 +186,14 @@ def send_query(statement) flush end + def send_query_params(statement, *params) + size = params.size + params = Strings.new(params) + check! Native.send_query_params(self, statement, size, nil, params.array, nil, nil, 0) + + flush + end + def next_result(types: @types) if result = self.get_result status = Native.result_status(result) diff --git a/test/db/postgres/connection.rb b/test/db/postgres/connection.rb index d1c0ef8..efbc6e4 100644 --- a/test/db/postgres/connection.rb +++ b/test/db/postgres/connection.rb @@ -26,6 +26,16 @@ ensure connection.close end + + it "should execute query with arguments" do + connection.send_query_params("SELECT $1::BIGINT AS LIFE, $2 AS ANSWER", 42, "Life, the universe and everything") + + result = connection.next_result + + expect(result.to_a).to be == [[42, "Life, the universe and everything"]] + ensure + connection.close + end it "should execute multiple queries" do connection.send_query("SELECT 42 AS LIFE; SELECT 24 AS LIFE") From 6abe95a107c84d3e7e0fa6d946389e756653baaf Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 31 Mar 2025 13:09:30 +0200 Subject: [PATCH 2/3] Support bytea data type for results This is a binary text format --- lib/db/postgres/native/field.rb | 2 ++ lib/db/postgres/native/types.rb | 10 ++++++++++ test/db/postgres/connection.rb | 11 +++++++++++ 3 files changed, 23 insertions(+) diff --git a/lib/db/postgres/native/field.rb b/lib/db/postgres/native/field.rb index c15039c..58c953b 100644 --- a/lib/db/postgres/native/field.rb +++ b/lib/db/postgres/native/field.rb @@ -40,6 +40,8 @@ module Native # These are hard coded OIDs. 16 => Types::Boolean.new, + 17 => Types::ByteA.new, + 20 => Types::Integer.new("int8"), 21 => Types::Integer.new("int2"), 23 => Types::Integer.new("int4"), diff --git a/lib/db/postgres/native/types.rb b/lib/db/postgres/native/types.rb index a70f0d7..8370e1a 100644 --- a/lib/db/postgres/native/types.rb +++ b/lib/db/postgres/native/types.rb @@ -44,6 +44,16 @@ def parse(string) end end + class ByteA + def name + "BYTEA" + end + + def parse(string) + [string[2..]].pack('H*') if string + end + end + class Decimal def name "DECIMAL" diff --git a/test/db/postgres/connection.rb b/test/db/postgres/connection.rb index efbc6e4..20e20a3 100644 --- a/test/db/postgres/connection.rb +++ b/test/db/postgres/connection.rb @@ -60,6 +60,17 @@ connection.close end + it "can handle bytea output" do + connection.send_query("SELECT '\\x414243003839'::BYTEA") + + result = connection.next_result + cell = result.to_a.first.first + expect(cell).to be == "ABC\x0089".b + expect(cell.encoding).to be == Encoding::ASCII_8BIT + ensure + connection.close + end + with '#append_string' do it "should escape string" do expect(connection.append_string("Hello 'World'")).to be == "'Hello ''World'''" From 8db9e0bb5d4e14d882d862f964291c254cdb3abc Mon Sep 17 00:00:00 2001 From: Herwin Date: Mon, 31 Mar 2025 13:24:41 +0200 Subject: [PATCH 3/3] Support bytea data type for input --- lib/db/postgres/native/connection.rb | 19 +++++++++++++++++-- test/db/postgres/connection.rb | 11 +++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/db/postgres/native/connection.rb b/lib/db/postgres/native/connection.rb index c30c40b..8cf8cf2 100644 --- a/lib/db/postgres/native/connection.rb +++ b/lib/db/postgres/native/connection.rb @@ -187,9 +187,24 @@ def send_query(statement) end def send_query_params(statement, *params) + params = params.map(&:to_s) size = params.size - params = Strings.new(params) - check! Native.send_query_params(self, statement, size, nil, params.array, nil, nil, 0) + is_binary = params.map { |param| param.include?("\x00".b) } + + # type 17 => bytea (https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat) + paramTypes = FFI::MemoryPointer.new(:int, params.size) + paramTypes.write_array_of_type(FFI::Type::INT, :put_int, is_binary.map { |binary| binary ? 17 : 0 }) + + paramValues = FFI::MemoryPointer.new(:pointer, params.size) + paramValues.write_array_of_pointer(params.map { |param| FFI::MemoryPointer.from_string(param) }) + + paramLengths = FFI::MemoryPointer.new(:int, params.size) + paramLengths.write_array_of_type(FFI::Type::INT, :put_int, params.zip(is_binary).map { |param, binary| binary ? param.bytesize : 0 }) + + paramFormats = FFI::MemoryPointer.new(:int, params.size) + paramFormats.write_array_of_type(FFI::Type::INT, :put_int, is_binary.map { |binary| binary ? 1 : 0 }) + + check! Native.send_query_params(self, statement, size, paramTypes, paramValues, paramLengths, paramFormats, 0) flush end diff --git a/test/db/postgres/connection.rb b/test/db/postgres/connection.rb index 20e20a3..f8dc4ab 100644 --- a/test/db/postgres/connection.rb +++ b/test/db/postgres/connection.rb @@ -71,6 +71,17 @@ connection.close end + it "can handle bytea argument" do + connection.send_query_params("SELECT $1::BYTEA", "ABC\x0089".b) + + result = connection.next_result + cell = result.to_a.first.first + expect(cell).to be == "ABC\x0089".b + expect(cell.encoding).to be == Encoding::ASCII_8BIT + ensure + connection.close + end + with '#append_string' do it "should escape string" do expect(connection.append_string("Hello 'World'")).to be == "'Hello ''World'''"