org.neo4j.jdbc.CallableStatementImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-jdbc Show documentation
Show all versions of neo4j-jdbc Show documentation
Implementation of the Neo4j JDBC driver.
/*
* Copyright (c) 2023-2024 "Neo4j,"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.neo4j.jdbc;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.NClob;
import java.sql.Ref;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
final class CallableStatementImpl extends PreparedStatementImpl implements Neo4jCallableStatement {
private ParameterType parameterType;
static CallableStatement prepareCall(Connection connection, Neo4jTransactionSupplier transactionSupplier,
boolean rewriteBatchedStatements, String sql) throws SQLException {
// We should cache the descriptor if this gets widely used.
var descriptor = parse(sql);
var parameterOrder = new HashMap();
var meta = connection.getMetaData();
// We might not be able to spot all the function calls
if (descriptor.isFunctionCall() == null) {
boolean isFunction;
try (var procedures = meta.getProcedures(null, null, descriptor.fqn())) {
isFunction = !procedures.next();
}
descriptor = new Descriptor(descriptor.fqn, descriptor.returnType, descriptor.yieldedValues,
descriptor.parameterList, isFunction);
}
if (descriptor.isUsingNamedParameters()) {
try (var columns = descriptor.isFunctionCall() ? meta.getFunctionColumns(null, null, descriptor.fqn(), null)
: meta.getProcedureColumns(null, null, descriptor.fqn(), null)) {
while (columns.next()) {
parameterOrder.put(columns.getString("COLUMN_NAME"), columns.getInt("ORDINAL_POSITION"));
}
}
for (String value : descriptor.parameterList.namedParameters().values()) {
if (!parameterOrder.containsKey(value)) {
throw new SQLException(
"Procedure `" + descriptor.fqn() + "` does not have a named parameter `" + value + "`");
}
}
}
// We can always store the descriptor with the statement to check for yielded /
// return values if wished / needed
return new CallableStatementImpl(connection, transactionSupplier, rewriteBatchedStatements,
descriptor.toCypher(parameterOrder));
}
CallableStatementImpl(Connection connection, Neo4jTransactionSupplier transactionSupplier,
boolean rewriteBatchedStatements, String sql) {
super(connection, transactionSupplier, UnaryOperator.identity(), null, rewriteBatchedStatements, sql);
}
@Override
public void clearParameters() throws SQLException {
super.clearParameters();
this.parameterType = null;
}
@Override
public void clearBatch() throws SQLException {
super.clearBatch();
this.parameterType = null;
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType) {
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, int scale) {
}
@SuppressWarnings("resource")
@Override
public boolean wasNull() throws SQLException {
return assertCallAndPositionAtFirstRow().wasNull();
}
@SuppressWarnings("resource")
@Override
public String getString(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getString(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public boolean getBoolean(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getBoolean(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public byte getByte(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getByte(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public short getShort(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getShort(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public int getInt(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getInt(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public long getLong(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getLong(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public float getFloat(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getFloat(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public double getDouble(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getDouble(parameterIndex);
}
@Override
@SuppressWarnings({ "deprecation", "resource" })
public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
return assertCallAndPositionAtFirstRow().getBigDecimal(parameterIndex, scale);
}
@SuppressWarnings("resource")
@Override
public byte[] getBytes(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getBytes(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Date getDate(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getDate(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Time getTime(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getTime(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Timestamp getTimestamp(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getTimestamp(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Object getObject(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getBigDecimal(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Object getObject(int parameterIndex, Map> map) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterIndex, map);
}
@SuppressWarnings("resource")
@Override
public Ref getRef(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getRef(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Blob getBlob(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getBlob(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Clob getClob(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getClob(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Array getArray(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getArray(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getDate(parameterIndex, cal);
}
@SuppressWarnings("resource")
@Override
public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getTime(parameterIndex, cal);
}
@SuppressWarnings("resource")
@Override
public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getTimestamp(parameterIndex, cal);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, String typeName) {
}
@Override
public void registerOutParameter(String parameterName, int sqlType) {
}
@Override
public void registerOutParameter(String parameterName, int sqlType, int scale) {
}
@Override
public void registerOutParameter(String parameterName, int sqlType, String typeName) {
}
@Override
public URL getURL(int parameterIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setURL(String parameterName, URL value) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNull(String parameterName, int sqlType) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setNull(parameterName, sqlType);
}
@Override
public void setBoolean(String parameterName, boolean value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setBoolean(parameterName, value);
}
@Override
public void setByte(String parameterName, byte value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setByte(parameterName, value);
}
@Override
public void setShort(String parameterName, short value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setShort(parameterName, value);
}
@Override
public void setInt(String parameterName, int value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setInt(parameterName, value);
}
@Override
public void setLong(String parameterName, long value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setLong(parameterName, value);
}
@Override
public void setFloat(String parameterName, float value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setFloat(parameterName, value);
}
@Override
public void setDouble(String parameterName, double value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setDouble(parameterName, value);
}
@Override
public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBigDecimal(parameterIndex, x);
}
@Override
public void setBigDecimal(String parameterName, BigDecimal value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setBigDecimal(parameterName, value);
}
@Override
public void setString(String parameterName, String value) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setString(parameterName, value);
}
@Override
public void setBytes(String parameterName, byte[] bytes) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setBytes(parameterName, bytes);
}
@Override
public void setDate(String parameterName, Date date) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setDate(parameterName, date);
}
@Override
public void setTime(String parameterName, Time time) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setTime(parameterName, time);
}
@Override
public void setTimestamp(String parameterName, Timestamp timestamp) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setTimestamp(parameterName, timestamp);
}
@Override
public void setAsciiStream(String parameterName, InputStream inputStream, int length) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setAsciiStream0(parameterName, inputStream, length);
}
@Override
public void setBinaryStream(String parameterName, InputStream inputStream, int length) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setBinaryStream0(parameterName, inputStream, length);
}
@Override
public void setObject(String parameterName, Object object, int targetSqlType, int scale) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setObject(String parameterName, Object object, int targetSqlType) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setObject(String parameterName, Object object) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setObject(parameterName, object);
}
@Override
public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setCharacterStream0(parameterName, reader, length);
}
@Override
public void setDate(String parameterName, Date date, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setDate0(parameterName, date, calendar);
}
@Override
public void setTime(String parameterName, Time time, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setTime0(parameterName, time, calendar);
}
@Override
public void setTimestamp(String parameterName, Timestamp timestamp, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setTimestamp0(parameterName, timestamp, calendar);
}
@Override
public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@SuppressWarnings("resource")
@Override
public String getString(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getString(parameterName);
}
@SuppressWarnings("resource")
@Override
public boolean getBoolean(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getBoolean(parameterName);
}
@SuppressWarnings("resource")
@Override
public byte getByte(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getByte(parameterName);
}
@SuppressWarnings("resource")
@Override
public short getShort(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getShort(parameterName);
}
@SuppressWarnings("resource")
@Override
public int getInt(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getInt(parameterName);
}
@SuppressWarnings("resource")
@Override
public long getLong(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getLong(parameterName);
}
@SuppressWarnings("resource")
@Override
public float getFloat(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getFloat(parameterName);
}
@SuppressWarnings("resource")
@Override
public double getDouble(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getDouble(parameterName);
}
@SuppressWarnings("resource")
@Override
public byte[] getBytes(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getBytes(parameterName);
}
@SuppressWarnings("resource")
@Override
public Date getDate(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getDate(parameterName);
}
@SuppressWarnings("resource")
@Override
public Time getTime(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getTime(parameterName);
}
@SuppressWarnings("resource")
@Override
public Timestamp getTimestamp(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getTimestamp(parameterName);
}
@SuppressWarnings("resource")
@Override
public Object getObject(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterName);
}
@SuppressWarnings("resource")
@Override
public BigDecimal getBigDecimal(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getBigDecimal(parameterName);
}
@SuppressWarnings("resource")
@Override
public Object getObject(String parameterName, Map> map) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterName, map);
}
@SuppressWarnings("resource")
@Override
public Ref getRef(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getRef(parameterName);
}
@SuppressWarnings("resource")
@Override
public Blob getBlob(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getBlob(parameterName);
}
@SuppressWarnings("resource")
@Override
public Clob getClob(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getClob(parameterName);
}
@SuppressWarnings("resource")
@Override
public Array getArray(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getArray(parameterName);
}
@SuppressWarnings("resource")
@Override
public Date getDate(String parameterName, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getDate(parameterName, cal);
}
@SuppressWarnings("resource")
@Override
public Time getTime(String parameterName, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getTime(parameterName, cal);
}
@SuppressWarnings("resource")
@Override
public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
return assertCallAndPositionAtFirstRow().getTimestamp(parameterName, cal);
}
@SuppressWarnings("resource")
@Override
public URL getURL(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getURL(parameterName);
}
@SuppressWarnings("resource")
@Override
public RowId getRowId(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getRowId(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public RowId getRowId(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getRowId(parameterName);
}
@Override
public void setRowId(String parameterName, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNString(String parameterName, String value) throws SQLException {
setString(parameterName, value);
}
@Override
public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(String parameterName, NClob value) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setClob(String parameterName, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(String parameterName, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@SuppressWarnings("resource")
@Override
public NClob getNClob(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getNClob(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public NClob getNClob(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getNClob(parameterName);
}
@Override
public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@SuppressWarnings("resource")
@Override
public SQLXML getSQLXML(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getSQLXML(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public SQLXML getSQLXML(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getSQLXML(parameterName);
}
@SuppressWarnings("resource")
@Override
public String getNString(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getNString(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public String getNString(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getNString(parameterName);
}
@SuppressWarnings("resource")
@Override
public Reader getNCharacterStream(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getNCharacterStream(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Reader getNCharacterStream(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getNCharacterStream(parameterName);
}
@SuppressWarnings("resource")
@Override
public Reader getCharacterStream(int parameterIndex) throws SQLException {
return assertCallAndPositionAtFirstRow().getCharacterStream(parameterIndex);
}
@SuppressWarnings("resource")
@Override
public Reader getCharacterStream(String parameterName) throws SQLException {
return assertCallAndPositionAtFirstRow().getCharacterStream(parameterName);
}
@Override
public void setBlob(String parameterName, Blob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setClob(String parameterName, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException {
setAsciiStream(parameterName, x, getLengthAsInt(length));
}
@Override
public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException {
setBinaryStream(parameterName, x, getLengthAsInt(length));
}
@Override
public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException {
setCharacterStream(parameterName, reader, getLengthAsInt(length));
}
@Override
public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setAsciiStream(parameterIndex, x);
}
@Override
public void setAsciiStream(String parameterName, InputStream x) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setAsciiStream(parameterName, x);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBinaryStream(parameterIndex, x);
}
@Override
public void setBinaryStream(String parameterName, InputStream x) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setBinaryStream(parameterName, x);
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setCharacterStream(parameterIndex, reader);
}
@Override
public void setCharacterStream(String parameterName, Reader reader) throws SQLException {
assertParameterType(ParameterType.NAMED);
super.setCharacterStream(parameterName, reader);
}
@Override
public void setNCharacterStream(String parameterName, Reader reader) throws SQLException {
setCharacterStream(parameterName, reader);
}
@Override
public void setClob(String parameterName, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setBlob(String parameterName, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
public void setNClob(String parameterName, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@SuppressWarnings("resource")
@Override
public T getObject(int parameterIndex, Class type) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterIndex, type);
}
@SuppressWarnings("resource")
@Override
public T getObject(String parameterName, Class type) throws SQLException {
return assertCallAndPositionAtFirstRow().getObject(parameterName, type);
}
@Override
public void setNull(int parameterIndex, int sqlType) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setNull(parameterIndex, sqlType);
}
@Override
public void setBoolean(int parameterIndex, boolean value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBoolean(parameterIndex, value);
}
@Override
public void setByte(int parameterIndex, byte value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setByte(parameterIndex, value);
}
@Override
public void setShort(int parameterIndex, short value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setShort(parameterIndex, value);
}
@Override
public void setInt(int parameterIndex, int value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setInt(parameterIndex, value);
}
@Override
public void setLong(int parameterIndex, long value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setLong(parameterIndex, value);
}
@Override
public void setFloat(int parameterIndex, float value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setFloat(parameterIndex, value);
}
@Override
public void setDouble(int parameterIndex, double value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setDouble(parameterIndex, value);
}
@Override
public void setString(int parameterIndex, String value) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setString(parameterIndex, value);
}
@Override
public void setBytes(int parameterIndex, byte[] bytes) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBytes(parameterIndex, bytes);
}
@Override
public void setDate(int parameterIndex, Date date) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setDate(parameterIndex, date);
}
@Override
public void setTime(int parameterIndex, Time time) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setTime(parameterIndex, time);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp timestamp) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setTimestamp(parameterIndex, timestamp);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream inputStream, int length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setAsciiStream(parameterIndex, inputStream, length);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream inputStream, int length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBinaryStream(parameterIndex, inputStream, length);
}
@Override
public void setObject(int parameterIndex, Object object) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setObject(parameterIndex, object);
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setCharacterStream(parameterIndex, reader, length);
}
@Override
public void setDate(int parameterIndex, Date date, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setDate(parameterIndex, date, calendar);
}
@Override
public void setTime(int parameterIndex, Time time, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setTime(parameterIndex, time, calendar);
}
@Override
public void setTimestamp(int parameterIndex, Timestamp timestamp, Calendar calendar) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setTimestamp(parameterIndex, timestamp, calendar);
}
@Override
public void setAsciiStream(int parameterIndex, InputStream inputStream, long length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setAsciiStream(parameterIndex, inputStream, length);
}
@Override
public void setBinaryStream(int parameterIndex, InputStream inputStream, long length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setBinaryStream(parameterIndex, inputStream, length);
}
@Override
public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
assertParameterType(ParameterType.ORDINAL);
super.setCharacterStream(parameterIndex, reader, length);
}
private void assertParameterType(ParameterType parameterType) throws SQLException {
if (this.parameterType == null) {
this.parameterType = parameterType;
}
else {
if (this.parameterType != parameterType) {
throw new SQLException(String.format("%s parameter can not be mixed with %s parameter(s)",
parameterType, this.parameterType));
}
}
}
@Override
public int executeUpdate() throws SQLException {
throw newIllegalMethodInvocation();
}
@Override
public int[] executeBatch() throws SQLException {
throw newIllegalMethodInvocation();
}
private enum ParameterType {
ORDINAL, NAMED
}
static ParameterListDescriptor parseParameterList(String parameterList) {
var ordinalParameters = new HashMap();
var namedParameters = new HashMap();
var constants = new HashMap();
if (parameterList != null) {
int cnt = 0;
for (String s : PARAMETER_LIST_SPLITTER.split(parameterList.trim())) {
++cnt;
var possibleParameter = s.trim();
if (possibleParameter.isEmpty()) {
continue;
}
if ("?".equals(possibleParameter)) {
ordinalParameters.put(cnt, -1);
}
else if (IS_NUMBER.test(possibleParameter)) {
ordinalParameters.put(cnt, Integer.parseInt(possibleParameter.replace("$", "")));
}
else if (possibleParameter.startsWith("$") || possibleParameter.startsWith(":")) {
var v = possibleParameter.substring(1);
var matcher = VALID_IDENTIFIER_PATTERN.matcher(v);
if (matcher.matches() || "0".equals(v)) {
namedParameters.put(cnt, v);
}
}
else {
constants.put(cnt, possibleParameter);
}
}
}
if (!(ordinalParameters.isEmpty() || namedParameters.isEmpty())) {
throw new IllegalArgumentException("Index- and named ordinalParameters cannot be mixed");
}
if (!ordinalParameters.isEmpty()) {
var used = ordinalParameters.values().stream().filter(i -> i > 0).collect(Collectors.toSet());
int max = 1;
for (Map.Entry entry : ordinalParameters.entrySet()) {
Integer key = entry.getKey();
Integer v = entry.getValue();
if (v < 0) {
while (used.contains(max)) {
++max;
}
v = max++;
}
ordinalParameters.put(key, v);
}
}
return new ParameterListDescriptor(ordinalParameters, namedParameters, constants);
}
/**
* Parses a statement into a {@link Descriptor descriptor}. Supported formats are
*
* - The JDBC syntax
{call fqn(<?, ...>)}
or
* {? = call fqn(<?, ...>)}
* - The Cypher simplified variant
{CALL fqn(<?, ...>)
* - Cypher function calls
RETURN fqn(<?, ...>)
*
* Other formats are not supported
* @param statement the statement to be parsed
* @return a descriptor to be used within this {@link CallableStatementImpl callable
* statement implementation}
* @throws IllegalArgumentException if the statement cannot be parsed
*/
static Descriptor parse(String statement) {
if (Objects.requireNonNull(statement, "Callable statements cannot be null").isBlank()) {
throw new IllegalArgumentException("Callable statements cannot be blank");
}
statement = statement.trim();
var matcher = JDBC_CALL.matcher(statement);
try {
if (matcher.matches()) {
var returnParameter = Optional.ofNullable(matcher.group("returnParameter"))
.map(String::trim)
.orElse("");
var returnType = ReturnType.NONE;
List returnParameterName = new ArrayList<>();
if (!returnParameter.isBlank()) {
Optional.ofNullable(matcher.group("returnParameterName"))
.map(String::trim)
.map(s -> s.substring(1))
.ifPresent(returnParameterName::add);
returnType = returnParameterName.isEmpty() ? ReturnType.ORDINAL : ReturnType.NAMED;
}
var parameterList = parseParameterList(matcher.group("parameterList"));
return new Descriptor(matcher.group("fqn"), returnType, returnParameterName, parameterList, null);
}
matcher = CYPHER_RETURN_CALL.matcher(statement);
if (matcher.matches()) {
var parameterList = parseParameterList(matcher.group("parameterList"));
return new Descriptor(matcher.group("fqn"), ReturnType.ORDINAL, null, parameterList, true);
}
matcher = CYPHER_YIELD_CALL.matcher(statement);
if (matcher.matches()) {
var yieldedStuff = Optional.ofNullable(matcher.group("yieldedValues")).map(String::trim).orElse("");
List returnParameterName = new ArrayList<>();
for (String s : PARAMETER_LIST_SPLITTER.split(yieldedStuff)) {
if (!s.isBlank()) {
returnParameterName.add(s.trim());
}
}
var returnType = returnParameterName.isEmpty() ? ReturnType.ORDINAL : ReturnType.NAMED;
var parameterList = parseParameterList(matcher.group("parameterList"));
return new Descriptor(matcher.group("fqn"), returnType, returnParameterName, parameterList, false);
}
matcher = CYPHER_SIDE_EFFECT_CALL.matcher(statement);
if (matcher.matches()) {
var parameterList = parseParameterList(matcher.group("parameterList"));
return new Descriptor(matcher.group("fqn"), ReturnType.NONE, null, parameterList, false);
}
}
catch (IllegalArgumentException ex) {
throw new IllegalArgumentException(ex.getMessage() + ": `" + statement + "`");
}
throw new IllegalArgumentException("Cannot create a callable statement from `" + statement + "`");
}
private static final Pattern PARAMETER_LIST_SPLITTER = Pattern
.compile(",(?=(?:[^\"']*[\"'][^\"']*[\"'])*[^\"']*\\Z)");
private static final String VALID_IDENTIFIER = "\\p{javaJavaIdentifierStart}[.\\p{javaJavaIdentifierPart}]*";
private static final Predicate IS_NUMBER = Pattern.compile("\\$?[1-9]+").asMatchPredicate();
private static final Pattern VALID_IDENTIFIER_PATTERN = Pattern.compile(VALID_IDENTIFIER);
private static final String WS = "\\s*+";
private static final String RETURN_PARAMETER = "(?" + WS + "(?:\\?|(?[$:]"
+ VALID_IDENTIFIER_PATTERN.pattern() + "))" + WS + "=" + WS + ")?";
private static final String PARAMETER_LIST = "(?:\\((?.*)\\))?";
public static final String FQN_AND_PARAMETER_LIST = "(?" + VALID_IDENTIFIER + ")" + WS + PARAMETER_LIST;
private static final Pattern JDBC_CALL = Pattern
.compile("(?i)" + WS + "\\{" + RETURN_PARAMETER + "call " + WS + FQN_AND_PARAMETER_LIST + WS + "}");
private static final Pattern CYPHER_RETURN_CALL = Pattern
.compile("(?i)" + WS + "RETURN " + WS + FQN_AND_PARAMETER_LIST);
private static final Pattern CYPHER_YIELD_CALL = Pattern
.compile("(?i)" + WS + "CALL " + WS + FQN_AND_PARAMETER_LIST + WS + "YIELD " + WS + "(\\*|(?"
+ VALID_IDENTIFIER_PATTERN.pattern() + "(?:," + WS + VALID_IDENTIFIER_PATTERN.pattern() + ")*))");
private static final Pattern CYPHER_SIDE_EFFECT_CALL = Pattern
.compile("(?i)" + WS + "CALL " + WS + FQN_AND_PARAMETER_LIST);
enum ReturnType {
NONE, ORDINAL, NAMED, YIELD
}
/**
* Tuple needed for describing the parameter list.
*
* @param ordinalParameters all ordinal parameters
* @param namedParameters all named parameters
* @param constants all constant values
*/
record ParameterListDescriptor(Map ordinalParameters, Map namedParameters,
Map constants) {
String toCypher(Map parameterOrder) {
if (this.ordinalParameters.isEmpty() && this.namedParameters().isEmpty() && this.constants.isEmpty()) {
return "";
}
var all = new TreeMap();
this.ordinalParameters.forEach((k, v) -> all.put(k, "$" + v));
this.namedParameters.forEach((k, v) -> {
var idx = parameterOrder.getOrDefault(v, k);
all.put(idx, "$" + v);
});
all.putAll(this.constants);
return all.values().stream().collect(Collectors.joining(", ", "(", ")"));
}
}
/**
* A descriptor of a callable statement containing the fully qualified name of the
* function or method to be called as well as the list of ordinalParameters and their
* type (in, out, inout).
*
* @param fqn the fully qualified name of the function or procedure to call
* @param returnType return type for the statement
* @param yieldedValues optional name for the out (return parameter)
* @param parameterList parameter list
* @param isFunctionCall {@literal true} when the statement is represented as `RETURN
* xyz()` function call
*/
record Descriptor(String fqn, ReturnType returnType, List yieldedValues,
ParameterListDescriptor parameterList, Boolean isFunctionCall) {
Descriptor {
if (yieldedValues != null && !yieldedValues.isEmpty() && returnType != ReturnType.NAMED) {
throw new IllegalArgumentException(
"A name for the return parameter is only supported with named returns");
}
if (returnType == ReturnType.NAMED && !parameterList.ordinalParameters.isEmpty()
|| !(parameterList.ordinalParameters.isEmpty() || parameterList.namedParameters().isEmpty())) {
throw new IllegalArgumentException("Index- and named ordinalParameters cannot be mixed");
}
if (!(parameterList.namedParameters.isEmpty() || parameterList.constants.isEmpty())) {
throw new IllegalArgumentException("Named parameters cannot be used together with constant arguments");
}
}
boolean isUsingNamedParameters() {
return !this.parameterList.namedParameters.isEmpty();
}
String toCypher(Map parameterOrder) {
var sb = new StringBuilder();
var isSafeFunctionCall = Boolean.TRUE.equals(this.isFunctionCall);
if (isSafeFunctionCall) {
sb.append("RETURN");
}
else {
sb.append("CALL");
}
sb.append(" ").append(this.fqn).append(this.parameterList.toCypher(parameterOrder));
if (this.returnType == ReturnType.ORDINAL && !isSafeFunctionCall) {
sb.append(" YIELD *");
}
else if (this.returnType == ReturnType.NAMED) {
sb.append(" YIELD ").append(String.join(", ", this.yieldedValues));
}
return sb.toString();
}
}
}