summaryrefslogtreecommitdiffstats
path: root/dbaccess/source/core/api/RowSetCache.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'dbaccess/source/core/api/RowSetCache.cxx')
-rw-r--r--dbaccess/source/core/api/RowSetCache.cxx1713
1 files changed, 1713 insertions, 0 deletions
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 <memory>
+#include "BookmarkSet.hxx"
+#include "KeySet.hxx"
+#include "OptimisticSet.hxx"
+#include "RowSetBase.hxx"
+#include "RowSetCache.hxx"
+#include "StaticSet.hxx"
+#include "WrappedResultSet.hxx"
+#include <core_resource.hxx>
+#include <strings.hrc>
+#include <strings.hxx>
+
+#include <com/sun/star/sdbc/ColumnValue.hpp>
+#include <com/sun/star/sdbc/ResultSetConcurrency.hpp>
+#include <com/sun/star/sdbc/ResultSetType.hpp>
+#include <com/sun/star/sdbcx/CompareBookmark.hpp>
+#include <com/sun/star/sdbcx/Privilege.hpp>
+#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
+#include <com/sun/star/sdbcx/XTablesSupplier.hpp>
+
+#include <comphelper/extract.hxx>
+#include <comphelper/types.hxx>
+#include <connectivity/dbexception.hxx>
+#include <connectivity/dbtools.hxx>
+#include <connectivity/sqliterator.hxx>
+#include <connectivity/sqlnode.hxx>
+#include <connectivity/sqlparse.hxx>
+#include <sqlbison.hxx>
+#include <tools/diagnose_ex.h>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <algorithm>
+
+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<XComponentContext>& _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<XTablesSupplier> xTabSup(_xAnalyzer,UNO_QUERY);
+ OSL_ENSURE(xTabSup.is(),"ORowSet::execute composer isn't a tablesupplier!");
+ Reference<XNameAccess> 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<OptimisticSet> 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> 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<XPropertySet> xSet(m_aUpdateTable,UNO_QUERY);
+ const Reference<XNameAccess> xPrimaryKeyColumns = dbtools::getPrimaryKeyColumns_throw(xSet);
+ if ( xPrimaryKeyColumns.is() )
+ {
+ Reference<XColumnsSupplier> xColSup(_xAnalyzer,UNO_QUERY);
+ if ( xColSup.is() )
+ {
+ Reference<XNameAccess> xSelColumns = xColSup->getColumns();
+ Reference<XDatabaseMetaData> 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<XResultSetUpdate>(_xRs,UNO_QUERY).is()) // this interface is optional so we have to check it
+ {
+ Reference<XPropertySet> 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<XDatabaseMetaData> xMeta = xConnection->getMetaData();
+ SelectColumnsMetaData aColumnNames(xMeta.is() && xMeta->supportsMixedCaseQuotedIdentifiers());
+ Reference<XColumnsSupplier> xColSup(_xAnalyzer,UNO_QUERY);
+ Reference<XNameAccess> xSelColumns = xColSup->getColumns();
+ Reference<XNameAccess> 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<XPropertySet> 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<OKeySet> pKeySet = new OKeySet(m_aUpdateTable, aUpdateTableName ,_xAnalyzer,_aParameterValueForCache,i_nMaxRows,m_nRowCount);
+ try
+ {
+ m_xCacheSet = pKeySet;
+ pKeySet->construct(_xRs,i_sRowSetFilter);
+
+ if(Reference<XResultSetUpdate>(_xRs,UNO_QUERY).is()) // this interface is optional so we have to check it
+ {
+ Reference<XPropertySet> 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<sal_Int32> aPositions;
+ std::map<sal_Int32,bool> 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<sal_Int32>::const_iterator aIter = aPositions.begin();
+ ORowSetCacheMap::iterator aCacheIter = m_aCacheIterators.begin();
+ for(const auto& rPosChange : aCacheIterToChange)
+ {
+ if ( rPosChange.second )
+ {
+ OSL_ENSURE((*aIter >= static_cast<ORowSetMatrix::difference_type>(0)) && (*aIter < static_cast<sal_Int32>(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<sal_Int32>& 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<sal_Int32>& 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<sal_Int32>& o_ChangedColumns
+ )
+{
+ checkUpdateConditions(columnIndex);
+
+ Sequence<sal_Int8> 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<sal_Int32>& 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<sal_Int32>& 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<sal_Int32>(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<sal_Int32>(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<ORowSetMatrix::difference_type>(0)) &&
+ ((nDist + nStartPosOffset) < static_cast<sal_Int32>(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<ORowSetMatrix::difference_type>(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<ORowSetMatrix::difference_type>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>((*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<ORowSetMatrix::difference_type>(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<sal_Int32> 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: */