bump product version to 7.2.5.1
[LibreOffice.git] / connectivity / source / drivers / dbase / DIndex.cxx
blobd168de39eb77733dd4d4b6a4a1ee15ab3c07e9f4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <dbase/DIndex.hxx>
21 #include <dbase/DIndexColumns.hxx>
22 #include <dbase/DTable.hxx>
23 #include <dbase/DIndexIter.hxx>
24 #include <osl/file.hxx>
25 #include <sal/log.hxx>
26 #include <tools/config.hxx>
27 #include <connectivity/CommonTools.hxx>
28 #include <com/sun/star/sdbc/XResultSet.hpp>
29 #include <com/sun/star/sdbc/XRow.hpp>
30 #include <unotools/ucbhelper.hxx>
31 #include <comphelper/servicehelper.hxx>
32 #include <comphelper/types.hxx>
33 #include <cppuhelper/typeprovider.hxx>
34 #include <connectivity/dbexception.hxx>
35 #include <dbase/DResultSet.hxx>
36 #include <strings.hrc>
37 #include <unotools/sharedunocomponent.hxx>
39 using namespace ::comphelper;
41 using namespace connectivity;
42 using namespace utl;
43 using namespace ::cppu;
44 using namespace connectivity::file;
45 using namespace connectivity::sdbcx;
46 using namespace connectivity::dbase;
47 using namespace com::sun::star::sdbc;
48 using namespace com::sun::star::sdbcx;
49 using namespace com::sun::star::uno;
50 using namespace com::sun::star::beans;
51 using namespace com::sun::star::lang;
53 IMPLEMENT_SERVICE_INFO(ODbaseIndex,"com.sun.star.sdbcx.driver.dbase.Index","com.sun.star.sdbcx.Index");
55 ODbaseIndex::ODbaseIndex(ODbaseTable* _pTable)
56 : OIndex(true/*_pTable->getConnection()->getMetaData()->supportsMixedCaseQuotedIdentifiers()*/)
57 , m_nCurNode(NODE_NOTFOUND)
58 , m_nPageCount(0)
59 , m_nRootPage(0)
60 , m_pTable(_pTable)
61 , m_bUseCollector(false)
63 construct();
66 ODbaseIndex::ODbaseIndex( ODbaseTable* _pTable,
67 const NDXHeader& _rHeader,
68 const OUString& _rName)
69 : OIndex(_rName, OUString(), _rHeader.db_unique, false, false, true)
70 , m_aHeader(_rHeader)
71 , m_nCurNode(NODE_NOTFOUND)
72 , m_nPageCount(0)
73 , m_nRootPage(0)
74 , m_pTable(_pTable)
75 , m_bUseCollector(false)
77 construct();
80 ODbaseIndex::~ODbaseIndex()
82 closeImpl();
85 void ODbaseIndex::refreshColumns()
87 ::osl::MutexGuard aGuard( m_aMutex );
89 ::std::vector< OUString> aVector;
90 if(!isNew())
92 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
93 OSL_ENSURE(m_aHeader.db_name[0] != '\0',"Invalid name for the column!");
94 aVector.push_back(OUString::createFromAscii(m_aHeader.db_name));
97 if(m_pColumns)
98 m_pColumns->reFill(aVector);
99 else
100 m_pColumns.reset(new ODbaseIndexColumns(this,m_aMutex,aVector));
103 Sequence< sal_Int8 > ODbaseIndex::getUnoTunnelId()
105 static ::cppu::OImplementationId implId;
107 return implId.getImplementationId();
110 // XUnoTunnel
112 sal_Int64 ODbaseIndex::getSomething( const Sequence< sal_Int8 > & rId )
114 return (isUnoTunnelId<ODbaseIndex>(rId))
115 ? reinterpret_cast< sal_Int64 >( this )
116 : ODbaseIndex_BASE::getSomething(rId);
119 ONDXPagePtr const & ODbaseIndex::getRoot()
121 openIndexFile();
122 if (!m_aRoot.Is())
124 m_nRootPage = m_aHeader.db_rootpage;
125 m_nPageCount = m_aHeader.db_pagecount;
126 m_aRoot = CreatePage(m_nRootPage,nullptr,true);
128 return m_aRoot;
131 void ODbaseIndex::openIndexFile()
133 if(m_pFileStream)
134 return;
136 OUString sFile = getCompletePath();
137 if(UCBContentHelper::Exists(sFile))
139 m_pFileStream = OFileTable::createStream_simpleError(sFile, StreamMode::READWRITE | StreamMode::NOCREATE | StreamMode::SHARE_DENYWRITE);
140 if (!m_pFileStream)
141 m_pFileStream = OFileTable::createStream_simpleError(sFile, StreamMode::READ | StreamMode::NOCREATE | StreamMode::SHARE_DENYNONE);
142 if(m_pFileStream)
144 m_pFileStream->SetEndian(SvStreamEndian::LITTLE);
145 m_pFileStream->SetBufferSize(DINDEX_PAGE_SIZE);
146 (*m_pFileStream) >> *this;
149 if(!m_pFileStream)
151 const OUString sError( m_pTable->getConnection()->getResources().getResourceStringWithSubstitution(
152 STR_COULD_NOT_LOAD_FILE,
153 "$filename$", sFile
154 ) );
155 ::dbtools::throwGenericSQLException( sError, *this );
159 std::unique_ptr<OIndexIterator> ODbaseIndex::createIterator()
161 openIndexFile();
162 return std::make_unique<OIndexIterator>(this);
165 bool ODbaseIndex::ConvertToKey(ONDXKey* rKey, sal_uInt32 nRec, const ORowSetValue& rValue)
167 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
168 // Search a specific value in Index
169 // If the Index is unique, the key doesn't matter
172 if (m_aHeader.db_keytype == 0)
174 *rKey = ONDXKey(rValue.getString(), nRec );
176 else
178 if (rValue.isNull())
179 *rKey = ONDXKey(rValue.getDouble(), DataType::DOUBLE, nRec );
180 else
181 *rKey = ONDXKey(rValue.getDouble(), nRec );
184 catch (Exception&)
186 OSL_ASSERT(false);
187 return false;
189 return true;
193 bool ODbaseIndex::Find(sal_uInt32 nRec, const ORowSetValue& rValue)
195 openIndexFile();
196 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
197 // Search a specific value in Index
198 // If the Index is unique, the key doesn't matter
199 ONDXKey aKey;
200 return ConvertToKey(&aKey, nRec, rValue) && getRoot()->Find(aKey);
204 bool ODbaseIndex::Insert(sal_uInt32 nRec, const ORowSetValue& rValue)
206 openIndexFile();
207 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
208 ONDXKey aKey;
210 // Does the value already exist
211 // Use Find() always to determine the actual leaf
212 if (!ConvertToKey(&aKey, nRec, rValue) || (getRoot()->Find(aKey) && isUnique()))
213 return false;
215 ONDXNode aNewNode(aKey);
217 // insert in the current leaf
218 if (!m_aCurLeaf.Is())
219 return false;
221 bool bResult = m_aCurLeaf->Insert(aNewNode);
222 Release(bResult);
224 return bResult;
228 bool ODbaseIndex::Update(sal_uInt32 nRec, const ORowSetValue& rOldValue,
229 const ORowSetValue& rNewValue)
231 openIndexFile();
232 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
233 ONDXKey aKey;
234 if (!ConvertToKey(&aKey, nRec, rNewValue) || (isUnique() && getRoot()->Find(aKey)))
235 return false;
236 else
237 return Delete(nRec, rOldValue) && Insert(nRec,rNewValue);
241 bool ODbaseIndex::Delete(sal_uInt32 nRec, const ORowSetValue& rValue)
243 openIndexFile();
244 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
245 // Does the value already exist
246 // Always use Find() to determine the actual leaf
247 ONDXKey aKey;
248 if (!ConvertToKey(&aKey, nRec, rValue) || !getRoot()->Find(aKey))
249 return false;
251 // insert in the current leaf
252 if (!m_aCurLeaf.Is())
253 return false;
254 #if OSL_DEBUG_LEVEL > 1
255 m_aRoot->PrintPage();
256 #endif
258 m_aCurLeaf->Delete(m_nCurNode);
259 return true;
262 void ODbaseIndex::Collect(ONDXPage* pPage)
264 if (pPage)
265 m_aCollector.push_back(pPage);
268 void ODbaseIndex::Release(bool bSave)
270 // Release the Index-resources
271 m_bUseCollector = false;
273 if (m_aCurLeaf.Is())
275 m_aCurLeaf->Release(bSave);
276 m_aCurLeaf.Clear();
279 // Release the root
280 if (m_aRoot.Is())
282 m_aRoot->Release(bSave);
283 m_aRoot.Clear();
285 // Release all references, before the FileStream will be closed
286 for (auto& i : m_aCollector)
287 i->QueryDelete();
289 m_aCollector.clear();
291 // Header modified?
292 if (bSave && (m_aHeader.db_rootpage != m_nRootPage ||
293 m_aHeader.db_pagecount != m_nPageCount))
295 m_aHeader.db_rootpage = m_nRootPage;
296 m_aHeader.db_pagecount = m_nPageCount;
297 WriteODbaseIndex( *m_pFileStream, *this );
299 m_nRootPage = m_nPageCount = 0;
300 m_nCurNode = NODE_NOTFOUND;
302 closeImpl();
305 void ODbaseIndex::closeImpl()
307 m_pFileStream.reset();
310 ONDXPage* ODbaseIndex::CreatePage(sal_uInt32 nPagePos, ONDXPage* pParent, bool bLoad)
312 OSL_ENSURE(m_pFileStream,"FileStream is not opened!");
314 ONDXPage* pPage;
315 if ( !m_aCollector.empty() )
317 pPage = *(m_aCollector.rbegin());
318 m_aCollector.pop_back();
319 pPage->SetPagePos(nPagePos);
320 pPage->SetParent(pParent);
322 else
323 pPage = new ONDXPage(*this, nPagePos, pParent);
325 if (bLoad)
326 (*m_pFileStream) >> *pPage;
328 return pPage;
331 void connectivity::dbase::ReadHeader(
332 SvStream & rStream, ODbaseIndex::NDXHeader & rHeader)
334 #if !defined(NDEBUG)
335 sal_uInt64 const nOldPos(rStream.Tell());
336 #endif
337 rStream.ReadUInt32(rHeader.db_rootpage);
338 rStream.ReadUInt32(rHeader.db_pagecount);
339 rStream.ReadBytes(&rHeader.db_free, 4);
340 rStream.ReadUInt16(rHeader.db_keylen);
341 rStream.ReadUInt16(rHeader.db_maxkeys);
342 rStream.ReadUInt16(rHeader.db_keytype);
343 rStream.ReadUInt16(rHeader.db_keyrec);
344 rStream.ReadBytes(&rHeader.db_free1, 3);
345 rStream.ReadUChar(rHeader.db_unique);
346 rStream.ReadBytes(&rHeader.db_name, 488);
347 assert(rStream.GetError() || rStream.Tell() == nOldPos + DINDEX_PAGE_SIZE);
350 SvStream& connectivity::dbase::operator >> (SvStream &rStream, ODbaseIndex& rIndex)
352 rStream.Seek(0);
353 ReadHeader(rStream, rIndex.m_aHeader);
355 rIndex.m_nRootPage = rIndex.m_aHeader.db_rootpage;
356 rIndex.m_nPageCount = rIndex.m_aHeader.db_pagecount;
357 return rStream;
360 SvStream& connectivity::dbase::WriteODbaseIndex(SvStream &rStream, ODbaseIndex& rIndex)
362 rStream.Seek(0);
363 rStream.WriteUInt32(rIndex.m_aHeader.db_rootpage);
364 rStream.WriteUInt32(rIndex.m_aHeader.db_pagecount);
365 rStream.WriteBytes(&rIndex.m_aHeader.db_free, 4);
366 rStream.WriteUInt16(rIndex.m_aHeader.db_keylen);
367 rStream.WriteUInt16(rIndex.m_aHeader.db_maxkeys);
368 rStream.WriteUInt16(rIndex.m_aHeader.db_keytype);
369 rStream.WriteUInt16(rIndex.m_aHeader.db_keyrec);
370 rStream.WriteBytes(&rIndex.m_aHeader.db_free1, 3);
371 rStream.WriteUChar(rIndex.m_aHeader.db_unique);
372 rStream.WriteBytes(&rIndex.m_aHeader.db_name, 488);
373 assert(rStream.GetError() || rStream.Tell() == DINDEX_PAGE_SIZE);
374 SAL_WARN_IF(rStream.GetError(), "connectivity.dbase", "write error");
375 return rStream;
378 OUString ODbaseIndex::getCompletePath() const
380 OUString sDir = m_pTable->getConnection()->getURL() +
381 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER) +
382 m_Name + ".ndx";
383 return sDir;
386 void ODbaseIndex::createINFEntry()
388 // synchronize inf-file
389 const OUString sEntry(m_Name + ".ndx");
391 OUString sCfgFile(m_pTable->getConnection()->getURL() +
392 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER) +
393 m_pTable->getName() +
394 ".inf");
396 OUString sPhysicalPath;
397 osl::FileBase::getSystemPathFromFileURL(sCfgFile, sPhysicalPath);
399 Config aInfFile(sPhysicalPath);
400 aInfFile.SetGroup(dBASE_III_GROUP);
402 sal_uInt16 nSuffix = aInfFile.GetKeyCount();
403 OString aNewEntry,aKeyName;
404 bool bCase = isCaseSensitive();
405 while (aNewEntry.isEmpty())
407 aNewEntry = OString("NDX");
408 aNewEntry += OString::number(++nSuffix);
409 for (sal_uInt16 i = 0; i < aInfFile.GetKeyCount(); i++)
411 aKeyName = aInfFile.GetKeyName(i);
412 if (bCase ? aKeyName == aNewEntry : aKeyName.equalsIgnoreAsciiCase(aNewEntry))
414 aNewEntry.clear();
415 break;
419 aInfFile.WriteKey(aNewEntry, OUStringToOString(sEntry, m_pTable->getConnection()->getTextEncoding()));
422 void ODbaseIndex::DropImpl()
424 closeImpl();
426 OUString sPath = getCompletePath();
427 if(UCBContentHelper::Exists(sPath))
429 if(!UCBContentHelper::Kill(sPath))
430 m_pTable->getConnection()->throwGenericSQLException(STR_COULD_NOT_DELETE_INDEX,*m_pTable);
433 // synchronize inf-file
434 OUString sCfgFile = m_pTable->getConnection()->getURL() +
435 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER) +
436 m_pTable->getName() + ".inf";
438 OUString sPhysicalPath;
439 OSL_VERIFY( osl::FileBase::getSystemPathFromFileURL(sCfgFile, sPhysicalPath)
440 == osl::FileBase::E_None );
442 Config aInfFile(sPhysicalPath);
443 aInfFile.SetGroup(dBASE_III_GROUP);
444 sal_uInt16 nKeyCnt = aInfFile.GetKeyCount();
445 OString aKeyName;
446 OUString sEntry = m_Name + ".ndx";
448 // delete entries from the inf file
449 for (sal_uInt16 nKey = 0; nKey < nKeyCnt; nKey++)
451 // References the Key to an Index-file?
452 aKeyName = aInfFile.GetKeyName( nKey );
453 if (aKeyName.startsWith("NDX"))
455 if(sEntry == OStringToOUString(aInfFile.ReadKey(aKeyName),m_pTable->getConnection()->getTextEncoding()))
457 aInfFile.DeleteKey(aKeyName);
458 break;
464 void ODbaseIndex::impl_killFileAndthrowError_throw(const char* pErrorId, const OUString& _sFile)
466 closeImpl();
467 if(UCBContentHelper::Exists(_sFile))
468 UCBContentHelper::Kill(_sFile);
469 m_pTable->getConnection()->throwGenericSQLException(pErrorId, *this);
472 void ODbaseIndex::CreateImpl()
474 // Create the Index
475 const OUString sFile = getCompletePath();
476 if(UCBContentHelper::Exists(sFile))
478 const OUString sError( m_pTable->getConnection()->getResources().getResourceStringWithSubstitution(
479 STR_COULD_NOT_CREATE_INDEX_NAME,
480 "$filename$", sFile
481 ) );
482 ::dbtools::throwGenericSQLException( sError, *this );
484 // Index comprises only one column
485 if (m_pColumns->getCount() > 1)
486 m_pTable->getConnection()->throwGenericSQLException(STR_ONL_ONE_COLUMN_PER_INDEX,*this);
488 Reference<XFastPropertySet> xCol(m_pColumns->getByIndex(0),UNO_QUERY);
490 // Is the column already indexed?
491 if ( !xCol.is() )
492 ::dbtools::throwFunctionSequenceException(*this);
494 // create the index file
495 m_pFileStream = OFileTable::createStream_simpleError(sFile,StreamMode::READWRITE | StreamMode::SHARE_DENYWRITE | StreamMode::TRUNC);
496 if (!m_pFileStream)
498 const OUString sError( m_pTable->getConnection()->getResources().getResourceStringWithSubstitution(
499 STR_COULD_NOT_LOAD_FILE,
500 "$filename$", sFile
501 ) );
502 ::dbtools::throwGenericSQLException( sError, *this );
505 m_pFileStream->SetEndian(SvStreamEndian::LITTLE);
506 m_pFileStream->SetBufferSize(DINDEX_PAGE_SIZE);
508 // firstly the result must be sorted
509 utl::SharedUNOComponent<XStatement> xStmt;
510 utl::SharedUNOComponent<XResultSet> xSet;
511 OUString aName;
514 xStmt.set( m_pTable->getConnection()->createStatement(), UNO_SET_THROW);
516 aName = getString(xCol->getFastPropertyValue(PROPERTY_ID_NAME));
518 const OUString aQuote(m_pTable->getConnection()->getMetaData()->getIdentifierQuoteString());
519 OUString aStatement( "SELECT " + aQuote + aName + aQuote +" FROM " + aQuote + m_pTable->getName() + aQuote + " ORDER BY " + aQuote + aName + aQuote);
521 xSet.set( xStmt->executeQuery(aStatement),UNO_SET_THROW );
523 catch(const Exception& )
525 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX,sFile);
527 if (!xSet.is())
529 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX,sFile);
532 // Set the header info
533 memset(&m_aHeader,0,sizeof(m_aHeader));
534 sal_Int32 nType = 0;
535 ::rtl::Reference<OSQLColumns> aCols = m_pTable->getTableColumns();
536 const Reference< XPropertySet > xTableCol(*find(aCols->begin(),aCols->end(),aName,::comphelper::UStringMixEqual(isCaseSensitive())));
538 xTableCol->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE)) >>= nType;
540 m_aHeader.db_keytype = (nType == DataType::VARCHAR || nType == DataType::CHAR) ? 0 : 1;
541 m_aHeader.db_keylen = (m_aHeader.db_keytype) ? 8 : static_cast<sal_uInt16>(getINT32(xTableCol->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_PRECISION))));
542 m_aHeader.db_keylen = (( m_aHeader.db_keylen - 1) / 4 + 1) * 4;
543 m_aHeader.db_maxkeys = (DINDEX_PAGE_SIZE - 4) / (8 + m_aHeader.db_keylen);
544 if ( m_aHeader.db_maxkeys < 3 )
546 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_KEYSIZE,sFile);
549 m_pFileStream->SetStreamSize(DINDEX_PAGE_SIZE);
551 OString aCol(OUStringToOString(aName, m_pTable->getConnection()->getTextEncoding()));
552 strncpy(m_aHeader.db_name, aCol.getStr(), std::min<size_t>(sizeof(m_aHeader.db_name), aCol.getLength()));
553 m_aHeader.db_unique = m_IsUnique ? 1: 0;
554 m_aHeader.db_keyrec = m_aHeader.db_keylen + 8;
556 // modifications of the header are detected by differences between
557 // the HeaderInfo and nRootPage or nPageCount respectively
558 m_nRootPage = 1;
559 m_nPageCount = 2;
561 m_aCurLeaf = m_aRoot = CreatePage(m_nRootPage);
562 m_aRoot->SetModified(true);
564 m_bUseCollector = true;
566 sal_Int32 nRowsLeft = 0;
567 Reference<XRow> xRow(xSet,UNO_QUERY);
569 if(xSet->last())
571 Reference< XUnoTunnel> xTunnel(xSet, UNO_QUERY_THROW);
572 ODbaseResultSet* pDbaseRes = reinterpret_cast< ODbaseResultSet* >( xTunnel->getSomething(ODbaseResultSet::getUnoTunnelId()) );
573 assert(pDbaseRes); //"No dbase resultset found? What's going on here!
574 nRowsLeft = xSet->getRow();
576 xSet->beforeFirst();
577 ORowSetValue atmpValue;
578 ONDXKey aKey(atmpValue, nType, 0);
579 ONDXKey aInsertKey(atmpValue, nType, 0);
580 // Create the index structure
581 while (xSet->next())
583 ORowSetValue aValue(m_aHeader.db_keytype ? ORowSetValue(xRow->getDouble(1)) : ORowSetValue(xRow->getString(1)));
584 // checking for duplicate entries
585 if (m_IsUnique && m_nCurNode != NODE_NOTFOUND)
587 aKey.setValue(aValue);
588 if (aKey == (*m_aCurLeaf)[m_nCurNode].GetKey())
590 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_NOT_UNIQUE,sFile);
593 aInsertKey.setValue(aValue);
594 aInsertKey.setRecord(pDbaseRes->getCurrentFilePos());
596 ONDXNode aNewNode(aInsertKey);
597 if (!m_aCurLeaf->Insert(aNewNode, --nRowsLeft))
598 break;
602 if(nRowsLeft)
604 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX,sFile);
606 Release();
607 createINFEntry();
611 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */