diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/AbstractBasicLob.java b/pgjdbc/src/main/java/org/postgresql/jdbc/AbstractBasicLob.java new file mode 100644 index 0000000..81b5ba1 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/AbstractBasicLob.java @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2005-2014, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- +*/ +package org.postgresql.jdbc; + +import java.sql.SQLException; +import org.postgresql.util.GT; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; + +public class AbstractBasicLob { + + /** + * Throws an exception if the pos value exceeds the max value by which the large object API can index. + * + * @param pos Position to write at. + * @throws SQLException if something goes wrong + */ + protected void assertPosition(long pos) throws SQLException { + assertPosition(pos, 0); + } + + /** + * Throws an exception if the pos value exceeds the max value by which the large object API can index. + * + * @param pos Position to write at. + * @param len number of bytes to write. + * @throws SQLException if something goes wrong + */ + protected void assertPosition(long pos, long len) throws SQLException { + if (pos < 1) { + throw new PSQLException(GT.tr("LOB positioning offsets start at 1."), + PSQLState.INVALID_PARAMETER_VALUE); + } + if (pos + len - 1 > Integer.MAX_VALUE) { + throw new PSQLException(GT.tr("PostgreSQL LOBs can only index to: {0}", Integer.MAX_VALUE), + PSQLState.INVALID_PARAMETER_VALUE); + } + } + +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgByteaBlob.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgByteaBlob.java new file mode 100644 index 0000000..36e6d8f --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgByteaBlob.java @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2004-2015, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- + */ + +package org.postgresql.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.Blob; +import java.sql.SQLException; +import java.util.Arrays; + +/** + * + * @author Thomas Kellerer + */ +public class PgByteaBlob + extends AbstractBasicLob + implements java.sql.Blob { + + private byte[] content; + + public PgByteaBlob(byte[] content) { + this.content = content; + } + + @Override + public long length() + throws SQLException { + return content.length; + } + + @Override + public byte[] getBytes(long pos, int length) + throws SQLException { + + assertPosition(pos); + + return Arrays.copyOfRange(content, (int)(pos - 1), (int)length); + } + + public byte[] getBytes() + throws SQLException { + + return Arrays.copyOf(content, content.length); + } + + @Override + public InputStream getBinaryStream() + throws SQLException { + + return new ByteArrayInputStream(content); + } + + @Override + public long position(byte[] pattern, long start) + throws SQLException { + + assertPosition(start); + + for (int i = 0; i < content.length; i++) { + int pi = 0; + while (content[i] == pattern[pi] && pi < pattern.length) { + pi++; + } + if (pi == pattern.length) { + return i + 1; + } + } + return -1; + } + + @Override + public long position(Blob pattern, long start) + throws SQLException { + + byte[] toSearch = pattern.getBytes(1, (int)pattern.length()); + + return position(toSearch, start); + } + + @Override + public int setBytes(long pos, byte[] bytes) + throws SQLException { + + assertPosition(pos); + + return setBytes(pos, bytes, 0, bytes.length); + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) + throws SQLException { + + if (bytes == null) { + return 0; + } + + assertPosition(pos); + + pos = pos - 1; + + if (pos + len > content.length) { + content = Arrays.copyOf(content, (int)pos + len); + } + + System.arraycopy(bytes, offset, content, (int)pos, len); + + return len; + } + + @Override + public OutputStream setBinaryStream(long pos) + throws SQLException { + + throw org.postgresql.Driver.notImplemented(this.getClass(), "setBinaryStream(long)"); + } + + @Override + public void truncate(long len) + throws SQLException { + + content = Arrays.copyOfRange(content, 0, (int)len); + } + + @Override + public void free() + throws SQLException { + + // nothing to do + } + + @Override + public InputStream getBinaryStream(long pos, long length) + throws SQLException { + + return new ByteArrayInputStream(content, (int)(pos - 1), (int)length); + } + +} diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java index 088f303..b81ecf8 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java @@ -8,29 +8,6 @@ package org.postgresql.jdbc; -import org.postgresql.PGResultSetMetaData; -import org.postgresql.core.BaseConnection; -import org.postgresql.core.BaseStatement; -import org.postgresql.core.Encoding; -import org.postgresql.core.Field; -import org.postgresql.core.Oid; -import org.postgresql.core.Query; -import org.postgresql.core.ResultCursor; -import org.postgresql.core.ResultHandler; -import org.postgresql.core.ServerVersion; -import org.postgresql.core.TypeInfo; -import org.postgresql.core.Utils; -import org.postgresql.largeobject.LargeObject; -import org.postgresql.largeobject.LargeObjectManager; -import org.postgresql.util.ByteConverter; -import org.postgresql.util.GT; -import org.postgresql.util.HStoreConverter; -import org.postgresql.util.PGbytea; -import org.postgresql.util.PGobject; -import org.postgresql.util.PGtokenizer; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; - import java.io.ByteArrayInputStream; import java.io.CharArrayReader; import java.io.IOException; @@ -59,13 +36,11 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; -//#endif import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; @@ -76,6 +51,28 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.TimeZone; import java.util.UUID; +import org.postgresql.PGResultSetMetaData; +import org.postgresql.core.BaseConnection; +import org.postgresql.core.BaseStatement; +import org.postgresql.core.Encoding; +import org.postgresql.core.Field; +import org.postgresql.core.Oid; +import org.postgresql.core.Query; +import org.postgresql.core.ResultCursor; +import org.postgresql.core.ResultHandler; +import org.postgresql.core.ServerVersion; +import org.postgresql.core.TypeInfo; +import org.postgresql.core.Utils; +import org.postgresql.largeobject.LargeObject; +import org.postgresql.largeobject.LargeObjectManager; +import org.postgresql.util.ByteConverter; +import org.postgresql.util.GT; +import org.postgresql.util.HStoreConverter; +import org.postgresql.util.PGbytea; +import org.postgresql.util.PGobject; +import org.postgresql.util.PGtokenizer; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultSet { @@ -420,6 +417,13 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS return null; } + switch (getSQLType(i)) { + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + return new PgByteaBlob(getBytes(i)); + } + return makeBlob(getLong(i)); } @@ -474,6 +478,14 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS return null; } + int type = getSQLType(i); + switch (type) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + return new PgStringClob(getString(i)); + } + return makeClob(getLong(i)); } diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgStringClob.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStringClob.java new file mode 100644 index 0000000..0382d77 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStringClob.java @@ -0,0 +1,165 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2004-2015, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- + */ + +package org.postgresql.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.sql.Clob; +import java.sql.SQLException; + +/** + * + * @author Thomas Kellerer + */ +public class PgStringClob + extends AbstractBasicLob + implements java.sql.Clob { + + private String content; + + public PgStringClob(String data) { + + this.content = data; + } + + @Override + public long length() + throws SQLException { + + return content.length(); + } + + @Override + public String getSubString(long pos, int length) + throws SQLException { + + assertPosition(pos); + + return content.substring((int)(pos - 1), (int)pos + length - 1); + } + + @Override + public Reader getCharacterStream() + throws SQLException { + + return new StringReader(content); + } + + @Override + public InputStream getAsciiStream() + throws SQLException { + + try { + return new ByteArrayInputStream(content.getBytes("UTF-8")); + } catch (UnsupportedEncodingException ex) { + // should not happen + throw new SQLException("UTF-8 not supported"); + } + } + + @Override + public long position(String searchstr, long start) + throws SQLException { + + assertPosition(start); + + return content.indexOf(searchstr, (int)start - 1) + 1; + } + + @Override + public long position(Clob searchstr, long start) + throws SQLException { + + assertPosition(start); + + String toSearch = searchstr.getSubString(1, (int)searchstr.length()); + return position(toSearch, start); + } + + @Override + public int setString(long pos, String str) + throws SQLException { + + assertPosition(pos); + + if (str == null) { + return 0; + } + + StringBuilder buffer = new StringBuilder(content.length()); + buffer.append(content.substring(0, (int)(pos - 1))); + buffer.append(str); + if (str.length() + (pos - 1) < content.length()) { + buffer.append(content.substring(str.length() + (int)(pos - 1))); + } + content = buffer.toString(); + return str.length(); + } + + @Override + public int setString(long pos, String str, int offset, int len) + throws SQLException { + + assertPosition(pos); + + if (str == null) { + return 0; + } + return setString(pos, str.substring(offset, offset + len)); + } + + @Override + public OutputStream setAsciiStream(long pos) + throws SQLException { + + throw org.postgresql.Driver.notImplemented(this.getClass(), "setAsciiStream(long)"); + } + + @Override + public Writer setCharacterStream(long pos) + throws SQLException { + + throw org.postgresql.Driver.notImplemented(this.getClass(), "setCharacterStream(long)"); + } + + @Override + public void truncate(long len) + throws SQLException { + + if (len > content.length()) { + // nothing to do + return; + } + content = content.substring(0, (int)len); + } + + @Override + public void free() + throws SQLException { + // nothing to do + } + + @Override + public Reader getCharacterStream(long pos, long length) + throws SQLException { + + return new StringReader(content.substring((int)(pos - 1), (int)length)); + } + + @Override + public String toString() { + return content; + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PgByteaBlobTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PgByteaBlobTest.java new file mode 100644 index 0000000..8eb5897 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgByteaBlobTest.java @@ -0,0 +1,114 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2004-2015, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- + */ + +package org.postgresql.jdbc; + +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +/** + * + * @author Thomas Kellerer + */ +public class PgByteaBlobTest { + + public PgByteaBlobTest() { + } + + @Test + public void testLength() throws Exception { + byte[] data = new byte[]{1, 2, 3, 4}; + PgByteaBlob blob = new PgByteaBlob(data); + assertEquals(data.length, blob.length()); + } + + @Test + public void testGetBytes() throws Exception { + byte[] data = new byte[]{1, 2, 3, 4}; + PgByteaBlob blob = new PgByteaBlob(data); + assertArrayEquals(data, blob.getBytes(1, data.length)); + } + + @Test + public void testSetBytes1() throws Exception { + byte[] data = new byte[]{1, 2, 3, 4}; + PgByteaBlob blob = new PgByteaBlob(data); + blob.setBytes(2, new byte[]{0, 0}); + byte[] newData = blob.getBytes(); + assertEquals(4, newData.length); + assertEquals(1, newData[0]); + assertEquals(0, newData[1]); + assertEquals(0, newData[2]); + assertEquals(4, newData[3]); + + blob = new PgByteaBlob(data); + blob.setBytes(1, new byte[]{10, 11, 12, 13, 14, 15, 16}); + newData = blob.getBytes(); + assertEquals(7, newData.length); + for (int i = 0; i < newData.length; i++) { + assertEquals(10 + i, newData[i]); + } + } + + @Test + public void testSetBytes2() throws Exception { + byte[] data = new byte[]{1, 2, 3, 4}; + PgByteaBlob blob = new PgByteaBlob(data); + + byte[] data2 = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; + blob.setBytes(1, data2, 4, 1); + byte[] current = blob.getBytes(); + assertEquals(data.length, current.length); + assertEquals(5, current[0]); + + byte[] data3 = new byte[]{10, 11, 12, 13, 14, 15, 16}; + blob = new PgByteaBlob(new byte[]{1, 2, 3, 4}); + blob.setBytes(2, data3, 0, 7); + current = blob.getBytes(); + assertEquals(8, current.length); + assertEquals(1, current[0]); + for (int i = 1; i < current.length; i++) { + assertEquals(10 + i - 1, current[i]); + } + } + + @Test + public void testPosition1() + throws Exception { + + PgByteaBlob blob = new PgByteaBlob(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + long pos = blob.position(new byte[]{3, 4, 5}, 1); + assertEquals(3, pos); + + pos = blob.position(new byte[]{11, 4, 5}, 1); + assertEquals(-1, pos); + + pos = blob.position(new byte[]{1, 2, 3}, 3); + assertEquals(-1, pos); + + pos = blob.position(new byte[]{9, 10}, 1); + assertEquals(9, pos); + + pos = blob.position(new byte[]{9, 10}, 5); + assertEquals(9, pos); + + pos = blob.position(new byte[]{8, 9, 10}, 9); + assertEquals(-1, pos); + } + + @Test + public void testTruncate() throws Exception { + byte[] data = new byte[]{1, 2, 3, 4}; + PgByteaBlob blob = new PgByteaBlob(data); + blob.truncate(2); + assertEquals(2, blob.length()); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/jdbc/PgStringClobTest.java b/pgjdbc/src/test/java/org/postgresql/jdbc/PgStringClobTest.java new file mode 100644 index 0000000..6372f15 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgStringClobTest.java @@ -0,0 +1,91 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2004-2015, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- + */ + +package org.postgresql.jdbc; + +import static junit.framework.TestCase.assertEquals; + +import org.junit.Test; + +/** + * + * @author Thomas Kellerer + */ +public class PgStringClobTest { + + public PgStringClobTest() { + } + + @Test + public void testLength() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + assertEquals(data.length(), clob.length()); + } + + @Test + public void testGetSubString() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + String result = clob.getSubString(1, 5); + assertEquals("Hello", result); + } + + @Test + public void testPositionString() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + long result = clob.position(",", 1); + assertEquals(data.indexOf(',') + 1, result); + } + + @Test + public void testPositionClob() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + PgStringClob searchstr = new PgStringClob("Clob"); + long result = clob.position(searchstr, 1); + assertEquals(data.indexOf("Clob") + 1, result); + } + + @Test + public void testSetString1() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + clob.setString(8, "World"); + assertEquals("Hello, World", clob.toString()); + } + + @Test + public void testSetString2() + throws Exception { + } + + @Test + public void testTruncate() + throws Exception { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + clob.truncate(5); + assertEquals(5, clob.length()); + assertEquals("Hello", clob.toString()); + } + + @Test + public void testToString() { + String data = "Hello, Clob"; + PgStringClob clob = new PgStringClob(data); + assertEquals(data, clob.toString()); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java index 1bf861c..143dbb2 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java @@ -8,16 +8,16 @@ package org.postgresql.test.jdbc2; -import org.postgresql.test.TestUtil; -import org.postgresql.util.PGobject; - -import junit.framework.TestCase; - +import java.sql.Blob; +import java.sql.Clob; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Locale; +import junit.framework.TestCase; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PGobject; /* * ResultSet tests. @@ -95,9 +95,10 @@ public class ResultSetTest extends TestCase { TestUtil.createTable(con, "testpgobject", "id integer NOT NULL, d date, PRIMARY KEY (id)"); stmt.execute("INSERT INTO testpgobject VALUES(1, '2010-11-3')"); - stmt.close(); - + TestUtil.createTable(con, "lob_test", "id integer NOT NULL, clob_data text, blob_data bytea, PRIMARY KEY (id)"); + stmt.executeUpdate("INSERT INTO lob_test VALUES(1, 'Hello, Clob', E'\\\\x01020304');"); + stmt.close(); } protected void tearDown() throws SQLException { @@ -112,6 +113,26 @@ public class ResultSetTest extends TestCase { TestUtil.closeDB(con); } + public void testGetLob() throws SQLException { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select id, clob_data, blob_data from lob_test"); + assertTrue(rs.next()); + int id = rs.getInt(1); + assertEquals(1, id); + Clob clob = rs.getClob(2); + assertNotNull(clob); + assertEquals("Hello, Clob", clob.getSubString(1, (int)clob.length())); + Blob blob = rs.getBlob(3); + assertNotNull(blob); + byte[] bytes = blob.getBytes(1, (int)blob.length()); + assertEquals(4, bytes.length); + assertEquals(1, bytes[0]); + assertEquals(2, bytes[1]); + assertEquals(3, bytes[2]); + assertEquals(4, bytes[3]); + } + public void testBackward() throws SQLException { Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);