/* -*- 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: */