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 <com/sun/star/lang/DisposedException.hpp>
23 #include "connectivity/sdbcx/VColumn.hxx"
24 #include <comphelper/sequence.hxx>
25 #include "dbase/DTable.hxx"
26 #include "dbase/DIndexIter.hxx"
27 #include <tools/config.hxx>
28 #include "connectivity/CommonTools.hxx"
29 #include <com/sun/star/sdbc/XResultSetMetaData.hpp>
30 #include <com/sun/star/sdbc/XResultSet.hpp>
31 #include <com/sun/star/sdbcx/XRowLocate.hpp>
32 #include <com/sun/star/sdbc/XRow.hpp>
33 #include <comphelper/extract.hxx>
34 #include <unotools/localfilehelper.hxx>
35 #include <unotools/ucbhelper.hxx>
36 #include <comphelper/types.hxx>
37 #include <connectivity/dbexception.hxx>
38 #include "dbase/DResultSet.hxx"
39 #include "diagnose_ex.h"
40 #include "resource/dbase_res.hrc"
41 #include <unotools/sharedunocomponent.hxx>
43 using namespace ::comphelper
;
45 using namespace connectivity
;
47 using namespace ::cppu
;
48 using namespace connectivity::file
;
49 using namespace connectivity::sdbcx
;
50 using namespace connectivity::dbase
;
51 using namespace com::sun::star::sdbc
;
52 using namespace com::sun::star::sdbcx
;
53 using namespace com::sun::star::uno
;
54 using namespace com::sun::star::beans
;
55 using namespace com::sun::star::lang
;
57 IMPLEMENT_SERVICE_INFO(ODbaseIndex
,"com.sun.star.sdbcx.driver.dbase.Index","com.sun.star.sdbcx.Index");
59 ODbaseIndex::ODbaseIndex(ODbaseTable
* _pTable
)
60 : OIndex(true/*_pTable->getConnection()->getMetaData()->supportsMixedCaseQuotedIdentifiers()*/)
62 , m_nCurNode(NODE_NOTFOUND
)
66 , m_bUseCollector(false)
68 memset(&m_aHeader
, 0, sizeof(m_aHeader
));
72 ODbaseIndex::ODbaseIndex( ODbaseTable
* _pTable
,
73 const NDXHeader
& _rHeader
,
74 const OUString
& _rName
)
75 : OIndex(_rName
, OUString(), _rHeader
.db_unique
, false, false, true)
78 , m_nCurNode(NODE_NOTFOUND
)
82 , m_bUseCollector(false)
87 ODbaseIndex::~ODbaseIndex()
92 void ODbaseIndex::refreshColumns()
94 ::osl::MutexGuard
aGuard( m_aMutex
);
96 TStringVector aVector
;
99 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
100 OSL_ENSURE(m_aHeader
.db_name
[0] != '\0',"Invalid name for the column!");
101 aVector
.push_back(OUString::createFromAscii(m_aHeader
.db_name
));
105 m_pColumns
->reFill(aVector
);
107 m_pColumns
= new ODbaseIndexColumns(this,m_aMutex
,aVector
);
110 Sequence
< sal_Int8
> ODbaseIndex::getUnoTunnelImplementationId()
112 static ::cppu::OImplementationId
* pId
= 0;
115 ::osl::MutexGuard
aGuard( ::osl::Mutex::getGlobalMutex() );
118 static ::cppu::OImplementationId aId
;
122 return pId
->getImplementationId();
127 sal_Int64
ODbaseIndex::getSomething( const Sequence
< sal_Int8
> & rId
) throw (RuntimeException
, std::exception
)
129 return (rId
.getLength() == 16 && 0 == memcmp(getUnoTunnelImplementationId().getConstArray(), rId
.getConstArray(), 16 ) )
130 ? reinterpret_cast< sal_Int64
>( this )
131 : ODbaseIndex_BASE::getSomething(rId
);
134 ONDXPagePtr
ODbaseIndex::getRoot()
139 m_nRootPage
= m_aHeader
.db_rootpage
;
140 m_nPageCount
= m_aHeader
.db_pagecount
;
141 m_aRoot
= CreatePage(m_nRootPage
,NULL
,true);
146 bool ODbaseIndex::openIndexFile()
150 OUString sFile
= getCompletePath();
151 if(UCBContentHelper::Exists(sFile
))
153 m_pFileStream
= OFileTable::createStream_simpleError(sFile
, STREAM_READWRITE
| STREAM_NOCREATE
| STREAM_SHARE_DENYWRITE
);
155 m_pFileStream
= OFileTable::createStream_simpleError(sFile
,STREAM_READ
| STREAM_NOCREATE
| STREAM_SHARE_DENYNONE
);
158 m_pFileStream
->SetNumberFormatInt(NUMBERFORMAT_INT_LITTLEENDIAN
);
159 m_pFileStream
->SetBufferSize(DINDEX_PAGE_SIZE
);
160 (*m_pFileStream
) >> *this;
165 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
166 STR_COULD_NOT_LOAD_FILE
,
169 ::dbtools::throwGenericSQLException( sError
, *this );
173 return m_pFileStream
!= NULL
;
176 OIndexIterator
* ODbaseIndex::createIterator(OBoolOperator
* pOp
,
177 const OOperand
* pOperand
)
180 return new OIndexIterator(this, pOp
, pOperand
);
183 bool ODbaseIndex::ConvertToKey(ONDXKey
* rKey
, sal_uInt32 nRec
, const ORowSetValue
& rValue
)
185 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
186 // Search a specific value in Index
187 // If the Index is unique, the key doesn't matter
190 if (m_aHeader
.db_keytype
== 0)
192 *rKey
= ONDXKey(rValue
.getString(), nRec
);
197 *rKey
= ONDXKey(rValue
.getDouble(), DataType::DOUBLE
, nRec
);
199 *rKey
= ONDXKey(rValue
.getDouble(), nRec
);
211 bool ODbaseIndex::Find(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
214 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
215 // Search a specific value in Index
216 // If the Index is unique, the key doesn't matter
218 return ConvertToKey(&aKey
, nRec
, rValue
) && getRoot()->Find(aKey
);
222 bool ODbaseIndex::Insert(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
225 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
228 // Does the value already exist
229 // Use Find() always to determine the actual leaf
230 if (!ConvertToKey(&aKey
, nRec
, rValue
) || (getRoot()->Find(aKey
) && isUnique()))
233 ONDXNode
aNewNode(aKey
);
235 // insert in the current leaf
236 if (!m_aCurLeaf
.Is())
239 bool bResult
= m_aCurLeaf
->Insert(aNewNode
);
246 bool ODbaseIndex::Update(sal_uInt32 nRec
, const ORowSetValue
& rOldValue
,
247 const ORowSetValue
& rNewValue
)
250 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
252 if (!ConvertToKey(&aKey
, nRec
, rNewValue
) || (isUnique() && getRoot()->Find(aKey
)))
255 return Delete(nRec
, rOldValue
) && Insert(nRec
,rNewValue
);
259 bool ODbaseIndex::Delete(sal_uInt32 nRec
, const ORowSetValue
& rValue
)
262 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
263 // Does the value already exist
264 // Always use Find() to determine the actual leaf
266 if (!ConvertToKey(&aKey
, nRec
, rValue
) || !getRoot()->Find(aKey
))
269 ONDXNode
aNewNode(aKey
);
271 // insert in the current leaf
272 if (!m_aCurLeaf
.Is())
274 #if OSL_DEBUG_LEVEL > 1
275 m_aRoot
->PrintPage();
278 return m_aCurLeaf
->Delete(m_nCurNode
);
281 void ODbaseIndex::Collect(ONDXPage
* pPage
)
284 m_aCollector
.push_back(pPage
);
287 void ODbaseIndex::Release(bool bSave
)
289 // Release the Index-recources
290 m_bUseCollector
= false;
294 m_aCurLeaf
->Release(bSave
);
301 m_aRoot
->Release(bSave
);
304 // Release all references, before the FileStream will be closed
305 for (sal_uIntPtr i
= 0; i
< m_aCollector
.size(); i
++)
306 m_aCollector
[i
]->QueryDelete();
308 m_aCollector
.clear();
311 if (bSave
&& (m_aHeader
.db_rootpage
!= m_nRootPage
||
312 m_aHeader
.db_pagecount
!= m_nPageCount
))
314 m_aHeader
.db_rootpage
= m_nRootPage
;
315 m_aHeader
.db_pagecount
= m_nPageCount
;
316 WriteODbaseIndex( *m_pFileStream
, *this );
318 m_nRootPage
= m_nPageCount
= 0;
319 m_nCurNode
= NODE_NOTFOUND
;
324 void ODbaseIndex::closeImpl()
328 delete m_pFileStream
;
329 m_pFileStream
= NULL
;
333 ONDXPage
* ODbaseIndex::CreatePage(sal_uInt32 nPagePos
, ONDXPage
* pParent
, bool bLoad
)
335 OSL_ENSURE(m_pFileStream
,"FileStream is not opened!");
338 if ( !m_aCollector
.empty() )
340 pPage
= *(m_aCollector
.rbegin());
341 m_aCollector
.pop_back();
342 pPage
->SetPagePos(nPagePos
);
343 pPage
->SetParent(pParent
);
346 pPage
= new ONDXPage(*this, nPagePos
, pParent
);
349 (*m_pFileStream
) >> *pPage
;
355 SvStream
& connectivity::dbase::operator >> (SvStream
&rStream
, ODbaseIndex
& rIndex
)
358 rStream
.Read(&rIndex
.m_aHeader
,DINDEX_PAGE_SIZE
);
360 rIndex
.m_nRootPage
= rIndex
.m_aHeader
.db_rootpage
;
361 rIndex
.m_nPageCount
= rIndex
.m_aHeader
.db_pagecount
;
365 SvStream
& connectivity::dbase::WriteODbaseIndex(SvStream
&rStream
, ODbaseIndex
& rIndex
)
368 OSL_VERIFY_EQUALS( rStream
.Write(&rIndex
.m_aHeader
,DINDEX_PAGE_SIZE
), DINDEX_PAGE_SIZE
, "Write not successful: Wrong header size for dbase index!");
372 OUString
ODbaseIndex::getCompletePath()
374 OUString sDir
= m_pTable
->getConnection()->getURL() +
375 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
380 void ODbaseIndex::createINFEntry()
382 // synchronize inf-file
383 const OUString
sEntry(m_Name
+ ".ndx");
385 OUString
sCfgFile(m_pTable
->getConnection()->getURL() +
386 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
387 m_pTable
->getName() +
390 OUString sPhysicalPath
;
391 LocalFileHelper::ConvertURLToPhysicalName(sCfgFile
, sPhysicalPath
);
393 Config
aInfFile(sPhysicalPath
);
394 aInfFile
.SetGroup(dBASE_III_GROUP
);
396 sal_uInt16 nSuffix
= aInfFile
.GetKeyCount();
397 OString aNewEntry
,aKeyName
;
398 bool bCase
= isCaseSensitive();
399 while (aNewEntry
.isEmpty())
401 aNewEntry
= OString("NDX");
402 aNewEntry
+= OString::number(++nSuffix
);
403 for (sal_uInt16 i
= 0; i
< aInfFile
.GetKeyCount(); i
++)
405 aKeyName
= aInfFile
.GetKeyName(i
);
406 if (bCase
? aKeyName
.equals(aNewEntry
) : aKeyName
.equalsIgnoreAsciiCase(aNewEntry
))
408 aNewEntry
= OString();
413 aInfFile
.WriteKey(aNewEntry
, OUStringToOString(sEntry
, m_pTable
->getConnection()->getTextEncoding()));
416 bool ODbaseIndex::DropImpl()
420 OUString sPath
= getCompletePath();
421 if(UCBContentHelper::Exists(sPath
))
423 if(!UCBContentHelper::Kill(sPath
))
424 m_pTable
->getConnection()->throwGenericSQLException(STR_COULD_NOT_DELETE_INDEX
,*m_pTable
);
427 // synchronize inf-file
428 OUString sCfgFile
= m_pTable
->getConnection()->getURL() +
429 OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_DELIMITER
) +
430 m_pTable
->getName() + ".inf";
432 OUString sPhysicalPath
;
433 OSL_VERIFY_RES( LocalFileHelper::ConvertURLToPhysicalName(sCfgFile
, sPhysicalPath
),
434 "Can not convert Config Filename into Physical Name!");
436 Config
aInfFile(sPhysicalPath
);
437 aInfFile
.SetGroup(dBASE_III_GROUP
);
438 sal_uInt16 nKeyCnt
= aInfFile
.GetKeyCount();
440 OUString sEntry
= m_Name
+ ".ndx";
442 // delete entries from the inf file
443 for (sal_uInt16 nKey
= 0; nKey
< nKeyCnt
; nKey
++)
445 // References the Key to an Index-file?
446 aKeyName
= aInfFile
.GetKeyName( nKey
);
447 if (aKeyName
.copy(0,3) == "NDX")
449 if(sEntry
== OStringToOUString(aInfFile
.ReadKey(aKeyName
),m_pTable
->getConnection()->getTextEncoding()))
451 aInfFile
.DeleteKey(aKeyName
);
459 void ODbaseIndex::impl_killFileAndthrowError_throw(sal_uInt16 _nErrorId
,const OUString
& _sFile
)
462 if(UCBContentHelper::Exists(_sFile
))
463 UCBContentHelper::Kill(_sFile
);
464 m_pTable
->getConnection()->throwGenericSQLException(_nErrorId
,*this);
467 bool ODbaseIndex::CreateImpl()
470 const OUString sFile
= getCompletePath();
471 if(UCBContentHelper::Exists(sFile
))
473 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
474 STR_COULD_NOT_CREATE_INDEX_NAME
,
477 ::dbtools::throwGenericSQLException( sError
, *this );
479 // Index comprises only one column
480 if (m_pColumns
->getCount() > 1)
481 m_pTable
->getConnection()->throwGenericSQLException(STR_ONL_ONE_COLUMN_PER_INDEX
,*this);
483 Reference
<XFastPropertySet
> xCol(m_pColumns
->getByIndex(0),UNO_QUERY
);
485 // Is the column already indexed?
487 ::dbtools::throwFunctionSequenceException(*this);
489 // create the index file
490 m_pFileStream
= OFileTable::createStream_simpleError(sFile
,STREAM_READWRITE
| STREAM_SHARE_DENYWRITE
| STREAM_TRUNC
);
493 const OUString
sError( m_pTable
->getConnection()->getResources().getResourceStringWithSubstitution(
494 STR_COULD_NOT_LOAD_FILE
,
497 ::dbtools::throwGenericSQLException( sError
, *this );
500 m_pFileStream
->SetNumberFormatInt(NUMBERFORMAT_INT_LITTLEENDIAN
);
501 m_pFileStream
->SetBufferSize(DINDEX_PAGE_SIZE
);
503 // firstly the result must be sorted
504 utl::SharedUNOComponent
<XStatement
> xStmt
;
505 utl::SharedUNOComponent
<XResultSet
> xSet
;
509 xStmt
.set( m_pTable
->getConnection()->createStatement(), UNO_SET_THROW
);
511 aName
= getString(xCol
->getFastPropertyValue(PROPERTY_ID_NAME
));
513 const OUString
aQuote(m_pTable
->getConnection()->getMetaData()->getIdentifierQuoteString());
514 OUString
aStatement( "SELECT " + aQuote
+ aName
+ aQuote
+" FROM " + aQuote
+ m_pTable
->getName() + aQuote
+ " ORDER BY " + aQuote
+ aName
+ aQuote
);
516 xSet
.set( xStmt
->executeQuery(aStatement
),UNO_SET_THROW
);
518 catch(const Exception
& )
520 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
524 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
527 // Set the header info
528 memset(&m_aHeader
,0,sizeof(m_aHeader
));
530 ::rtl::Reference
<OSQLColumns
> aCols
= m_pTable
->getTableColumns();
531 const Reference
< XPropertySet
> xTableCol(*find(aCols
->get().begin(),aCols
->get().end(),aName
,::comphelper::UStringMixEqual(isCaseSensitive())));
533 xTableCol
->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_TYPE
)) >>= nType
;
535 m_aHeader
.db_keytype
= (nType
== DataType::VARCHAR
|| nType
== DataType::CHAR
) ? 0 : 1;
536 m_aHeader
.db_keylen
= (m_aHeader
.db_keytype
) ? 8 : (sal_uInt16
)getINT32(xTableCol
->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_PRECISION
)));
537 m_aHeader
.db_keylen
= (( m_aHeader
.db_keylen
- 1) / 4 + 1) * 4;
538 m_aHeader
.db_maxkeys
= (DINDEX_PAGE_SIZE
- 4) / (8 + m_aHeader
.db_keylen
);
539 if ( m_aHeader
.db_maxkeys
< 3 )
541 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_KEYSIZE
,sFile
);
544 m_pFileStream
->SetStreamSize(DINDEX_PAGE_SIZE
);
546 OString
aCol(OUStringToOString(aName
, m_pTable
->getConnection()->getTextEncoding()));
547 strncpy(m_aHeader
.db_name
, aCol
.getStr(), std::min
<size_t>(sizeof(m_aHeader
.db_name
), aCol
.getLength()));
548 m_aHeader
.db_unique
= m_IsUnique
? 1: 0;
549 m_aHeader
.db_keyrec
= m_aHeader
.db_keylen
+ 8;
551 // modifications of the header are detected by differences between
552 // the HeaderInfo and nRootPage or nPageCount respectively
556 m_aCurLeaf
= m_aRoot
= CreatePage(m_nRootPage
);
557 m_aRoot
->SetModified(true);
559 m_bUseCollector
= true;
561 sal_Int32 nRowsLeft
= 0;
562 Reference
<XRow
> xRow(xSet
,UNO_QUERY
);
566 Reference
< XUnoTunnel
> xTunnel(xSet
, UNO_QUERY_THROW
);
567 ODbaseResultSet
* pDbaseRes
= reinterpret_cast< ODbaseResultSet
* >( xTunnel
->getSomething(ODbaseResultSet::getUnoTunnelImplementationId()) );
568 assert(pDbaseRes
); //"No dbase resultset found? What's going on here!
569 Reference
<XRowLocate
> xRowLocate(xSet
,UNO_QUERY
);
570 nRowsLeft
= xSet
->getRow();
573 ORowSetValue atmpValue
=ORowSetValue();
574 ONDXKey
aKey(atmpValue
, nType
, 0);
575 ONDXKey
aInsertKey(atmpValue
, nType
, 0);
576 // Create the index structure
579 ORowSetValue
aValue(m_aHeader
.db_keytype
? ORowSetValue(xRow
->getDouble(1)) : ORowSetValue(xRow
->getString(1)));
580 // checking for duplicate entries
581 if (m_IsUnique
&& m_nCurNode
!= NODE_NOTFOUND
)
583 aKey
.setValue(aValue
);
584 if (aKey
== (*m_aCurLeaf
)[m_nCurNode
].GetKey())
586 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX_NOT_UNIQUE
,sFile
);
589 aInsertKey
.setValue(aValue
);
590 aInsertKey
.setRecord(pDbaseRes
->getCurrentFilePos());
592 ONDXNode
aNewNode(aInsertKey
);
593 if (!m_aCurLeaf
->Insert(aNewNode
, --nRowsLeft
))
600 impl_killFileAndthrowError_throw(STR_COULD_NOT_CREATE_INDEX
,sFile
);
608 void SAL_CALL
ODbaseIndex::acquire() throw()
610 ODbaseIndex_BASE::acquire();
613 void SAL_CALL
ODbaseIndex::release() throw()
615 ODbaseIndex_BASE::release();
621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */