Skip to content

Commit

Permalink
fix: support PreparedStatement.setBlob(1, Blob) and PreparedStatement…
Browse files Browse the repository at this point in the history
….setClob(1, Clob) for lobs that return -1 for length

Historically, Hibernate was creating such clobs and it relied on the fact that pgjdbc
did not use the length in case it was -1.

Now the negative length is treated as if it was infinite (no eplicit limit)
The length of 0 means "empty lob".

Fixes #3134
  • Loading branch information
vlsi committed Apr 14, 2024
1 parent d5cfb04 commit 6a2c732
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,14 @@ public void setBlob(@Positive int i, @Nullable Blob x) throws SQLException {

InputStream inStream = x.getBinaryStream();
try {
long oid = createBlob(i, inStream, x.length());
long maxLength = x.length();
if (maxLength < 0) {
// Hibernate used to create blob instances that report -1 length, so we ignore the length
// and assume we need to read all the data from the stream.
// See https://github.com/pgjdbc/pgjdbc/issues/3134
maxLength = Long.MAX_VALUE;
}
long oid = createBlob(i, inStream, maxLength);
setLong(i, oid);
} finally {
try {
Expand Down Expand Up @@ -1321,6 +1328,10 @@ public void setClob(@Positive int i, @Nullable Clob x) throws SQLException {

Reader inStream = x.getCharacterStream();
int length = (int) x.length();
if (length < 0) {
// See #setBlob(int, Blob)
length = Integer.MAX_VALUE;
}
LargeObjectManager lom = connection.getLargeObjectAPI();
long oid = lom.createLO();
LargeObject lob = lom.open(oid);
Expand Down
83 changes: 83 additions & 0 deletions pgjdbc/src/test/java/org/postgresql/test/jdbc2/BlobTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.postgresql.test.jdbc2;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -21,6 +22,8 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -32,6 +35,10 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.concurrent.ThreadLocalRandom;

import javax.sql.rowset.serial.SerialBlob;
import javax.sql.rowset.serial.SerialClob;

/**
* Some simple tests based on problems reported by users. Hopefully these will help prevent previous
Expand Down Expand Up @@ -170,6 +177,82 @@ void set() throws SQLException {
}
}

@ValueSource(ints = {0, 1, 13, 123423})
@ParameterizedTest
void setBlobMinusOneLengthAndGivenByteContents(int length) throws Exception {
byte[] contents = new byte[length];
ThreadLocalRandom.current().nextBytes(contents);
try (PreparedStatement pstmt =
con.prepareStatement("INSERT INTO testblob(id, lo) VALUES (?, ?)")) {
pstmt.setString(1, "setBlobNegativeLength");
pstmt.setBlob(2, new SerialBlob(contents) {
@Override
public long length() {
return -1;
}
});
pstmt.executeUpdate();
}
// Read the value back and compare with original
try (Statement stmt = con.createStatement()) {
try (ResultSet rs =
stmt.executeQuery("SELECT lo FROM testblob where id = 'setBlobNegativeLength'")) {
assertTrue(rs.next(), "rs.next()");
Blob blob = rs.getBlob(1);
assertArrayEquals(
contents,
blob.getBytes(1, contents.length),
"blob.getBytes(1, contents.length)"
);
assertArrayEquals(
contents,
blob.getBytes(1, contents.length * 2),
"blob.getBytes(1, contents.length * 2)"
);
assertEquals(contents.length, blob.length(), "blob.length()");
}
}
}

@ValueSource(ints = {0, 1, 13, 123423})
@ParameterizedTest
void setClobMinusOneLengthAndGivenByteContents(int length) throws Exception {
char[] contents = new char[length];
for (int i = 0; i < contents.length; i++) {
contents[i] = (char) ('a' + ThreadLocalRandom.current().nextInt(26));
}
try (PreparedStatement pstmt =
con.prepareStatement("INSERT INTO testblob(id, lo) VALUES (?, ?)")) {
pstmt.setString(1, "setClobNegativeLength");
pstmt.setClob(2, new SerialClob(contents) {
@Override
public long length() {
return -1;
}
});
pstmt.executeUpdate();
}
// Read the value back and compare with original
try (Statement stmt = con.createStatement()) {
try (ResultSet rs =
stmt.executeQuery("SELECT lo FROM testblob where id = 'setClobNegativeLength'")) {
assertTrue(rs.next(), "rs.next()");
Clob clob = rs.getClob(1);
assertEquals(
new String(contents),
clob.getSubString(1, contents.length),
"clob.getSubString(1, contents.length)"
);
assertEquals(
new String(contents),
clob.getSubString(1, contents.length * 2),
"clob.getSubString(1, contents.length * 2)"
);
assertEquals(contents.length, clob.length(), "clob.length()");
}
}
}

/*
* Tests one method of uploading a blob to the database
*/
Expand Down

0 comments on commit 6a2c732

Please sign in to comment.