view src/main/java/org/monetdb/jdbc/MonetBlob.java @ 970:f90d811e97eb default tip

Adjust getTableTypes() test for new table type: LOCAL TEMPORARY VIEW, added in 11.53.4 (Mar2025-SP1)
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 03 Apr 2025 15:01:33 +0200 (4 days ago)
parents ff075ed5ce81
children
line wrap: on
line source
/*
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0.  If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2024, 2025 MonetDB Foundation;
 * Copyright August 2008 - 2023 MonetDB B.V.;
 * Copyright 1997 - July 2008 CWI.
 */

package org.monetdb.jdbc;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;

/**
 *<pre>
 * The MonetBlob class implements the {@link java.sql.Blob} interface.
 *
 * Because MonetDB/SQL currently has no support for streams, this class is a
 * shallow wrapper of a byte[]. It is more or less supplied to
 * enable an application that depends on it to run. It may be obvious
 * that it is a real resource expensive workaround that contradicts the
 * benefits for a Blob: avoidance of huge resource consumption.
 * <b>Use of this class is highly discouraged.</b>
 *</pre>
 *
 * @author Fabian Groffen
 */
public final class MonetBlob implements Blob {
	private byte[] buf;

	/* constructors */
	protected MonetBlob(final byte[] data) {
		buf = data;
	}

	protected MonetBlob(final String hexString) {
		buf = hexStrToByteArray(hexString);
	}


	/* class utility methods */
	static final byte[] hexStrToByteArray(final String hexString) {
		// unpack the HEX (BLOB) notation to real bytes
		final int len = hexString.length() / 2;
		final byte[] buf = new byte[len];
		for (int i = 0; i < len; i++) {
//	was		buf[i] = (byte)Integer.parseInt(hexString.substring(2 * i, (2 * i) + 2), 16);
			buf[i] = (byte) ((Character.digit(hexString.charAt(2 * i), 16) << 4)
					+ Character.digit(hexString.charAt((2 * i) +1), 16));
		}
		return buf;
	}

	/* internal utility method */
	private final void checkBufIsNotNull() throws SQLException {
		if (buf == null)
			throw new SQLException("This MonetBlob has been freed", "M1M20");
	}

	//== begin interface Blob

	/**
	 * This method frees the Blob object and releases the resources that
	 * it holds. The object is invalid once the free method is called.
	 *
	 * After free has been called, any attempt to invoke a method other
	 * than free will result in a SQLException being thrown. If free is
	 * called multiple times, the subsequent calls to free are treated
	 * as a no-op.
	 *
	 * @throws SQLException if an error occurs releasing the Blob's resources
	 */
	@Override
	public void free() throws SQLException {
		buf = null;
	}

	/**
	 * Retrieves the BLOB value designated by this Blob instance as a stream.
	 *
	 * @return a stream containing the BLOB data
	 * @throws SQLException if there is an error accessing the BLOB value
	 */
	@Override
	public InputStream getBinaryStream() throws SQLException {
		checkBufIsNotNull();
		return new ByteArrayInputStream(buf);
	}

	/**
	 * Returns an InputStream object that contains a partial Blob value,
	 * starting with the byte specified by pos, which is length bytes in
	 * length.
	 *
	 * @param pos the offset to the first byte of the partial value to
	 *        be retrieved. The first byte in the Blob is at position 1
	 * @param length the length in bytes of the partial value to be retrieved
	 * @return InputStream through which the partial Blob value can be read.
	 * @throws SQLException if pos is less than 1 or if pos is
	 *         greater than the number of bytes in the Blob or if pos +
	 *         length is greater than the number of bytes in the Blob
	 */
	@Override
	public InputStream getBinaryStream(final long pos, final long length)
		throws SQLException
	{
		checkBufIsNotNull();
		if (pos < 1 || pos > buf.length) {
			throw new SQLException("Invalid pos value: " + pos, "M1M05");
		}
		if (length < 0 || pos - 1 + length > buf.length) {
			throw new SQLException("Invalid length value: " + length, "M1M05");
		}
		return new ByteArrayInputStream(buf, (int) pos - 1, (int) length);
	}

	/**
	 * Retrieves all or part of the BLOB value that this Blob object
	 * represents, as an array of bytes. This byte array contains up to
	 * length consecutive bytes starting at position pos.
	 *
	 * @param pos the ordinal position of the first byte in the BLOB
	 *        value to be extracted; the first byte is at position 1.
	 * @param length the number of consecutive bytes to be copied
	 * @return a byte array containing up to length consecutive bytes
	 *         from the BLOB value designated by this Blob object,
	 *         starting with the byte at position pos.
	 * @throws SQLException if there is an error accessing the BLOB value
	 */
	@Override
	public byte[] getBytes(final long pos, final int length) throws SQLException {
		checkBufIsNotNull();
		if (pos < 1 || pos > buf.length) {
			throw new SQLException("Invalid pos value: " + pos, "M1M05");
		}
		if (length < 0 || pos - 1 + length > buf.length) {
			throw new SQLException("Invalid length value: " + length, "M1M05");
		}

		try {
			return java.util.Arrays.copyOfRange(buf, (int) pos - 1, (int) pos - 1 + length);
		} catch (IndexOutOfBoundsException e) {
			throw new SQLException(e.getMessage(), "M0M10");
		}
	}

	/**
	 * Returns the number of bytes in the BLOB value designated by this
	 * Blob object.
	 *
	 * @return length of the BLOB in bytes
	 * @throws SQLException if there is an error accessing the length
	 *         of the BLOB value
	 */
	@Override
	public long length() throws SQLException {
		checkBufIsNotNull();
		return (long)buf.length;
	}

	/**
	 * Retrieves the byte position in the BLOB value designated by this
	 * Blob object at which pattern begins. The search begins at position start.
	 *
	 * @param pattern the Blob object designating the BLOB value for
	 *        which to search
	 * @param start the position in the BLOB value at which to begin
	 *        searching; the first position is 1
	 * @return the position at which the pattern begins, else -1
	 * @throws SQLException if there is an error accessing the BLOB value
	 */
	@Override
	public long position(final Blob pattern, final long start) throws SQLException {
		if (pattern == null) {
			throw new SQLException("Missing pattern object", "M1M05");
		}
		// buf and input argument start will be checked in method position(byte{}, long)
		return position(pattern.getBytes(1L, (int)pattern.length()), start);
	}

	/**
	 * Retrieves the byte position at which the specified byte array
	 * pattern begins within the BLOB value that this Blob object
	 * represents. The search for pattern begins at position start.
	 *
	 * @param pattern the byte array for which to search
	 * @param start the position at which to begin searching;
	 *        the first position is 1
	 * @return the position at which the pattern appears, else -1
	 * @throws SQLException if there is an error accessing the BLOB value
	 */
	@Override
	public long position(final byte[] pattern, final long start) throws SQLException {
		checkBufIsNotNull();
		if (pattern == null) {
			throw new SQLException("Missing pattern object", "M1M05");
		}
		if (start < 1 || start > buf.length) {
			throw new SQLException("Invalid start value: " + start, "M1M05");
		}
		try {
			final int patternLength = pattern.length;
			final int maxPos = buf.length - patternLength;
			for (int i = (int)(start - 1); i < maxPos; i++) {
				int j;
				for (j = 0; j < patternLength; j++) {
					if (buf[i + j] != pattern[j])
						break;
				}
				if (j == patternLength)
					// found a match
					return i;
			}
		} catch (IndexOutOfBoundsException e) {
			throw new SQLException(e.getMessage(), "M0M10");
		}
		return -1;
	}

	/**
	 * Retrieves a stream that can be used to write to the BLOB value
	 * that this Blob object represents. The stream begins at position
	 * pos. The bytes written to the stream will overwrite the existing
	 * bytes in the Blob object starting at the position pos. If the end
	 * of the Blob value is reached while writing to the stream, then
	 * the length of the Blob value will be increased to accommodate the
	 * extra bytes.
	 *
	 * @param pos the position in the BLOB value at which to start
	 *            writing; the first position is 1
	 * @return a java.io.OutputStream object to which data can be written
	 * @throws SQLException if there is an error accessing the BLOB
	 *         value or if pos is less than 1
	 * @throws SQLFeatureNotSupportedException if the JDBC driver does
	 *         not support this method
	 */
	@Override
	public OutputStream setBinaryStream(final long pos) throws SQLException {
		throw MonetWrapper.newSQLFeatureNotSupportedException("setBinaryStream");
	}

	/**
	 * Writes the given array of bytes to the BLOB value that this Blob
	 * object represents, starting at position pos, and returns the
	 * number of bytes written.
	 *
	 * @param pos the position in the BLOB object at which to start writing
	 * @param bytes the array of bytes to be written to the BLOB value
	 *        that this Blob object represents
	 * @return the number of bytes written
	 * @throws SQLException if there is an error accessing the
	 *         BLOB value or if pos is less than 1
	 */
	@Override
	public int setBytes(final long pos, final byte[] bytes) throws SQLException {
		// buf and input arguments will be checked in method setBytes(long, byte{}, int, int)
		final int len = (bytes != null) ? bytes.length : 0;
		return setBytes(pos, bytes, 1, len);
	}

	/**
	 * Writes all or part of the given byte array to the BLOB value that
	 * this Blob object represents and returns the number of bytes written.
	 * Writing starts at position pos in the BLOB value; len bytes from
	 * the given byte array are written.
	 *
	 * @param pos the position in the BLOB object at which to start writing
	 * @param bytes the array of bytes to be written to this BLOB object
	 * @param offset the offset into the array bytes at which to start
	 *        reading the bytes to be set
	 * @param len the number of bytes to be written to the BLOB value
	 *        from the array of bytes bytes
	 * @return the number of bytes written
	 * @throws SQLException if there is an error accessing the
	 *         BLOB value or if pos is less than 1
	 */
	@Override
	public int setBytes(final long pos, final byte[] bytes, int offset, final int len)
		throws SQLException
	{
		checkBufIsNotNull();
		if (bytes == null) {
			throw new SQLException("Missing bytes[] object", "M1M05");
		}
		if (pos < 1 || pos > buf.length) {
			throw new SQLException("Invalid pos value: " + pos, "M1M05");
		}
		if (len < 0 || pos + len > buf.length) {
			throw new SQLException("Invalid len value: " + len, "M1M05");
		}
		if (offset < 0 || offset > bytes.length) {
			throw new SQLException("Invalid offset value: " + offset, "M1M05");
		}

		try {
			offset--;
			/* transactions? what are you talking about? */
			if (len - (int) pos >= 0)
				System.arraycopy(bytes, offset + (int) pos, buf, (int) pos, len - (int) pos);
		} catch (IndexOutOfBoundsException e) {
			throw new SQLException(e.getMessage(), "M0M10");
		}
		return len;
	}

	/**
	 * Truncates the BLOB value that this Blob object represents to be
	 * len bytes in length.
	 *
	 * @param len the length, in bytes, to which the BLOB value
	 *        should be truncated
	 * @throws SQLException if there is an error accessing the BLOB value
	 */
	@Override
	public void truncate(final long len) throws SQLException {
		checkBufIsNotNull();
		if (len < 0 || len > buf.length) {
			throw new SQLException("Invalid len value: " + len, "M1M05");
		}
		if (buf.length > len) {
			buf = java.util.Arrays.copyOf(buf, (int)len);
		}
	}
}