/*
 * Copyright (c) 2005, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.jdbc;

import org.postgresql.core.BaseConnection;
import org.postgresql.core.ServerVersion;
import org.postgresql.largeobject.LargeObject;
import org.postgresql.largeobject.LargeObjectManager;
import org.postgresql.util.GT;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;

This class holds all of the methods common to both Blobs and Clobs.
Author:Michael Barker
/** * This class holds all of the methods common to both Blobs and Clobs. * * @author <a href="mailto:mike@middlesoft.co.uk">Michael Barker</a> */
public abstract class AbstractBlobClob { protected BaseConnection conn; private LargeObject currentLo; private boolean currentLoIsWriteable; private boolean support64bit;
We create separate LargeObjects for methods that use streams so they won't interfere with each other.
/** * We create separate LargeObjects for methods that use streams so they won't interfere with each * other. */
private ArrayList<LargeObject> subLOs; private final long oid; public AbstractBlobClob(BaseConnection conn, long oid) throws SQLException { this.conn = conn; this.oid = oid; this.currentLo = null; this.currentLoIsWriteable = false; support64bit = conn.haveMinimumServerVersion(90300); subLOs = new ArrayList<LargeObject>(); } public synchronized void free() throws SQLException { if (currentLo != null) { currentLo.close(); currentLo = null; currentLoIsWriteable = false; } for (LargeObject subLO : subLOs) { subLO.close(); } subLOs = null; }
For Blobs this should be in bytes while for Clobs it should be in characters. Since we really haven't figured out how to handle character sets for Clobs the current implementation uses bytes for both Blobs and Clobs.
Params:
  • len – maximum length
Throws:
/** * For Blobs this should be in bytes while for Clobs it should be in characters. Since we really * haven't figured out how to handle character sets for Clobs the current implementation uses * bytes for both Blobs and Clobs. * * @param len maximum length * @throws SQLException if operation fails */
public synchronized void truncate(long len) throws SQLException { checkFreed(); if (!conn.haveMinimumServerVersion(ServerVersion.v8_3)) { throw new PSQLException( GT.tr("Truncation of large objects is only implemented in 8.3 and later servers."), PSQLState.NOT_IMPLEMENTED); } if (len < 0) { throw new PSQLException(GT.tr("Cannot truncate LOB to a negative length."), PSQLState.INVALID_PARAMETER_VALUE); } if (len > Integer.MAX_VALUE) { if (support64bit) { getLo(true).truncate64(len); } else { throw new PSQLException(GT.tr("PostgreSQL LOBs can only index to: {0}", Integer.MAX_VALUE), PSQLState.INVALID_PARAMETER_VALUE); } } else { getLo(true).truncate((int) len); } } public synchronized long length() throws SQLException { checkFreed(); if (support64bit) { return getLo(false).size64(); } else { return getLo(false).size(); } } public synchronized byte[] getBytes(long pos, int length) throws SQLException { assertPosition(pos); getLo(false).seek((int) (pos - 1), LargeObject.SEEK_SET); return getLo(false).read(length); } public synchronized InputStream getBinaryStream() throws SQLException { checkFreed(); LargeObject subLO = getLo(false).copy(); addSubLO(subLO); subLO.seek(0, LargeObject.SEEK_SET); return subLO.getInputStream(); } public synchronized OutputStream setBinaryStream(long pos) throws SQLException { assertPosition(pos); LargeObject subLO = getLo(true).copy(); addSubLO(subLO); subLO.seek((int) (pos - 1)); return subLO.getOutputStream(); }
Iterate over the buffer looking for the specified pattern.
Params:
  • pattern – A pattern of bytes to search the blob for
  • start – The position to start reading from
Throws:
Returns:position of the specified pattern
/** * Iterate over the buffer looking for the specified pattern. * * @param pattern A pattern of bytes to search the blob for * @param start The position to start reading from * @return position of the specified pattern * @throws SQLException if something wrong happens */
public synchronized long position(byte[] pattern, long start) throws SQLException { assertPosition(start, pattern.length); int position = 1; int patternIdx = 0; long result = -1; int tmpPosition = 1; for (LOIterator i = new LOIterator(start - 1); i.hasNext(); position++) { byte b = i.next(); if (b == pattern[patternIdx]) { if (patternIdx == 0) { tmpPosition = position; } patternIdx++; if (patternIdx == pattern.length) { result = tmpPosition; break; } } else { patternIdx = 0; } } return result; }
Iterates over a large object returning byte values. Will buffer the data from the large object.
/** * Iterates over a large object returning byte values. Will buffer the data from the large object. */
private class LOIterator { private static final int BUFFER_SIZE = 8096; private byte[] buffer = new byte[BUFFER_SIZE]; private int idx = BUFFER_SIZE; private int numBytes = BUFFER_SIZE; LOIterator(long start) throws SQLException { getLo(false).seek((int) start); } public boolean hasNext() throws SQLException { boolean result; if (idx < numBytes) { result = true; } else { numBytes = getLo(false).read(buffer, 0, BUFFER_SIZE); idx = 0; result = (numBytes > 0); } return result; } private byte next() { return buffer[idx++]; } }
This is simply passing the byte value of the pattern Blob.
Params:
  • pattern – search pattern
  • start – start position
Throws:
Returns:position of given pattern
/** * This is simply passing the byte value of the pattern Blob. * * @param pattern search pattern * @param start start position * @return position of given pattern * @throws SQLException if something goes wrong */
public synchronized long position(Blob pattern, long start) throws SQLException { return position(pattern.getBytes(1, (int) pattern.length()), start); }
Throws an exception if the pos value exceeds the max value by which the large object API can index.
Params:
  • pos – Position to write at.
Throws:
/** * 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.
Params:
  • pos – Position to write at.
  • len – number of bytes to write.
Throws:
/** * 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 { checkFreed(); 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); } }
Checks that this LOB hasn't been free()d already.
Throws:
  • SQLException – if LOB has been freed.
/** * Checks that this LOB hasn't been free()d already. * * @throws SQLException if LOB has been freed. */
protected void checkFreed() throws SQLException { if (subLOs == null) { throw new PSQLException(GT.tr("free() was called on this LOB previously"), PSQLState.OBJECT_NOT_IN_STATE); } } protected synchronized LargeObject getLo(boolean forWrite) throws SQLException { if (this.currentLo != null) { if (forWrite && !currentLoIsWriteable) { // Reopen the stream in read-write, at the same pos. int currentPos = this.currentLo.tell(); LargeObjectManager lom = conn.getLargeObjectAPI(); LargeObject newLo = lom.open(oid, LargeObjectManager.READWRITE); this.subLOs.add(this.currentLo); this.currentLo = newLo; if (currentPos != 0) { this.currentLo.seek(currentPos); } } return this.currentLo; } LargeObjectManager lom = conn.getLargeObjectAPI(); currentLo = lom.open(oid, forWrite ? LargeObjectManager.READWRITE : LargeObjectManager.READ); currentLoIsWriteable = forWrite; return currentLo; } protected void addSubLO(LargeObject subLO) { subLOs.add(subLO); } }