/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you 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 http://www.apache.org/licenses/LICENSE-2.0 . */ #include "ResultSet.hxx" #include "ResultSetMetaData.hxx" #include "Util.hxx" #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::comphelper; using namespace ::connectivity; using namespace ::connectivity::firebird; using namespace ::cppu; using namespace ::dbtools; using namespace ::osl; using namespace ::rtl; using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::sdbcx; using namespace ::com::sun::star::container; using namespace ::com::sun::star::io; using namespace ::com::sun::star::util; OResultSet::OResultSet(Connection* pConnection, const uno::Reference< XInterface >& xStatement, isc_stmt_handle& aStatementHandle, XSQLDA* pSqlda) : OResultSet_BASE(pConnection->getMutex()) , OPropertyContainer(OResultSet_BASE::rBHelper) , m_bIsBookmarkable(false) , m_nFetchSize(1) , m_nResultSetType(::com::sun::star::sdbc::ResultSetType::FORWARD_ONLY) , m_nFetchDirection(::com::sun::star::sdbc::FetchDirection::FORWARD) , m_nResultSetConcurrency(::com::sun::star::sdbc::ResultSetConcurrency::READ_ONLY) , m_pConnection(pConnection) , m_xStatement(xStatement) , m_xMetaData(0) , m_pSqlda(pSqlda) , m_statementHandle(aStatementHandle) , m_bWasNull(false) , m_currentRow(0) , m_bIsAfterLastRow(false) , m_fieldCount(pSqlda? pSqlda->sqld : 0) { SAL_INFO("connectivity.firebird", "OResultSet()."); registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISBOOKMARKABLE), PROPERTY_ID_ISBOOKMARKABLE, PropertyAttribute::READONLY, &m_bIsBookmarkable, ::getCppuType(&m_bIsBookmarkable)); registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHSIZE), PROPERTY_ID_FETCHSIZE, PropertyAttribute::READONLY, &m_nFetchSize, ::getCppuType(&m_nFetchSize)); registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETTYPE), PROPERTY_ID_RESULTSETTYPE, PropertyAttribute::READONLY, &m_nResultSetType, ::getCppuType(&m_nResultSetType)); registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHDIRECTION), PROPERTY_ID_FETCHDIRECTION, PropertyAttribute::READONLY, &m_nFetchDirection, ::getCppuType(&m_nFetchDirection)); registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETCONCURRENCY), PROPERTY_ID_RESULTSETCONCURRENCY, PropertyAttribute::READONLY, &m_nResultSetConcurrency, ::getCppuType(&m_nResultSetConcurrency)); if (!pSqlda) return; // TODO: what? } OResultSet::~OResultSet() { } // ---- XResultSet -- Row retrieval methods ------------------------------------ sal_Int32 SAL_CALL OResultSet::getRow() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_currentRow; } sal_Bool SAL_CALL OResultSet::next() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); m_currentRow++; ISC_STATUS fetchStat = isc_dsql_fetch(m_statusVector, &m_statementHandle, 1, m_pSqlda); if (fetchStat == 0) // SUCCESSFUL { return sal_True; } else if (fetchStat == 100L) // END OF DATASET { m_bIsAfterLastRow = true; return sal_False; } else { SAL_WARN("connectivity.firebird", "Error when fetching data"); // Throws sql exception as appropriate evaluateStatusVector(m_statusVector, "isc_dsql_fetch", *this); return sal_False; } } sal_Bool SAL_CALL OResultSet::previous() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("previous not supported in firebird", *this); return sal_False; } sal_Bool SAL_CALL OResultSet::isLast() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("isLast not supported in firebird", *this); return sal_False; } sal_Bool SAL_CALL OResultSet::isBeforeFirst() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_currentRow == 0; } sal_Bool SAL_CALL OResultSet::isAfterLast() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_bIsAfterLastRow; } sal_Bool SAL_CALL OResultSet::isFirst() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_currentRow == 1 && !m_bIsAfterLastRow; } void SAL_CALL OResultSet::beforeFirst() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if (m_currentRow != 0) ::dbtools::throwFunctionNotSupportedException("beforeFirst not supported in firebird", *this); } void SAL_CALL OResultSet::afterLast() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if (!m_bIsAfterLastRow) ::dbtools::throwFunctionNotSupportedException("afterLast not supported in firebird", *this); } sal_Bool SAL_CALL OResultSet::first() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if (m_currentRow == 0) { return next(); } else if (m_currentRow == 1 && !m_bIsAfterLastRow) { return sal_True; } else { ::dbtools::throwFunctionNotSupportedException("first not supported in firebird", *this); return sal_False; } } sal_Bool SAL_CALL OResultSet::last() throw(SQLException, RuntimeException) { // We need to iterate past the last row to know when we've passed the last // row, hence we can't actually move to last. ::dbtools::throwFunctionNotSupportedException("last not supported in firebird", *this); return sal_False; } sal_Bool SAL_CALL OResultSet::absolute(sal_Int32 aRow) throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if (aRow > m_currentRow) { sal_Int32 aIterations = aRow - m_currentRow; return relative(aIterations); } else { ::dbtools::throwFunctionNotSupportedException("absolute not supported in firebird", *this); return sal_False; } } sal_Bool SAL_CALL OResultSet::relative(sal_Int32 row) throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if (row > 0) { while (row--) { if (!next()) return sal_False; } return sal_True; } else { ::dbtools::throwFunctionNotSupportedException("relative not supported in firebird", *this); return sal_False; } } void SAL_CALL OResultSet::checkColumnIndex(sal_Int32 index) throw (SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if( index < 1 || index > m_fieldCount ) { throw SQLException( "Column Index is outwith valid range", *this, OUString(), 1, Any() ); } } void SAL_CALL OResultSet::checkRowIndex() throw (SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if((m_currentRow < 1) || m_bIsAfterLastRow) { throw SQLException( "Row index is out of valid range.", *this, OUString(),1, Any() ); } } Any SAL_CALL OResultSet::queryInterface( const Type & rType ) throw(RuntimeException) { Any aRet = OPropertySetHelper::queryInterface(rType); return aRet.hasValue() ? aRet : OResultSet_BASE::queryInterface(rType); } Sequence< Type > SAL_CALL OResultSet::getTypes() throw( RuntimeException) { return concatSequences(OPropertySetHelper::getTypes(), OResultSet_BASE::getTypes()); } // ---- XColumnLocate --------------------------------------------------------- sal_Int32 SAL_CALL OResultSet::findColumn(const OUString& rColumnName) throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); uno::Reference< XResultSetMetaData > xMeta = getMetaData(); sal_Int32 nLen = xMeta->getColumnCount(); sal_Int32 i; for(i = 1; i<=nLen; ++i) { // We assume case sensitive, otherwise you'd have to test // xMeta->isCaseSensitive and use qualsIgnoreAsciiCase as needed. if (rColumnName == xMeta->getColumnName(i)) return i; } // The API documentation (XRowLocate) doesn't specify what should happen // if the column name isn't found. The JDBC api specifies that an SQLException // should be thrown. Most drivers return either -1 (some don't check for this // case and just return nLen), however the JDBC specification seems more // correct (in the case of the JDBC/HSQLDB drivers the SQLException is // just propagated from the JDBC call, hence should be expected by any // SDBC user too). ::dbtools::throwSQLException("Invalid column name", SQL_COLUMN_NOT_FOUND, *this); return -1; // Never reached } // ------------------------------------------------------------------------- uno::Reference< XInputStream > SAL_CALL OResultSet::getBinaryStream( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { (void) columnIndex; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return NULL; } // ------------------------------------------------------------------------- uno::Reference< XInputStream > SAL_CALL OResultSet::getCharacterStream( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { (void) columnIndex; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return NULL; } // ---- Internal Utilities --------------------------------------------------- bool OResultSet::isNull(sal_Int32 columnIndex) { assert(columnIndex <= m_fieldCount); XSQLVAR* pVar = m_pSqlda->sqlvar; if (pVar[columnIndex-1].sqltype & 1) // Indicates column may contain null { if (*pVar[columnIndex-1].sqlind == -1) return true; } return false; } template T OResultSet::retrieveValue(sal_Int32 columnIndex) { // TODO: check we have the right type. if ((m_bWasNull = isNull(columnIndex))) return T(); return *((T*) m_pSqlda->sqlvar[columnIndex-1].sqldata); } template <> OUString OResultSet::retrieveValue(sal_Int32 columnIndex) { if ((m_bWasNull = isNull(columnIndex))) return OUString(); // &~1 to remove the "can contain NULL" indicator int aSqlType = m_pSqlda->sqlvar[columnIndex-1].sqltype & ~1; if (aSqlType == SQL_TEXT ) { return OUString(m_pSqlda->sqlvar[columnIndex-1].sqldata, m_pSqlda->sqlvar[columnIndex-1].sqllen, RTL_TEXTENCODING_UTF8); } else if (aSqlType == SQL_VARYING) { // First 2 bytes are a short containing the length of the string // No idea if sqllen is still valid here? short aLength = *((short*) m_pSqlda->sqlvar[columnIndex-1].sqldata); return OUString(m_pSqlda->sqlvar[columnIndex-1].sqldata + 2, aLength, RTL_TEXTENCODING_UTF8); } else { return OUString(); // TODO: Possibly do some sort of type conversion? } } template <> ISC_QUAD* OResultSet::retrieveValue(sal_Int32 columnIndex) { // TODO: this is probably wrong if ((m_bWasNull = isNull(columnIndex))) return 0; return (ISC_QUAD*) m_pSqlda->sqlvar[columnIndex-1].sqldata; } template T OResultSet::safelyRetrieveValue(sal_Int32 columnIndex) throw (SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); checkColumnIndex(columnIndex); checkRowIndex(); return retrieveValue< T >(columnIndex); } // ---- XRow ----------------------------------------------------------------- sal_Bool SAL_CALL OResultSet::wasNull() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_bWasNull; } // ---- XRow: Simple Numerical types ------------------------------------------ sal_Bool SAL_CALL OResultSet::getBoolean(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { // TODO: maybe retrieve as string and test for "true", "t", "1" etc. instead? return safelyRetrieveValue< bool >(columnIndex); } sal_Int8 SAL_CALL OResultSet::getByte( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { return safelyRetrieveValue< sal_Int8 >(columnIndex); } Sequence< sal_Int8 > SAL_CALL OResultSet::getBytes(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { (void) columnIndex; return Sequence< sal_Int8 >(); // TODO: implement //return safelyRetrieveValue(columnIndex); } sal_Int16 SAL_CALL OResultSet::getShort(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< sal_Int16 >(columnIndex); } sal_Int32 SAL_CALL OResultSet::getInt(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< sal_Int32 >(columnIndex); } sal_Int64 SAL_CALL OResultSet::getLong(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< sal_Int64 >(columnIndex); } float SAL_CALL OResultSet::getFloat(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< float >(columnIndex); } double SAL_CALL OResultSet::getDouble(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< double >(columnIndex); } // ---- XRow: More complex types ---------------------------------------------- OUString SAL_CALL OResultSet::getString(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { return safelyRetrieveValue< OUString >(columnIndex); } Date SAL_CALL OResultSet::getDate(sal_Int32 nIndex) throw(SQLException, RuntimeException) { ISC_DATE aISCDate = safelyRetrieveValue< ISC_DATE >(nIndex); struct tm aCTime; isc_decode_sql_date(&aISCDate, &aCTime); return Date(aCTime.tm_mday, aCTime.tm_mon, aCTime.tm_year); } Time SAL_CALL OResultSet::getTime(sal_Int32 nIndex) throw(SQLException, RuntimeException) { ISC_TIME aISCTime = safelyRetrieveValue< ISC_TIME >(nIndex); struct tm aCTime; isc_decode_sql_time(&aISCTime, &aCTime); // first field is nanoseconds -- not supported in firebird or struct tm. // last field denotes UTC (true) or unknown (false) return Time(0, aCTime.tm_sec, aCTime.tm_min, aCTime.tm_hour, false); } DateTime SAL_CALL OResultSet::getTimestamp(sal_Int32 nIndex) throw(SQLException, RuntimeException) { ISC_TIMESTAMP aISCTimestamp = safelyRetrieveValue< ISC_TIMESTAMP >(nIndex); struct tm aCTime; isc_decode_timestamp(&aISCTimestamp, &aCTime); // first field is nanoseconds -- not supported in firebird or struct tm. // last field denotes UTC (true) or unknown (false) return DateTime(0, aCTime.tm_sec, aCTime.tm_min, aCTime.tm_hour, aCTime.tm_mday, aCTime.tm_mon, aCTime.tm_year, false); } // ------------------------------------------------------------------------- uno::Reference< XResultSetMetaData > SAL_CALL OResultSet::getMetaData( ) throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); if(!m_xMetaData.is()) m_xMetaData = new OResultSetMetaData(m_pConnection, m_pSqlda); return m_xMetaData; } // ------------------------------------------------------------------------- uno::Reference< XArray > SAL_CALL OResultSet::getArray( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { (void) columnIndex; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return NULL; } // ------------------------------------------------------------------------- uno::Reference< XClob > SAL_CALL OResultSet::getClob( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { (void) columnIndex; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return NULL; } uno::Reference< XBlob > SAL_CALL OResultSet::getBlob(sal_Int32 columnIndex) throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); ISC_QUAD* pBlobID = safelyRetrieveValue< ISC_QUAD* >(columnIndex); if (!pBlobID) return 0; return m_pConnection->createBlob(pBlobID); } // ------------------------------------------------------------------------- uno::Reference< XRef > SAL_CALL OResultSet::getRef( sal_Int32 columnIndex ) throw(SQLException, RuntimeException) { (void) columnIndex; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return NULL; } // ------------------------------------------------------------------------- Any SAL_CALL OResultSet::getObject( sal_Int32 columnIndex, const uno::Reference< ::com::sun::star::container::XNameAccess >& typeMap ) throw(SQLException, RuntimeException) { (void) columnIndex; (void) typeMap; MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return Any(); } // ------------------------------------------------------------------------- void SAL_CALL OResultSet::close() throw(SQLException, RuntimeException) { SAL_INFO("connectivity.firebird", "close()."); { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); } dispose(); } uno::Reference< XInterface > SAL_CALL OResultSet::getStatement() throw(SQLException, RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); return m_xStatement; } //----- XResultSet: unsupported change detection methods --------------------- sal_Bool SAL_CALL OResultSet::rowDeleted() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("rowDeleted not supported in firebird", *this); return sal_False; } sal_Bool SAL_CALL OResultSet::rowInserted() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("rowInserted not supported in firebird", *this); return sal_False; } sal_Bool SAL_CALL OResultSet::rowUpdated() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("rowUpdated not supported in firebird", *this); return sal_False; } void SAL_CALL OResultSet::refreshRow() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("refreshRow not supported in firebird", *this); } // ------------------------------------------------------------------------- void SAL_CALL OResultSet::cancel( ) throw(RuntimeException) { MutexGuard aGuard(m_pConnection->getMutex()); checkDisposed(OResultSet_BASE::rBHelper.bDisposed); } //----- XWarningsSupplier UNSUPPORTED ----------------------------------------- void SAL_CALL OResultSet::clearWarnings() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("clearWarnings not supported in firebird", *this); } Any SAL_CALL OResultSet::getWarnings() throw(SQLException, RuntimeException) { ::dbtools::throwFunctionNotSupportedException("getWarnings not supported in firebird", *this); return Any(); } //----- OIdPropertyArrayUsageHelper ------------------------------------------ IPropertyArrayHelper* OResultSet::createArrayHelper() const { Sequence< Property > aProperties; describeProperties(aProperties); return new ::cppu::OPropertyArrayHelper(aProperties); } IPropertyArrayHelper & OResultSet::getInfoHelper() { return *const_cast(this)->getArrayHelper(); } // ----------------------------------------------------------------------------- void SAL_CALL OResultSet::acquire() throw() { OResultSet_BASE::acquire(); } // ----------------------------------------------------------------------------- void SAL_CALL OResultSet::release() throw() { OResultSet_BASE::release(); } // ----------------------------------------------------------------------------- uno::Reference< ::com::sun::star::beans::XPropertySetInfo > SAL_CALL OResultSet::getPropertySetInfo( ) throw(::com::sun::star::uno::RuntimeException) { return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); } // ---- XServiceInfo ----------------------------------------------------------- OUString SAL_CALL OResultSet::getImplementationName() throw ( RuntimeException) { return OUString("com.sun.star.sdbcx.firebird.ResultSet"); } Sequence< OUString > SAL_CALL OResultSet::getSupportedServiceNames() throw( RuntimeException) { Sequence< OUString > aSupported(2); aSupported[0] = OUString("com.sun.star.sdbc.ResultSet"); aSupported[1] = OUString("com.sun.star.sdbcx.ResultSet"); return aSupported; } sal_Bool SAL_CALL OResultSet::supportsService(const OUString& _rServiceName) throw( RuntimeException) { Sequence< OUString > aSupported(getSupportedServiceNames()); const OUString* pSupported = aSupported.getConstArray(); const OUString* pEnd = pSupported + aSupported.getLength(); for (;pSupported != pEnd && !pSupported->equals(_rServiceName); ++pSupported) ; return pSupported != pEnd; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */