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 <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
;
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
)
61 , m_bUseCollector(false)
66 ODbaseIndex::ODbaseIndex( ODbaseTable
* _pTable
,
67 const NDXHeader
& _rHeader
,
68 const OUString
& _rName
)
69 : OIndex(_rName
, OUString(), _rHeader
.db_unique
, false, false, true)
71 , m_nCurNode(NODE_NOTFOUND
)
75 , m_bUseCollector(false)
80 ODbaseIndex::~ODbaseIndex()
85 void ODbaseIndex::refreshColumns()
87 ::osl::MutexGuard
aGuard( m_aMutex
);
89 ::std::vector
< OUString
> aVector
;
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
));
98 m_pColumns
->reFill(aVector
);
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();
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()
124 m_nRootPage
= m_aHeader
.db_rootpage
;
125 m_nPageCount
= m_aHeader
.db_pagecount
;
126 m_aRoot
= CreatePage(m_nRootPage
,nullptr,true);
131 void ODbaseIndex::openIndexFile()
136 OUString sFile
= getCompletePath();
137 if(UCBContentHelper::Exists(sFile
))
139 m_pFileStream
= OFileTable::createStream_simpleError(sFile
, StreamMode::READWRITE
| StreamMode::NOCREATE
| StreamMode::SHARE_DENYWRITE
);
141 m_pFileStream
= OFileTable::createStream_simpleError(sFile
, StreamMode::READ
| StreamMode::NOCREATE
| StreamMode::SHARE_DENYNONE
);
144 m_pFileStream
->SetEndian(SvStreamEndian::LITTLE
);
145 m_pFileStream
->SetBufferSize(DINDEX_PAGE_SIZE
);
146 (*m_pFileStream
) >> *this;
151 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
152 STR_COULD_NOT_LOAD_FILE
,
155 ::dbtools::throwGenericSQLException( sError
, *this );
159 std::unique_ptr
<OIndexIterator
> ODbaseIndex::createIterator()
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
);
179 *rKey
= ONDXKey(rValue
.getDouble(), DataType::DOUBLE
, nRec
);
181 *rKey
= ONDXKey(rValue
.getDouble(), nRec
);
193 bool ODbaseIndex::Find(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
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
200 return ConvertToKey(&aKey
, nRec
, rValue
) && getRoot()->Find(aKey
);
204 bool ODbaseIndex::Insert(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
207 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
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()))
215 ONDXNode
aNewNode(aKey
);
217 // insert in the current leaf
218 if (!m_aCurLeaf
.Is())
221 bool bResult
= m_aCurLeaf
->Insert(aNewNode
);
228 bool ODbaseIndex::Update(sal_uInt32 nRec
, const ORowSetValue
& rOldValue
,
229 const ORowSetValue
& rNewValue
)
232 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
234 if (!ConvertToKey(&aKey
, nRec
, rNewValue
) || (isUnique() && getRoot()->Find(aKey
)))
237 return Delete(nRec
, rOldValue
) && Insert(nRec
,rNewValue
);
241 bool ODbaseIndex::Delete(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
244 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
245 // Does the value already exist
246 // Always use Find() to determine the actual leaf
248 if (!ConvertToKey(&aKey
, nRec
, rValue
) || !getRoot()->Find(aKey
))
251 // insert in the current leaf
252 if (!m_aCurLeaf
.Is())
254 #if OSL_DEBUG_LEVEL > 1
255 m_aRoot
->PrintPage();
258 m_aCurLeaf
->Delete(m_nCurNode
);
262 void ODbaseIndex::Collect(ONDXPage
* pPage
)
265 m_aCollector
.push_back(pPage
);
268 void ODbaseIndex::Release(bool bSave
)
270 // Release the Index-resources
271 m_bUseCollector
= false;
275 m_aCurLeaf
->Release(bSave
);
282 m_aRoot
->Release(bSave
);
285 // Release all references, before the FileStream will be closed
286 for (auto& i
: m_aCollector
)
289 m_aCollector
.clear();
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
;
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!");
315 if ( !m_aCollector
.empty() )
317 pPage
= *(m_aCollector
.rbegin());
318 m_aCollector
.pop_back();
319 pPage
->SetPagePos(nPagePos
);
320 pPage
->SetParent(pParent
);
323 pPage
= new ONDXPage(*this, nPagePos
, pParent
);
326 (*m_pFileStream
) >> *pPage
;
331 void connectivity::dbase::ReadHeader(
332 SvStream
& rStream
, ODbaseIndex::NDXHeader
& rHeader
)
335 sal_uInt64
const nOldPos(rStream
.Tell());
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
)
353 ReadHeader(rStream
, rIndex
.m_aHeader
);
355 rIndex
.m_nRootPage
= rIndex
.m_aHeader
.db_rootpage
;
356 rIndex
.m_nPageCount
= rIndex
.m_aHeader
.db_pagecount
;
360 SvStream
& connectivity::dbase::WriteODbaseIndex(SvStream
&rStream
, ODbaseIndex
& rIndex
)
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");
378 OUString
ODbaseIndex::getCompletePath() const
380 OUString sDir
= m_pTable
->getConnection()->getURL() +
381 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
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() +
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
))
419 aInfFile
.WriteKey(aNewEntry
, OUStringToOString(sEntry
, m_pTable
->getConnection()->getTextEncoding()));
422 void ODbaseIndex::DropImpl()
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();
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
);
464 void ODbaseIndex::impl_killFileAndthrowError_throw(const char* pErrorId
, const OUString
& _sFile
)
467 if(UCBContentHelper::Exists(_sFile
))
468 UCBContentHelper::Kill(_sFile
);
469 m_pTable
->getConnection()->throwGenericSQLException(pErrorId
, *this);
472 void ODbaseIndex::CreateImpl()
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
,
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?
492 ::dbtools::throwFunctionSequenceException(*this);
494 // create the index file
495 m_pFileStream
= OFileTable::createStream_simpleError(sFile
,StreamMode::READWRITE
| StreamMode::SHARE_DENYWRITE
| StreamMode::TRUNC
);
498 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
499 STR_COULD_NOT_LOAD_FILE
,
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
;
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
);
529 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
532 // Set the header info
533 memset(&m_aHeader
,0,sizeof(m_aHeader
));
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
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
);
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();
577 ORowSetValue atmpValue
;
578 ONDXKey
aKey(atmpValue
, nType
, 0);
579 ONDXKey
aInsertKey(atmpValue
, nType
, 0);
580 // Create the index structure
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
))
604 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
611 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */