Modified:
trunk/SDK/MYSQLPlus/Library/MResult.cpp
trunk/SDK/MYSQLPlus/Library/MResultPlus.h
Log:
Modified: trunk/SDK/MYSQLPlus/Library/MResult.cpp
===================================================================
--- trunk/SDK/MYSQLPlus/Library/MResult.cpp 2006-11-28 14:39:16 UTC (rev 696)
+++ trunk/SDK/MYSQLPlus/Library/MResult.cpp 2006-11-28 14:40:00 UTC (rev 697)
@@ -77,13 +77,145 @@
MYODBCDbgReturn2();
}
-void MStatePutData::doClear()
+SQLSMALLINT MStatePutData::getConciseTypeC()
{
MYODBCDbgEnter();
+ SQLSMALLINT n = pDescriptorRecordC->getConciseType();
+
+ MYODBCDbgReturn3( "%d", n );
+}
+
+SQLSMALLINT MStatePutData::getConciseTypeSQL()
+{
+ MYODBCDbgEnter();
+
+ SQLSMALLINT n = pDescriptorRecordSQL->getConciseType();
+
+ MYODBCDbgReturn3( "%d", n );
+}
+
+SQLPOINTER MStatePutData::getDataPtr()
+{
+ MYODBCDbgEnter();
+
+ SQLPOINTER p = pDescriptorRecordC->getDataPtr();
+
+ MYODBCDbgReturn3( "%p", p );
+}
+
+SQLINTEGER *MStatePutData::getOctetLengthPtr()
+{
+ MYODBCDbgEnter();
+
+ SQLINTEGER *pn = pDescriptorRecordC->getOctetLengthPtr();
+
+ MYODBCDbgReturn3( "%p", pn );
+}
+
+/*!
+ \brief Gets the data pointer.
+
+ This is used to support copying app provided data into our data conversion
hub.
+
+ We get our data from bytearrayData when we have been chunking data in. We
+ get our data from the APD::SQL_DESC_DATA_PTR otherwise.
+*/
+char *MStatePutData::getData()
+{
+ MYODBCDbgEnter();
+
+ char *p;
+
+ /* did we chunk the data in? */
+ if ( MYODBCCIsDataAtExec( pDescriptorRecordC->getOctetLengthPtr() ) )
+ p = bytearrayData.data();
+ else
+ p = (char*)pDescriptorRecordC->getDataPtr();
+
+ MYODBCDbgReturn3( "%p", p );
+}
+
+SQLINTEGER MStatePutData::getDataLengthBytes()
+{
+ MYODBCDbgEnter();
+
+ if ( isNull() )
+ MYODBCDbgReturn3( "%d", 0 );
+
+ SQLINTEGER nDataLengthBytes = 0;
+ SQLINTEGER *pnOctetLengthPtr = pDescriptorRecordC->getOctetLengthPtr();
+
+ /* did we chunk the data in? */
+ if ( MYODBCCIsDataAtExec( pnOctetLengthPtr ) )
+ {
+ /*!
+ \internal MYODBC RULE
+
+ In this case the app could have explicitly stated the length and/or implied
the length. When both
+ appear to be set - we opt to use lowest value.
+ */
+ SQLINTEGER nExplicit = abs( *pnOctetLengthPtr ) - 100;
+ SQLINTEGER nImplicit = bytearrayData.length();
+ if ( *pnOctetLengthPtr < (-100) )
+ nDataLengthBytes = std::min( nImplicit, nExplicit );
+ else
+ nDataLengthBytes = nImplicit;
+ MYODBCDbgReturn3( "%d", nDataLengthBytes );
+ }
+
+ /* We can do this because isNull() gets caught above. */
+ MYODBCDbgReturn3( "%d", *pnOctetLengthPtr );
+}
+
+bool MStatePutData::isChunking()
+{
+ MYODBCDbgEnter();
+
+ SQLINTEGER *pnOctetLengthPtr = pDescriptorRecordC->getOctetLengthPtr();
+ if ( MYODBCCIsDataAtExec( pnOctetLengthPtr ) )
+ MYODBCDbgReturn3( "%d", true );
+
+ MYODBCDbgReturn3( "%d", false );
+}
+
+bool MStatePutData::isNull()
+{
+ MYODBCDbgEnter();
+
+ SQLINTEGER *pnOctetLengthPtr = pDescriptorRecordC->getOctetLengthPtr();
+
+ /* Failed to get length? */
+ if ( !pnOctetLengthPtr )
+ MYODBCDbgReturn3( "%d", true );
+
+ /* Did we get SQL_NULL_DATA before execute? */
+ if ( *pnOctetLengthPtr == SQL_NULL_DATA )
+ MYODBCDbgReturn3( "%d", true );
+
+ /* Did we get SQL_NULL_DATA from doPutData (or otherwise end up with null data)? */
+ if ( MYODBCCIsDataAtExec( pnOctetLengthPtr ) )
+ {
+ if ( bytearrayData.isNull() )
+ MYODBCDbgReturn3( "%d", true );
+ }
+ else
+ {
+ if ( !pDescriptorRecordC->getDataPtr() )
+ MYODBCDbgReturn3( "%d", true );
+ }
+
+ MYODBCDbgReturn3( "%d", false );
+}
+
+void MStatePutData::doClear( MDescriptorRecordAPD *pDescriptorRecordC,
MDescriptorRecordIPD *pDescriptorRecordSQL )
+{
+ MYODBCDbgEnter();
+
+ bytearrayData.clear();
variantData.clear();
- pDescriptorRecordIPD = NULL;
- pDescriptorRecordAPD = NULL;
+ this->pDescriptorRecordSQL = pDescriptorRecordSQL;
+ this->pDescriptorRecordC = pDescriptorRecordC;
MYODBCDbgReturn2();
}
@@ -101,9 +233,9 @@
{
MYODBCDbgEnter();
- nPrevCall = PREV_CALL_NONE;
- pDescriptorIPD = NULL;
- pDescriptorAPD = NULL;
+ nPrevCall = PREV_CALL_CLEAR;
+ pDescriptorSQL = NULL;
+ pDescriptorC = NULL;
stringCommand.clear();
stringlistCommandSegments.clear();
nParameterFirst = 0;
@@ -691,6 +823,39 @@
SQLRETURN nReturn = SQL_SUCCESS;
+ switch ( stateExecute.nPrevCall )
+ {
+ /*!
+ \internal ODBC RULE (DM)
+
+ The previous function call was not a call to SQLPutData or SQLParamData.
+ */
+ case MStateExecute::PREV_CALL_CLEAR:
+ case MStateExecute::PREV_CALL_EXECUTE:
+ MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY010 ) );
+
+ case MStateExecute::PREV_CALL_PARAM_DATA:
+ case MStateExecute::PREV_CALL_PUT_DATA:
+ break;
+ }
+
+ switch ( nStrLenOrInd )
+ {
+ case SQL_NTS:
+ stateExecute.statePutData.bytearrayData.append( (char *)pData );
+ break;
+ case SQL_NULL_DATA:
+ case SQL_DEFAULT_PARAM:
+ break;
+ default:
+ {
+ int nSize = stateExecute.statePutData.bytearrayData.size();
+ stateExecute.statePutData.bytearrayData.resize( nSize + nStrLenOrInd );
+ char *p = stateExecute.statePutData.bytearrayData.data();
+ memcpy( &(p[nSize]), pData, nStrLenOrInd );
+ }
+ }
+
stateExecute.nPrevCall = MStateExecute::PREV_CALL_PUT_DATA;
MYODBCDbgReturn( nReturn );
@@ -1060,13 +1225,57 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY000, 0,
tr("Unknown condition in doNext().") ) );
}
-SQLRETURN MResult::doParamData( SQLPOINTER pValue )
+SQLRETURN MResult::doParamData( SQLPOINTER *ppValue )
{
MYODBCDbgEnter();
- SQLRETURN nReturn = doSubmitCommand( &stateExecute, pValue );
- stateExecute.nPrevCall = MStateExecute::PREV_CALL_PARAM_DATA;
+ SQLRETURN nReturn;
+ switch ( stateExecute.nPrevCall )
+ {
+ /*!
+ \internal ODBC RULE (DM)
+
+ The previous function call was not a call to SQLExecDirect, SQLExecute,
SQLBulkOperations, or SQLSetPos
+ where the return code was SQL_NEED_DATA.
+ */
+ case MStateExecute::PREV_CALL_CLEAR:
+ MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY010 ) );
+
+ /* First call after execute? */
+ case MStateExecute::PREV_CALL_EXECUTE:
+ /* The purpose of this call is to provide the app with app provided data in
APD::SQL_DESC_DATA_PTR... */
+ if ( ppValue )
+ *ppValue = stateExecute.statePutData.getDataPtr();
+ MYODBCDbgReturn( SQL_NEED_DATA );
+
+ /*!
+ \internal ODBC RULE
+
+ The previous function call was a call to SQLParamData.
+ */
+ case MStateExecute::PREV_CALL_PARAM_DATA:
+ MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY010 ) );
+
+ /* App has provided data via calls to doPutData? */
+ case MStateExecute::PREV_CALL_PUT_DATA:
+ nReturn = doInsertParameter( &stateExecute );
+ if ( !SQL_SUCCEEDED( nReturn ) )
+ MYODBCDbgReturn( nReturn );
+ }
+
+ /* Insert more parameters (if any)... */
+ nReturn = doInsertParameters( &stateExecute );
+ if ( nReturn == SQL_NEED_DATA )
+ {
+ /* Parameter is data-at-exec so app needs to provide via doPutData calls... */
+ stateExecute.nPrevCall = MStateExecute::PREV_CALL_PARAM_DATA;
+ MYODBCDbgReturn( nReturn );
+ }
+
+ if ( SQL_SUCCEEDED( nReturn ) )
+ nReturn = doSubmitCommand( stateExecute.stringCommand );
+
MYODBCDbgReturn( nReturn );
}
@@ -3738,26 +3947,26 @@
MYODBCDbgReturn( SQL_ERROR );
}
-SQLRETURN MResult::fromC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
#if MYODBC_DBG > 1
- MYODBCDbgInfo( QString( "APD::SQL_DESC_DATA_PTR=%1" ).arg( (unsigned
long)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ MYODBCDbgInfo( QString( "APD::SQL_DESC_DATA_PTR=%1" ).arg( (unsigned
long)pStatePutData->getDataPtr() ) );
#endif
- if ( !pResultPutData->pDescriptorRecordAPD->getDataPtr() )
+ if ( !pStatePutData->getDataPtr() )
{
/* for the moment we are assuming we are being used to create an SQL statement */
- pResultPutData->variantData.setValue( QString( "NULL" ) );
+ pStatePutData->variantData.setValue( QString( "NULL" ) );
MYODBCDbgReturn( SQL_SUCCESS );
}
- switch ( pResultPutData->pDescriptorRecordAPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeC() )
{
case SQL_C_CHAR:
case SQL_C_WCHAR:
- MYODBCDbgReturn( fromCharacterC( pResultPutData ) );
+ MYODBCDbgReturn( fromCharacterC( pStatePutData ) );
case SQL_C_STINYINT:
case SQL_C_UTINYINT:
case SQL_C_TINYINT:
@@ -3772,26 +3981,26 @@
case SQL_C_SBIGINT:
case SQL_C_UBIGINT:
case SQL_C_NUMERIC:
- MYODBCDbgReturn( fromNumericC( pResultPutData ) );
+ MYODBCDbgReturn( fromNumericC( pStatePutData ) );
case SQL_C_BIT:
- MYODBCDbgReturn( fromBitC( pResultPutData ) );
+ MYODBCDbgReturn( fromBitC( pStatePutData ) );
case SQL_C_BINARY:
- MYODBCDbgReturn( fromBinaryC( pResultPutData ) );
+ MYODBCDbgReturn( fromBinaryC( pStatePutData ) );
case SQL_C_GUID:
- MYODBCDbgReturn( fromGuidC( pResultPutData ) );
+ MYODBCDbgReturn( fromGuidC( pStatePutData ) );
case SQL_C_TYPE_DATE:
case SQL_C_DATE: /* ODBC v2 - should already be validated */
- MYODBCDbgReturn( fromDateC( pResultPutData ) );
+ MYODBCDbgReturn( fromDateC( pStatePutData ) );
case SQL_C_TYPE_TIME:
case SQL_C_TIME: /* ODBC v2 - should already be validated */
- MYODBCDbgReturn( fromTimeC( pResultPutData ) );
+ MYODBCDbgReturn( fromTimeC( pStatePutData ) );
case SQL_C_TYPE_TIMESTAMP:
case SQL_C_TIMESTAMP: /* ODBC v2 - should already be validated */
- MYODBCDbgReturn( fromTimeStampC( pResultPutData ) );
+ MYODBCDbgReturn( fromTimeStampC( pStatePutData ) );
case SQL_C_INTERVAL_MONTH:
case SQL_C_INTERVAL_YEAR:
case SQL_C_INTERVAL_YEAR_TO_MONTH:
- MYODBCDbgReturn( fromIntervalYearMonthC( pResultPutData ) );
+ MYODBCDbgReturn( fromIntervalYearMonthC( pStatePutData ) );
case SQL_C_INTERVAL_DAY:
case SQL_C_INTERVAL_HOUR:
case SQL_C_INTERVAL_MINUTE:
@@ -3802,20 +4011,20 @@
case SQL_C_INTERVAL_HOUR_TO_MINUTE:
case SQL_C_INTERVAL_HOUR_TO_SECOND:
case SQL_C_INTERVAL_MINUTE_TO_SECOND:
- MYODBCDbgReturn( fromIntervalDayTimeC( pResultPutData ) );
+ MYODBCDbgReturn( fromIntervalDayTimeC( pStatePutData ) );
/* case SQL_C_DEFAULT: */
}
- MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY000, 0, QString(
tr("unknown C data type %1") ).arg(
pResultPutData->pDescriptorRecordAPD->getConciseType() ) ) );
+ MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HY000, 0, QString(
tr("unknown C data type %1") ).arg( pStatePutData->getConciseTypeC() ) ) );
}
-SQLRETURN MResult::fromCharacterC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromCharacterC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- if ( pResultPutData->pDescriptorRecordAPD->getConciseType() == SQL_C_WCHAR )
+ if ( pStatePutData->getConciseTypeC() == SQL_C_WCHAR )
{
- SQLINTEGER *pnOctetLengthPtr =
pResultPutData->pDescriptorRecordAPD->getOctetLengthPtr();
+ SQLINTEGER *pnOctetLengthPtr = pStatePutData->getOctetLengthPtr();
SQLINTEGER nLenBytes = ( pnOctetLengthPtr ? *pnOctetLengthPtr : -1 );
SQLINTEGER nLenChars = ( nLenBytes > 1 ? nLenBytes /
sizeof(SQLWCHAR) : -1 );
@@ -3823,12 +4032,12 @@
MYODBCDbgInfo( QString( "pnOctetLengthPtr=%1, nLenBytes=%2, nLenChars=%3" ).arg(
(qulonglong)pnOctetLengthPtr ).arg( nLenBytes ).arg( nLenChars ) );
#endif
- pResultPutData->variantData.setValue( MYODBCC::QString_fromWCharArray(
(SQLWCHAR*)pResultPutData->pDescriptorRecordAPD->getDataPtr(), nLenChars ) );
+ pStatePutData->variantData.setValue( MYODBCC::QString_fromWCharArray(
(SQLWCHAR*)pStatePutData->getDataPtr(), nLenChars ) );
}
else
- pResultPutData->variantData.setValue( QString::fromAscii(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( QString::fromAscii(
(char*)pStatePutData->getDataPtr() ) );
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -3836,8 +4045,7 @@
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
- pResultPutData->variantData.setValue( "'" + MYODBCC::getEscaped(
- pResultPutData->variantData.toString().toUtf8() ) + "'" );
+ pStatePutData->variantData.setValue( "'" + MYODBCC::getEscaped(
pStatePutData->variantData.toString().toUtf8() ) + "'" );
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -3855,7 +4063,7 @@
case SQL_INTERVAL_HOUR:
case SQL_INTERVAL_MINUTE:
case SQL_INTERVAL_SECOND:
- if ( pResultPutData->variantData.convert( QVariant::LongLong ) )
+ if ( pStatePutData->variantData.convert( QVariant::LongLong ) )
MYODBCDbgReturn( SQL_SUCCESS );
break;
}
@@ -3870,53 +4078,50 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromNumericC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromNumericC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
/* get number into variant */
- switch ( pResultPutData->pDescriptorRecordAPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeC() )
{
case SQL_C_TINYINT:
case SQL_C_STINYINT:
-#if MYODBC_DBG > 1
- MYODBCDbgInfo( QString( "*APD::SQL_DESC_DATA_PTR=%1" ).arg( *(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) ) );
-#endif
- pResultPutData->variantData.setValue( *(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(char*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_UTINYINT:
- pResultPutData->variantData.setValue( *(
(SQLCHAR*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLCHAR*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_SHORT:
case SQL_C_SSHORT:
- pResultPutData->variantData.setValue( *(
(SQLSMALLINT*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLSMALLINT*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_USHORT:
- pResultPutData->variantData.setValue( *(
(SQLUSMALLINT*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLUSMALLINT*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_LONG:
case SQL_C_SLONG:
- /*! \todo casting to SQLINTEGER confuses QVariant when data being pulled out
to a string - it comes out blank. Cast to (int) works. */
- pResultPutData->variantData.setValue( *(
(int*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ /*! \note casting to SQLINTEGER confuses QVariant when data being pulled out
to a string - it comes out blank. Cast to (int) works. */
+ pStatePutData->variantData.setValue( *(
(int*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_ULONG:
- pResultPutData->variantData.setValue( *(
(SQLUINTEGER*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLUINTEGER*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_FLOAT:
- pResultPutData->variantData.setValue( *(
(SQLREAL*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLREAL*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_DOUBLE:
- pResultPutData->variantData.setValue( *(
(SQLDOUBLE*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLDOUBLE*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_SBIGINT:
- pResultPutData->variantData.setValue( *(
(SQLBIGINT*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLBIGINT*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_UBIGINT:
- pResultPutData->variantData.setValue( *(
(SQLUBIGINT*)pResultPutData->pDescriptorRecordAPD->getDataPtr() ) );
+ pStatePutData->variantData.setValue( *(
(SQLUBIGINT*)pStatePutData->getDataPtr() ) );
break;
case SQL_C_NUMERIC:
{
- SQL_NUMERIC_STRUCT *pNumeric =
(SQL_NUMERIC_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_NUMERIC_STRUCT *pNumeric =
(SQL_NUMERIC_STRUCT*)pStatePutData->getDataPtr();
char szFormatBuffer[128];
/*!
\internal
@@ -3932,7 +4137,7 @@
}
/* touch up based upon the requested SQL type */
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -3940,9 +4145,9 @@
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
- if ( !pResultPutData->variantData.convert( QVariant::String ) )
+ if ( !pStatePutData->variantData.convert( QVariant::String ) )
break;
- pResultPutData->variantData.setValue( "'" +
pResultPutData->variantData.toString() + "'" );
+ pStatePutData->variantData.setValue( "'" +
pStatePutData->variantData.toString() + "'" );
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -3973,13 +4178,13 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromBitC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromBitC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- pResultPutData->variantData.setValue(
*(SQLCHAR*)pResultPutData->pDescriptorRecordAPD->getDataPtr() );
+ pStatePutData->variantData.setValue( *(SQLCHAR*)pStatePutData->getDataPtr() );
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -3987,9 +4192,9 @@
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
- if ( !pResultPutData->variantData.convert( QVariant::String ) )
+ if ( !pStatePutData->variantData.convert( QVariant::String ) )
break;
- pResultPutData->variantData.setValue( "'" +
pResultPutData->variantData.toString() + "'" );
+ pStatePutData->variantData.setValue( "'" +
pStatePutData->variantData.toString() + "'" );
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -4014,11 +4219,11 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromBinaryC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromBinaryC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4026,10 +4231,10 @@
case SQL_WCHAR:
case SQL_WVARCHAR:
case SQL_WLONGVARCHAR:
- pResultPutData->variantData.setValue( QByteArray(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr(),
pResultPutData->pDescriptorRecordAPD->getOctetLength() ) );
- if ( !pResultPutData->variantData.convert( QVariant::String ) )
+ pStatePutData->variantData.setValue( QByteArray(
pStatePutData->getData(), pStatePutData->getDataLengthBytes() ) );
+ if ( !pStatePutData->variantData.convert( QVariant::String ) )
break;
- pResultPutData->variantData.setValue( "'" +
pResultPutData->variantData.toString() + "'" );
+ pStatePutData->variantData.setValue( "'" +
pStatePutData->variantData.toString() + "'" );
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -4048,8 +4253,8 @@
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_BIT:
- pResultPutData->variantData.setValue( QByteArray(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr(),
pResultPutData->pDescriptorRecordAPD->getOctetLength() ) );
- if ( pResultPutData->variantData.convert( QVariant::LongLong ) )
+ pStatePutData->variantData.setValue( QByteArray(
pStatePutData->getData(), pStatePutData->getDataLengthBytes() ) );
+ if ( pStatePutData->variantData.convert( QVariant::LongLong ) )
MYODBCDbgReturn( SQL_SUCCESS );
break;
case SQL_TYPE_DATE:
@@ -4064,60 +4269,36 @@
We assume that these are NOT stored in SQL_*_STRUCT format but are
instead
stored in string format.
*/
- pResultPutData->variantData.setValue( QByteArray(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr(),
pResultPutData->pDescriptorRecordAPD->getOctetLength() ) );
- if ( !pResultPutData->variantData.convert( QVariant::String ) )
+ pStatePutData->variantData.setValue( QByteArray(
pStatePutData->getData(), pStatePutData->getDataLengthBytes() ) );
+ if ( !pStatePutData->variantData.convert( QVariant::String ) )
break;
- pResultPutData->variantData.setValue( "'" +
pResultPutData->variantData.toString() + "'" );
+ pStatePutData->variantData.setValue( "'" +
pStatePutData->variantData.toString() + "'" );
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_BINARY:
case SQL_VARBINARY:
case SQL_LONGVARBINARY:
{
- SQLINTEGER nBytes = 0;
+ pStatePutData->variantData.setValue( QByteArray(
pStatePutData->getData(), pStatePutData->getDataLengthBytes() ) );
+ if ( !pStatePutData->variantData.canConvert( QVariant::ByteArray ) )
+ break;
- if ( pResultPutData->pDescriptorRecordAPD->getOctetLengthPtr() )
- {
- nBytes =
*pResultPutData->pDescriptorRecordAPD->getOctetLengthPtr();
- switch ( nBytes )
- {
- case SQL_NTS:
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("SQL_NTS not supported at this time") ) );
- case SQL_NULL_DATA:
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("SQL_NULL_DATA not supported at this time") ) );
- case SQL_DEFAULT_PARAM:
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("SQL_DEFAULT_PARAM not supported at this time") ) );
- case SQL_DATA_AT_EXEC:
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006 ) );
- default:
-#if MYODBC_DBG > 1
- MYODBCDbgInfo( QString( "nBytes=%1" ).arg( nBytes ) );
-#endif
- /* SQL_LEN_DATA_AT_EXEC(length) macro? */
- if ( nBytes < (-100) )
- {
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("SQL_LEN_DATA_AT_EXEC not supported at this time") ) );
- }
- else if ( nBytes == (-100) )
- {
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("0 length specified with SQL_LEN_DATA_AT_EXEC not
supported at this time") ) );
- }
- nBytes = abs( nBytes ) - 100;
- }
- }
- else
- {
- MYODBCDbgReturn( getDiagnostic()->doAppend(
MDiagnostic::STATE_07006, 0, tr("APD::SQL_DESC_OCTET_LENGTH_PTR (StrlenInd) null") ) );
- }
-#if MYODBC_DBG > 1
- MYODBCDbgInfo( QString( "nBytes=%1" ).arg( nBytes ) );
-#endif
+ /*!
+ \internal MYODBC RULE
-// pResultPutData->variantData.setValue( QByteArray(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr(),
pResultPutData->pDescriptorRecordAPD->getOctetLength() ) );
- pResultPutData->variantData.setValue( QByteArray(
(char*)pResultPutData->pDescriptorRecordAPD->getDataPtr(), nBytes ) );
- if ( !pResultPutData->variantData.canConvert( QVariant::ByteArray ) )
- break;
- pResultPutData->variantData.setValue( "'" + MYODBCC::getEscaped(
pResultPutData->variantData.toByteArray() ) + "'" );
+ With MResultRes; binary values in command string (SQL etc) are a
quoted, escaped, string of chars. This is not
+ be the case for MResultStmt which allows binding buffer 'directly' to
the server-side.
+ */
+ /*!
+ \internal MYSQL RULE
+ A communication packet is a single SQL statement sent to the MySQL
server or a single row that is sent to the
+ client.
+
+ Both the client and the server have their own max_allowed_packet
variable, so if you want to handle big packets,
+ you must increase this variable both in the client and in the server.
+ */
+ pStatePutData->variantData.setValue( "'" + MYODBCC::getEscaped(
pStatePutData->variantData.toByteArray() ) + "'" );
+
MYODBCDbgReturn( SQL_SUCCESS );
}
}
@@ -4132,14 +4313,14 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromDateC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromDateC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQL_DATE_STRUCT * pDate =
(SQL_DATE_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_DATE_STRUCT * pDate = (SQL_DATE_STRUCT*)pStatePutData->getDataPtr();
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4158,7 +4339,7 @@
pDate->year,
pDate->month,
pDate->day );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_TIMESTAMP:
case SQL_TIMESTAMP:
@@ -4171,7 +4352,7 @@
pDate->year,
pDate->month,
pDate->day );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
}
@@ -4185,14 +4366,14 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromGuidC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromGuidC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQLGUID * pGuid =
(SQLGUID*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQLGUID * pGuid = (SQLGUID*)pStatePutData->getDataPtr();
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4221,14 +4402,14 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromTimeC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromTimeC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQL_TIME_STRUCT * pTime =
(SQL_TIME_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_TIME_STRUCT * pTime = (SQL_TIME_STRUCT*)pStatePutData->getDataPtr();
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4245,7 +4426,7 @@
pTime->hour,
pTime->minute,
pTime->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_TIME:
@@ -4259,7 +4440,7 @@
pTime->hour,
pTime->minute,
pTime->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_TIMESTAMP:
@@ -4273,7 +4454,7 @@
pTime->hour,
pTime->minute,
pTime->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
}
@@ -4287,14 +4468,14 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromTimeStampC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromTimeStampC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQL_TIMESTAMP_STRUCT * pTimestamp =
(SQL_TIMESTAMP_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_TIMESTAMP_STRUCT * pTimestamp =
(SQL_TIMESTAMP_STRUCT*)pStatePutData->getDataPtr();
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4314,7 +4495,7 @@
pTimestamp->hour,
pTimestamp->minute,
pTimestamp->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_DATE:
@@ -4328,7 +4509,7 @@
pTimestamp->year,
pTimestamp->month,
pTimestamp->day );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_TIME:
@@ -4342,7 +4523,7 @@
pTimestamp->hour,
pTimestamp->minute,
pTimestamp->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_TYPE_TIMESTAMP:
@@ -4359,7 +4540,7 @@
pTimestamp->hour,
pTimestamp->minute,
pTimestamp->second );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
}
@@ -4374,15 +4555,15 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromIntervalYearMonthC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromIntervalYearMonthC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQL_INTERVAL_STRUCT * pInterval =
(SQL_INTERVAL_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_INTERVAL_STRUCT * pInterval =
(SQL_INTERVAL_STRUCT*)pStatePutData->getDataPtr();
SQL_YEAR_MONTH_STRUCT * pYearMonth = &pInterval->intval.year_month;
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4398,7 +4579,7 @@
sprintf( szFormatBuffer, "'%04d:%02d'",
pYearMonth->year,
pYearMonth->month );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -4412,9 +4593,9 @@
We convert to months.
*/
if ( pInterval->interval_sign == SQL_FALSE )
- pResultPutData->variantData.setValue( pYearMonth->year * 12 +
pYearMonth->month );
+ pStatePutData->variantData.setValue( pYearMonth->year * 12 +
pYearMonth->month );
else
- pResultPutData->variantData.setValue( 0 - (pYearMonth->year * 12 +
pYearMonth->month) );
+ pStatePutData->variantData.setValue( 0 - (pYearMonth->year * 12 +
pYearMonth->month) );
MYODBCDbgReturn( SQL_SUCCESS );
@@ -4434,15 +4615,15 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006 ) );
}
-SQLRETURN MResult::fromIntervalDayTimeC( MStatePutData *pResultPutData )
+SQLRETURN MResult::fromIntervalDayTimeC( MStatePutData *pStatePutData )
{
MYODBCDbgEnter();
- SQL_INTERVAL_STRUCT * pInterval =
(SQL_INTERVAL_STRUCT*)pResultPutData->pDescriptorRecordAPD->getDataPtr();
+ SQL_INTERVAL_STRUCT * pInterval =
(SQL_INTERVAL_STRUCT*)pStatePutData->getDataPtr();
SQL_DAY_SECOND_STRUCT * pDaySecond = &pInterval->intval.day_second;
char szFormatBuffer[128];
- switch ( pResultPutData->pDescriptorRecordIPD->getConciseType() )
+ switch ( pStatePutData->getConciseTypeSQL() )
{
case SQL_CHAR:
case SQL_VARCHAR:
@@ -4461,7 +4642,7 @@
pDaySecond->minute,
pDaySecond->second,
pDaySecond->fraction );
- pResultPutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
+ pStatePutData->variantData.setValue( QString::fromAscii( szFormatBuffer )
); ;
MYODBCDbgReturn( SQL_SUCCESS );
case SQL_DECIMAL:
case SQL_NUMERIC:
@@ -4475,9 +4656,9 @@
We convert to seconds (any fraction is truncated).
*/
if ( pInterval->interval_sign == SQL_FALSE )
- pResultPutData->variantData.setValue( ((pDaySecond->day * 24 +
pDaySecond->hour) * 60 + pDaySecond->minute) * 60 + pDaySecond->second );
+ pStatePutData->variantData.setValue( ((pDaySecond->day * 24 +
pDaySecond->hour) * 60 + pDaySecond->minute) * 60 + pDaySecond->second );
else
- pResultPutData->variantData.setValue( 0 - (((pDaySecond->day * 24 +
pDaySecond->hour) * 60 + pDaySecond->minute) * 60 + pDaySecond->second) );
+ pStatePutData->variantData.setValue( 0 - (((pDaySecond->day * 24 +
pDaySecond->hour) * 60 + pDaySecond->minute) * 60 + pDaySecond->second) );
if ( pDaySecond->fraction )
{
@@ -5112,4 +5293,126 @@
MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_HYC00, 0,
tr("Row-wise binding not supported.") ) );
}
+/*!
+ \brief Inserts any bound parameters into command.
+ This call is used to support doExecute. This call also supports doParamData
+ for case when a bound parameter is SQL_LEN_DATA_AT_EXEC.
+
+ \param pStateExecute Ref. to a viable MStateExecute.
+
+ \return SQLRETURN
+
+ \retval SQL_SUCCESS
+ \retval SQL_SUCCESS_WITH_INFO
+ \retval SQL_NEED_DATA
+ \retval SQL_ERROR
+*/
+SQLRETURN MResult::doInsertParameters( MStateExecute *pStateExecute )
+{
+ MYODBCDbgEnter();
+
+ SQLRETURN nReturn = SQL_SUCCESS;
+
+ /*!
+ \internal ODBC RULE
+ \todo
+
+ SQLBindParameter was called with ParameterValuePtr set to a null pointer,
StrLen_or_IndPtr not set to SQL_NULL_DATA
+ or SQL_DATA_AT_EXEC, and InputOutputType not set to SQL_PARAM_OUTPUT, so that the
number of parameters specified in
+ SQLBindParameter was greater than the number of parameters in the SQL statement
contained in *StatementText.
+ */
+
+ /* Construct our command... */
+ while ( pStateExecute->nCommandSegment <
pStateExecute->stringlistCommandSegments.count() )
+ {
+ /* Parameter marker? */
+ if ( pStateExecute->stringlistCommandSegments.at(
pStateExecute->nCommandSegment ) == "?" )
+ {
+ /* Init MStatePutData (contains our data conversion hub)... */
+ pStateExecute->statePutData.doClear(
(MDescriptorRecordAPD*)pStateExecute->pDescriptorC->getRecord(
pStateExecute->nParameterFirst + pStateExecute->nParameter ),
+
(MDescriptorRecordIPD*)pStateExecute->pDescriptorSQL->getRecord(
pStateExecute->nParameterFirst + pStateExecute->nParameter ) );
+
+ /* Are we going to chunk the data in? */
+ SQLINTEGER *pnOctetLengthPtr =
pStateExecute->statePutData.getOctetLengthPtr();
+ if ( MYODBCCIsDataAtExec( pnOctetLengthPtr ) )
+ {
+ /* App needs to provide data via doParamData & doPutData. */
+ MYODBCDbgReturn( SQL_NEED_DATA );
+ }
+ /* Not chunking? */
+ else
+ {
+ /* APD::SQL_DESC_DATA_PTR contains all of our parameter data (if any)...
*/
+ SQLRETURN nReturnInternal = doInsertParameter( pStateExecute );
+ if ( !SQL_SUCCEEDED( nReturnInternal ) )
+ MYODBCDbgReturn( nReturnInternal );
+ if ( nReturnInternal == SQL_SUCCESS_WITH_INFO )
+ nReturn = nReturnInternal;
+ }
+ }
+ /* Not parameter marker... */
+ else
+ {
+ /* Simply append the command segment... */
+ pStateExecute->stringCommand +=
pStateExecute->stringlistCommandSegments.at( pStateExecute->nCommandSegment );
+ /* Go to next command segment... */
+ pStateExecute->nCommandSegment++;
+ }
+
+ } // while segments
+
+ /* All parameters inserted. */
+ MYODBCDbgReturn( nReturn );
+}
+
+SQLRETURN MResult::doInsertParameter( MStateExecute *pStateExecute )
+{
+ MYODBCDbgEnter();
+
+ /* Advance... */
+ pStateExecute->nParameter++;
+ pStateExecute->nCommandSegment++;
+
+ /* Preempt some length cases... */
+ SQLINTEGER *pnOctetLengthPtr = pStateExecute->statePutData.getOctetLengthPtr();
+ if ( pnOctetLengthPtr )
+ {
+ switch ( *pnOctetLengthPtr )
+ {
+ case SQL_NTS:
+ break;
+
+ case SQL_NULL_DATA:
+ pStateExecute->stringCommand += "NULL";
+ MYODBCDbgReturn( SQL_SUCCESS );
+
+ case SQL_DEFAULT_PARAM:
+ MYODBCDbgReturn( getDiagnostic()->doAppend( MDiagnostic::STATE_07006,
0, tr("SQL_DEFAULT_PARAM not supported at this time") ) );
+ }
+ }
+
+ /* Get data into our data conversion hub.. */
+ SQLRETURN nReturn = fromC( &pStateExecute->statePutData );
+ switch ( nReturn )
+ {
+ case SQL_SUCCESS:
+ case SQL_SUCCESS_WITH_INFO:
+ break;
+ case SQL_ERROR:
+ default:
+ doStateRollBack( STATE_INITIALIZED );
+ MYODBCDbgReturn( nReturn );
+ }
+
+#if MYODBC_DBG > 1
+MYODBCDbgInfo( QString( "variantData.isNull=%1" ).arg(
pStateExecute->statePutData.variantData.isNull() ) );
+MYODBCDbgInfo( QString( "variantData=%1" ).arg(
pStateExecute->statePutData.variantData.toString() ) );
+#endif
+
+ /* Convert data into a string as we copy from hub to command... */
+ pStateExecute->stringCommand +=
pStateExecute->statePutData.variantData.toString();
+
+ MYODBCDbgReturn( nReturn );
+}
+
Modified: trunk/SDK/MYSQLPlus/Library/MResultPlus.h
===================================================================
--- trunk/SDK/MYSQLPlus/Library/MResultPlus.h 2006-11-28 14:39:16 UTC (rev 696)
+++ trunk/SDK/MYSQLPlus/Library/MResultPlus.h 2006-11-28 14:40:00 UTC (rev 697)
@@ -83,7 +83,6 @@
SQLRETURN doStateRollBack( STATE nState );
SQLRETURN doApplyCursorRestrictions();
SQLRETURN doAppendVarChar();
- SQLRETURN doSubmitCommand( MStateExecute *, SQLPOINTER ) { MYODBCDbgEnter();
MYODBCDbgReturn( SQL_SUCCESS ); }
SQLRETURN doSubmitCommand( const QString & ) { MYODBCDbgEnter(); MYODBCDbgReturn(
SQL_SUCCESS ); }
/*!
| Thread |
|---|
| • Connector/ODBC 5 commit: r697 - trunk/SDK/MYSQLPlus/Library | pharvey | 28 Nov |