1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <connectivity/dbexception.hxx>
34 #include <dbase/DResultSet.hxx>
35 #include <strings.hrc>
36 #include <unotools/sharedunocomponent.hxx>
38 using namespace ::comphelper
;
40 using namespace connectivity
;
42 using namespace ::cppu
;
43 using namespace connectivity::file
;
44 using namespace connectivity::dbase
;
45 using namespace com::sun::star::sdbc
;
46 using namespace com::sun::star::uno
;
47 using namespace com::sun::star::beans
;
49 IMPLEMENT_SERVICE_INFO(ODbaseIndex
,u
"com.sun.star.sdbcx.driver.dbase.Index"_ustr
,u
"com.sun.star.sdbcx.Index"_ustr
);
51 ODbaseIndex::ODbaseIndex(ODbaseTable
* _pTable
)
52 : OIndex(true/*_pTable->getConnection()->getMetaData()->supportsMixedCaseQuotedIdentifiers()*/)
53 , m_nCurNode(NODE_NOTFOUND
)
57 , m_bUseCollector(false)
62 ODbaseIndex::ODbaseIndex( ODbaseTable
* _pTable
,
63 const NDXHeader
& _rHeader
,
64 const OUString
& _rName
)
65 : OIndex(_rName
, OUString(), _rHeader
.db_unique
, false, false, true)
67 , m_nCurNode(NODE_NOTFOUND
)
71 , m_bUseCollector(false)
76 ODbaseIndex::~ODbaseIndex()
81 void ODbaseIndex::refreshColumns()
83 ::osl::MutexGuard
aGuard( m_aMutex
);
85 ::std::vector
< OUString
> aVector
;
88 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
89 OSL_ENSURE(m_aHeader
.db_name
[0] != '\0',"Invalid name for the column!");
90 aVector
.push_back(OUString::createFromAscii(m_aHeader
.db_name
));
94 m_pColumns
->reFill(aVector
);
96 m_pColumns
.reset(new ODbaseIndexColumns(this,m_aMutex
,aVector
));
99 ONDXPagePtr
const & ODbaseIndex::getRoot()
104 m_nRootPage
= m_aHeader
.db_rootpage
;
105 m_nPageCount
= m_aHeader
.db_pagecount
;
106 m_aRoot
= CreatePage(m_nRootPage
,nullptr,true);
111 void ODbaseIndex::openIndexFile()
116 OUString sFile
= getCompletePath();
117 if(UCBContentHelper::Exists(sFile
))
119 m_pFileStream
= OFileTable::createStream_simpleError(sFile
, StreamMode::READWRITE
| StreamMode::NOCREATE
| StreamMode::SHARE_DENYWRITE
);
121 m_pFileStream
= OFileTable::createStream_simpleError(sFile
, StreamMode::READ
| StreamMode::NOCREATE
| StreamMode::SHARE_DENYNONE
);
124 m_pFileStream
->SetEndian(SvStreamEndian::LITTLE
);
125 m_pFileStream
->SetBufferSize(DINDEX_PAGE_SIZE
);
126 (*m_pFileStream
) >> *this;
131 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
132 STR_COULD_NOT_LOAD_FILE
,
135 ::dbtools::throwGenericSQLException( sError
, *this );
139 std::unique_ptr
<OIndexIterator
> ODbaseIndex::createIterator()
142 return std::make_unique
<OIndexIterator
>(this);
145 bool ODbaseIndex::ConvertToKey(ONDXKey
* rKey
, sal_uInt32 nRec
, const ORowSetValue
& rValue
)
147 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
148 // Search a specific value in Index
149 // If the Index is unique, the key doesn't matter
152 if (m_aHeader
.db_keytype
== 0)
154 *rKey
= ONDXKey(rValue
.getString(), nRec
);
159 *rKey
= ONDXKey(rValue
.getDouble(), DataType::DOUBLE
, nRec
);
161 *rKey
= ONDXKey(rValue
.getDouble(), nRec
);
173 bool ODbaseIndex::Find(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
176 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
177 // Search a specific value in Index
178 // If the Index is unique, the key doesn't matter
180 return ConvertToKey(&aKey
, nRec
, rValue
) && getRoot()->Find(aKey
);
184 bool ODbaseIndex::Insert(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
187 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
190 // Does the value already exist
191 // Use Find() always to determine the actual leaf
192 if (!ConvertToKey(&aKey
, nRec
, rValue
) || (getRoot()->Find(aKey
) && isUnique()))
195 ONDXNode
aNewNode(aKey
);
197 // insert in the current leaf
198 if (!m_aCurLeaf
.Is())
201 bool bResult
= m_aCurLeaf
->Insert(aNewNode
);
208 bool ODbaseIndex::Update(sal_uInt32 nRec
, const ORowSetValue
& rOldValue
,
209 const ORowSetValue
& rNewValue
)
212 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
214 if (!ConvertToKey(&aKey
, nRec
, rNewValue
) || (isUnique() && getRoot()->Find(aKey
)))
217 return Delete(nRec
, rOldValue
) && Insert(nRec
,rNewValue
);
221 bool ODbaseIndex::Delete(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
224 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
225 // Does the value already exist
226 // Always use Find() to determine the actual leaf
228 if (!ConvertToKey(&aKey
, nRec
, rValue
) || !getRoot()->Find(aKey
))
231 // insert in the current leaf
232 if (!m_aCurLeaf
.Is())
234 #if OSL_DEBUG_LEVEL > 1
235 m_aRoot
->PrintPage();
238 m_aCurLeaf
->Delete(m_nCurNode
);
242 void ODbaseIndex::Collect(ONDXPage
* pPage
)
245 m_aCollector
.push_back(pPage
);
248 void ODbaseIndex::Release(bool bSave
)
250 // Release the Index-resources
251 m_bUseCollector
= false;
255 m_aCurLeaf
->Release(bSave
);
262 m_aRoot
->Release(bSave
);
265 // Release all references, before the FileStream will be closed
266 for (auto& i
: m_aCollector
)
269 m_aCollector
.clear();
272 if (bSave
&& (m_aHeader
.db_rootpage
!= m_nRootPage
||
273 m_aHeader
.db_pagecount
!= m_nPageCount
))
275 m_aHeader
.db_rootpage
= m_nRootPage
;
276 m_aHeader
.db_pagecount
= m_nPageCount
;
277 WriteODbaseIndex( *m_pFileStream
, *this );
279 m_nRootPage
= m_nPageCount
= 0;
280 m_nCurNode
= NODE_NOTFOUND
;
285 void ODbaseIndex::closeImpl()
287 m_pFileStream
.reset();
290 ONDXPage
* ODbaseIndex::CreatePage(sal_uInt32 nPagePos
, ONDXPage
* pParent
, bool bLoad
)
292 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
295 if ( !m_aCollector
.empty() )
297 pPage
= *(m_aCollector
.rbegin());
298 m_aCollector
.pop_back();
299 pPage
->SetPagePos(nPagePos
);
300 pPage
->SetParent(pParent
);
303 pPage
= new ONDXPage(*this, nPagePos
, pParent
);
306 (*m_pFileStream
) >> *pPage
;
311 void connectivity::dbase::ReadHeader(
312 SvStream
& rStream
, ODbaseIndex::NDXHeader
& rHeader
)
315 sal_uInt64
const nOldPos(rStream
.Tell());
317 rStream
.ReadUInt32(rHeader
.db_rootpage
);
318 rStream
.ReadUInt32(rHeader
.db_pagecount
);
319 rStream
.ReadBytes(&rHeader
.db_free
, 4);
320 rStream
.ReadUInt16(rHeader
.db_keylen
);
321 rStream
.ReadUInt16(rHeader
.db_maxkeys
);
322 rStream
.ReadUInt16(rHeader
.db_keytype
);
323 rStream
.ReadUInt16(rHeader
.db_keyrec
);
324 rStream
.ReadBytes(&rHeader
.db_free1
, 3);
325 rStream
.ReadUChar(rHeader
.db_unique
);
326 rStream
.ReadBytes(&rHeader
.db_name
, 488);
327 assert(rStream
.GetError() || rStream
.Tell() == nOldPos
+ DINDEX_PAGE_SIZE
);
330 SvStream
& connectivity::dbase::operator >> (SvStream
&rStream
, ODbaseIndex
& rIndex
)
333 ReadHeader(rStream
, rIndex
.m_aHeader
);
335 rIndex
.m_nRootPage
= rIndex
.m_aHeader
.db_rootpage
;
336 rIndex
.m_nPageCount
= rIndex
.m_aHeader
.db_pagecount
;
340 SvStream
& connectivity::dbase::WriteODbaseIndex(SvStream
&rStream
, const ODbaseIndex
& rIndex
)
343 rStream
.WriteUInt32(rIndex
.m_aHeader
.db_rootpage
);
344 rStream
.WriteUInt32(rIndex
.m_aHeader
.db_pagecount
);
345 rStream
.WriteBytes(&rIndex
.m_aHeader
.db_free
, 4);
346 rStream
.WriteUInt16(rIndex
.m_aHeader
.db_keylen
);
347 rStream
.WriteUInt16(rIndex
.m_aHeader
.db_maxkeys
);
348 rStream
.WriteUInt16(rIndex
.m_aHeader
.db_keytype
);
349 rStream
.WriteUInt16(rIndex
.m_aHeader
.db_keyrec
);
350 rStream
.WriteBytes(&rIndex
.m_aHeader
.db_free1
, 3);
351 rStream
.WriteUChar(rIndex
.m_aHeader
.db_unique
);
352 rStream
.WriteBytes(&rIndex
.m_aHeader
.db_name
, 488);
353 assert(rStream
.GetError() || rStream
.Tell() == DINDEX_PAGE_SIZE
);
354 SAL_WARN_IF(rStream
.GetError(), "connectivity.dbase", "write error");
358 OUString
ODbaseIndex::getCompletePath() const
360 OUString sDir
= m_pTable
->getConnection()->getURL() +
361 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
366 void ODbaseIndex::createINFEntry()
368 // synchronize inf-file
369 const OUString
sEntry(m_Name
+ ".ndx");
371 OUString
sCfgFile(m_pTable
->getConnection()->getURL() +
372 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
373 m_pTable
->getName() +
376 OUString sPhysicalPath
;
377 osl::FileBase::getSystemPathFromFileURL(sCfgFile
, sPhysicalPath
);
379 Config
aInfFile(sPhysicalPath
);
380 aInfFile
.SetGroup(dBASE_III_GROUP
);
382 sal_uInt16 nSuffix
= aInfFile
.GetKeyCount();
383 OString aNewEntry
,aKeyName
;
384 bool bCase
= isCaseSensitive();
385 while (aNewEntry
.isEmpty())
387 aNewEntry
= "NDX" + OString::number(++nSuffix
);
388 for (sal_uInt16 i
= 0; i
< aInfFile
.GetKeyCount(); i
++)
390 aKeyName
= aInfFile
.GetKeyName(i
);
391 if (bCase
? aKeyName
== aNewEntry
: aKeyName
.equalsIgnoreAsciiCase(aNewEntry
))
398 aInfFile
.WriteKey(aNewEntry
, OUStringToOString(sEntry
, m_pTable
->getConnection()->getTextEncoding()));
401 void ODbaseIndex::DropImpl()
405 OUString sPath
= getCompletePath();
406 if(UCBContentHelper::Exists(sPath
))
408 if(!UCBContentHelper::Kill(sPath
))
409 m_pTable
->getConnection()->throwGenericSQLException(STR_COULD_NOT_DELETE_INDEX
,*m_pTable
);
412 // synchronize inf-file
413 OUString sCfgFile
= m_pTable
->getConnection()->getURL() +
414 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
415 m_pTable
->getName() + ".inf";
417 OUString sPhysicalPath
;
418 OSL_VERIFY( osl::FileBase::getSystemPathFromFileURL(sCfgFile
, sPhysicalPath
)
419 == osl::FileBase::E_None
);
421 Config
aInfFile(sPhysicalPath
);
422 aInfFile
.SetGroup(dBASE_III_GROUP
);
423 sal_uInt16 nKeyCnt
= aInfFile
.GetKeyCount();
425 OUString sEntry
= m_Name
+ ".ndx";
427 // delete entries from the inf file
428 for (sal_uInt16 nKey
= 0; nKey
< nKeyCnt
; nKey
++)
430 // References the Key to an Index-file?
431 aKeyName
= aInfFile
.GetKeyName( nKey
);
432 if (aKeyName
.startsWith("NDX"))
434 if(sEntry
== OStringToOUString(aInfFile
.ReadKey(aKeyName
),m_pTable
->getConnection()->getTextEncoding()))
436 aInfFile
.DeleteKey(aKeyName
);
443 void ODbaseIndex::impl_killFileAndthrowError_throw(TranslateId pErrorId
, const OUString
& _sFile
)
446 if(UCBContentHelper::Exists(_sFile
))
447 UCBContentHelper::Kill(_sFile
);
448 m_pTable
->getConnection()->throwGenericSQLException(pErrorId
, *this);
451 void ODbaseIndex::CreateImpl()
454 const OUString sFile
= getCompletePath();
455 if(UCBContentHelper::Exists(sFile
))
457 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
458 STR_COULD_NOT_CREATE_INDEX_NAME
,
461 ::dbtools::throwGenericSQLException( sError
, *this );
463 // Index comprises only one column
464 if (m_pColumns
->getCount() > 1)
465 m_pTable
->getConnection()->throwGenericSQLException(STR_ONL_ONE_COLUMN_PER_INDEX
,*this);
467 Reference
<XFastPropertySet
> xCol(m_pColumns
->getByIndex(0),UNO_QUERY
);
469 // Is the column already indexed?
471 ::dbtools::throwFunctionSequenceException(*this);
473 // create the index file
474 m_pFileStream
= OFileTable::createStream_simpleError(sFile
,StreamMode::READWRITE
| StreamMode::SHARE_DENYWRITE
| StreamMode::TRUNC
);
477 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
478 STR_COULD_NOT_LOAD_FILE
,
481 ::dbtools::throwGenericSQLException( sError
, *this );
484 m_pFileStream
->SetEndian(SvStreamEndian::LITTLE
);
485 m_pFileStream
->SetBufferSize(DINDEX_PAGE_SIZE
);
487 // firstly the result must be sorted
488 utl::SharedUNOComponent
<XStatement
> xStmt
;
489 utl::SharedUNOComponent
<XResultSet
> xSet
;
493 xStmt
.set( m_pTable
->getConnection()->createStatement(), UNO_SET_THROW
);
495 aName
= getString(xCol
->getFastPropertyValue(PROPERTY_ID_NAME
));
497 const OUString
aQuote(m_pTable
->getConnection()->getMetaData()->getIdentifierQuoteString());
498 OUString
aStatement( "SELECT " + aQuote
+ aName
+ aQuote
+" FROM " + aQuote
+ m_pTable
->getName() + aQuote
+ " ORDER BY " + aQuote
+ aName
+ aQuote
);
500 xSet
.set( xStmt
->executeQuery(aStatement
),UNO_SET_THROW
);
502 catch(const Exception
& )
504 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
508 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
511 // Set the header info
512 memset(&m_aHeader
,0,sizeof(m_aHeader
));
514 ::rtl::Reference
<OSQLColumns
> aCols
= m_pTable
->getTableColumns();
515 const Reference
< XPropertySet
> xTableCol(*find(aCols
->begin(),aCols
->end(),aName
,::comphelper::UStringMixEqual(isCaseSensitive())));
517 xTableCol
->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE
)) >>= nType
;
519 m_aHeader
.db_keytype
= (nType
== DataType::VARCHAR
|| nType
== DataType::CHAR
) ? 0 : 1;
520 m_aHeader
.db_keylen
= (m_aHeader
.db_keytype
) ? 8 : static_cast<sal_uInt16
>(getINT32(xTableCol
->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_PRECISION
))));
521 m_aHeader
.db_keylen
= (( m_aHeader
.db_keylen
- 1) / 4 + 1) * 4;
522 m_aHeader
.db_maxkeys
= (DINDEX_PAGE_SIZE
- 4) / (8 + m_aHeader
.db_keylen
);
523 if ( m_aHeader
.db_maxkeys
< 3 )
525 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_KEYSIZE
,sFile
);
528 m_pFileStream
->SetStreamSize(DINDEX_PAGE_SIZE
);
530 OString
aCol(OUStringToOString(aName
, m_pTable
->getConnection()->getTextEncoding()));
531 strncpy(m_aHeader
.db_name
, aCol
.getStr(), std::min
<size_t>(sizeof(m_aHeader
.db_name
), aCol
.getLength()));
532 m_aHeader
.db_unique
= m_IsUnique
? 1: 0;
533 m_aHeader
.db_keyrec
= m_aHeader
.db_keylen
+ 8;
535 // modifications of the header are detected by differences between
536 // the HeaderInfo and nRootPage or nPageCount respectively
540 m_aCurLeaf
= m_aRoot
= CreatePage(m_nRootPage
);
541 m_aRoot
->SetModified(true);
543 m_bUseCollector
= true;
545 sal_Int32 nRowsLeft
= 0;
546 Reference
<XRow
> xRow(xSet
,UNO_QUERY
);
550 ODbaseResultSet
* pDbaseRes
= dynamic_cast<ODbaseResultSet
*>(xSet
.getTyped().get());
551 assert(pDbaseRes
); //"No dbase resultset found? What's going on here!
552 nRowsLeft
= xSet
->getRow();
555 ONDXKey
aKey(ORowSetValue(), nType
, 0);
556 ONDXKey
aInsertKey(ORowSetValue(), nType
, 0);
557 // Create the index structure
560 ORowSetValue
aValue(m_aHeader
.db_keytype
? ORowSetValue(xRow
->getDouble(1)) : ORowSetValue(xRow
->getString(1)));
561 // checking for duplicate entries
562 if (m_IsUnique
&& m_nCurNode
!= NODE_NOTFOUND
)
564 aKey
.setValue(aValue
);
565 if (aKey
== (*m_aCurLeaf
)[m_nCurNode
].GetKey())
567 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_NOT_UNIQUE
,sFile
);
570 aInsertKey
.setValue(aValue
);
571 aInsertKey
.setRecord(pDbaseRes
->getCurrentFilePos());
573 ONDXNode
aNewNode(aInsertKey
);
574 if (!m_aCurLeaf
->Insert(aNewNode
, --nRowsLeft
))
581 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
588 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */