From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- dbaccess/source/core/api/RowSetCache.cxx | 1713 ++++++++++++++++++++++++++++++ 1 file changed, 1713 insertions(+) create mode 100644 dbaccess/source/core/api/RowSetCache.cxx (limited to 'dbaccess/source/core/api/RowSetCache.cxx') diff --git a/dbaccess/source/core/api/RowSetCache.cxx b/dbaccess/source/core/api/RowSetCache.cxx new file mode 100644 index 000000000..8a23d6624 --- /dev/null +++ b/dbaccess/source/core/api/RowSetCache.cxx @@ -0,0 +1,1713 @@ +/* -*- 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 +#include "BookmarkSet.hxx" +#include "KeySet.hxx" +#include "OptimisticSet.hxx" +#include "RowSetBase.hxx" +#include "RowSetCache.hxx" +#include "StaticSet.hxx" +#include "WrappedResultSet.hxx" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace dbaccess; +using namespace dbtools; +using namespace connectivity; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdb; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::cppu; +using namespace ::osl; + +// This class calls m_pCacheSet->FOO_checked(..., sal_False) +// (where FOO is absolute, last, previous) +// when it does not immediately care about the values in the row's columns. +// As a corollary, m_pCacheSet may be left in an inconsistent state, +// and all ->fillFOO calls (and ->getFOO) may fail or give wrong results, +// until m_pCacheSet is moved (or refreshed) again. +// So always make sure m_pCacheSet is moved or refreshed before accessing column values. + + +ORowSetCache::ORowSetCache(const Reference< XResultSet >& _xRs, + const Reference< XSingleSelectQueryAnalyzer >& _xAnalyzer, + const Reference& _rContext, + const OUString& _rUpdateTableName, + bool& _bModified, + bool& _bNew, + const ORowSetValueVector& _aParameterValueForCache, + const OUString& i_sRowSetFilter, + sal_Int32 i_nMaxRows) + :m_xSet(_xRs) + ,m_xMetaData(Reference< XResultSetMetaDataSupplier >(_xRs,UNO_QUERY_THROW)->getMetaData()) + ,m_aContext( _rContext ) + ,m_nFetchSize(0) + ,m_nRowCount(0) + ,m_nPrivileges( Privilege::SELECT ) + ,m_nPosition(0) + ,m_nStartPos(0) + ,m_nEndPos(0) + ,m_bRowCountFinal(false) + ,m_bBeforeFirst(true) + ,m_bAfterLast( false ) + ,m_bModified(_bModified) + ,m_bNew(_bNew) +{ + + // first try if the result can be used to do inserts and updates + Reference< XPropertySet> xProp(_xRs,UNO_QUERY); + Reference< XPropertySetInfo > xPropInfo = xProp->getPropertySetInfo(); + bool bBookmarkable = false; + try + { + Reference< XResultSetUpdate> xUp(_xRs,UNO_QUERY_THROW); + bBookmarkable = xPropInfo->hasPropertyByName(PROPERTY_ISBOOKMARKABLE) && + any2bool(xProp->getPropertyValue(PROPERTY_ISBOOKMARKABLE)) && Reference< XRowLocate >(_xRs, UNO_QUERY).is(); + if ( bBookmarkable ) + { + xUp->moveToInsertRow(); + xUp->cancelRowUpdates(); + _xRs->beforeFirst(); + m_nPrivileges = Privilege::SELECT|Privilege::DELETE|Privilege::INSERT|Privilege::UPDATE; + m_xCacheSet = new WrappedResultSet(i_nMaxRows); + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + return; + } + } + catch(const Exception&) + { + DBG_UNHANDLED_EXCEPTION("dbaccess.core"); + } + try + { + if ( xPropInfo->hasPropertyByName(PROPERTY_RESULTSETTYPE) && + ::comphelper::getINT32(xProp->getPropertyValue(PROPERTY_RESULTSETTYPE)) != ResultSetType::FORWARD_ONLY) + _xRs->beforeFirst(); + } + catch(const SQLException&) + { + TOOLS_WARN_EXCEPTION("dbaccess.core", "ORowSetCache"); + } + + // check if all keys of the updateable table are fetched + bool bAllKeysFound = false; + sal_Int32 nTablesCount = 0; + + bool bNeedKeySet = !bBookmarkable || (xPropInfo->hasPropertyByName(PROPERTY_RESULTSETCONCURRENCY) && + ::comphelper::getINT32(xProp->getPropertyValue(PROPERTY_RESULTSETCONCURRENCY)) == ResultSetConcurrency::READ_ONLY); + + OUString aUpdateTableName = _rUpdateTableName; + Reference< XConnection> xConnection; + // first we need a connection + Reference< XStatement> xStmt(_xRs->getStatement(),UNO_QUERY); + if(xStmt.is()) + xConnection = xStmt->getConnection(); + else + { + Reference< XPreparedStatement> xPrepStmt(_xRs->getStatement(),UNO_QUERY); + xConnection = xPrepStmt->getConnection(); + } + OSL_ENSURE(xConnection.is(),"No connection!"); + if(_xAnalyzer.is()) + { + try + { + Reference xTabSup(_xAnalyzer,UNO_QUERY); + OSL_ENSURE(xTabSup.is(),"ORowSet::execute composer isn't a tablesupplier!"); + Reference xTables = xTabSup->getTables(); + Sequence< OUString> aTableNames = xTables->getElementNames(); + if ( aTableNames.getLength() > 1 && _rUpdateTableName.isEmpty() && bNeedKeySet ) + {// here we have a join or union and nobody told us which table to update, so we update them all + m_nPrivileges = Privilege::SELECT|Privilege::DELETE|Privilege::INSERT|Privilege::UPDATE; + rtl::Reference pCursor = new OptimisticSet(m_aContext,xConnection,_xAnalyzer,_aParameterValueForCache,i_nMaxRows,m_nRowCount); + m_xCacheSet = pCursor; + try + { + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + if ( pCursor->isReadOnly() ) + m_nPrivileges = Privilege::SELECT; + m_aKeyColumns = pCursor->getJoinedKeyColumns(); + return; + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("dbaccess.core", "ORowSetCache"); + } + m_xCacheSet.clear(); + } + else + { + if(!_rUpdateTableName.isEmpty() && xTables->hasByName(_rUpdateTableName)) + xTables->getByName(_rUpdateTableName) >>= m_aUpdateTable; + else if(xTables->getElementNames().hasElements()) + { + aUpdateTableName = xTables->getElementNames()[0]; + xTables->getByName(aUpdateTableName) >>= m_aUpdateTable; + } + Reference xIndexAccess(xTables,UNO_QUERY); + if(xIndexAccess.is()) + nTablesCount = xIndexAccess->getCount(); + else + nTablesCount = xTables->getElementNames().getLength(); + + if(m_aUpdateTable.is() && nTablesCount < 3) // for we can't handle more than 2 tables in our keyset + { + Reference xSet(m_aUpdateTable,UNO_QUERY); + const Reference xPrimaryKeyColumns = dbtools::getPrimaryKeyColumns_throw(xSet); + if ( xPrimaryKeyColumns.is() ) + { + Reference xColSup(_xAnalyzer,UNO_QUERY); + if ( xColSup.is() ) + { + Reference xSelColumns = xColSup->getColumns(); + Reference xMeta = xConnection->getMetaData(); + SelectColumnsMetaData aColumnNames(xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers()); + ::dbaccess::getColumnPositions(xSelColumns,xPrimaryKeyColumns->getElementNames(),aUpdateTableName,aColumnNames); + bAllKeysFound = !aColumnNames.empty() && aColumnNames.size() == o3tl::make_unsigned(xPrimaryKeyColumns->getElementNames().getLength()); + } + } + } + } + } + catch (Exception const&) + { + TOOLS_WARN_EXCEPTION("dbaccess.core", "ORowSetCache"); + } + } + + // first check if resultset is bookmarkable + if(!bNeedKeySet) + { + try + { + m_xCacheSet = new OBookmarkSet(i_nMaxRows); + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + + // check privileges + m_nPrivileges = Privilege::SELECT; + if(Reference(_xRs,UNO_QUERY).is()) // this interface is optional so we have to check it + { + Reference xTable(m_aUpdateTable,UNO_QUERY); + if(xTable.is() && xTable->getPropertySetInfo()->hasPropertyByName(PROPERTY_PRIVILEGES)) + { + m_nPrivileges = 0; + xTable->getPropertyValue(PROPERTY_PRIVILEGES) >>= m_nPrivileges; + if(!m_nPrivileges) + m_nPrivileges = Privilege::SELECT; + } + } + } + catch (const SQLException&) + { + TOOLS_WARN_EXCEPTION("dbaccess.core", "ORowSetCache"); + bNeedKeySet = true; + } + + } + if(bNeedKeySet) + { + // need to check if we could handle this select clause + bAllKeysFound = bAllKeysFound && (nTablesCount == 1 || checkJoin(xConnection,_xAnalyzer,aUpdateTableName)); + + if(!bAllKeysFound ) + { + if ( bBookmarkable ) + { + // here I know that we have a read only bookmarkable cursor + _xRs->beforeFirst(); + m_nPrivileges = Privilege::SELECT; + m_xCacheSet = new WrappedResultSet(i_nMaxRows); + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + return; + } + m_xCacheSet = new OStaticSet(i_nMaxRows); + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + m_nPrivileges = Privilege::SELECT; + } + else + { + Reference xMeta = xConnection->getMetaData(); + SelectColumnsMetaData aColumnNames(xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers()); + Reference xColSup(_xAnalyzer,UNO_QUERY); + Reference xSelColumns = xColSup->getColumns(); + Reference xColumns = m_aUpdateTable->getColumns(); + ::dbaccess::getColumnPositions(xSelColumns,xColumns->getElementNames(),aUpdateTableName,aColumnNames); + + // check privileges + m_nPrivileges = Privilege::SELECT; + bool bNoInsert = false; + + Sequence< OUString> aNames(xColumns->getElementNames()); + const OUString* pIter = aNames.getConstArray(); + const OUString* pEnd = pIter + aNames.getLength(); + for(;pIter != pEnd;++pIter) + { + Reference xColumn(xColumns->getByName(*pIter),UNO_QUERY); + OSL_ENSURE(xColumn.is(),"Column in table is null!"); + if(xColumn.is()) + { + sal_Int32 nNullable = 0; + xColumn->getPropertyValue(PROPERTY_ISNULLABLE) >>= nNullable; + if(nNullable == ColumnValue::NO_NULLS && aColumnNames.find(*pIter) == aColumnNames.end()) + { // we found a column where null is not allowed so we can't insert new values + bNoInsert = true; + break; // one column is enough + } + } + } + + rtl::Reference pKeySet = new OKeySet(m_aUpdateTable, aUpdateTableName ,_xAnalyzer,_aParameterValueForCache,i_nMaxRows,m_nRowCount); + try + { + m_xCacheSet = pKeySet; + pKeySet->construct(_xRs,i_sRowSetFilter); + + if(Reference(_xRs,UNO_QUERY).is()) // this interface is optional so we have to check it + { + Reference xTable(m_aUpdateTable,UNO_QUERY); + if(xTable.is() && xTable->getPropertySetInfo()->hasPropertyByName(PROPERTY_PRIVILEGES)) + { + m_nPrivileges = 0; + xTable->getPropertyValue(PROPERTY_PRIVILEGES) >>= m_nPrivileges; + if(!m_nPrivileges) + m_nPrivileges = Privilege::SELECT; + } + } + if(bNoInsert) + m_nPrivileges |= ~Privilege::INSERT; // remove the insert privilege + } + catch (const SQLException&) + { + TOOLS_WARN_EXCEPTION("dbaccess.core", "ORowSetCache"); + // we couldn't create a keyset here so we have to create a static cache + m_xCacheSet = new OStaticSet(i_nMaxRows); + m_xCacheSet->construct(_xRs,i_sRowSetFilter); + m_nPrivileges = Privilege::SELECT; + } + } + + } + // last check + if(!bAllKeysFound && xProp->getPropertySetInfo()->hasPropertyByName(PROPERTY_RESULTSETCONCURRENCY) && + ::comphelper::getINT32(xProp->getPropertyValue(PROPERTY_RESULTSETCONCURRENCY)) == ResultSetConcurrency::READ_ONLY) + m_nPrivileges = Privilege::SELECT; +} + +ORowSetCache::~ORowSetCache() +{ + m_xCacheSet.clear(); + if(m_pMatrix) + { + m_pMatrix->clear(); + m_pMatrix.reset(); + } + + if(m_pInsertMatrix) + { + m_pInsertMatrix->clear(); + m_pInsertMatrix.reset(); + } + m_xSet = WeakReference< XResultSet>(); + m_xMetaData = nullptr; + m_aUpdateTable = nullptr; +} + +void ORowSetCache::setFetchSize(sal_Int32 _nSize) +{ + if(_nSize == m_nFetchSize) + return; + + m_nFetchSize = _nSize; + if(!m_pMatrix) + { + m_pMatrix.reset( new ORowSetMatrix(_nSize) ); + m_aMatrixIter = m_pMatrix->end(); + m_aMatrixEnd = m_pMatrix->end(); + + m_pInsertMatrix.reset( new ORowSetMatrix(1) ); // a little bit overkill but ??? :-) + m_aInsertRow = m_pInsertMatrix->end(); + } + else + { + // now correct the iterator in our iterator vector + std::vector aPositions; + std::map aCacheIterToChange; + // first get the positions where they stand now + for(const auto& [rIndex, rHelper] : m_aCacheIterators) + { + aCacheIterToChange[rIndex] = false; + if ( !rHelper.pRowSet->isInsertRow() + /*&& rHelper.aIterator != m_pMatrix->end()*/ && !m_bModified ) + { + ptrdiff_t nDist = rHelper.aIterator - m_pMatrix->begin(); + aPositions.push_back(nDist); + aCacheIterToChange[rIndex] = true; + } + } + sal_Int32 nKeyPos = m_aMatrixIter - m_pMatrix->begin(); + m_pMatrix->resize(_nSize); + + if ( nKeyPos < _nSize ) + m_aMatrixIter = m_pMatrix->begin() + nKeyPos; + else + m_aMatrixIter = m_pMatrix->end(); + m_aMatrixEnd = m_pMatrix->end(); + + // now adjust their positions because a resize invalidates all iterators + std::vector::const_iterator aIter = aPositions.begin(); + ORowSetCacheMap::iterator aCacheIter = m_aCacheIterators.begin(); + for(const auto& rPosChange : aCacheIterToChange) + { + if ( rPosChange.second ) + { + OSL_ENSURE((*aIter >= static_cast(0)) && (*aIter < static_cast(m_pMatrix->size())),"Position is invalid!"); + if ( *aIter < _nSize ) + aCacheIter->second.aIterator = m_pMatrix->begin() + *aIter++; + else + aCacheIter->second.aIterator = m_pMatrix->end(); + } + ++aCacheIter; + } + } + if(!m_nPosition) + { + sal_Int32 nNewSt = 0; + fillMatrix(nNewSt,_nSize); + OSL_ENSURE(nNewSt == 0, "fillMatrix set new start to unexpected value"); + m_nStartPos = 0; + m_nEndPos = _nSize; + } + else if (m_nStartPos < m_nPosition && m_nPosition <= m_nEndPos) + { + sal_Int32 nNewSt = -1; + _nSize += m_nStartPos; + fillMatrix(nNewSt, _nSize); + if (nNewSt >= 0) + { + m_nStartPos = nNewSt; + m_nEndPos = _nSize; + m_aMatrixIter = calcPosition(); + } + else + { + m_nEndPos = m_nStartPos + m_nFetchSize; + } + } + else + { + OSL_FAIL("m_nPosition not between m_nStartPos and m_nEndpos"); + // try to repair + moveWindow(); + m_aMatrixIter = calcPosition(); + } +} + +// XResultSetMetaDataSupplier + +static Any lcl_getBookmark(ORowSetValue& i_aValue,OCacheSet* i_pCacheSet) +{ + switch ( i_aValue.getTypeKind() ) + { + case DataType::TINYINT: + case DataType::SMALLINT: + case DataType::INTEGER: + return Any(i_aValue.getInt32()); + default: + if ( i_pCacheSet && i_aValue.isNull()) + i_aValue = i_pCacheSet->getBookmark(); + return i_aValue.getAny(); + } +} + +// css::sdbcx::XRowLocate +Any ORowSetCache::getBookmark( ) +{ + if(m_bAfterLast) + throwFunctionSequenceException(m_xSet.get()); + + if ( m_aMatrixIter >= m_pMatrix->end() || m_aMatrixIter < m_pMatrix->begin() || !(*m_aMatrixIter).is()) + { + return Any(); // this is allowed here because the rowset knows what it is doing + } + + return lcl_getBookmark((**m_aMatrixIter)[0],m_xCacheSet.get()); +} + +bool ORowSetCache::moveToBookmark( const Any& bookmark ) +{ + if ( m_xCacheSet->moveToBookmark(bookmark) ) + { + m_bBeforeFirst = false; + m_nPosition = m_xCacheSet->getRow(); + + checkPositionFlags(); + + if(!m_bAfterLast) + { + moveWindow(); + checkPositionFlags(); + if ( !m_bAfterLast ) + { + m_aMatrixIter = calcPosition(); + OSL_ENSURE(m_aMatrixIter->is(),"Iterator after moveToBookmark not valid"); + } + else + m_aMatrixIter = m_pMatrix->end(); + } + else + m_aMatrixIter = m_pMatrix->end(); + } + else + return false; + + return m_aMatrixIter != m_pMatrix->end() && (*m_aMatrixIter).is(); +} + +bool ORowSetCache::moveRelativeToBookmark( const Any& bookmark, sal_Int32 rows ) +{ + bool bRet( moveToBookmark( bookmark ) ); + if ( bRet ) + { + m_nPosition = m_xCacheSet->getRow() + rows; + absolute(m_nPosition); + + bRet = m_aMatrixIter != m_pMatrix->end() && (*m_aMatrixIter).is(); + } + + return bRet; +} + +sal_Int32 ORowSetCache::compareBookmarks( const Any& _first, const Any& _second ) +{ + return (!_first.hasValue() || !_second.hasValue()) ? CompareBookmark::NOT_COMPARABLE : m_xCacheSet->compareBookmarks(_first,_second); +} + +bool ORowSetCache::hasOrderedBookmarks( ) +{ + return m_xCacheSet->hasOrderedBookmarks(); +} + +sal_Int32 ORowSetCache::hashBookmark( const Any& bookmark ) +{ + return m_xCacheSet->hashBookmark(bookmark); +} + +// XRowUpdate +void ORowSetCache::updateNull(sal_Int32 columnIndex,ORowSetValueVector::Vector& io_aRow + ,std::vector& o_ChangedColumns + ) +{ + checkUpdateConditions(columnIndex); + + ORowSetValueVector::Vector& rInsert = **m_aInsertRow; + if ( !rInsert[columnIndex].isNull() ) + { + rInsert[columnIndex].setBound(true); + rInsert[columnIndex].setNull(); + rInsert[columnIndex].setModified(true); + io_aRow[columnIndex].setNull(); + + m_xCacheSet->mergeColumnValues(columnIndex,rInsert,io_aRow,o_ChangedColumns); + impl_updateRowFromCache_throw(io_aRow,o_ChangedColumns); + } +} + +void ORowSetCache::updateValue(sal_Int32 columnIndex,const ORowSetValue& x + ,ORowSetValueVector::Vector& io_aRow + ,std::vector& o_ChangedColumns + ) +{ + checkUpdateConditions(columnIndex); + + ORowSetValueVector::Vector& rInsert = **m_aInsertRow; + if ( rInsert[columnIndex] != x ) + { + rInsert[columnIndex].setBound(true); + rInsert[columnIndex] = x; + rInsert[columnIndex].setModified(true); + io_aRow[columnIndex] = rInsert[columnIndex]; + + m_xCacheSet->mergeColumnValues(columnIndex,rInsert,io_aRow,o_ChangedColumns); + impl_updateRowFromCache_throw(io_aRow,o_ChangedColumns); + } +} + +void ORowSetCache::updateCharacterStream( sal_Int32 columnIndex, const Reference< css::io::XInputStream >& x + , sal_Int32 length,ORowSetValueVector::Vector& io_aRow + ,std::vector& o_ChangedColumns + ) +{ + checkUpdateConditions(columnIndex); + + Sequence aSeq; + if(x.is()) + x->readBytes(aSeq,length); + + ORowSetValueVector::Vector& rInsert = **m_aInsertRow; + rInsert[columnIndex].setBound(true); + rInsert[columnIndex] = aSeq; + rInsert[columnIndex].setModified(true); + io_aRow[columnIndex] = Any(x); + + m_xCacheSet->mergeColumnValues(columnIndex,rInsert,io_aRow,o_ChangedColumns); + impl_updateRowFromCache_throw(io_aRow,o_ChangedColumns); +} + +void ORowSetCache::updateObject( sal_Int32 columnIndex, const Any& x + ,ORowSetValueVector::Vector& io_aRow + ,std::vector& o_ChangedColumns + ) +{ + checkUpdateConditions(columnIndex); + + ORowSetValueVector::Vector& rInsert = **m_aInsertRow; + ORowSetValue aTemp; + aTemp.fill(x); + if ( rInsert[columnIndex] != aTemp ) + { + rInsert[columnIndex].setBound(true); + rInsert[columnIndex] = aTemp; + rInsert[columnIndex].setModified(true); + io_aRow[columnIndex] = rInsert[columnIndex]; + + m_xCacheSet->mergeColumnValues(columnIndex,rInsert,io_aRow,o_ChangedColumns); + impl_updateRowFromCache_throw(io_aRow,o_ChangedColumns); + } +} + +void ORowSetCache::updateNumericObject( sal_Int32 columnIndex, const Any& x + ,ORowSetValueVector::Vector& io_aRow + ,std::vector& o_ChangedColumns + ) +{ + checkUpdateConditions(columnIndex); + + ORowSetValueVector::Vector& rInsert = **m_aInsertRow; + ORowSetValue aTemp; + aTemp.fill(x); + if ( rInsert[columnIndex] != aTemp ) + { + rInsert[columnIndex].setBound(true); + rInsert[columnIndex] = aTemp; + rInsert[columnIndex].setModified(true); + io_aRow[columnIndex] = rInsert[columnIndex]; + + m_xCacheSet->mergeColumnValues(columnIndex,rInsert,io_aRow,o_ChangedColumns); + impl_updateRowFromCache_throw(io_aRow,o_ChangedColumns); + } +} + +// XResultSet +bool ORowSetCache::next( ) +{ + if(!isAfterLast()) + { + m_bBeforeFirst = false; + ++m_nPosition; + + // after we increment the position we have to check if we are already after the last row + checkPositionFlags(); + if(!m_bAfterLast) + { + moveWindow(); + + OSL_ENSURE(((m_nPosition - m_nStartPos) - 1) < static_cast(m_pMatrix->size()),"Position is behind end()!"); + m_aMatrixIter = calcPosition(); + checkPositionFlags(); + } + } + + return !m_bAfterLast; +} + + +bool ORowSetCache::isFirst( ) const +{ + return m_nPosition == 1; // ask resultset for +} + +bool ORowSetCache::isLast( ) const +{ + return m_nPosition == m_nRowCount; +} + +void ORowSetCache::beforeFirst( ) +{ + if(!m_bBeforeFirst) + { + m_bAfterLast = false; + m_nPosition = 0; + m_bBeforeFirst = true; + m_xCacheSet->beforeFirst(); + moveWindow(); + m_aMatrixIter = m_pMatrix->end(); + } +} + +void ORowSetCache::afterLast( ) +{ + if(m_bAfterLast) + return; + + m_bBeforeFirst = false; + m_bAfterLast = true; + + if(!m_bRowCountFinal) + { + m_xCacheSet->last(); + m_bRowCountFinal = true; + m_nRowCount = m_xCacheSet->getRow();// + 1 removed + } + m_xCacheSet->afterLast(); + + m_nPosition = 0; + m_aMatrixIter = m_pMatrix->end(); +} + +bool ORowSetCache::fillMatrix(sal_Int32& _nNewStartPos, sal_Int32 &_nNewEndPos) +{ + OSL_ENSURE((_nNewStartPos != _nNewEndPos) || (_nNewStartPos == 0 && _nNewEndPos == 0 && m_nRowCount == 0), + "ORowSetCache::fillMatrix: StartPos and EndPos can not be equal (unless the recordset is empty)!"); + // If _nNewStartPos >= 0, then fill the whole window with new data + // Else if _nNewStartPos == -1, then fill only segment [m_nEndPos, _nNewEndPos) + // Else, undefined (invalid argument) + OSL_ENSURE( _nNewStartPos >= -1, "ORowSetCache::fillMatrix: invalid _nNewStartPos" ); + + ORowSetMatrix::iterator aIter; + sal_Int32 i; + bool bCheck; + sal_Int32 requestedStartPos; + if ( _nNewStartPos == -1 ) + { + aIter = m_pMatrix->begin() + (m_nEndPos - m_nStartPos); + i = m_nEndPos + 1; + requestedStartPos = m_nStartPos; + } + else + { + aIter = m_pMatrix->begin(); + i = _nNewStartPos + 1; + requestedStartPos = _nNewStartPos; + } + bCheck = m_xCacheSet->absolute(i); + + + for(; i <= _nNewEndPos; ++i,++aIter) + { + if(bCheck) + { + if(!aIter->is()) + *aIter = new ORowSetValueVector(m_xMetaData->getColumnCount()); + m_xCacheSet->fillValueRow(*aIter,i); + } + else + { // there are no more rows found so we can fetch some before start + + if(!m_bRowCountFinal) + { + if(m_xCacheSet->previous()) // because we stand after the last row + m_nRowCount = m_xCacheSet->getRow(); // here we have the row count + if(!m_nRowCount) + m_nRowCount = i-1; // it can be that getRow return zero + m_bRowCountFinal = true; + } + const ORowSetMatrix::iterator aEnd = aIter; + ORowSetMatrix::const_iterator aRealEnd = m_pMatrix->end(); + sal_Int32 nPos; + if (m_nRowCount >= m_nFetchSize) + { + nPos = m_nRowCount - m_nFetchSize; + } + else + { + nPos = 0; + } + _nNewStartPos = nPos; + _nNewEndPos = m_nRowCount; + ++nPos; + bCheck = m_xCacheSet->absolute(nPos); + + for(;bCheck && nPos <= requestedStartPos && aIter != aRealEnd; ++aIter, ++nPos) + { + if(!aIter->is()) + *aIter = new ORowSetValueVector(m_xMetaData->getColumnCount()); + m_xCacheSet->fillValueRow(*aIter, nPos); + bCheck = m_xCacheSet->next(); + } + if(aIter != aEnd) + std::rotate(m_pMatrix->begin(),aEnd,aIter); + break; + } + bCheck = m_xCacheSet->next(); + } + // we have to read one row forward to ensure that we know when we are on last row + // but only when we don't know it already + if(!m_bRowCountFinal) + { + if(!m_xCacheSet->next()) + { + if(m_xCacheSet->previous()) // because we stand after the last row + m_nRowCount = m_xCacheSet->getRow(); // here we have the row count + m_bRowCountFinal = true; + } + else + m_nRowCount = std::max(i,m_nRowCount); + + } + return bCheck; +} + +// If m_nPosition is out of the current window, +// move it and update m_nStartPos and m_nEndPos +// Caller is responsible for updating m_aMatrixIter +void ORowSetCache::moveWindow() +{ + OSL_ENSURE(m_nStartPos >= 0,"ORowSetCache::moveWindow: m_nStartPos is less than 0!"); + OSL_ENSURE(m_nEndPos >= m_nStartPos,"ORowSetCache::moveWindow: m_nStartPos not smaller than m_nEndPos"); + OSL_ENSURE(m_nEndPos-m_nStartPos <= m_nFetchSize,"ORowSetCache::moveWindow: m_nStartPos and m_nEndPos too far apart"); + + if ( m_nStartPos < m_nPosition && m_nPosition <= m_nEndPos ) + { + // just move inside the window + OSL_ENSURE((m_nPosition - m_nStartPos) <= static_cast(m_pMatrix->size()),"Position is behind end()!"); + // make double plus sure that we have fetched that row + m_aMatrixIter = calcPosition(); + OSL_ENSURE(m_aMatrixIter != m_pMatrix->end(), "New m_aMatrixIter is at end(), but should not."); + if(!m_aMatrixIter->is()) + { + bool bOk( m_xCacheSet->absolute( m_nPosition ) ); + if ( bOk ) + { + *m_aMatrixIter = new ORowSetValueVector(m_xMetaData->getColumnCount()); + m_xCacheSet->fillValueRow(*m_aMatrixIter,m_nPosition); + // we have to read one row forward to ensure that we know when we are on last row + // but only when we don't know it already + if ( !m_bRowCountFinal ) + { + bOk = m_xCacheSet->absolute( m_nPosition + 1 ); + if ( bOk ) + m_nRowCount = std::max(sal_Int32(m_nPosition+1),m_nRowCount); + } + } + if(!bOk && !m_bRowCountFinal) + { + // because we stand after the last row + m_nRowCount = m_xCacheSet->previous() ? m_xCacheSet->getRow() : 0; + m_bRowCountFinal = true; + } + } + return; + } + + sal_Int32 nDiff = (m_nFetchSize - 1) / 2; + sal_Int32 nNewStartPos = (m_nPosition - nDiff) - 1; //m_nPosition is 1-based, but m_nStartPos is 0-based + sal_Int32 nNewEndPos = nNewStartPos + m_nFetchSize; + + if ( nNewStartPos < 0 ) + { + // The computed new window crashes through the floor (begins before first row); + // nNew*Pos has to be shifted by -nNewStartPos + nNewEndPos -= nNewStartPos; + nNewStartPos = 0; + } + + if ( nNewStartPos < m_nStartPos ) + { // need to fill data *before* m_nStartPos + if ( nNewEndPos > m_nStartPos ) + { // The two regions are overlapping. + // We'll first rotate the contents of m_pMatrix so that the overlap area + // is positioned right; in the old window it is at the beginning, + // it has to go to the end. + // then we fill in the rows between new and old start pos. + + bool bCheck; + bCheck = m_xCacheSet->absolute(nNewStartPos + 1); + + // m_nEndPos < nNewEndPos when window not filled (e.g. there are fewer rows in total than window size) + m_nEndPos = std::min(nNewEndPos, m_nEndPos); + const sal_Int32 nOverlapSize = m_nEndPos - m_nStartPos; + const sal_Int32 nStartPosOffset = m_nStartPos - nNewStartPos; // by how much m_nStartPos moves + m_nStartPos = nNewStartPos; + OSL_ENSURE( o3tl::make_unsigned(nOverlapSize) <= m_pMatrix->size(), "new window end is after end of cache matrix!" ); + // the first position in m_pMatrix whose data we don't keep; + // content will be moved to m_pMatrix.begin() + ORowSetMatrix::iterator aEnd (m_pMatrix->begin() + nOverlapSize); + // the first unused position after we are done; it == m_pMatrix.end() if and only if the window is full + ORowSetMatrix::iterator aNewEnd (aEnd + nStartPosOffset); + // *m_pMatrix now looks like: + // [0; nOverlapSize) i.e. [begin(); aEnd): data kept + // [nOverlapSize; nOverlapSize + nStartPosOffset) i.e. [aEnd, aNewEnd): new data of positions < old m_nStartPos + // [nOverlapSize + nStartPosOffset; size()) i.e. [aNewEnd, end()): unused + // Note that nOverlapSize + nStartPosOffset == m_nEndPos - m_nStartPos (new values) + // When we are finished: + // [0; nStartPosOffset) i.e. [begin(); aEnd): new data of positions < old m_nStartPos + // [nStartPosOffset; nOverlapSize + nStartPosOffset) i.e. [aEnd, aNewEnd): kept + // [nOverlapSize + nStartPosOffset; size()) i.e. [aNewEnd, end()): unused + + if ( bCheck ) + { + { + ORowSetMatrix::iterator aIter(aEnd); + sal_Int32 nPos = m_nStartPos + 1; + fill(aIter, aNewEnd, nPos, bCheck); + } + + std::rotate(m_pMatrix->begin(), aEnd, aNewEnd); + if (!m_bModified) + { + // now correct the iterator in our iterator vector + // rotateCacheIterator(aEnd-m_pMatrix->begin()); //can't be used because they decrement and here we need to increment + for(auto& rCacheIter : m_aCacheIterators) + { + if ( !rCacheIter.second.pRowSet->isInsertRow() + && rCacheIter.second.aIterator != m_pMatrix->end() ) + { + const ptrdiff_t nDist = rCacheIter.second.aIterator - m_pMatrix->begin(); + if ( nDist >= nOverlapSize ) + { + // That's from outside the overlap area; invalidate iterator. + rCacheIter.second.aIterator = m_pMatrix->end(); + } + else + { + // Inside overlap area: move to correct position + OSL_ENSURE(((nDist + nStartPosOffset) >= static_cast(0)) && + ((nDist + nStartPosOffset) < static_cast(m_pMatrix->size())),"Position is invalid!"); + rCacheIter.second.aIterator += nStartPosOffset; + OSL_ENSURE(rCacheIter.second.aIterator >= m_pMatrix->begin() + && rCacheIter.second.aIterator < m_pMatrix->end(),"Iterator out of area!"); + } + } + } + } + } + else + { // normally this should never happen + OSL_FAIL("What the hell is happen here!"); + return; + } + } + else + {// no rows can be reused so fill again + reFillMatrix(nNewStartPos,nNewEndPos); + } + } + + OSL_ENSURE(nNewStartPos >= m_nStartPos, "ORowSetCache::moveWindow internal error: new start pos before current start pos"); + if ( m_nEndPos < nNewEndPos ) + { // need to fill data *after* m_nEndPos + if( nNewStartPos < m_nEndPos ) + { // The two regions are overlapping. + const sal_Int32 nRowsInCache = m_nEndPos - m_nStartPos; + if ( nRowsInCache < m_nFetchSize ) + { + // There is some unused space in *m_pMatrix; fill it + OSL_ENSURE((nRowsInCache >= static_cast(0)) && (o3tl::make_unsigned(nRowsInCache) < m_pMatrix->size()),"Position is invalid!"); + sal_Int32 nPos = m_nEndPos + 1; + bool bCheck = m_xCacheSet->absolute(nPos); + ORowSetMatrix::iterator aIter = m_pMatrix->begin() + nRowsInCache; + const sal_Int32 nRowsToFetch = std::min(nNewEndPos-m_nEndPos, m_nFetchSize-nRowsInCache); + const ORowSetMatrix::const_iterator aEnd = aIter + nRowsToFetch; + bCheck = fill(aIter, aEnd, nPos, bCheck); + m_nEndPos = nPos - 1; + OSL_ENSURE( (!bCheck && m_nEndPos <= nNewEndPos ) || + ( bCheck && m_nEndPos == nNewEndPos ), + "ORowSetCache::moveWindow opportunistic fetch-after-current-end went badly"); + } + + // A priori, the rows from begin() [inclusive] to (begin() + nNewStartPos - m_nStartPos) [exclusive] + // have to be refilled with new to-be-fetched rows. + // The rows behind this can be reused + ORowSetMatrix::iterator aIter = m_pMatrix->begin(); + const sal_Int32 nNewStartPosInMatrix = nNewStartPos - m_nStartPos; + OSL_ENSURE((nNewStartPosInMatrix >= static_cast(0)) && (o3tl::make_unsigned(nNewStartPosInMatrix) < m_pMatrix->size()),"Position is invalid!"); + // first position we reuse + const ORowSetMatrix::const_iterator aEnd = m_pMatrix->begin() + nNewStartPosInMatrix; + // End of used portion of the matrix. Is < m_pMatrix->end() if less data than window size + ORowSetMatrix::iterator aDataEnd = m_pMatrix->begin() + (m_nEndPos - m_nStartPos); + + sal_Int32 nPos = m_nEndPos + 1; + bool bCheck = m_xCacheSet->absolute(nPos); + bCheck = fill(aIter, aEnd, nPos, bCheck); // refill the region we don't need anymore + //aIter and nPos are now the position *after* last filled in one! + + // bind end to front + if(bCheck) + { + OSL_ENSURE(aIter == aEnd, "fill() said went till end, but did not."); + // rotate the end to the front + std::rotate(m_pMatrix->begin(), aIter, aDataEnd); + // now correct the iterator in our iterator vector + rotateCacheIterator( nNewStartPosInMatrix ); + m_nStartPos = nNewStartPos; + m_nEndPos = nNewEndPos; + // now I can say how many rows we have + // we have to read one row forward to ensure that we know when we are on last row + // but only when we don't know it already + bool bOk = true; + if(!m_bRowCountFinal) + bOk = m_xCacheSet->next(); + if(!bOk) + { + m_xCacheSet->previous(); // because we stand after the last row + m_nRowCount = nPos; // here we have the row count + OSL_ENSURE(nPos == m_xCacheSet->getRow(),"nPos is not valid!"); + m_bRowCountFinal = true; + } + else if(!m_bRowCountFinal) + m_nRowCount = std::max(nPos+1, m_nRowCount); //+1 because we successfully moved to row after nPos + else + OSL_ENSURE(m_nRowCount >= nPos, "Final m_nRowCount is smaller than row I moved to!"); + } + else + { // the end was reached before or at end() so we can set the start before or at nNewStartPos + // and possibly keep more of m_pMatrix than planned. + const ORowSetMatrix::const_iterator::difference_type nFetchedRows = aIter - m_pMatrix->begin(); + // *m_pMatrix now looks like: + // [0; nFetchedRows) i.e. [begin(); aIter): newly fetched data for positions m_nEndPos to m_nEndPos+nFetchedRows + // [nFetchedRows; ???) i.e. [aIter; aDataEnd]: data to be kept for positions m_nStartPos+nFetchedRows to ??? + + nPos -= 1; + m_nStartPos += nFetchedRows; + m_nEndPos = nPos; + std::rotate(m_pMatrix->begin(), aIter, aDataEnd); + // now correct the iterator in our iterator vector + rotateCacheIterator( nFetchedRows ); + + if ( !m_bRowCountFinal ) + { + m_xCacheSet->previous(); // because we stand after the last row + m_nRowCount = std::max(m_nRowCount, nPos); // here we have the row count + OSL_ENSURE(nPos == m_xCacheSet->getRow(),"nPos isn't valid!"); + m_bRowCountFinal = true; + } + + } + // here we need only to check if the beginning row is valid. If not we have to fetch it. + if(!m_pMatrix->begin()->is()) + { + aIter = m_pMatrix->begin(); + + nPos = m_nStartPos + 1; + bCheck = m_xCacheSet->absolute(nPos); + for(; !aIter->is() && bCheck;++aIter, ++nPos) + { + OSL_ENSURE(aIter != m_pMatrix->end(),"Invalid iterator"); + + *aIter = new ORowSetValueVector(m_xMetaData->getColumnCount()); + m_xCacheSet->fillValueRow(*aIter, nPos); + + bCheck = m_xCacheSet->next(); + } + } + } + else // no rows can be reused so fill again + reFillMatrix(nNewStartPos,nNewEndPos); + } + + if(!m_bRowCountFinal) + m_nRowCount = std::max(m_nPosition,m_nRowCount); + OSL_ENSURE(m_nStartPos >= 0,"ORowSetCache::moveWindow: m_nStartPos is less than 0!"); + OSL_ENSURE(m_nEndPos > m_nStartPos,"ORowSetCache::moveWindow: m_nStartPos not smaller than m_nEndPos"); + OSL_ENSURE(m_nEndPos-m_nStartPos <= m_nFetchSize,"ORowSetCache::moveWindow: m_nStartPos and m_nEndPos too far apart"); +} + +bool ORowSetCache::first( ) +{ + // First move to the first row. + // Then check if the cache window is at the beginning. + // If not, then position the window and fill it with data. + // We move the window smartly, i.e. we clear only the rows that are out of range + bool bRet = m_xCacheSet->first(); + if(bRet) + { + m_bBeforeFirst = m_bAfterLast = false; + m_nPosition = 1; + moveWindow(); + m_aMatrixIter = m_pMatrix->begin(); + } + else + { + m_bRowCountFinal = m_bBeforeFirst = m_bAfterLast = true; + m_nRowCount = m_nPosition = 0; + + OSL_ENSURE(m_bBeforeFirst || m_bNew,"ORowSetCache::first return false and BeforeFirst isn't true"); + m_aMatrixIter = m_pMatrix->end(); + } + return bRet; +} + +bool ORowSetCache::last( ) +{ + bool bRet = m_xCacheSet->last(); + if(bRet) + { + m_bBeforeFirst = m_bAfterLast = false; + if(!m_bRowCountFinal) + { + m_bRowCountFinal = true; + m_nRowCount = m_xCacheSet->getRow(); // not + 1 + } + m_nPosition = m_xCacheSet->getRow(); + moveWindow(); + // we have to repositioning because moveWindow can modify the cache + m_xCacheSet->last(); + OSL_ENSURE(((m_nPosition - m_nStartPos) - 1) < static_cast(m_pMatrix->size()),"Position is behind end()!"); + m_aMatrixIter = calcPosition(); + } + else + { + m_bRowCountFinal = m_bBeforeFirst = m_bAfterLast = true; + m_nRowCount = m_nPosition = 0; + OSL_ENSURE(m_bBeforeFirst,"ORowSetCache::last return false and BeforeFirst isn't true"); + m_aMatrixIter = m_pMatrix->end(); + } +#if OSL_DEBUG_LEVEL > 0 + if(bRet) + { + assert((*m_aMatrixIter).is() && "ORowSetCache::last: Row not valid!"); + } +#endif + + return bRet; +} + +sal_Int32 ORowSetCache::getRow( ) const +{ + return (isBeforeFirst() || isAfterLast()) ? 0 : m_nPosition; +} + +bool ORowSetCache::absolute( sal_Int32 row ) +{ + if(!row ) + throw SQLException(DBA_RES(RID_STR_NO_ABS_ZERO),nullptr,SQLSTATE_GENERAL,1000,Any() ); + + if(row < 0) + { + // here we have to scroll from the last row to backward so we have to go to last row and + // and to the previous + if(m_bRowCountFinal || last()) + { + m_nPosition = m_nRowCount + row + 1; // + row because row is negative and +1 because row==-1 means last row + if(m_nPosition < 1) + { + m_bBeforeFirst = true; + m_bAfterLast = false; + m_aMatrixIter = m_pMatrix->end(); + } + else + { + m_bBeforeFirst = false; + m_bAfterLast = m_nPosition > m_nRowCount; + moveWindow(); + OSL_ENSURE(((m_nPosition - m_nStartPos) - 1) < static_cast(m_pMatrix->size()),"Position is behind end()!"); + m_aMatrixIter = calcPosition(); + } + } + else + m_aMatrixIter = m_pMatrix->end(); + } + else + { + m_nPosition = row; + // the position flags + m_bBeforeFirst = false; + checkPositionFlags(); + + if(!m_bAfterLast) + { + moveWindow(); + checkPositionFlags(); + if(!m_bAfterLast) + m_aMatrixIter = calcPosition(); + else + m_aMatrixIter = m_pMatrix->end(); + } + else + m_aMatrixIter = m_pMatrix->end(); + } + + return !(m_bAfterLast || m_bBeforeFirst); +} + +bool ORowSetCache::relative( sal_Int32 rows ) +{ + bool bErg = true; + if(rows) + { + sal_Int32 nNewPosition = m_nPosition + rows; + + if ( m_bBeforeFirst && rows > 0 ) + nNewPosition = rows; + else if ( m_bRowCountFinal && m_bAfterLast && rows < 0 ) + nNewPosition = m_nRowCount + 1 + rows; + else + if ( m_bBeforeFirst || ( m_bRowCountFinal && m_bAfterLast ) ) + throw SQLException( DBA_RES( RID_STR_NO_RELATIVE ), nullptr, SQLSTATE_GENERAL, 1000, Any() ); + if ( nNewPosition ) + { + bErg = absolute( nNewPosition ); + bErg = bErg && !isAfterLast() && !isBeforeFirst(); + } + else + { + m_bBeforeFirst = true; + bErg = false; + } + } + return bErg; +} + +bool ORowSetCache::previous( ) +{ + bool bRet = false; + if(!isBeforeFirst()) + { + if(m_bAfterLast) // we stand after the last row so one before is the last row + bRet = last(); + else + { + m_bAfterLast = false; + --m_nPosition; + moveWindow(); + OSL_ENSURE(((m_nPosition - m_nStartPos) - 1) < static_cast(m_pMatrix->size()),"Position is behind end()!"); + + checkPositionFlags(); + + if(!m_nPosition) + { + m_bBeforeFirst = true; + m_aMatrixIter = m_pMatrix->end(); + } + else + { + m_aMatrixIter = calcPosition(); + bRet = (*m_aMatrixIter).is(); + } + } + } + return bRet; +} + +void ORowSetCache::refreshRow( ) +{ + if(isAfterLast()) + throw SQLException(DBA_RES(RID_STR_NO_REFRESH_AFTERLAST),nullptr,SQLSTATE_GENERAL,1000,Any() ); + OSL_ENSURE(m_aMatrixIter != m_pMatrix->end(),"refreshRow() called for invalid row!"); + m_xCacheSet->refreshRow(); + m_xCacheSet->fillValueRow(*m_aMatrixIter,m_nPosition); + if ( m_bNew ) + { + cancelRowModification(); + } +} + +bool ORowSetCache::rowUpdated( ) +{ + return m_xCacheSet->rowUpdated(); +} + +bool ORowSetCache::rowInserted( ) +{ + return m_xCacheSet->rowInserted(); +} + +// XResultSetUpdate +bool ORowSetCache::insertRow(std::vector< Any >& o_aBookmarks) +{ + if ( !m_bNew || !m_aInsertRow->is() ) + throw SQLException(DBA_RES(RID_STR_NO_MOVETOINSERTROW_CALLED),nullptr,SQLSTATE_GENERAL,1000,Any() ); + + m_xCacheSet->insertRow(*m_aInsertRow,m_aUpdateTable); + + bool bRet( rowInserted() ); + if ( bRet ) + { + ++m_nRowCount; + Any aBookmark = (**m_aInsertRow)[0].makeAny(); + m_bAfterLast = m_bBeforeFirst = false; + if(aBookmark.hasValue()) + { + moveToBookmark(aBookmark); + // update the cached values + ORowSetValueVector::Vector& rCurrentRow = **m_aMatrixIter; + ORowSetMatrix::const_iterator aIter = m_pMatrix->begin(); + for(;aIter != m_pMatrix->end();++aIter) + { + if ( m_aMatrixIter != aIter && aIter->is() && m_xCacheSet->columnValuesUpdated(**aIter,rCurrentRow) ) + { + o_aBookmarks.push_back(lcl_getBookmark((**aIter)[0], m_xCacheSet.get())); + } + } + } + else + { + OSL_FAIL("There must be a bookmark after the row was inserted!"); + } + } + return bRet; +} + +void ORowSetCache::resetInsertRow(bool _bClearInsertRow) +{ + if ( _bClearInsertRow ) + clearInsertRow(); + m_bNew = false; + m_bModified = false; +} + +void ORowSetCache::cancelRowModification() +{ + // clear the insertrow references -> implies that the current row of the rowset changes as well + for(auto& rCacheIter : m_aCacheIterators) + { + if ( rCacheIter.second.pRowSet->isInsertRow() && rCacheIter.second.aIterator == m_aInsertRow ) + rCacheIter.second.aIterator = m_pMatrix->end(); + } + resetInsertRow(false); +} + +void ORowSetCache::updateRow( ORowSetMatrix::iterator const & _rUpdateRow, std::vector< Any >& o_aBookmarks ) +{ + if(isAfterLast() || isBeforeFirst()) + throw SQLException(DBA_RES(RID_STR_NO_UPDATEROW),nullptr,SQLSTATE_GENERAL,1000,Any() ); + + Any aBookmark = (**_rUpdateRow)[0].makeAny(); + OSL_ENSURE(aBookmark.hasValue(),"Bookmark must have a value!"); + // here we don't have to reposition our CacheSet, when we try to update a row, + // the row was already fetched + moveToBookmark(aBookmark); + m_xCacheSet->updateRow(*_rUpdateRow,*m_aMatrixIter,m_aUpdateTable); + // refetch the whole row + (*m_aMatrixIter) = nullptr; + + if ( moveToBookmark(aBookmark) ) + { + // update the cached values + ORowSetValueVector::Vector& rCurrentRow = **m_aMatrixIter; + ORowSetMatrix::const_iterator aIter = m_pMatrix->begin(); + for(;aIter != m_pMatrix->end();++aIter) + { + if ( m_aMatrixIter != aIter && aIter->is() && m_xCacheSet->columnValuesUpdated(**aIter,rCurrentRow) ) + { + o_aBookmarks.push_back(lcl_getBookmark((**aIter)[0], m_xCacheSet.get())); + } + } + } + + m_bModified = false; +} + +bool ORowSetCache::deleteRow( ) +{ + if(isAfterLast() || isBeforeFirst()) + throw SQLException(DBA_RES(RID_STR_NO_DELETEROW),nullptr,SQLSTATE_GENERAL,1000,Any() ); + + m_xCacheSet->deleteRow(*m_aMatrixIter,m_aUpdateTable); + if ( !m_xCacheSet->rowDeleted() ) + return false; + + --m_nRowCount; + OSL_ENSURE(((m_nPosition - m_nStartPos) - 1) < static_cast(m_pMatrix->size()),"Position is behind end()!"); + ORowSetMatrix::iterator aPos = calcPosition(); + (*aPos) = nullptr; + + ORowSetMatrix::const_iterator aEnd = m_pMatrix->end(); + for(++aPos;aPos != aEnd && aPos->is();++aPos) + { + *(aPos-1) = *aPos; + (*aPos) = nullptr; + } + m_aMatrixIter = m_pMatrix->end(); + + --m_nPosition; + return true; +} + +void ORowSetCache::cancelRowUpdates( ) +{ + m_bNew = m_bModified = false; + if(!m_nPosition) + { + OSL_FAIL("cancelRowUpdates:Invalid positions pos == 0"); + ::dbtools::throwFunctionSequenceException(nullptr); + } + + if(m_xCacheSet->absolute(m_nPosition)) + m_xCacheSet->fillValueRow(*m_aMatrixIter,m_nPosition); + else + { + OSL_FAIL("cancelRowUpdates couldn't position right with absolute"); + ::dbtools::throwFunctionSequenceException(nullptr); + } +} + +void ORowSetCache::moveToInsertRow( ) +{ + m_bNew = true; + m_bAfterLast = false; + + m_aInsertRow = m_pInsertMatrix->begin(); + if(!m_aInsertRow->is()) + *m_aInsertRow = new ORowSetValueVector(m_xMetaData->getColumnCount()); + + // we don't unbound the bookmark column + ORowSetValueVector::Vector::iterator aIter = (*m_aInsertRow)->begin()+1; + ORowSetValueVector::Vector::const_iterator aEnd = (*m_aInsertRow)->end(); + for(sal_Int32 i = 1;aIter != aEnd;++aIter,++i) + { + aIter->setBound(false); + aIter->setModified(false); + aIter->setNull(); + aIter->setTypeKind(m_xMetaData->getColumnType(i)); + } +} + +ORowSetCacheIterator ORowSetCache::createIterator(ORowSetBase* _pRowSet) +{ + ORowSetCacheIterator_Helper aHelper; + aHelper.aIterator = m_pMatrix->end(); + aHelper.pRowSet = _pRowSet; + return ORowSetCacheIterator(m_aCacheIterators.insert(m_aCacheIterators.begin(),ORowSetCacheMap::value_type(m_aCacheIterators.size()+1,aHelper)),this,_pRowSet); +} + +void ORowSetCache::deleteIterator(const ORowSetBase* _pRowSet) +{ + ORowSetCacheMap::const_iterator aCacheIter = m_aCacheIterators.begin(); + for(;aCacheIter != m_aCacheIterators.end();) + { + if ( aCacheIter->second.pRowSet == _pRowSet ) + { + aCacheIter = m_aCacheIterators.erase(aCacheIter); + } + else + ++aCacheIter; + } +} + +void ORowSetCache::rotateCacheIterator(ORowSetMatrix::difference_type _nDist) +{ + if (m_bModified) + return; + + if(!_nDist) + return; + + // now correct the iterator in our iterator vector + for(auto& rCacheIter : m_aCacheIterators) + { + if ( !rCacheIter.second.pRowSet->isInsertRow() + && rCacheIter.second.aIterator != m_pMatrix->end()) + { + ptrdiff_t nDist = rCacheIter.second.aIterator - m_pMatrix->begin(); + if(nDist < _nDist) + { + rCacheIter.second.aIterator = m_pMatrix->end(); + } + else + { + OSL_ENSURE((rCacheIter.second.aIterator - m_pMatrix->begin()) >= _nDist,"Invalid Dist value!"); + rCacheIter.second.aIterator -= _nDist; + OSL_ENSURE(rCacheIter.second.aIterator >= m_pMatrix->begin() + && rCacheIter.second.aIterator < m_pMatrix->end(),"Iterator out of area!"); + } + } + } +} + +void ORowSetCache::rotateAllCacheIterators() +{ + if (m_bModified) + return; + + // now correct the iterator in our iterator vector + for (auto& rCacheIter : m_aCacheIterators) + { + if (!rCacheIter.second.pRowSet->isInsertRow()) + { + rCacheIter.second.aIterator = m_pMatrix->end(); + } + } +} + +void ORowSetCache::setUpdateIterator(const ORowSetMatrix::iterator& _rOriginalRow) +{ + m_aInsertRow = m_pInsertMatrix->begin(); + if(!m_aInsertRow->is()) + *m_aInsertRow = new ORowSetValueVector(m_xMetaData->getColumnCount()); + + (*(*m_aInsertRow)) = *(*_rOriginalRow); + // we don't unbound the bookmark column + for(auto& rItem : **m_aInsertRow) + rItem.setModified(false); +} + +void ORowSetCache::checkPositionFlags() +{ + if(m_bRowCountFinal) + { + m_bAfterLast = m_nPosition > m_nRowCount; + if(m_bAfterLast) + m_nPosition = 0;//m_nRowCount; + } +} + +void ORowSetCache::checkUpdateConditions(sal_Int32 columnIndex) +{ + if(m_bAfterLast || columnIndex >= static_cast((*m_aInsertRow)->size())) + throwFunctionSequenceException(m_xSet.get()); +} + +bool ORowSetCache::checkInnerJoin(const ::connectivity::OSQLParseNode *pNode,const Reference< XConnection>& _xConnection,const OUString& _sUpdateTableName) +{ + bool bOk = false; + if (pNode->count() == 3 && // expression in parentheses + SQL_ISPUNCTUATION(pNode->getChild(0),"(") && + SQL_ISPUNCTUATION(pNode->getChild(2),")")) + { + bOk = checkInnerJoin(pNode->getChild(1),_xConnection,_sUpdateTableName); + } + else if ((SQL_ISRULE(pNode,search_condition) || SQL_ISRULE(pNode,boolean_term)) && // AND/OR link + pNode->count() == 3) + { + // only allow an AND link + if ( SQL_ISTOKEN(pNode->getChild(1),AND) ) + bOk = checkInnerJoin(pNode->getChild(0),_xConnection,_sUpdateTableName) + && checkInnerJoin(pNode->getChild(2),_xConnection,_sUpdateTableName); + } + else if (SQL_ISRULE(pNode,comparison_predicate)) + { + // only the comparison of columns is allowed + OSL_ENSURE(pNode->count() == 3,"checkInnerJoin: Error in Parse Tree"); + if (!(SQL_ISRULE(pNode->getChild(0),column_ref) && + SQL_ISRULE(pNode->getChild(2),column_ref) && + pNode->getChild(1)->getNodeType() == SQLNodeType::Equal)) + { + bOk = false; + } + else + { + OUString sColumnName,sTableRange; + OSQLParseTreeIterator::getColumnRange( pNode->getChild(0), _xConnection, sColumnName, sTableRange ); + bOk = sTableRange == _sUpdateTableName; + if ( !bOk ) + { + OSQLParseTreeIterator::getColumnRange( pNode->getChild(2), _xConnection, sColumnName, sTableRange ); + bOk = sTableRange == _sUpdateTableName; + } + } + } + return bOk; +} + +bool ORowSetCache::checkJoin(const Reference< XConnection>& _xConnection, + const Reference< XSingleSelectQueryAnalyzer >& _xAnalyzer, + const OUString& _sUpdateTableName ) +{ + bool bOk = false; + OUString sSql = _xAnalyzer->getQuery(); + OUString sErrorMsg; + ::connectivity::OSQLParser aSqlParser( m_aContext ); + std::unique_ptr< ::connectivity::OSQLParseNode> pSqlParseNode( aSqlParser.parseTree(sErrorMsg,sSql)); + if ( pSqlParseNode && SQL_ISRULE(pSqlParseNode, select_statement) ) + { + OSQLParseNode* pTableRefCommalist = pSqlParseNode->getByRule(::connectivity::OSQLParseNode::table_ref_commalist); + OSL_ENSURE(pTableRefCommalist,"NO tables why!?"); + if(pTableRefCommalist && pTableRefCommalist->count() == 1) + { + // we found only one element so it must some kind of join here + OSQLParseNode* pJoin = pTableRefCommalist->getByRule(::connectivity::OSQLParseNode::qualified_join); + if(pJoin) + { // we are only interested in qualified joins like RIGHT or LEFT + OSQLParseNode* pJoinType = pJoin->getChild(1); + OSQLParseNode* pOuterType = nullptr; + if(SQL_ISRULE(pJoinType,join_type) && pJoinType->count() == 2) + pOuterType = pJoinType->getChild(0); + else if(SQL_ISRULE(pJoinType,outer_join_type)) + pOuterType = pJoinType; + + bool bCheck = false; + bool bLeftSide = false; + if(pOuterType) + { // found outer join + bLeftSide = SQL_ISTOKEN(pOuterType->getChild(0),LEFT); + bCheck = bLeftSide || SQL_ISTOKEN(pOuterType->getChild(0),RIGHT); + } + + if(bCheck) + { // here we know that we have to check on which side our table resides + const OSQLParseNode* pTableRef; + if(bLeftSide) + pTableRef = pJoin->getChild(0); + else + pTableRef = pJoin->getChild(3); + OSL_ENSURE(SQL_ISRULE(pTableRef,table_ref),"Must be a tableref here!"); + + OUString sTableRange = OSQLParseNode::getTableRange(pTableRef); + if(sTableRange.isEmpty()) + pTableRef->getChild(0)->parseNodeToStr( sTableRange, _xConnection, nullptr, false, false ); + bOk = sTableRange == _sUpdateTableName; + } + } + } + else + { + OSQLParseNode* pWhereOpt = pSqlParseNode->getChild(3)->getChild(1); + if ( pWhereOpt && !pWhereOpt->isLeaf() ) + bOk = checkInnerJoin(pWhereOpt->getChild(1),_xConnection,_sUpdateTableName); + } + } + return bOk; +} + +void ORowSetCache::clearInsertRow() +{ + // we don't unbound the bookmark column + if ( m_aInsertRow != m_pInsertMatrix->end() && m_aInsertRow->is() ) + { + ORowSetValueVector::Vector::iterator aIter = (*m_aInsertRow)->begin()+1; + ORowSetValueVector::Vector::const_iterator aEnd = (*m_aInsertRow)->end(); + for(;aIter != aEnd;++aIter) + { + aIter->setBound(false); + aIter->setModified(false); + aIter->setNull(); + } + } +} + +ORowSetMatrix::iterator ORowSetCache::calcPosition() const +{ + sal_Int32 nValue = (m_nPosition - m_nStartPos) - 1; + OSL_ENSURE((nValue >= static_cast(0)) && (o3tl::make_unsigned(nValue) < m_pMatrix->size()),"Position is invalid!"); + return ( nValue < 0 || o3tl::make_unsigned(nValue) >= m_pMatrix->size() ) ? m_pMatrix->end() : (m_pMatrix->begin() + nValue); +} + +TORowSetOldRowHelperRef ORowSetCache::registerOldRow() +{ + TORowSetOldRowHelperRef pRef = new ORowSetOldRowHelper(ORowSetRow()); + m_aOldRows.push_back(pRef); + return pRef; +} + +void ORowSetCache::deregisterOldRow(const TORowSetOldRowHelperRef& _rRow) +{ + TOldRowSetRows::iterator aOldRowIter = std::find_if(m_aOldRows.begin(), m_aOldRows.end(), + [&_rRow](const TORowSetOldRowHelperRef& rxOldRow) { return rxOldRow.get() == _rRow.get(); }); + if (aOldRowIter != m_aOldRows.end()) + m_aOldRows.erase(aOldRowIter); +} + +bool ORowSetCache::reFillMatrix(sal_Int32 _nNewStartPos, sal_Int32 _nNewEndPos) +{ + for (const auto& rxOldRow : m_aOldRows) + { + if ( rxOldRow.is() && rxOldRow->getRow().is() ) + rxOldRow->setRow(new ORowSetValueVector( *(rxOldRow->getRow()) ) ); + } + sal_Int32 nNewSt = _nNewStartPos; + bool bRet = fillMatrix(nNewSt,_nNewEndPos); + m_nStartPos = nNewSt; + m_nEndPos = _nNewEndPos; + rotateAllCacheIterators(); // invalidate every iterator + return bRet; +} + +bool ORowSetCache::fill(ORowSetMatrix::iterator& _aIter, const ORowSetMatrix::const_iterator& _aEnd, sal_Int32& _nPos, bool _bCheck) +{ + const sal_Int32 nColumnCount = m_xMetaData->getColumnCount(); + for (; _bCheck && _aIter != _aEnd; ++_aIter, ++_nPos) + { + if ( !_aIter->is() ) + *_aIter = new ORowSetValueVector(nColumnCount); + else + { + for (const auto& rxOldRow : m_aOldRows) + { + if ( rxOldRow->getRow() == *_aIter ) + *_aIter = new ORowSetValueVector(nColumnCount); + } + } + m_xCacheSet->fillValueRow(*_aIter, _nPos); + _bCheck = m_xCacheSet->next(); + } + return _bCheck; +} + +bool ORowSetCache::isResultSetChanged() const +{ + return m_xCacheSet->isResultSetChanged(); +} + +void ORowSetCache::reset(const Reference< XResultSet>& _xDriverSet) +{ + m_xSet = _xDriverSet; + m_xMetaData.set(Reference< XResultSetMetaDataSupplier >(_xDriverSet,UNO_QUERY_THROW)->getMetaData()); + m_xCacheSet->reset(_xDriverSet); + + m_bRowCountFinal = false; + m_nRowCount = 0; + reFillMatrix(m_nStartPos,m_nEndPos); +} + +void ORowSetCache::impl_updateRowFromCache_throw(ORowSetValueVector::Vector& io_aRow + ,std::vector const & o_ChangedColumns) +{ + if ( o_ChangedColumns.size() > 1 ) + { + for (auto const& elem : *m_pMatrix) + { + if ( elem.is() && m_xCacheSet->updateColumnValues(*elem,io_aRow,o_ChangedColumns)) + { + return; + } + } + m_xCacheSet->fillMissingValues(io_aRow); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3