946 lines
35 KiB
C++
946 lines
35 KiB
C++
/* -*- 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 <flat/ETable.hxx>
|
|
#include <com/sun/star/sdbc/ColumnValue.hpp>
|
|
#include <com/sun/star/sdbc/DataType.hpp>
|
|
#include <com/sun/star/sdbc/XRow.hpp>
|
|
#include <com/sun/star/ucb/XContentAccess.hpp>
|
|
#include <flat/EConnection.hxx>
|
|
#include <flat/EColumns.hxx>
|
|
#include <o3tl/safeint.hxx>
|
|
#include <rtl/math.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <tools/urlobj.hxx>
|
|
#include <cppuhelper/queryinterface.hxx>
|
|
#include <comphelper/numbers.hxx>
|
|
#include <comphelper/servicehelper.hxx>
|
|
#include <com/sun/star/util/NumberFormat.hpp>
|
|
#include <com/sun/star/util/NumberFormatter.hpp>
|
|
#include <com/sun/star/util/NumberFormatsSupplier.hpp>
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
#include <connectivity/dbconversion.hxx>
|
|
#include <connectivity/sdbcx/VColumn.hxx>
|
|
#include <file/quotedstring.hxx>
|
|
#include <file/FDriver.hxx>
|
|
#include <unotools/syslocale.hxx>
|
|
#include <unotools/charclass.hxx>
|
|
|
|
using namespace ::comphelper;
|
|
using namespace connectivity;
|
|
using namespace connectivity::flat;
|
|
using namespace connectivity::file;
|
|
using namespace ::cppu;
|
|
using namespace ::com::sun::star::uno;
|
|
using namespace ::com::sun::star::ucb;
|
|
using namespace ::com::sun::star::beans;
|
|
using namespace ::com::sun::star::sdbcx;
|
|
using namespace ::com::sun::star::sdbc;
|
|
using namespace ::com::sun::star::container;
|
|
using namespace ::com::sun::star::lang;
|
|
using namespace ::com::sun::star::util;
|
|
using std::vector;
|
|
using std::lower_bound;
|
|
|
|
|
|
void OFlatTable::fillColumns(const css::lang::Locale& _aLocale)
|
|
{
|
|
m_bNeedToReadLine = true; // we overwrite m_aCurrentLine, seek the stream, ...
|
|
m_pFileStream->Seek(0);
|
|
// tdf#123055 - start to read unicode text in order to avoid the BOM
|
|
m_pFileStream->StartReadingUnicodeText(RTL_TEXTENCODING_DONTKNOW);
|
|
m_aCurrentLine = QuotedTokenizedString();
|
|
bool bRead = true;
|
|
|
|
const OFlatConnection* const pConnection = getFlatConnection();
|
|
const bool bHasHeaderLine = pConnection->isHeaderLine();
|
|
|
|
QuotedTokenizedString aHeaderLine;
|
|
const sal_Int32 nPos = static_cast<sal_Int32>(m_pFileStream->Tell());
|
|
TRowPositionInFile rowPos(nPos, nPos);
|
|
sal_Int32 rowNum(0);
|
|
if ( bHasHeaderLine )
|
|
{
|
|
bRead = readLine(&rowPos.second, &rowPos.first, true);
|
|
if(bRead)
|
|
aHeaderLine = m_aCurrentLine;
|
|
}
|
|
setRowPos(rowNum++, rowPos);
|
|
|
|
// read first row
|
|
if(bRead)
|
|
{
|
|
bRead = readLine(&rowPos.second, &rowPos.first);
|
|
if(bRead)
|
|
setRowPos(rowNum++, rowPos);
|
|
}
|
|
|
|
if ( !bHasHeaderLine || !aHeaderLine.Len())
|
|
{
|
|
// use first non-empty row as headerline because we need the number of columns
|
|
while(bRead && m_aCurrentLine.Len() == 0)
|
|
{
|
|
bRead = readLine(&rowPos.second, &rowPos.first);
|
|
if(bRead)
|
|
setRowPos(rowNum++, rowPos);
|
|
}
|
|
aHeaderLine = m_aCurrentLine;
|
|
}
|
|
// column count
|
|
const sal_Int32 nFieldCount = aHeaderLine.GetTokenCount(m_cFieldDelimiter,m_cStringDelimiter);
|
|
|
|
if(!m_aColumns.is())
|
|
m_aColumns = new OSQLColumns();
|
|
else
|
|
m_aColumns->clear();
|
|
|
|
m_aTypes.clear();
|
|
m_aPrecisions.clear();
|
|
m_aScales.clear();
|
|
// reserve some space
|
|
m_aColumns->reserve(nFieldCount+1);
|
|
m_aTypes.assign(nFieldCount+1,DataType::SQLNULL);
|
|
m_aPrecisions.assign(nFieldCount+1,-1);
|
|
m_aScales.assign(nFieldCount+1,-1);
|
|
|
|
const bool bCase = m_pConnection->getMetaData()->supportsMixedCaseQuotedIdentifiers();
|
|
CharClass aCharClass( pConnection->getDriver()->getComponentContext(), LanguageTag( _aLocale));
|
|
// read description
|
|
const sal_Unicode cDecimalDelimiter = pConnection->getDecimalDelimiter();
|
|
const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
|
|
::comphelper::UStringMixEqual aCase(bCase);
|
|
vector<OUString> aColumnNames;
|
|
vector<OUString> aTypeNames;
|
|
aTypeNames.resize(nFieldCount);
|
|
const sal_Int32 nMaxRowsToScan = pConnection->getMaxRowsToScan();
|
|
sal_Int32 nRowCount = 0;
|
|
|
|
do
|
|
{
|
|
sal_Int32 nStartPosHeaderLine = 0; // use for efficient way to get the tokens
|
|
sal_Int32 nStartPosFirstLine = 0; // use for efficient way to get the tokens
|
|
sal_Int32 nStartPosFirstLine2 = 0;
|
|
for( sal_Int32 i = 0; i < nFieldCount; i++ )
|
|
{
|
|
if ( nRowCount == 0)
|
|
{
|
|
OUString aColumnName;
|
|
if ( bHasHeaderLine )
|
|
{
|
|
aColumnName = aHeaderLine.GetTokenSpecial(nStartPosHeaderLine,m_cFieldDelimiter,m_cStringDelimiter);
|
|
}
|
|
if ( aColumnName.isEmpty() )
|
|
{
|
|
aColumnName = "C" + OUString::number(i+1);
|
|
}
|
|
aColumnNames.push_back(aColumnName);
|
|
}
|
|
if(bRead)
|
|
{
|
|
impl_fillColumnInfo_nothrow(m_aCurrentLine, nStartPosFirstLine, nStartPosFirstLine2,
|
|
m_aTypes[i], m_aPrecisions[i], m_aScales[i], aTypeNames[i],
|
|
cDecimalDelimiter, cThousandDelimiter, aCharClass);
|
|
}
|
|
}
|
|
++nRowCount;
|
|
bRead = readLine(&rowPos.second, &rowPos.first);
|
|
if(bRead)
|
|
setRowPos(rowNum++, rowPos);
|
|
}
|
|
while(nRowCount < nMaxRowsToScan && bRead);
|
|
|
|
for( sal_Int32 i = 0; i < nFieldCount; i++ )
|
|
{
|
|
// check if the columname already exists
|
|
OUString aAlias(aColumnNames[i]);
|
|
OSQLColumns::const_iterator aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
|
|
sal_Int32 nExprCnt = 0;
|
|
while(aFind != m_aColumns->end())
|
|
{
|
|
aAlias = aColumnNames[i] + OUString::number(++nExprCnt);
|
|
aFind = connectivity::find(m_aColumns->begin(),m_aColumns->end(),aAlias,aCase);
|
|
}
|
|
|
|
rtl::Reference<sdbcx::OColumn> pColumn = new sdbcx::OColumn(aAlias,aTypeNames[i],OUString(),OUString(),
|
|
ColumnValue::NULLABLE,
|
|
m_aPrecisions[i],
|
|
m_aScales[i],
|
|
m_aTypes[i],
|
|
false,
|
|
false,
|
|
false,
|
|
bCase,
|
|
m_CatalogName, getSchema(), getName());
|
|
m_aColumns->push_back(pColumn);
|
|
}
|
|
|
|
m_pFileStream->Seek(m_aRowPosToFilePos[0].second);
|
|
}
|
|
|
|
void OFlatTable::impl_fillColumnInfo_nothrow(QuotedTokenizedString const & aFirstLine, sal_Int32& nStartPosFirstLine, sal_Int32& nStartPosFirstLine2,
|
|
sal_Int32& io_nType, sal_Int32& io_nPrecisions, sal_Int32& io_nScales, OUString& o_sTypeName,
|
|
const sal_Unicode cDecimalDelimiter, const sal_Unicode cThousandDelimiter, const CharClass& aCharClass)
|
|
{
|
|
if ( io_nType != DataType::VARCHAR )
|
|
{
|
|
bool bNumeric = io_nType == DataType::SQLNULL || io_nType == DataType::DOUBLE || io_nType == DataType::DECIMAL || io_nType == DataType::INTEGER;
|
|
sal_Int32 nIndex = 0;
|
|
|
|
if ( bNumeric )
|
|
{
|
|
// first without fielddelimiter
|
|
OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
|
|
if (aField.isEmpty() ||
|
|
(m_cStringDelimiter && m_cStringDelimiter == aField[0]))
|
|
{
|
|
bNumeric = false;
|
|
if ( m_cStringDelimiter != '\0' )
|
|
aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
|
|
else
|
|
nStartPosFirstLine2 = nStartPosFirstLine;
|
|
}
|
|
else
|
|
{
|
|
OUString aField2;
|
|
if ( m_cStringDelimiter != '\0' )
|
|
aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
|
|
else
|
|
aField2 = aField;
|
|
|
|
if (aField2.isEmpty())
|
|
{
|
|
bNumeric = false;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nDot = 0;
|
|
sal_Int32 nDecimalDelCount = 0;
|
|
sal_Int32 nSpaceCount = 0;
|
|
for( sal_Int32 j = 0; j < aField2.getLength(); j++ )
|
|
{
|
|
const sal_Unicode c = aField2[j];
|
|
if ( j == nSpaceCount && m_cFieldDelimiter != 32 && c == 32 )
|
|
{
|
|
++nSpaceCount;
|
|
continue;
|
|
}
|
|
// just digits, decimal- and thousands-delimiter?
|
|
if ( ( !cDecimalDelimiter || c != cDecimalDelimiter ) &&
|
|
( !cThousandDelimiter || c != cThousandDelimiter ) &&
|
|
!aCharClass.isDigit(aField2,j) &&
|
|
( j != 0 || (c != '+' && c != '-' ) ) )
|
|
{
|
|
bNumeric = false;
|
|
break;
|
|
}
|
|
if (cDecimalDelimiter && c == cDecimalDelimiter)
|
|
{
|
|
io_nPrecisions = 15; // we have a decimal value
|
|
io_nScales = 2;
|
|
++nDecimalDelCount;
|
|
} // if (cDecimalDelimiter && c == cDecimalDelimiter)
|
|
if ( c == '.' )
|
|
++nDot;
|
|
}
|
|
|
|
if (nDecimalDelCount > 1 || nDot > 1 ) // if there is more than one dot it isn't a number
|
|
bNumeric = false;
|
|
if (bNumeric && cThousandDelimiter)
|
|
{
|
|
// Is the delimiter correct?
|
|
const std::u16string_view aValue = o3tl::getToken(aField2, 0, cDecimalDelimiter);
|
|
for( sal_Int32 j = static_cast<sal_Int32>(aValue.size()) - 4; j >= 0; j -= 4)
|
|
{
|
|
const sal_Unicode c = aValue[j];
|
|
// just digits, decimal- and thousands-delimiter?
|
|
if (c == cThousandDelimiter && j)
|
|
continue;
|
|
else
|
|
{
|
|
bNumeric = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// now also check for a date field
|
|
if (!bNumeric)
|
|
{
|
|
try
|
|
{
|
|
nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
|
|
}
|
|
catch(Exception&)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( io_nType == DataType::DATE || io_nType == DataType::TIMESTAMP || io_nType == DataType::TIME)
|
|
{
|
|
OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
|
|
if (aField.isEmpty() ||
|
|
(m_cStringDelimiter && m_cStringDelimiter == aField[0]))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
OUString aField2;
|
|
if ( m_cStringDelimiter != '\0' )
|
|
aField2 = aFirstLine.GetTokenSpecial(nStartPosFirstLine2,m_cFieldDelimiter,m_cStringDelimiter);
|
|
else
|
|
aField2 = aField;
|
|
if (!aField2.isEmpty() )
|
|
{
|
|
try
|
|
{
|
|
nIndex = m_xNumberFormatter->detectNumberFormat(css::util::NumberFormat::ALL,aField2);
|
|
}
|
|
catch(Exception&)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bNumeric)
|
|
{
|
|
if (cDecimalDelimiter)
|
|
{
|
|
if(io_nPrecisions)
|
|
{
|
|
io_nType = DataType::DECIMAL;
|
|
o_sTypeName = "DECIMAL";
|
|
}
|
|
else
|
|
{
|
|
io_nType = DataType::DOUBLE;
|
|
o_sTypeName = "DOUBLE";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
io_nType = DataType::INTEGER;
|
|
io_nPrecisions = 0;
|
|
io_nScales = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (comphelper::getNumberFormatType(m_xNumberFormatter,nIndex))
|
|
{
|
|
case css::util::NumberFormat::DATE:
|
|
io_nType = DataType::DATE;
|
|
o_sTypeName = "DATE";
|
|
break;
|
|
case css::util::NumberFormat::DATETIME:
|
|
io_nType = DataType::TIMESTAMP;
|
|
o_sTypeName = "TIMESTAMP";
|
|
break;
|
|
case css::util::NumberFormat::TIME:
|
|
io_nType = DataType::TIME;
|
|
o_sTypeName = "TIME";
|
|
break;
|
|
default:
|
|
io_nType = DataType::VARCHAR;
|
|
io_nPrecisions = 0; // nyi: Data can be longer!
|
|
io_nScales = 0;
|
|
o_sTypeName = "VARCHAR";
|
|
};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OUString aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine,m_cFieldDelimiter);
|
|
if (aField.isEmpty() ||
|
|
(m_cStringDelimiter && m_cStringDelimiter == aField[0]))
|
|
{
|
|
if ( m_cStringDelimiter != '\0' )
|
|
aField = aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
|
|
else
|
|
nStartPosFirstLine2 = nStartPosFirstLine;
|
|
}
|
|
else
|
|
{
|
|
if ( m_cStringDelimiter != '\0' )
|
|
aFirstLine.GetTokenSpecial(nStartPosFirstLine2, m_cFieldDelimiter, m_cStringDelimiter);
|
|
}
|
|
}
|
|
}
|
|
|
|
OFlatTable::OFlatTable(sdbcx::OCollection* _pTables,OFlatConnection* _pConnection,
|
|
const OUString& Name,
|
|
const OUString& Type,
|
|
const OUString& Description ,
|
|
const OUString& SchemaName,
|
|
const OUString& CatalogName
|
|
) : OFlatTable_BASE(_pTables,_pConnection,Name,
|
|
Type,
|
|
Description,
|
|
SchemaName,
|
|
CatalogName)
|
|
,m_nRowPos(0)
|
|
,m_nMaxRowCount(0)
|
|
,m_cStringDelimiter(_pConnection->getStringDelimiter())
|
|
,m_cFieldDelimiter(_pConnection->getFieldDelimiter())
|
|
,m_bNeedToReadLine(false)
|
|
{
|
|
|
|
}
|
|
|
|
void OFlatTable::construct()
|
|
{
|
|
SvtSysLocale aLocale;
|
|
css::lang::Locale aAppLocale(aLocale.GetLanguageTag().getLocale());
|
|
|
|
Reference< XNumberFormatsSupplier > xSupplier = NumberFormatsSupplier::createWithLocale( m_pConnection->getDriver()->getComponentContext(), aAppLocale );
|
|
m_xNumberFormatter.set( NumberFormatter::create( m_pConnection->getDriver()->getComponentContext()), UNO_QUERY_THROW);
|
|
m_xNumberFormatter->attachNumberFormatsSupplier(xSupplier);
|
|
Reference<XPropertySet> xProp = xSupplier->getNumberFormatSettings();
|
|
xProp->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;
|
|
|
|
INetURLObject aURL;
|
|
aURL.SetURL(getEntry());
|
|
|
|
if(aURL.getExtension() != m_pConnection->getExtension())
|
|
aURL.setExtension(m_pConnection->getExtension());
|
|
|
|
OUString aFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE);
|
|
|
|
m_pFileStream = createStream_simpleError( aFileName, StreamMode::READWRITE | StreamMode::NOCREATE | StreamMode::SHARE_DENYWRITE);
|
|
|
|
if(!m_pFileStream)
|
|
m_pFileStream = createStream_simpleError( aFileName, StreamMode::READ | StreamMode::NOCREATE | StreamMode::SHARE_DENYNONE);
|
|
|
|
if(!m_pFileStream)
|
|
return;
|
|
|
|
sal_uInt64 const nSize = m_pFileStream->remainingSize();
|
|
|
|
// Buffersize is dependent on the file-size
|
|
m_pFileStream->SetBufferSize(nSize > 1000000 ? 32768 :
|
|
nSize > 100000 ? 16384 :
|
|
nSize > 10000 ? 4096 : 1024);
|
|
|
|
fillColumns(aAppLocale);
|
|
|
|
refreshColumns();
|
|
}
|
|
|
|
OUString OFlatTable::getEntry() const
|
|
{
|
|
OUString sURL;
|
|
try
|
|
{
|
|
Reference< XResultSet > xDir = m_pConnection->getDir()->getStaticResultSet();
|
|
Reference< XRow> xRow(xDir,UNO_QUERY);
|
|
OUString sName;
|
|
OUString sExt;
|
|
|
|
INetURLObject aURL;
|
|
xDir->beforeFirst();
|
|
while(xDir->next())
|
|
{
|
|
sName = xRow->getString(1);
|
|
aURL.SetSmartProtocol(INetProtocol::File);
|
|
OUString sUrl = m_pConnection->getURL() + "/" + sName;
|
|
aURL.SetSmartURL( sUrl );
|
|
|
|
// cut the extension
|
|
sExt = aURL.getExtension();
|
|
|
|
// name and extension have to coincide
|
|
if ( m_pConnection->matchesExtension( sExt ) )
|
|
{
|
|
if ( !sExt.isEmpty() )
|
|
sName = sName.replaceAt(sName.getLength() - (sExt.getLength() + 1), sExt.getLength()+1, u"");
|
|
if ( sName == m_Name )
|
|
{
|
|
Reference< XContentAccess > xContentAccess( xDir, UNO_QUERY );
|
|
sURL = xContentAccess->queryContentIdentifierString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
xDir->beforeFirst(); // move back to before first record
|
|
}
|
|
catch(const Exception&)
|
|
{
|
|
OSL_ASSERT(false);
|
|
}
|
|
return sURL;
|
|
}
|
|
|
|
void OFlatTable::refreshColumns()
|
|
{
|
|
::osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
::std::vector< OUString> aVector;
|
|
aVector.reserve(m_aColumns->size());
|
|
|
|
for (auto const& column : *m_aColumns)
|
|
aVector.push_back(Reference< XNamed>(column,UNO_QUERY_THROW)->getName());
|
|
|
|
if(m_xColumns)
|
|
m_xColumns->reFill(aVector);
|
|
else
|
|
m_xColumns.reset(new OFlatColumns(this,m_aMutex,aVector));
|
|
}
|
|
|
|
|
|
void SAL_CALL OFlatTable::disposing()
|
|
{
|
|
OFileTable::disposing();
|
|
::osl::MutexGuard aGuard(m_aMutex);
|
|
m_aColumns = nullptr;
|
|
}
|
|
|
|
Sequence< Type > SAL_CALL OFlatTable::getTypes( )
|
|
{
|
|
Sequence< Type > aTypes = OTable_TYPEDEF::getTypes();
|
|
vector<Type> aOwnTypes;
|
|
aOwnTypes.reserve(aTypes.getLength());
|
|
const Type* pBegin = aTypes.getConstArray();
|
|
const Type* pEnd = pBegin + aTypes.getLength();
|
|
for(;pBegin != pEnd;++pBegin)
|
|
{
|
|
if(!(*pBegin == cppu::UnoType<XKeysSupplier>::get()||
|
|
*pBegin == cppu::UnoType<XRename>::get()||
|
|
*pBegin == cppu::UnoType<XIndexesSupplier>::get()||
|
|
*pBegin == cppu::UnoType<XAlterTable>::get()||
|
|
*pBegin == cppu::UnoType<XDataDescriptorFactory>::get()))
|
|
{
|
|
aOwnTypes.push_back(*pBegin);
|
|
}
|
|
}
|
|
return Sequence< Type >(aOwnTypes.data(), aOwnTypes.size());
|
|
}
|
|
|
|
|
|
Any SAL_CALL OFlatTable::queryInterface( const Type & rType )
|
|
{
|
|
if( rType == cppu::UnoType<XKeysSupplier>::get()||
|
|
rType == cppu::UnoType<XIndexesSupplier>::get()||
|
|
rType == cppu::UnoType<XRename>::get()||
|
|
rType == cppu::UnoType<XAlterTable>::get()||
|
|
rType == cppu::UnoType<XDataDescriptorFactory>::get())
|
|
return Any();
|
|
|
|
Any aRet = OTable_TYPEDEF::queryInterface(rType);
|
|
return aRet;
|
|
}
|
|
|
|
|
|
bool OFlatTable::fetchRow(OValueRefRow& _rRow, const OSQLColumns & _rCols, bool bRetrieveData)
|
|
{
|
|
*(*_rRow)[0] = m_nFilePos;
|
|
|
|
if (!bRetrieveData)
|
|
return true;
|
|
|
|
bool result = false;
|
|
if ( m_bNeedToReadLine )
|
|
{
|
|
m_pFileStream->Seek(m_nFilePos);
|
|
TRowPositionInFile rowPos(0, 0);
|
|
if(readLine(&rowPos.second, &rowPos.first))
|
|
{
|
|
setRowPos(m_nRowPos, rowPos);
|
|
m_bNeedToReadLine = false;
|
|
result = true;
|
|
}
|
|
// else let run through so that we set _rRow to all NULL
|
|
}
|
|
|
|
const OFlatConnection * const pConnection = getFlatConnection();
|
|
const sal_Unicode cDecimalDelimiter = pConnection->getDecimalDelimiter();
|
|
const sal_Unicode cThousandDelimiter = pConnection->getThousandDelimiter();
|
|
// Fields:
|
|
sal_Int32 nStartPos = 0;
|
|
OSQLColumns::const_iterator aIter = _rCols.begin();
|
|
OSQLColumns::const_iterator aEnd = _rCols.end();
|
|
const OValueRefVector::size_type nCount = _rRow->size();
|
|
for (OValueRefVector::size_type i = 1;
|
|
aIter != aEnd && i < nCount;
|
|
++aIter, i++)
|
|
{
|
|
OUString aStr = m_aCurrentLine.GetTokenSpecial(nStartPos,m_cFieldDelimiter,m_cStringDelimiter);
|
|
|
|
if (aStr.isEmpty())
|
|
{
|
|
(*_rRow)[i]->setNull();
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nType = m_aTypes[i-1];
|
|
switch(nType)
|
|
{
|
|
case DataType::TIMESTAMP:
|
|
case DataType::DATE:
|
|
case DataType::TIME:
|
|
{
|
|
try
|
|
{
|
|
double nRes = m_xNumberFormatter->convertStringToNumber(css::util::NumberFormat::ALL,aStr);
|
|
|
|
switch(nType)
|
|
{
|
|
case DataType::DATE:
|
|
*(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDate(nRes,m_aNullDate));
|
|
break;
|
|
case DataType::TIMESTAMP:
|
|
*(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toDateTime(nRes,m_aNullDate));
|
|
break;
|
|
default:
|
|
*(*_rRow)[i] = ::dbtools::DBTypeConversion::toDouble(::dbtools::DBTypeConversion::toTime(nRes));
|
|
}
|
|
}
|
|
catch(Exception&)
|
|
{
|
|
(*_rRow)[i]->setNull();
|
|
}
|
|
} break;
|
|
case DataType::DOUBLE:
|
|
case DataType::INTEGER:
|
|
case DataType::DECIMAL:
|
|
case DataType::NUMERIC:
|
|
{
|
|
|
|
OUString aStrConverted;
|
|
if ( DataType::INTEGER != nType )
|
|
{
|
|
OSL_ENSURE((cDecimalDelimiter && nType != DataType::INTEGER) ||
|
|
(!cDecimalDelimiter && nType == DataType::INTEGER),
|
|
"Wrong type");
|
|
|
|
OUStringBuffer aBuf(aStr.getLength());
|
|
// convert to Standard-Notation (DecimalPOINT without thousands-comma):
|
|
for (sal_Int32 j = 0; j < aStr.getLength(); ++j)
|
|
{
|
|
const sal_Unicode cChar = aStr[j];
|
|
if (cDecimalDelimiter && cChar == cDecimalDelimiter)
|
|
aBuf.append('.');
|
|
else if ( cChar == '.' ) // special case, if decimal separator isn't '.' we have to put the string after it
|
|
continue;
|
|
else if (cThousandDelimiter && cChar == cThousandDelimiter)
|
|
{
|
|
// leave out
|
|
}
|
|
else
|
|
aBuf.append(cChar);
|
|
} // for (j = 0; j < aStr.(); ++j)
|
|
aStrConverted = aBuf.makeStringAndClear();
|
|
} // if ( DataType::INTEGER != nType )
|
|
else
|
|
{
|
|
if ( cThousandDelimiter )
|
|
aStrConverted = aStr.replaceAll(OUStringChar(cThousandDelimiter), "");
|
|
else
|
|
aStrConverted = aStr;
|
|
}
|
|
const double nVal = ::rtl::math::stringToDouble(aStrConverted,'.',',');
|
|
|
|
// #99178# OJ
|
|
if ( DataType::DECIMAL == nType || DataType::NUMERIC == nType )
|
|
*(*_rRow)[i] = OUString::number(nVal);
|
|
else
|
|
*(*_rRow)[i] = nVal;
|
|
} break;
|
|
|
|
default:
|
|
{
|
|
// Copy Value as String in Row-Variable
|
|
*(*_rRow)[i] = ORowSetValue(aStr);
|
|
}
|
|
break;
|
|
} // switch(nType)
|
|
(*_rRow)[i]->setTypeKind(nType);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
void OFlatTable::refreshHeader()
|
|
{
|
|
SAL_INFO( "connectivity.flat", "flat lionel@mamane.lu OFlatTable::refreshHeader" );
|
|
}
|
|
|
|
|
|
namespace
|
|
{
|
|
template< typename Tp, typename Te> struct RangeBefore
|
|
{
|
|
bool operator() (const Tp &p, const Te &e)
|
|
{
|
|
assert(p.first <= p.second);
|
|
return p.second <= e;
|
|
}
|
|
};
|
|
}
|
|
|
|
bool OFlatTable::seekRow(IResultSetHelper::Movement eCursorPosition, sal_Int32 nOffset, sal_Int32& nCurPos)
|
|
{
|
|
OSL_ENSURE(m_pFileStream,"OFlatTable::seekRow: FileStream is NULL!");
|
|
|
|
|
|
switch(eCursorPosition)
|
|
{
|
|
case IResultSetHelper::FIRST:
|
|
m_nRowPos = 0;
|
|
[[fallthrough]];
|
|
case IResultSetHelper::NEXT:
|
|
{
|
|
assert(m_nRowPos >= 0);
|
|
if(m_nMaxRowCount != 0 && m_nRowPos > m_nMaxRowCount)
|
|
return false;
|
|
++m_nRowPos;
|
|
if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos))
|
|
{
|
|
m_bNeedToReadLine = true;
|
|
m_nFilePos = m_aRowPosToFilePos[m_nRowPos].first;
|
|
nCurPos = m_aRowPosToFilePos[m_nRowPos].second;
|
|
}
|
|
else
|
|
{
|
|
assert(m_aRowPosToFilePos.size() == static_cast< vector< TRowPositionInFile >::size_type >(m_nRowPos));
|
|
const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
|
|
// Our ResultSet is allowed to disagree with us only
|
|
// on the position of the first line
|
|
// (because of the special case of the header...)
|
|
assert(m_nRowPos == 1 || nCurPos == lastRowPos.second);
|
|
|
|
m_nFilePos = lastRowPos.second;
|
|
m_pFileStream->Seek(m_nFilePos);
|
|
|
|
TRowPositionInFile newRowPos;
|
|
if(!readLine(&newRowPos.second, &newRowPos.first))
|
|
{
|
|
m_nMaxRowCount = m_nRowPos - 1;
|
|
return false;
|
|
}
|
|
|
|
nCurPos = newRowPos.second;
|
|
setRowPos(m_nRowPos, newRowPos);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case IResultSetHelper::PRIOR:
|
|
assert(m_nRowPos >= 0);
|
|
|
|
if(m_nRowPos == 0)
|
|
return false;
|
|
|
|
--m_nRowPos;
|
|
{
|
|
assert (m_nRowPos >= 0);
|
|
assert(m_aRowPosToFilePos.size() >= o3tl::make_unsigned(m_nRowPos));
|
|
const TRowPositionInFile &aPositions(m_aRowPosToFilePos[m_nRowPos]);
|
|
m_nFilePos = aPositions.first;
|
|
nCurPos = aPositions.second;
|
|
m_bNeedToReadLine = true;
|
|
}
|
|
|
|
break;
|
|
case IResultSetHelper::LAST:
|
|
if (m_nMaxRowCount == 0)
|
|
{
|
|
while(seekRow(IResultSetHelper::NEXT, 1, nCurPos)) ; // run through after last row
|
|
}
|
|
// m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
|
|
return seekRow(IResultSetHelper::ABSOLUTE1, m_nMaxRowCount, nCurPos);
|
|
case IResultSetHelper::RELATIVE1:
|
|
{
|
|
const sal_Int32 nNewRowPos = m_nRowPos + nOffset;
|
|
if (nNewRowPos < 0)
|
|
return false;
|
|
// ABSOLUTE will take care of case nNewRowPos > nMaxRowCount
|
|
return seekRow(IResultSetHelper::ABSOLUTE1, nNewRowPos, nCurPos);
|
|
}
|
|
case IResultSetHelper::ABSOLUTE1:
|
|
{
|
|
if(nOffset < 0)
|
|
{
|
|
if (m_nMaxRowCount == 0)
|
|
{
|
|
if (!seekRow(IResultSetHelper::LAST, 0, nCurPos))
|
|
return false;
|
|
}
|
|
// m_nMaxRowCount can still be zero, but now it means there a genuinely zero rows in the table
|
|
nOffset = m_nMaxRowCount + nOffset;
|
|
}
|
|
if(nOffset < 0)
|
|
{
|
|
seekRow(IResultSetHelper::ABSOLUTE1, 0, nCurPos);
|
|
return false;
|
|
}
|
|
if(m_nMaxRowCount && nOffset > m_nMaxRowCount)
|
|
{
|
|
m_nRowPos = m_nMaxRowCount + 1;
|
|
const TRowPositionInFile &lastRowPos(m_aRowPosToFilePos.back());
|
|
m_nFilePos = lastRowPos.second;
|
|
nCurPos = lastRowPos.second;
|
|
return false;
|
|
}
|
|
|
|
assert(m_nRowPos >=0);
|
|
assert(m_aRowPosToFilePos.size() > o3tl::make_unsigned(m_nRowPos));
|
|
assert(nOffset >= 0);
|
|
if(m_aRowPosToFilePos.size() > o3tl::make_unsigned(nOffset))
|
|
{
|
|
m_nFilePos = m_aRowPosToFilePos[nOffset].first;
|
|
nCurPos = m_aRowPosToFilePos[nOffset].second;
|
|
m_nRowPos = nOffset;
|
|
m_bNeedToReadLine = true;
|
|
}
|
|
else
|
|
{
|
|
assert(m_nRowPos < nOffset);
|
|
while(m_nRowPos < nOffset)
|
|
{
|
|
if(!seekRow(IResultSetHelper::NEXT, 1, nCurPos))
|
|
return false;
|
|
}
|
|
assert(m_nRowPos == nOffset);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case IResultSetHelper::BOOKMARK:
|
|
{
|
|
vector< TRowPositionInFile >::const_iterator aFind = lower_bound(m_aRowPosToFilePos.begin(),
|
|
m_aRowPosToFilePos.end(),
|
|
nOffset,
|
|
RangeBefore< TRowPositionInFile, sal_Int32 >());
|
|
|
|
if(aFind == m_aRowPosToFilePos.end() || aFind->first != nOffset)
|
|
//invalid bookmark
|
|
return false;
|
|
|
|
m_bNeedToReadLine = true;
|
|
m_nFilePos = aFind->first;
|
|
nCurPos = aFind->second;
|
|
m_nRowPos = aFind - m_aRowPosToFilePos.begin();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool OFlatTable::readLine(sal_Int32 * const pEndPos, sal_Int32 * const pStartPos, const bool nonEmpty)
|
|
{
|
|
const rtl_TextEncoding nEncoding = m_pConnection->getTextEncoding();
|
|
m_aCurrentLine = QuotedTokenizedString();
|
|
do
|
|
{
|
|
if (pStartPos)
|
|
*pStartPos = static_cast<sal_Int32>(m_pFileStream->Tell());
|
|
m_pFileStream->ReadByteStringLine(m_aCurrentLine, nEncoding);
|
|
if (m_pFileStream->eof())
|
|
return false;
|
|
|
|
QuotedTokenizedString sLine = m_aCurrentLine; // check if the string continues on next line
|
|
sal_Int32 nLastOffset = 0;
|
|
bool isQuoted = false;
|
|
bool isFieldStarting = true;
|
|
while (true)
|
|
{
|
|
bool wasQuote = false;
|
|
const sal_Unicode *p = sLine.GetString().getStr() + nLastOffset;
|
|
while (*p)
|
|
{
|
|
if (isQuoted)
|
|
{
|
|
if (*p == m_cStringDelimiter)
|
|
wasQuote = !wasQuote;
|
|
else
|
|
{
|
|
if (wasQuote)
|
|
{
|
|
wasQuote = false;
|
|
isQuoted = false;
|
|
if (*p == m_cFieldDelimiter)
|
|
isFieldStarting = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isFieldStarting)
|
|
{
|
|
isFieldStarting = false;
|
|
if (*p == m_cStringDelimiter)
|
|
isQuoted = true;
|
|
else if (*p == m_cFieldDelimiter)
|
|
isFieldStarting = true;
|
|
}
|
|
else if (*p == m_cFieldDelimiter)
|
|
isFieldStarting = true;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
if (wasQuote)
|
|
isQuoted = false;
|
|
|
|
if (isQuoted)
|
|
{
|
|
nLastOffset = sLine.Len();
|
|
m_pFileStream->ReadByteStringLine(sLine,nEncoding);
|
|
if ( !m_pFileStream->eof() )
|
|
{
|
|
OUString aStr = m_aCurrentLine.GetString() + "\n" + sLine.GetString();
|
|
m_aCurrentLine.SetString(aStr);
|
|
sLine = m_aCurrentLine;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
while(nonEmpty && m_aCurrentLine.Len() == 0);
|
|
|
|
if(pEndPos)
|
|
*pEndPos = static_cast<sal_Int32>(m_pFileStream->Tell());
|
|
return true;
|
|
}
|
|
|
|
|
|
void OFlatTable::setRowPos(const vector<TRowPositionInFile>::size_type rowNum, const TRowPositionInFile &rowPos)
|
|
{
|
|
assert(m_aRowPosToFilePos.size() >= rowNum);
|
|
if(m_aRowPosToFilePos.size() == rowNum)
|
|
m_aRowPosToFilePos.push_back(rowPos);
|
|
else
|
|
{
|
|
SAL_WARN_IF(m_aRowPosToFilePos[rowNum] != rowPos,
|
|
"connectivity.flat",
|
|
"Setting position for row " << rowNum << " to (" << rowPos.first << ", " << rowPos.second << "), "
|
|
"but already had different position (" << m_aRowPosToFilePos[rowNum].first << ", " << m_aRowPosToFilePos[rowNum].second << ")");
|
|
m_aRowPosToFilePos[rowNum] = rowPos;
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|