postgresql-jdbc.git / summary / log / commit / refs

commit    8bf62dffda73887f311f9f0e776a1ccb5f74459d
Author:   Kris Jurka <books@ejurka.com>
Date:     Tue Feb 01 07:27:55 2005 +0000

    Implement the Describe Statement protocol message.  Reallow untyped
    nulls and get the backend to resolve the type for us (if necessary).
    Also use this message and a new non-executing query execution to
    implement PreparedStatement.getMetaData() for an unexecuted
    statement and PreparedStatement.getParameterMetaData().


build.xml | 3 +- org/postgresql/core/ParameterList.java | 9 +- org/postgresql/core/QueryExecutor.java | 8 +- org/postgresql/core/v2/FastpathParameterList.java | 6 +- org/postgresql/core/v2/QueryExecutorImpl.java | 8 +- org/postgresql/core/v2/SimpleParameterList.java | 6 +- org/postgresql/core/v3/CompositeParameterList.java | 11 +- org/postgresql/core/v3/QueryExecutorImpl.java | 127 ++++++++++++++++++--- org/postgresql/core/v3/SimpleParameterList.java | 30 ++++- org/postgresql/core/v3/SimpleQuery.java | 7 +- org/postgresql/jdbc2/AbstractJdbc2Statement.java | 30 +++-- org/postgresql/jdbc3/AbstractJdbc3Statement.java | 14 ++- org/postgresql/jdbc3/PSQLParameterMetaData.java | 85 ++++++++++++++ .../test/jdbc2/PreparedStatementTest.java | 38 ++---- .../test/jdbc2/ResultSetMetaDataTest.java | 117 ++++++++++--------- org/postgresql/test/jdbc3/Jdbc3TestSuite.java | 5 +- .../test/jdbc3/ParameterMetaDataTest.java | 85 ++++++++++++++ 17 files changed, 464 insertions(+), 125 deletions(-) diff --git a/build.xml b/build.xml index 0a06818a..654d961d 100644 --- a/build.xml +++ b/build.xml @@ -8,7 +8,7 @@ This file now requires Ant 1.4.1. 2002-04-18 - $PostgreSQL: pgjdbc/build.xml,v 1.61 2005/01/17 09:52:13 jurka Exp $ + $PostgreSQL: pgjdbc/build.xml,v 1.62 2005/01/21 11:53:26 jurka Exp $ --> @@ -137,6 +137,7 @@ <!-- jdbc3 subpackage --> <include name="${package}/jdbc3/Abstract*.java" if="jdbc3any" /> <include name="${package}/jdbc3/PSQLSavepoint.java" if="jdcb3any" /> + <include name="${package}/jdbc3/PSQLParameterMetaData.java" if="jdbc3any" /> <include name="${package}/jdbc3/Jdbc3Array.java" if="jdbc3"/> diff --git a/org/postgresql/core/ParameterList.java b/org/postgresql/core/ParameterList.java index 9cfd2343..285b5972 100644 --- a/org/postgresql/core/ParameterList.java +++ b/org/postgresql/core/ParameterList.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/ParameterList.java,v 1.4 2004/11/09 08:44:40 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/ParameterList.java,v 1.5 2005/01/11 08:25:43 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,13 @@ public interface ParameterList { */ int getParameterCount(); + + /** + * Return the oids of the parameters in this list. May be null for + * a ParameterList that does not support typing of parameters. + */ + public int[] getTypeOIDs(); + /** * Binds an integer value to a parameter. The type of the parameter is * implicitly 'int4'. diff --git a/org/postgresql/core/QueryExecutor.java b/org/postgresql/core/QueryExecutor.java index 6974cd84..b6fc3a2b 100644 --- a/org/postgresql/core/QueryExecutor.java +++ b/org/postgresql/core/QueryExecutor.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/QueryExecutor.java,v 1.38 2004/11/09 08:44:49 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/QueryExecutor.java,v 1.39 2005/01/11 08:25:43 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -77,6 +77,12 @@ public interface QueryExecutor { */ static int QUERY_SUPPRESS_BEGIN = 16; + /** + * Flag for query execution when we don't really want to execute, we just + * want to get the parameter metadata for the statement. + */ + static int QUERY_DESCRIBE_ONLY = 32; + /** * Execute a Query, passing results to a provided ResultHandler. * diff --git a/org/postgresql/core/v2/FastpathParameterList.java b/org/postgresql/core/v2/FastpathParameterList.java index a7b2836e..fc63a7df 100644 --- a/org/postgresql/core/v2/FastpathParameterList.java +++ b/org/postgresql/core/v2/FastpathParameterList.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v2/FastpathParameterList.java,v 1.5 2004/11/09 08:45:31 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v2/FastpathParameterList.java,v 1.6 2005/01/11 08:25:43 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,10 @@ class FastpathParameterList implements ParameterList { return paramValues.length; } + public int[] getTypeOIDs() { + return null; + } + public void setIntParameter(int index, int value) throws SQLException { if (index < 1 || index > paramValues.length) throw new PSQLException(GT.tr("The column index is out of range: {0}, number of columns: {1}.", new Object[]{new Integer(index), new Integer(paramValues.length)}), PSQLState.INVALID_PARAMETER_VALUE ); diff --git a/org/postgresql/core/v2/QueryExecutorImpl.java b/org/postgresql/core/v2/QueryExecutorImpl.java index 23e798ad..61e79459 100644 --- a/org/postgresql/core/v2/QueryExecutorImpl.java +++ b/org/postgresql/core/v2/QueryExecutorImpl.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v2/QueryExecutorImpl.java,v 1.8 2005/01/11 08:25:43 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v2/QueryExecutorImpl.java,v 1.9 2005/01/14 01:20:15 oliver Exp $ * *------------------------------------------------------------------------- */ @@ -275,6 +275,12 @@ public class QueryExecutorImpl implements QueryExecutor { ResultHandler handler, int maxRows, int flags) throws SQLException { + + // The V2 protocol has no support for retrieving metadata + // without executing the whole query. + if ((flags & QueryExecutor.QUERY_DESCRIBE_ONLY) != 0) + return; + if (parameters == null) parameters = (SimpleParameterList)query.createParameterList(); diff --git a/org/postgresql/core/v2/SimpleParameterList.java b/org/postgresql/core/v2/SimpleParameterList.java index 7f1076b4..5a0710fe 100644 --- a/org/postgresql/core/v2/SimpleParameterList.java +++ b/org/postgresql/core/v2/SimpleParameterList.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v2/SimpleParameterList.java,v 1.4 2004/11/09 08:45:31 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v2/SimpleParameterList.java,v 1.5 2005/01/11 08:25:43 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -36,6 +36,10 @@ class SimpleParameterList implements ParameterList { return paramValues.length; } + public int[] getTypeOIDs() { + return null; + } + public void setIntParameter(int index, int value) throws SQLException { setLiteralParameter(index, "" + value, Oid.INT4); } diff --git a/org/postgresql/core/v3/CompositeParameterList.java b/org/postgresql/core/v3/CompositeParameterList.java index b043aba0..0336b958 100644 --- a/org/postgresql/core/v3/CompositeParameterList.java +++ b/org/postgresql/core/v3/CompositeParameterList.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v3/CompositeParameterList.java,v 1.5 2004/11/09 08:45:54 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v3/CompositeParameterList.java,v 1.6 2005/01/11 08:25:44 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -47,6 +47,15 @@ class CompositeParameterList implements V3ParameterList { return total; } + public int[] getTypeOIDs() { + int oids[] = new int[total]; + for (int i=0; i<offsets.length; i++) { + int subOids[] = subparams[i].getTypeOIDs(); + System.arraycopy(subOids, 0, oids, offsets[i], subOids.length); + } + return oids; + } + public void setIntParameter(int index, int value) throws SQLException { int sub = findSubParam(index); subparams[sub].setIntParameter(index - offsets[sub], value); diff --git a/org/postgresql/core/v3/QueryExecutorImpl.java b/org/postgresql/core/v3/QueryExecutorImpl.java index 24455aec..945a9b80 100644 --- a/org/postgresql/core/v3/QueryExecutorImpl.java +++ b/org/postgresql/core/v3/QueryExecutorImpl.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java,v 1.19 2005/01/14 01:20:16 oliver Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v3/QueryExecutorImpl.java,v 1.20 2005/01/27 22:50:13 oliver Exp $ * *------------------------------------------------------------------------- */ @@ -159,8 +159,11 @@ public class QueryExecutorImpl implements QueryExecutor { if (parameters == null) parameters = SimpleQuery.NO_PARAMETERS; + boolean describeOnly = (QUERY_DESCRIBE_ONLY & flags) != 0; + // Check parameters are all set.. - ((V3ParameterList)parameters).checkAllParametersSet(); + if (!describeOnly) + ((V3ParameterList)parameters).checkAllParametersSet(); try { @@ -286,11 +289,14 @@ public class QueryExecutorImpl implements QueryExecutor { ", maxRows=" + maxRows + ", fetchSize=" + fetchSize + ", flags=" + flags); } + boolean describeOnly = (QUERY_DESCRIBE_ONLY & flags) != 0; // Check parameters and resolve OIDs. - for (int i = 0; i < parameterLists.length; ++i) - { - if (parameterLists[i] != null) - ((V3ParameterList)parameterLists[i]).checkAllParametersSet(); + if (!describeOnly) { + for (int i = 0; i < parameterLists.length; ++i) + { + if (parameterLists[i] != null) + ((V3ParameterList)parameterLists[i]).checkAllParametersSet(); + } } try @@ -644,7 +650,8 @@ public class QueryExecutorImpl implements QueryExecutor { // NB: Must clone the OID array, as it's a direct reference to // the SimpleParameterList's internal array that might be modified // under us. - query.setStatementName(statementName, (int[])typeOIDs.clone()); + query.setStatementName(statementName); + query.setStatementTypes((int[])typeOIDs.clone()); } byte[] encodedStatementName = query.getEncodedStatementName(); @@ -826,7 +833,7 @@ public class QueryExecutorImpl implements QueryExecutor { } } - private void sendDescribe(Portal portal) throws IOException { + private void sendDescribePortal(Portal portal) throws IOException { // // Send Describe. // @@ -849,6 +856,29 @@ public class QueryExecutorImpl implements QueryExecutor { pgStream.SendChar(0); // end of portal name } + private void sendDescribeStatement(SimpleQuery query, SimpleParameterList params, boolean describeOnly) throws IOException { + // Send Statement Describe + + if (Driver.logDebug) + { + Driver.debug(" FE=> Describe(statement=" + query.getStatementName()+")"); + } + + byte[] encodedStatementName = query.getEncodedStatementName(); + + // Total size = 4 (size field) + 1 (describe type, 'S') + N + 1 (portal name) + int encodedSize = 4 + 1 + (encodedStatementName == null ? 0 : encodedStatementName.length) + 1; + + pgStream.SendChar('D'); // Describe + pgStream.SendInteger4(encodedSize); // Message size + pgStream.SendChar('S'); // Describe (Statement); + if (encodedStatementName != null) + pgStream.Send(encodedStatementName); // Statement name + pgStream.SendChar(0); // end message + + pendingDescribeStatementQueue.add(new Object[]{query, params, new Boolean(describeOnly)}); + } + private void sendExecute(Query query, Portal portal, int limit) throws IOException { // // Send Execute. @@ -934,8 +964,10 @@ public class QueryExecutorImpl implements QueryExecutor { boolean noResults = (flags & QueryExecutor.QUERY_NO_RESULTS) != 0; boolean noMeta = (flags & QueryExecutor.QUERY_NO_METADATA) != 0; - boolean usePortal = (flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta && fetchSize > 0; + boolean describeOnly = (flags & QueryExecutor.QUERY_DESCRIBE_ONLY) != 0; + boolean usePortal = (flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly; boolean oneShot = (flags & QueryExecutor.QUERY_ONESHOT) != 0 && !usePortal; + boolean describeStatement = describeOnly || (params.hasUnresolvedTypes() && !oneShot); // Work out how many rows to fetch in this pass. @@ -959,6 +991,12 @@ public class QueryExecutorImpl implements QueryExecutor { sendParse(query, params, oneShot); + if (describeStatement) { + sendDescribeStatement(query, params, describeOnly); + if (describeOnly) + return; + } + // Construct a new portal if needed. Portal portal = null; if (usePortal) @@ -968,8 +1006,12 @@ public class QueryExecutorImpl implements QueryExecutor { } sendBind(query, params, portal); - if (!noMeta) - sendDescribe(portal); + + // A statement describe will also output a RowDescription, + // so don't reissue it here if we've already done so. + // + if (!noMeta && !describeStatement) + sendDescribePortal(portal); sendExecute(query, portal, rows); } @@ -1063,7 +1105,15 @@ public class QueryExecutorImpl implements QueryExecutor { int c; boolean endQuery = false; + // At the end of a command execution we have the CommandComplete + // message to tell us we're done, but with a describeOnly command + // we have no real flag to let us know we're done. We've got to + // look for the next RowDescription or NoData message and return + // from there. + boolean doneAfterRowDescNoData = false; + int parseIndex = 0; + int describeIndex = 0; int bindIndex = 0; int executeIndex = 0; @@ -1086,6 +1136,32 @@ public class QueryExecutorImpl implements QueryExecutor { registerParsedQuery(parsedQuery); break; + case 't': // ParameterDescription + pgStream.ReceiveIntegerR(4); // len, discarded + + if (Driver.logDebug) + Driver.debug(" <=BE ParameterDescription"); + + { + Object describeData[] = (Object[])pendingDescribeStatementQueue.get(describeIndex); + SimpleQuery query = (SimpleQuery)describeData[0]; + SimpleParameterList params = (SimpleParameterList)describeData[1]; + boolean describeOnly = ((Boolean)describeData[2]).booleanValue(); + + int numParams = pgStream.ReceiveIntegerR(2); + for (int i=1; i<=numParams; i++) { + int typeOid = pgStream.ReceiveIntegerR(4); + params.setResolvedType(i, typeOid); + } + query.setStatementTypes((int[])params.getTypeOIDs().clone()); + + if (describeOnly) + doneAfterRowDescNoData = true; + else + describeIndex++; + } + break; + case '2': // Bind Complete (response to Bind) pgStream.ReceiveIntegerR(4); // len, discarded @@ -1106,6 +1182,16 @@ public class QueryExecutorImpl implements QueryExecutor { pgStream.ReceiveIntegerR(4); // len, discarded if (Driver.logDebug) Driver.debug(" <=BE NoData"); + + if (doneAfterRowDescNoData) { + Object describeData[] = (Object[])pendingDescribeStatementQueue.get(describeIndex++); + Query currentQuery = (Query)describeData[0]; + + if (fields != null || tuples != null) + { // There was a resultset. + handler.handleResultRows(currentQuery, fields, tuples, null); + } + } break; case 's': // Portal Suspended (end of Execute) @@ -1131,6 +1217,8 @@ public class QueryExecutorImpl implements QueryExecutor { // Handle status. String status = receiveCommandStatus(); + doneAfterRowDescNoData = false; + { Object[] executeData = (Object[])pendingExecuteQueue.get(executeIndex++); Query currentQuery = (Query)executeData[0]; @@ -1220,6 +1308,15 @@ public class QueryExecutorImpl implements QueryExecutor { case 'T': // Row Description (response to Describe) fields = receiveFields(); tuples = new Vector(); + if (doneAfterRowDescNoData) { + Object describeData[] = (Object[])pendingDescribeStatementQueue.get(describeIndex++); + Query currentQuery = (Query)describeData[0]; + + if (fields != null || tuples != null) + { // There was a resultset. + handler.handleResultRows(currentQuery, fields, tuples, null); + } + } break; case 'Z': // Ready For Query (eventual response to Sync) @@ -1233,9 +1330,10 @@ public class QueryExecutorImpl implements QueryExecutor { failedQuery.unprepare(); } - pendingParseQueue.clear(); // No more ParseComplete messages expected. - pendingBindQueue.clear(); // No more BindComplete messages expected. - pendingExecuteQueue.clear(); // No more query executions expected. + pendingParseQueue.clear(); // No more ParseComplete messages expected. + pendingDescribeStatementQueue.clear(); // No more ParameterDescription messages expected. + pendingBindQueue.clear(); // No more BindComplete messages expected. + pendingExecuteQueue.clear(); // No more query executions expected. break; case 'G': // CopyInResponse @@ -1448,6 +1546,7 @@ public class QueryExecutorImpl implements QueryExecutor { private final ArrayList pendingParseQueue = new ArrayList(); // list of SimpleQuery instances private final ArrayList pendingBindQueue = new ArrayList(); // list of Portal instances private final ArrayList pendingExecuteQueue = new ArrayList(); // list of {SimpleQuery,Portal} object arrays + private final ArrayList pendingDescribeStatementQueue = new ArrayList(); // list of {SimpleQuery, SimpleParameterList, Boolean} object arrays private long nextUniqueID = 1; private final ProtocolConnectionImpl protoConnection; diff --git a/org/postgresql/core/v3/SimpleParameterList.java b/org/postgresql/core/v3/SimpleParameterList.java index a8abbc79..ac2399bf 100644 --- a/org/postgresql/core/v3/SimpleParameterList.java +++ b/org/postgresql/core/v3/SimpleParameterList.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v3/SimpleParameterList.java,v 1.6 2005/01/11 08:25:44 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v3/SimpleParameterList.java,v 1.7 2005/01/27 22:50:14 oliver Exp $ * *------------------------------------------------------------------------- */ @@ -41,6 +41,13 @@ class SimpleParameterList implements V3ParameterList { encoded[index] = null; paramValues[index] = value; + + // If we are setting something to null, don't overwrite our existing type + // for it. We don't need the correct type info to send NULL and we + // don't want to overwrite and require a reparse. + if (oid == Oid.INVALID && paramTypes[index] != Oid.INVALID) + return; + paramTypes[index] = oid; } @@ -111,6 +118,10 @@ class SimpleParameterList implements V3ParameterList { pgStream.SendStream(wrapper.getStream(), wrapper.getLength()); } + public int[] getTypeOIDs() { + return paramTypes; + } + // // Package-private V3 accessors // @@ -119,8 +130,21 @@ class SimpleParameterList implements V3ParameterList { return paramTypes[index -1]; } - int[] getTypeOIDs() { - return paramTypes; + boolean hasUnresolvedTypes() { + for (int i=0; i<paramTypes.length; i++) { + if (paramTypes[i] == Oid.INVALID) + return true; + } + return false; + } + + void setResolvedType(int index, int oid) { + // only allow overwriting an unknown value + if (paramTypes[index-1] == Oid.INVALID) { + paramTypes[index-1] = oid; + } else if (paramTypes[index-1] != oid) { + throw new IllegalArgumentException("Can't change resolved type for param: " + index + " from " + paramTypes[index] + " to " + oid); + } } boolean isNull(int index) { diff --git a/org/postgresql/core/v3/SimpleQuery.java b/org/postgresql/core/v3/SimpleQuery.java index c52a1e28..9ebdc168 100644 --- a/org/postgresql/core/v3/SimpleQuery.java +++ b/org/postgresql/core/v3/SimpleQuery.java @@ -4,7 +4,7 @@ * Copyright (c) 2004, Open Cloud Limited. * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/core/v3/SimpleQuery.java,v 1.6 2005/01/27 20:59:02 oliver Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/core/v3/SimpleQuery.java,v 1.7 2005/01/27 22:50:14 oliver Exp $ * *------------------------------------------------------------------------- */ @@ -70,9 +70,12 @@ class SimpleQuery implements V3Query { return fragments; } - void setStatementName(String statementName, int[] paramTypes) { + void setStatementName(String statementName) { this.statementName = statementName; this.encodedStatementName = Utils.encodeUTF8(statementName); + } + + void setStatementTypes(int[] paramTypes) { this.preparedTypes = paramTypes; } diff --git a/org/postgresql/jdbc2/AbstractJdbc2Statement.java b/org/postgresql/jdbc2/AbstractJdbc2Statement.java index 49fe815c..844f0490 100644 --- a/org/postgresql/jdbc2/AbstractJdbc2Statement.java +++ b/org/postgresql/jdbc2/AbstractJdbc2Statement.java @@ -3,7 +3,7 @@ * Copyright (c) 2004-2005, PostgreSQL Global Development Group * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v 1.66 2005/01/25 06:21:21 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v 1.67 2005/01/27 11:30:41 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -996,8 +996,8 @@ public abstract class AbstractJdbc2Statement implements BaseStatement } break; case Types.OTHER: - // We cannot determine an appropriate OID in this case. - throw new PSQLException(GT.tr("setNull(i,Types.OTHER) is not supported. Instead, use setObject(i,obj,Types.OTHER) where ((PGobject)obj).getValue()==null."), PSQLState.INVALID_PARAMETER_TYPE); + oid = Oid.INVALID; + break; default: // Bad Types value. throw new PSQLException(GT.tr("Unknown Types value."), PSQLState.INVALID_PARAMETER_TYPE); @@ -1634,12 +1634,8 @@ public abstract class AbstractJdbc2Statement implements BaseStatement { checkClosed(); if (x == null) - { - // We cannot determine an appropriate OID in this case. - throw new PSQLException(GT.tr("setObject(i,null) is not supported. Instead, use setNull(i,type) or setObject(i,null,type)"), PSQLState.INVALID_PARAMETER_TYPE); - } - - if (x instanceof String) + setNull(parameterIndex, Types.OTHER); + else if (x instanceof String) setString(parameterIndex, (String)x); else if (x instanceof BigDecimal) setBigDecimal(parameterIndex, (BigDecimal)x); @@ -2532,10 +2528,24 @@ public abstract class AbstractJdbc2Statement implements BaseStatement { checkClosed(); ResultSet rs = getResultSet(); + + if (rs == null) { + // OK, we haven't executed it yet, we've got to go to the backend + // for more info. We send the full query, but just don't + // execute it. + + int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; + StatementResultHandler handler = new StatementResultHandler(); + connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, flags); + ResultWrapper wrapper = handler.getResults(); + if (wrapper != null) { + rs = wrapper.getResultSet(); + } + } + if (rs != null) return rs.getMetaData(); - // Does anyone really know what this method does? return null; } diff --git a/org/postgresql/jdbc3/AbstractJdbc3Statement.java b/org/postgresql/jdbc3/AbstractJdbc3Statement.java index de730c81..ecbad08e 100644 --- a/org/postgresql/jdbc3/AbstractJdbc3Statement.java +++ b/org/postgresql/jdbc3/AbstractJdbc3Statement.java @@ -3,7 +3,7 @@ * Copyright (c) 2004-2005, PostgreSQL Global Development Group * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/jdbc3/AbstractJdbc3Statement.java,v 1.14 2004/12/11 04:13:39 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/jdbc3/AbstractJdbc3Statement.java,v 1.15 2005/01/11 08:25:46 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -16,6 +16,7 @@ import java.util.Vector; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; +import org.postgresql.core.QueryExecutor; import org.postgresql.core.Field; import org.postgresql.core.Oid; import org.postgresql.util.GT; @@ -363,7 +364,16 @@ public abstract class AbstractJdbc3Statement extends org.postgresql.jdbc2.Abstra */ public ParameterMetaData getParameterMetaData() throws SQLException { - throw org.postgresql.Driver.notImplemented(); + int flags = QueryExecutor.QUERY_ONESHOT | QueryExecutor.QUERY_DESCRIBE_ONLY | QueryExecutor.QUERY_SUPPRESS_BEGIN; + StatementResultHandler handler = new StatementResultHandler(); + connection.getQueryExecutor().execute(preparedQuery, preparedParameters, handler, 0, 0, flags); + + int oids[] = preparedParameters.getTypeOIDs(); + if (oids != null) + return new PSQLParameterMetaData(connection, oids); + + return null; + } /** diff --git a/org/postgresql/jdbc3/PSQLParameterMetaData.java b/org/postgresql/jdbc3/PSQLParameterMetaData.java new file mode 100755 index 00000000..b3e456ad --- /dev/null +++ b/org/postgresql/jdbc3/PSQLParameterMetaData.java @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2005, PostgreSQL Global Development Group +* +* IDENTIFICATION +* $PostgreSQL$ +* +*------------------------------------------------------------------------- +*/ +package org.postgresql.jdbc3; + +import java.sql.SQLException; +import java.sql.ParameterMetaData; + +import org.postgresql.util.GT; +import org.postgresql.util.PSQLState; +import org.postgresql.util.PSQLException; +import org.postgresql.core.BaseConnection; + +public class PSQLParameterMetaData implements ParameterMetaData { + + private final BaseConnection _connection; + private final int _oids[]; + + public PSQLParameterMetaData(BaseConnection connection, int oids[]) { + _connection = connection; + _oids = oids; + } + + public String getParameterClassName(int param) throws SQLException { + checkParamIndex(param); + return null; + } + + public int getParameterCount() { + return _oids.length; + } + + // For now report all parameters as inputs. CallableStatements may + // have one output, but ignore that for now. + public int getParameterMode(int param) throws SQLException { + checkParamIndex(param); + return ParameterMetaData.parameterModeIn; + } + + public int getParameterType(int param) throws SQLException { + checkParamIndex(param); + return _connection.getSQLType(_oids[param-1]); + } + + public String getParameterTypeName(int param) throws SQLException { + checkParamIndex(param); + return _connection.getPGType(_oids[param-1]); + } + + // we don't know this + public int getPrecision(int param) throws SQLException { + checkParamIndex(param); + return 0; + } + + // we don't know this + public int getScale(int param) throws SQLException { + checkParamIndex(param); + return 0; + } + + // we can't tell anything about nullability + public int isNullable(int param) throws SQLException { + checkParamIndex(param); + return ParameterMetaData.parameterNullableUnknown; + } + + // pg doesn't have unsigned numbers + public boolean isSigned(int param) throws SQLException { + checkParamIndex(param); + return true; + } + + private void checkParamIndex(int param) throws PSQLException { + if (param < 1 || param > _oids.length) + throw new PSQLException(GT.tr("The parameter index is out of range: {0}, number of parameters: {1}.", new Object[]{new Integer(param), new Integer(_oids.length)}), PSQLState.INVALID_PARAMETER_VALUE); + } + +} diff --git a/org/postgresql/test/jdbc2/PreparedStatementTest.java b/org/postgresql/test/jdbc2/PreparedStatementTest.java index be007402..d59aad46 100644 --- a/org/postgresql/test/jdbc2/PreparedStatementTest.java +++ b/org/postgresql/test/jdbc2/PreparedStatementTest.java @@ -3,7 +3,7 @@ * Copyright (c) 2004-2005, PostgreSQL Global Development Group * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java,v 1.8 2004/11/09 08:54:47 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc2/PreparedStatementTest.java,v 1.9 2005/01/11 08:25:48 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -229,35 +229,17 @@ public class PreparedStatementTest extends TestCase pstmt.setObject(1, dummy, Types.OTHER); pstmt.executeUpdate(); - // not valid: setObject() with no type info - try - { - pstmt.setObject(1, null); - fail("Expected an exception on setObject(1,null)"); - } - catch (SQLException e) - { - } + // setObject() with no type info + pstmt.setObject(1, null); + pstmt.executeUpdate(); - // not valid: setObject() with insufficient type info - try - { - pstmt.setObject(1, null, Types.OTHER); - fail("Expected an exception on setObject(1,null,Types.OTHER)"); - } - catch (SQLException e) - { - } + // setObject() with insufficient type info + pstmt.setObject(1, null, Types.OTHER); + pstmt.executeUpdate(); - // not valid: setNull() with insufficient type info - try - { - pstmt.setNull(1, Types.OTHER); - fail("Expected an exception on setNull(1,Types.OTHER)"); - } - catch (SQLException e) - { - } + // setNull() with insufficient type info + pstmt.setNull(1, Types.OTHER); + pstmt.executeUpdate(); pstmt.close(); } diff --git a/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java b/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java index 0e377437..b0990c4b 100644 --- a/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java +++ b/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java @@ -3,7 +3,7 @@ * Copyright (c) 2004-2005, PostgreSQL Global Development Group * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java,v 1.9 2005/01/05 00:53:08 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java,v 1.10 2005/01/11 08:25:48 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -18,10 +18,6 @@ public class ResultSetMetaDataTest extends TestCase { private Connection conn; - private Statement stmt; - private ResultSet rs; - private ResultSetMetaData rsmd; - private PGResultSetMetaData pgrsmd; public ResultSetMetaDataTest(String name) { @@ -32,13 +28,6 @@ public class ResultSetMetaDataTest extends TestCase { conn = TestUtil.openDB(); TestUtil.createTable(conn, "rsmd1", "a int primary key, b text, c decimal(10,2)", true); - - stmt = conn.createStatement(); - rs = stmt.executeQuery("SELECT a,b,c,a+c as total,oid,b as d FROM rsmd1"); - rsmd = rs.getMetaData(); - pgrsmd = (PGResultSetMetaData)rsmd; - - TestUtil.createTable(conn, "timetest", "tm time(3), tmtz timetz, ts timestamp without time zone, tstz timestamp(6) with time zone"); TestUtil.dropSequence( conn, "serialtest_a_seq"); @@ -53,25 +42,34 @@ public class ResultSetMetaDataTest extends TestCase TestUtil.dropTable(conn, "serialtest"); TestUtil.dropSequence( conn, "serialtest_a_seq"); TestUtil.dropSequence( conn, "serialtest_b_seq"); + TestUtil.closeDB(conn); + } + + public void testStandardResultSet() throws SQLException { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT a,b,c,a+c as total,oid,b as d FROM rsmd1"); + runStandardTests(rs.getMetaData()); rs.close(); stmt.close(); - TestUtil.closeDB(conn); - rsmd = null; - rs = null; - stmt = null; - conn = null; } - public void testGetColumnCount() throws SQLException { - assertEquals(6, rsmd.getColumnCount()); + public void testPreparedResultSet() throws SQLException { + if (!TestUtil.haveMinimumServerVersion(conn, "7.4")) + return; + + PreparedStatement pstmt = conn.prepareStatement("SELECT a,b,c,a+c as total,oid,b as d FROM rsmd1 WHERE b = ?"); + runStandardTests(pstmt.getMetaData()); + pstmt.close(); } - public void testGetColumnLabel() throws SQLException { + private void runStandardTests(ResultSetMetaData rsmd) throws SQLException { + PGResultSetMetaData pgrsmd = (PGResultSetMetaData)rsmd; + + assertEquals(6, rsmd.getColumnCount()); + assertEquals("a", rsmd.getColumnLabel(1)); assertEquals("total", rsmd.getColumnLabel(4)); - } - public void testGetColumnName() throws SQLException { assertEquals("a", rsmd.getColumnName(1)); assertEquals("oid", rsmd.getColumnName(5)); if (TestUtil.haveMinimumServerVersion(conn, "7.4")) @@ -79,27 +77,17 @@ public class ResultSetMetaDataTest extends TestCase assertEquals("", pgrsmd.getBaseColumnName(4)); assertEquals("b", pgrsmd.getBaseColumnName(6)); } - } - public void testGetColumnType() throws SQLException { assertEquals(Types.INTEGER, rsmd.getColumnType(1)); assertEquals(Types.VARCHAR, rsmd.getColumnType(2)); - } - public void testGetColumnTypeName() throws SQLException { assertEquals("int4", rsmd.getColumnTypeName(1)); assertEquals("text", rsmd.getColumnTypeName(2)); - } - public void testGetPrecision() throws SQLException { assertEquals(10, rsmd.getPrecision(3)); - } - public void testGetScale() throws SQLException { assertEquals(2, rsmd.getScale(3)); - } - public void testGetSchemaName() throws SQLException { assertEquals("", rsmd.getSchemaName(1)); assertEquals("", rsmd.getSchemaName(4)); if (TestUtil.haveMinimumServerVersion(conn, "7.4")) @@ -107,9 +95,7 @@ public class ResultSetMetaDataTest extends TestCase assertEquals("public", pgrsmd.getBaseSchemaName(1)); assertEquals("", pgrsmd.getBaseSchemaName(4)); } - } - public void testGetTableName() throws SQLException { assertEquals("", rsmd.getTableName(1)); assertEquals("", rsmd.getTableName(4)); if (TestUtil.haveMinimumServerVersion(conn, "7.4")) @@ -117,9 +103,7 @@ public class ResultSetMetaDataTest extends TestCase assertEquals("rsmd1", pgrsmd.getBaseTableName(1)); assertEquals("", pgrsmd.getBaseTableName(4)); } - } - public void testIsNullable() throws SQLException { if (TestUtil.haveMinimumServerVersion(conn, "7.4")) { assertEquals(ResultSetMetaData.columnNoNulls, rsmd.isNullable(1)); @@ -132,6 +116,25 @@ public class ResultSetMetaDataTest extends TestCase } } + // verify that a prepared update statement returns no metadata and doesn't execute. + public void testPreparedUpdate() throws SQLException { + PreparedStatement pstmt = conn.prepareStatement("INSERT INTO rsmd1(a,b) VALUES(?,?)"); + pstmt.setInt(1,1); + pstmt.setString(2,"hello"); + ResultSetMetaData rsmd = pstmt.getMetaData(); + assertNull(rsmd); + pstmt.close(); + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM rsmd1"); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + rs.close(); + stmt.close(); + } + + + public void testDatabaseMetaDataNames() throws SQLException { DatabaseMetaData databaseMetaData = conn.getMetaData(); ResultSet resultSet = databaseMetaData.getTableTypes(); @@ -142,41 +145,41 @@ public class ResultSetMetaDataTest extends TestCase } public void testTimestampInfo() throws SQLException { - Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery("SELECT tm, tmtz, ts, tstz FROM timetest"); - ResultSetMetaData rsmd2 = rs2.getMetaData(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT tm, tmtz, ts, tstz FROM timetest"); + ResultSetMetaData rsmd = rs.getMetaData(); // For reference: // TestUtil.createTable(conn, "timetest", "tm time(3), tmtz timetz, ts timestamp without time zone, tstz timestamp(6) with time zone"); - assertEquals(3, rsmd2.getScale(1)); - assertEquals(6, rsmd2.getScale(2)); - assertEquals(6, rsmd2.getScale(3)); - assertEquals(6, rsmd2.getScale(4)); + assertEquals(3, rsmd.getScale(1)); + assertEquals(6, rsmd.getScale(2)); + assertEquals(6, rsmd.getScale(3)); + assertEquals(6, rsmd.getScale(4)); - assertEquals(13, rsmd2.getColumnDisplaySize(1)); - assertEquals(21, rsmd2.getColumnDisplaySize(2)); - assertEquals(26, rsmd2.getColumnDisplaySize(3)); - assertEquals(32, rsmd2.getColumnDisplaySize(4)); + assertEquals(13, rsmd.getColumnDisplaySize(1)); + assertEquals(21, rsmd.getColumnDisplaySize(2)); + assertEquals(26, rsmd.getColumnDisplaySize(3)); + assertEquals(32, rsmd.getColumnDisplaySize(4)); - rs2.close(); - stmt2.close(); + rs.close(); + stmt.close(); } public void testIsAutoIncrement() throws SQLException { - Statement stmt2 = conn.createStatement(); - ResultSet rs2 = stmt2.executeQuery("SELECT c,b,a FROM serialtest"); - ResultSetMetaData rsmd2 = rs2.getMetaData(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT c,b,a FROM serialtest"); + ResultSetMetaData rsmd = rs.getMetaData(); - assertTrue(!rsmd2.isAutoIncrement(1)); + assertTrue(!rsmd.isAutoIncrement(1)); if (TestUtil.haveMinimumServerVersion(conn, "7.4")) { - assertTrue(rsmd2.isAutoIncrement(2)); - assertTrue(rsmd2.isAutoIncrement(3)); + assertTrue(rsmd.isAutoIncrement(2)); + assertTrue(rsmd.isAutoIncrement(3)); } - rs2.close(); - stmt2.close(); + rs.close(); + stmt.close(); } } diff --git a/org/postgresql/test/jdbc3/Jdbc3TestSuite.java b/org/postgresql/test/jdbc3/Jdbc3TestSuite.java index 13ef3c0f..62789c57 100644 --- a/org/postgresql/test/jdbc3/Jdbc3TestSuite.java +++ b/org/postgresql/test/jdbc3/Jdbc3TestSuite.java @@ -3,7 +3,7 @@ * Copyright (c) 2004-2005, PostgreSQL Global Development Group * * IDENTIFICATION -* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc3/Jdbc3TestSuite.java,v 1.10 2004/12/11 04:13:46 jurka Exp $ +* $PostgreSQL: pgjdbc/org/postgresql/test/jdbc3/Jdbc3TestSuite.java,v 1.11 2005/01/11 08:25:49 jurka Exp $ * *------------------------------------------------------------------------- */ @@ -25,7 +25,8 @@ public class Jdbc3TestSuite extends TestSuite TestSuite suite = new TestSuite(); suite.addTestSuite(Jdbc3SavepointTest.class); suite.addTestSuite(TypesTest.class); - suite.addTestSuite(ResultSetTest.class); + suite.addTestSuite(ResultSetTest.class); + suite.addTestSuite(ParameterMetaDataTest.class); return suite; } } diff --git a/org/postgresql/test/jdbc3/ParameterMetaDataTest.java b/org/postgresql/test/jdbc3/ParameterMetaDataTest.java new file mode 100644 index 00000000..8b57a908 --- /dev/null +++ b/org/postgresql/test/jdbc3/ParameterMetaDataTest.java @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- +* +* Copyright (c) 2005, PostgreSQL Global Development Group +* +* IDENTIFICATION +* $PostgreSQL$ +* +*------------------------------------------------------------------------- +*/ +package org.postgresql.test.jdbc3; + +import java.sql.*; +import junit.framework.TestCase; +import org.postgresql.test.TestUtil; + +public class ParameterMetaDataTest extends TestCase { + + private Connection _conn; + + public ParameterMetaDataTest(String name) { + super(name); + } + + protected void setUp() throws SQLException { + _conn = TestUtil.openDB(); + TestUtil.createTable(_conn, "parametertest", "a int4, b float8, c text"); + } + + protected void tearDown() throws SQLException { + TestUtil.dropTable(_conn, "parametertest"); + TestUtil.closeDB(_conn); + } + + public void testParameterMD() throws SQLException { + if (!TestUtil.haveMinimumServerVersion(_conn, "7.4")) + return; + + PreparedStatement pstmt = _conn.prepareStatement("SELECT a FROM parametertest WHERE b = ? AND c = ?"); + ParameterMetaData pmd = pstmt.getParameterMetaData(); + + assertEquals(2, pmd.getParameterCount()); + assertEquals(Types.DOUBLE, pmd.getParameterType(1)); + assertEquals("float8", pmd.getParameterTypeName(1)); + assertEquals(Types.VARCHAR, pmd.getParameterType(2)); + assertEquals("text", pmd.getParameterTypeName(2)); + + pstmt.close(); + } + + public void testFailsOnBadIndex() throws SQLException { + if (!TestUtil.haveMinimumServerVersion(_conn, "7.4")) + return; + + PreparedStatement pstmt = _conn.prepareStatement("SELECT a FROM parametertest WHERE b = ? AND c = ?"); + ParameterMetaData pmd = pstmt.getParameterMetaData(); + try { + pmd.getParameterType(0); + fail("Can't get parameter for index < 1."); + } catch (SQLException sqle) { } + try { + pmd.getParameterType(3); + fail("Can't get parameter for index 3 with only two parameters."); + } catch (SQLException sqle) { } + } + + // Make sure we work when mashing two queries into a single statement. + public void testMultiStatement() throws SQLException { + if (!TestUtil.haveMinimumServerVersion(_conn, "7.4")) + return; + + PreparedStatement pstmt = _conn.prepareStatement("SELECT a FROM parametertest WHERE b = ? AND c = ? ; SELECT b FROM parametertest WHERE a = ?"); + ParameterMetaData pmd = pstmt.getParameterMetaData(); + + assertEquals(3, pmd.getParameterCount()); + assertEquals(Types.DOUBLE, pmd.getParameterType(1)); + assertEquals("float8", pmd.getParameterTypeName(1)); + assertEquals(Types.VARCHAR, pmd.getParameterType(2)); + assertEquals("text", pmd.getParameterTypeName(2)); + assertEquals(Types.INTEGER, pmd.getParameterType(3)); + assertEquals("int4", pmd.getParameterTypeName(3)); + + pstmt.close(); + + } +} [parent: 76c8e5bb272e]