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..d85cb75 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgByteaBlob.java @@ -0,0 +1,137 @@ +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..511056e 100644 --- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java @@ -420,6 +420,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 +481,13 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS return null; } + switch (getSQLType(i)) { + 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..70245b2 --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStringClob.java @@ -0,0 +1,162 @@ +/*------------------------------------------------------------------------- +* +* 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..de8a1f6 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgByteaBlobTest.java @@ -0,0 +1,112 @@ +/*------------------------------------------------------------------------- +* +* 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..a0affbd --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/jdbc/PgStringClobTest.java @@ -0,0 +1,100 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2004-2015, PostgreSQL Global Development Group +* +* +*------------------------------------------------------------------------- +*/ +package org.postgresql.jdbc; + + +import static junit.framework.TestCase.*; +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()); + } + +}