diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /connectivity/source/drivers/firebird | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
46 files changed, 10168 insertions, 0 deletions
diff --git a/connectivity/source/drivers/firebird/Blob.cxx b/connectivity/source/drivers/firebird/Blob.cxx new file mode 100644 index 000000000..33ab36b8d --- /dev/null +++ b/connectivity/source/drivers/firebird/Blob.cxx @@ -0,0 +1,391 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Blob.hxx" +#include "Util.hxx" + +#include <com/sun/star/io/BufferSizeExceededException.hpp> +#include <com/sun/star/io/NotConnectedException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <connectivity/CommonTools.hxx> +#include <connectivity/dbexception.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::connectivity::firebird; + +using namespace ::cppu; +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +Blob::Blob(isc_db_handle* pDatabaseHandle, + isc_tr_handle* pTransactionHandle, + ISC_QUAD const & aBlobID): + Blob_BASE(m_aMutex), + m_pDatabaseHandle(pDatabaseHandle), + m_pTransactionHandle(pTransactionHandle), + m_blobID(aBlobID), +#if SAL_TYPES_SIZEOFPOINTER == 8 + m_blobHandle(0), +#else + m_blobHandle(nullptr), +#endif + m_bBlobOpened(false), + m_nBlobLength(0), + m_nMaxSegmentSize(0), + m_nBlobPosition(0) +{ +} + +void Blob::ensureBlobIsOpened() +{ + MutexGuard aGuard(m_aMutex); + + if (m_bBlobOpened) + return; + + ISC_STATUS aErr; + aErr = isc_open_blob2(m_statusVector, + m_pDatabaseHandle, + m_pTransactionHandle, + &m_blobHandle, + &m_blobID, + 0, + nullptr); + + if (aErr) + evaluateStatusVector(m_statusVector, u"isc_open_blob2", *this); + + m_bBlobOpened = true; + m_nBlobPosition = 0; + + char aBlobItems[] = { + isc_info_blob_total_length, + isc_info_blob_max_segment + }; + + // Assuming a data (e.g. length of blob) is maximum 64 bit. + // That means we need 8 bytes for data + 2 for length of data + 1 for item + // identifier for each item. + char aResultBuffer[11 + 11]; + + aErr = isc_blob_info(m_statusVector, + &m_blobHandle, + sizeof(aBlobItems), + aBlobItems, + sizeof(aResultBuffer), + aResultBuffer); + + if (aErr) + evaluateStatusVector(m_statusVector, u"isc_blob_info", *this); + + char* pIt = aResultBuffer; + while( *pIt != isc_info_end ) // info is in clusters + { + char item = *pIt++; + short aResultLength = static_cast<short>(isc_vax_integer(pIt, 2)); + + pIt += 2; + switch(item) + { + case isc_info_blob_total_length: + m_nBlobLength = isc_vax_integer(pIt, aResultLength); + break; + case isc_info_blob_max_segment: + m_nMaxSegmentSize = isc_vax_integer(pIt, aResultLength); + break; + default: + assert(false); + break; + } + pIt += aResultLength; + } +} + +sal_uInt16 Blob::getMaximumSegmentSize() +{ + ensureBlobIsOpened(); + + return m_nMaxSegmentSize; +} + +bool Blob::readOneSegment(std::vector<char>& rDataOut) +{ + checkDisposed(Blob_BASE::rBHelper.bDisposed); + ensureBlobIsOpened(); + + sal_uInt16 nMaxSize = getMaximumSegmentSize(); + + if(rDataOut.size() < nMaxSize) + rDataOut.resize(nMaxSize); + + sal_uInt16 nActualSize = 0; + ISC_STATUS aRet = isc_get_segment(m_statusVector, + &m_blobHandle, + &nActualSize, + nMaxSize, + rDataOut.data() ); + + if (aRet && aRet != isc_segstr_eof && IndicatesError(m_statusVector)) + { + OUString sError(StatusVectorToString(m_statusVector, u"isc_get_segment")); + throw IOException(sError, *this); + } + + if (rDataOut.size() > nActualSize) + rDataOut.resize(nActualSize); + m_nBlobPosition += nActualSize; + return aRet == isc_segstr_eof; // last segment read +} + + +void Blob::closeBlob() +{ + MutexGuard aGuard(m_aMutex); + + if (!m_bBlobOpened) + return; + + ISC_STATUS aErr; + aErr = isc_close_blob(m_statusVector, + &m_blobHandle); + if (aErr) + evaluateStatusVector(m_statusVector, u"isc_close_blob", *this); + + m_bBlobOpened = false; +#if SAL_TYPES_SIZEOFPOINTER == 8 + m_blobHandle = 0; +#else + m_blobHandle = nullptr; +#endif +} + +void SAL_CALL Blob::disposing() +{ + try + { + closeBlob(); + } + catch (const SQLException &) + { + // we cannot throw any exceptions here... + TOOLS_WARN_EXCEPTION("connectivity.firebird", "isc_close_blob failed"); + assert(false); + } + Blob_BASE::disposing(); +} + +sal_Int64 SAL_CALL Blob::length() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Blob_BASE::rBHelper.bDisposed); + ensureBlobIsOpened(); + + return m_nBlobLength; +} + +uno::Sequence< sal_Int8 > SAL_CALL Blob::getBytes(sal_Int64 nPosition, + sal_Int32 nBytes) +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Blob_BASE::rBHelper.bDisposed); + ensureBlobIsOpened(); + + if (nPosition > m_nBlobLength || nPosition < 1) + throw lang::IllegalArgumentException("nPosition out of range", *this, 0); + // We only have to read as many bytes as are available, i.e. nPosition+nBytes + // can legally be greater than the total length, hence we don't bother to check. + + if (nPosition -1 < m_nBlobPosition) + { + // Resets to the beginning (we can't seek these blobs) + closeBlob(); + ensureBlobIsOpened(); + } + + // nPosition is indexed from 1. + skipBytes(nPosition - m_nBlobPosition -1 ); + + // Don't bother preallocating: readBytes does the appropriate calculations + // and reallocates for us. + uno::Sequence< sal_Int8 > aBytes; + readBytes(aBytes, nBytes); + return aBytes; +} + +uno::Reference< XInputStream > SAL_CALL Blob::getBinaryStream() +{ + return this; +} + +sal_Int64 SAL_CALL Blob::position(const uno::Sequence< sal_Int8 >& /*rPattern*/, + sal_Int64 /*nStart*/) +{ + ::dbtools::throwFeatureNotImplementedSQLException("Blob::position", *this); + return 0; +} + +sal_Int64 SAL_CALL Blob::positionOfBlob(const uno::Reference< XBlob >& /*rPattern*/, + sal_Int64 /*aStart*/) +{ + ::dbtools::throwFeatureNotImplementedSQLException("Blob::positionOfBlob", *this); + return 0; +} + +// ---- XInputStream ---------------------------------------------------------- + +sal_Int32 SAL_CALL Blob::readBytes(uno::Sequence< sal_Int8 >& rDataOut, + sal_Int32 nBytes) +{ + MutexGuard aGuard(m_aMutex); + + try + { + checkDisposed(Blob_BASE::rBHelper.bDisposed); + ensureBlobIsOpened(); + } + catch (const NotConnectedException&) + { + throw; + } + catch (const BufferSizeExceededException&) + { + throw; + } + catch (const IOException&) + { + throw; + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception& e) + { + css::uno::Any a(cppu::getCaughtException()); + throw css::lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + css::uno::Reference<css::uno::XInterface>(), a); + } + + // Ensure we have enough space for the amount of data we can actually read. + const sal_Int64 nBytesAvailable = m_nBlobLength - m_nBlobPosition; + const sal_Int32 nBytesToRead = std::min<sal_Int64>(nBytes, nBytesAvailable); + + if (rDataOut.getLength() < nBytesToRead) + rDataOut.realloc(nBytesToRead); + + sal_Int32 nTotalBytesRead = 0; + ISC_STATUS aErr; + while (nTotalBytesRead < nBytesToRead) + { + sal_uInt16 nBytesRead = 0; + sal_uInt64 nDataRemaining = nBytesToRead - nTotalBytesRead; + sal_uInt16 nReadSize = std::min<sal_uInt64>(nDataRemaining, SAL_MAX_UINT16); + aErr = isc_get_segment(m_statusVector, + &m_blobHandle, + &nBytesRead, + nReadSize, + reinterpret_cast<char*>(rDataOut.getArray()) + nTotalBytesRead); + if (aErr && IndicatesError(m_statusVector)) + { + OUString sError(StatusVectorToString(m_statusVector, u"isc_get_segment")); + throw IOException(sError, *this); + } + nTotalBytesRead += nBytesRead; + m_nBlobPosition += nBytesRead; + } + + return nTotalBytesRead; +} + +sal_Int32 SAL_CALL Blob::readSomeBytes(uno::Sequence< sal_Int8 >& rDataOut, + sal_Int32 nMaximumBytes) +{ + // We don't have any way of verifying how many bytes are immediately available, + // hence we just pass through direct to readBytes + // (Spec: "reads the available number of bytes, at maximum nMaxBytesToRead.") + return readBytes(rDataOut, nMaximumBytes); +} + +void SAL_CALL Blob::skipBytes(sal_Int32 nBytesToSkip) +{ + // There is no way of directly skipping, hence we have to pretend to skip + // by reading & discarding the data. + uno::Sequence< sal_Int8 > aBytes; + readBytes(aBytes, nBytesToSkip); +} + +sal_Int32 SAL_CALL Blob::available() +{ + MutexGuard aGuard(m_aMutex); + + try + { + checkDisposed(Blob_BASE::rBHelper.bDisposed); + ensureBlobIsOpened(); + } + catch (const NotConnectedException&) + { + throw; + } + catch (const IOException&) + { + throw; + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception& e) + { + css::uno::Any a(cppu::getCaughtException()); + throw css::lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + css::uno::Reference<css::uno::XInterface>(), a); + } + + return m_nBlobLength - m_nBlobPosition; +} + +void SAL_CALL Blob::closeInput() +{ + try + { + closeBlob(); + } + catch (const NotConnectedException&) + { + throw; + } + catch (const IOException&) + { + throw; + } + catch (const RuntimeException&) + { + throw; + } + catch (const Exception& e) + { + css::uno::Any a(cppu::getCaughtException()); + throw css::lang::WrappedTargetRuntimeException( + "wrapped Exception " + e.Message, + css::uno::Reference<css::uno::XInterface>(), a); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Blob.hxx b/connectivity/source/drivers/firebird/Blob.hxx new file mode 100644 index 000000000..990108934 --- /dev/null +++ b/connectivity/source/drivers/firebird/Blob.hxx @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <ibase.h> + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/sdbc/XBlob.hpp> + +#include <vector> + +namespace connectivity::firebird + { + typedef ::cppu::WeakComponentImplHelper< css::sdbc::XBlob, + css::io::XInputStream > + Blob_BASE; + + class Blob : + public Blob_BASE + { + protected: + ::osl::Mutex m_aMutex; + + isc_db_handle* m_pDatabaseHandle; + isc_tr_handle* m_pTransactionHandle; + // We store our own copy of the blob id as typically the statement + // manages its own blob id, and blobs are independent of a statement + // in firebird. + ISC_QUAD m_blobID; + isc_blob_handle m_blobHandle; + + bool m_bBlobOpened; + sal_Int64 m_nBlobLength; + sal_uInt16 m_nMaxSegmentSize; + sal_Int64 m_nBlobPosition; + + ISC_STATUS_ARRAY m_statusVector; + + /// @throws css::sdbc::SQLException + void ensureBlobIsOpened(); + /** + * Closes the blob and cleans up resources -- can be used to reset + * the blob if we e.g. want to read from the beginning again. + * + * @throws css::sdbc::SQLException + */ + void closeBlob(); + sal_uInt16 getMaximumSegmentSize(); + + public: + Blob(isc_db_handle* pDatabaseHandle, + isc_tr_handle* pTransactionHandle, + ISC_QUAD const & aBlobID); + + bool readOneSegment(std::vector<char>& rDataOut); + + // ---- XBlob ---------------------------------------------------- + virtual sal_Int64 SAL_CALL + length() override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL + getBytes(sal_Int64 aPosition, sal_Int32 aLength) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL + getBinaryStream() override; + virtual sal_Int64 SAL_CALL + position(const css::uno::Sequence< sal_Int8 >& rPattern, + sal_Int64 aStart) override; + virtual sal_Int64 SAL_CALL + positionOfBlob(const css::uno::Reference< css::sdbc::XBlob >& rPattern, + sal_Int64 aStart) override; + + // ---- XInputStream ---------------------------------------------- + virtual sal_Int32 SAL_CALL + readBytes(css::uno::Sequence< sal_Int8 >& rDataOut, + sal_Int32 nBytes) override; + virtual sal_Int32 SAL_CALL + readSomeBytes(css::uno::Sequence< sal_Int8 >& rDataOut, + sal_Int32 nMaximumBytes) override; + virtual void SAL_CALL + skipBytes(sal_Int32 nBytes) override; + virtual sal_Int32 SAL_CALL + available() override; + virtual void SAL_CALL + closeInput() override; + + // ---- OComponentHelper ------------------------------------------ + virtual void SAL_CALL disposing() override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Catalog.cxx b/connectivity/source/drivers/firebird/Catalog.cxx new file mode 100644 index 000000000..2ef4f514b --- /dev/null +++ b/connectivity/source/drivers/firebird/Catalog.cxx @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Catalog.hxx" +#include "Tables.hxx" +#include "Users.hxx" +#include "Views.hxx" + +#include <com/sun/star/sdbc/XRow.hpp> + +using namespace ::connectivity::firebird; +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +Catalog::Catalog(const uno::Reference< XConnection >& rConnection): + OCatalog(rConnection), + m_xConnection(rConnection) +{ +} + +//----- OCatalog ------------------------------------------------------------- +void Catalog::refreshTables() +{ + Sequence< OUString > aTypes {"TABLE", "VIEW"}; + + uno::Reference< XResultSet > xTables = m_xMetaData->getTables(Any(), + "%", + "%", + aTypes); + + if (!xTables.is()) + return; + + ::std::vector< OUString> aTableNames; + + fillNames(xTables, aTableNames); + + if (!m_pTables) + m_pTables.reset( new Tables(m_xConnection->getMetaData(), + *this, + m_aMutex, + aTableNames) ); + else + m_pTables->reFill(aTableNames); + +} + +void Catalog::refreshViews() +{ + css::uno::Reference<css::sdbc::XResultSet> xViews + = m_xMetaData->getTables(css::uno::Any(), "%", "%", { "VIEW" }); + + if (!xViews.is()) + return; + + ::std::vector<OUString> aViewNames; + + fillNames(xViews, aViewNames); + + if (!m_pViews) + m_pViews.reset(new Views(m_xConnection, *this, m_aMutex, aViewNames)); + else + m_pViews->reFill(aViewNames); +} + +//----- IRefreshableGroups --------------------------------------------------- +void Catalog::refreshGroups() +{ + // TODO: implement me +} + +//----- IRefreshableUsers ---------------------------------------------------- +void Catalog::refreshUsers() +{ + Reference<XStatement> xStmt= m_xMetaData->getConnection()->createStatement(); + uno::Reference< XResultSet > xUsers = xStmt->executeQuery("SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES"); + + if (!xUsers.is()) + return; + + ::std::vector< OUString> aUserNames; + + uno::Reference< XRow > xRow(xUsers,UNO_QUERY); + while (xUsers->next()) + { + aUserNames.push_back(xRow->getString(1)); + } + + if (!m_pUsers) + m_pUsers.reset( new Users(m_xConnection->getMetaData(), + *this, + m_aMutex, + aUserNames) ); + else + m_pUsers->reFill(aUserNames); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Catalog.hxx b/connectivity/source/drivers/firebird/Catalog.hxx new file mode 100644 index 000000000..3ffb9238e --- /dev/null +++ b/connectivity/source/drivers/firebird/Catalog.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <sdbcx/VCatalog.hxx> + +namespace connectivity::firebird + { + class Catalog: public ::connectivity::sdbcx::OCatalog + { + css::uno::Reference< css::sdbc::XConnection > + m_xConnection; + + public: + explicit Catalog(const css::uno::Reference< css::sdbc::XConnection >& rConnection); + + // OCatalog + virtual void refreshTables() override; + virtual void refreshViews() override; + + // IRefreshableGroups + virtual void refreshGroups() override; + + // IRefreshableUsers + virtual void refreshUsers() override; + + sdbcx::OCollection* getPrivateTables() const { return m_pTables.get(); } + sdbcx::OCollection* getPrivateViews() const { return m_pViews.get(); } + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Clob.cxx b/connectivity/source/drivers/firebird/Clob.cxx new file mode 100644 index 000000000..dde050ede --- /dev/null +++ b/connectivity/source/drivers/firebird/Clob.cxx @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include "Clob.hxx" +#include "Blob.hxx" + +#include <connectivity/CommonTools.hxx> +#include <connectivity/dbexception.hxx> + +using namespace ::connectivity::firebird; + +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +Clob::Clob(isc_db_handle* pDatabaseHandle, + isc_tr_handle* pTransactionHandle, + ISC_QUAD const & aBlobID): + Clob_BASE(m_aMutex), + m_aBlob(new connectivity::firebird::Blob(pDatabaseHandle, pTransactionHandle, aBlobID)), + m_nCharCount(-1) +{ +} + +void SAL_CALL Clob::disposing() +{ + m_aBlob->dispose(); + m_aBlob.clear(); + Clob_BASE::disposing(); +} + +sal_Int64 SAL_CALL Clob::length() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Clob_BASE::rBHelper.bDisposed); + + if( m_nCharCount >= 0 ) + return m_nCharCount; + m_nCharCount = 0; + + // Read each segment, and calculate it's size by interpreting it as a + // character stream. Assume that no characters are split by the segments. + bool bLastSegmRead = false; + std::vector<char> aSegmentBytes; + do + { + bLastSegmRead = m_aBlob->readOneSegment( aSegmentBytes ); + OUString sSegment(aSegmentBytes.data(), aSegmentBytes.size(), RTL_TEXTENCODING_UTF8); + + if( !bLastSegmRead) + m_nCharCount += sSegment.getLength(); + }while( !bLastSegmRead ); + + m_aBlob->closeInput(); // reset position + return m_nCharCount; +} + +OUString SAL_CALL Clob::getSubString(sal_Int64 nPosition, + sal_Int32 nLength) +{ + if (nPosition < 1) // XClob is indexed from 1 + throw lang::IllegalArgumentException("nPosition < 1", *this, 0); + --nPosition; // make 0-based + + if (nLength < 0) + throw lang::IllegalArgumentException("nLength < 0", *this, 0); + + MutexGuard aGuard(m_aMutex); + checkDisposed(Clob_BASE::rBHelper.bDisposed); + // TODO do not reset position if it is not necessary + m_aBlob->closeInput(); // reset position + + OUStringBuffer sSegmentBuffer; + std::vector<char> aSegmentBytes; + + for (;;) + { + bool bLastRead = m_aBlob->readOneSegment( aSegmentBytes ); + // TODO: handle possible case of split UTF-8 character + OUString sSegment(aSegmentBytes.data(), aSegmentBytes.size(), RTL_TEXTENCODING_UTF8); + + // skip irrelevant parts + if (sSegment.getLength() < nPosition) + { + if (bLastRead) + throw lang::IllegalArgumentException("nPosition out of range", *this, 0); + nPosition -= sSegment.getLength(); + continue; + } + + // Getting here for the first time, nPosition may be > 0, meaning copy start offset. + // This also handles sSegment.getLength() == nPosition case, including nLength == 0. + const sal_Int32 nCharsToCopy = std::min<sal_Int32>(sSegment.getLength() - nPosition, + nLength - sSegmentBuffer.getLength()); + sSegmentBuffer.append(sSegment.subView(nPosition, nCharsToCopy)); + if (sSegmentBuffer.getLength() == nLength) + return sSegmentBuffer.makeStringAndClear(); + + assert(sSegmentBuffer.getLength() < nLength); + + if (bLastRead) + throw lang::IllegalArgumentException("out of range", *this, 0); + + nPosition = 0; // No offset after first append + } +} + +uno::Reference< XInputStream > SAL_CALL Clob::getCharacterStream() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Clob_BASE::rBHelper.bDisposed); + + return m_aBlob->getBinaryStream(); +} + +sal_Int64 SAL_CALL Clob::position(const OUString& /*rPattern*/, + sal_Int32 /*nStart*/) +{ + ::dbtools::throwFeatureNotImplementedSQLException("Clob::position", *this); + return 0; +} + +sal_Int64 SAL_CALL Clob::positionOfClob(const Reference <XClob >& /*rPattern*/, + sal_Int64 /*aStart*/) +{ + ::dbtools::throwFeatureNotImplementedSQLException("Clob::positionOfClob", *this); + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Clob.hxx b/connectivity/source/drivers/firebird/Clob.hxx new file mode 100644 index 000000000..7fc5459d5 --- /dev/null +++ b/connectivity/source/drivers/firebird/Clob.hxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include "Blob.hxx" + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/sdbc/XClob.hpp> +#include <rtl/ref.hxx> + +namespace connectivity::firebird + { + typedef ::cppu::WeakComponentImplHelper< css::sdbc::XClob > + Clob_BASE; + + class Clob : + public Clob_BASE + { + protected: + ::osl::Mutex m_aMutex; + + /* + * In Firebird Clob (textual Blob) is a subtype of blob, + * hence we store the data in a Blob, and the Clob class is + * a wrapper around that. + */ + rtl::Reference<connectivity::firebird::Blob> m_aBlob; + + sal_Int64 m_nCharCount; + + public: + Clob(isc_db_handle* pDatabaseHandle, + isc_tr_handle* pTransactionHandle, + ISC_QUAD const & aBlobID); + + // ---- XClob ---------------------------------------------------- + virtual sal_Int64 SAL_CALL + length() override; + virtual OUString SAL_CALL + getSubString(sal_Int64 aPosition, sal_Int32 aLength) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL + getCharacterStream() override; + virtual sal_Int64 SAL_CALL + position(const OUString& rPattern, + sal_Int32 aStart) override; + virtual sal_Int64 SAL_CALL + positionOfClob(const ::css::uno::Reference< ::css::sdbc::XClob >& rPattern, + sal_Int64 aStart) override; + // ---- OComponentHelper ------------------------------------------ + virtual void SAL_CALL disposing() override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Column.cxx b/connectivity/source/drivers/firebird/Column.cxx new file mode 100644 index 000000000..0a18ebe5b --- /dev/null +++ b/connectivity/source/drivers/firebird/Column.cxx @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Column.hxx" + +#include <TConnection.hxx> + +using namespace connectivity; +using namespace connectivity::firebird; +using namespace connectivity::sdbcx; + +Column::Column() + : OColumn( true ) // case sensitive +{ + construct(); +} + +void Column::construct() +{ + m_sAutoIncrement = "GENERATED BY DEFAULT AS IDENTITY"; + registerProperty(OMetaConnection::getPropMap().getNameByIndex( + PROPERTY_ID_AUTOINCREMENTCREATION), + PROPERTY_ID_AUTOINCREMENTCREATION, + 0, + &m_sAutoIncrement, + cppu::UnoType<decltype(m_sAutoIncrement)>::get() + ); +} + +::cppu::IPropertyArrayHelper* Column::createArrayHelper( sal_Int32 /*_nId*/ ) const +{ + return doCreateArrayHelper(); +} + +::cppu::IPropertyArrayHelper & SAL_CALL Column::getInfoHelper() +{ + return *Column_PROP::getArrayHelper(isNew() ? 1 : 0); +} + +css::uno::Sequence< OUString > SAL_CALL Column::getSupportedServiceNames( ) +{ + return { "com.sun.star.sdbc.Firebird" }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Column.hxx b/connectivity/source/drivers/firebird/Column.hxx new file mode 100644 index 000000000..c66287ce5 --- /dev/null +++ b/connectivity/source/drivers/firebird/Column.hxx @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#pragma once + +#include <connectivity/sdbcx/VColumn.hxx> + +namespace connectivity::firebird + { + class Column; + typedef ::comphelper::OIdPropertyArrayUsageHelper<Column> Column_PROP; + class Column : public sdbcx::OColumn, + public Column_PROP + { + OUString m_sAutoIncrement; + protected: + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( sal_Int32 _nId) const override; + virtual ::cppu::IPropertyArrayHelper & SAL_CALL getInfoHelper() override; + public: + Column(); + virtual void construct() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Columns.cxx b/connectivity/source/drivers/firebird/Columns.cxx new file mode 100644 index 000000000..200eec1fb --- /dev/null +++ b/connectivity/source/drivers/firebird/Columns.cxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Columns.hxx" +#include "Column.hxx" + +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::connectivity::sdbcx; + +using namespace ::cppu; +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +Columns::Columns(Table& rTable, + Mutex& rMutex, + const ::std::vector< OUString>& rVector): + OColumnsHelper(rTable, + true, // TODO: is this case sensitivity? + rMutex, + rVector, + /*bUseHardRef*/true) +{ + OColumnsHelper::setParent(&rTable); +} + +Reference< css::beans::XPropertySet > Columns::createDescriptor() +{ + return new Column; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Columns.hxx b/connectivity/source/drivers/firebird/Columns.hxx new file mode 100644 index 000000000..a211f70d1 --- /dev/null +++ b/connectivity/source/drivers/firebird/Columns.hxx @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include "Table.hxx" + +#include <connectivity/TColumnsHelper.hxx> + +namespace connectivity::firebird +{ + class Columns: public ::connectivity::OColumnsHelper + { + protected: + virtual css::uno::Reference< css::beans::XPropertySet > createDescriptor() override; + public: + Columns(Table& rTable, + ::osl::Mutex& rMutex, + const ::std::vector< OUString> &_rVector); + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Connection.cxx b/connectivity/source/drivers/firebird/Connection.cxx new file mode 100644 index 000000000..9230eb22f --- /dev/null +++ b/connectivity/source/drivers/firebird/Connection.cxx @@ -0,0 +1,984 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "Blob.hxx" +#include "Catalog.hxx" +#include "Clob.hxx" +#include "Connection.hxx" +#include "DatabaseMetaData.hxx" +#include "PreparedStatement.hxx" +#include "Statement.hxx" +#include "Util.hxx" + +#include <stdexcept> + +#include <com/sun/star/document/XDocumentEventBroadcaster.hpp> +#include <com/sun/star/embed/ElementModes.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/lang/WrappedTargetRuntimeException.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/TransactionIsolation.hpp> +#include <com/sun/star/ucb/SimpleFileAccess.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess2.hpp> + +#include <connectivity/dbexception.hxx> +#include <strings.hrc> +#include <resource/sharedresources.hxx> + +#include <comphelper/processfactory.hxx> +#include <comphelper/servicehelper.hxx> +#include <comphelper/storagehelper.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <unotools/tempfile.hxx> + +#include <osl/file.hxx> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> + +using namespace connectivity::firebird; +using namespace connectivity; + +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::embed; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::uno; + +/** + * Location within the .odb that an embedded .fdb will be stored. + * Only relevant for embedded dbs. + */ +constexpr OUStringLiteral our_sFDBLocation( u"firebird.fdb" ); +/** + * Older version of LO may store the database in a .fdb file + */ +constexpr OUStringLiteral our_sFBKLocation( u"firebird.fbk" ); + +Connection::Connection() + : Connection_BASE(m_aMutex) + , m_bIsEmbedded(false) + , m_bIsFile(false) + , m_bIsAutoCommit(true) + , m_bIsReadOnly(false) + , m_aTransactionIsolation(TransactionIsolation::REPEATABLE_READ) +#if SAL_TYPES_SIZEOFPOINTER == 8 + , m_aDBHandle(0) + , m_aTransactionHandle(0) +#else + , m_aDBHandle(nullptr) + , m_aTransactionHandle(nullptr) +#endif + , m_xCatalog(nullptr) + , m_xMetaData(nullptr) +{ +} + +Connection::~Connection() +{ + if(!isClosed()) + close(); +} + +namespace { + +struct ConnectionGuard +{ + oslInterlockedCount& m_refCount; + explicit ConnectionGuard(oslInterlockedCount& refCount) + : m_refCount(refCount) + { + osl_atomic_increment(&m_refCount); + } + ~ConnectionGuard() + { + osl_atomic_decrement(&m_refCount); + } +}; + +} + +void Connection::construct(const OUString& url, const Sequence< PropertyValue >& info) +{ + ConnectionGuard aGuard(m_refCount); + + try + { + m_sConnectionURL = url; + + bool bIsNewDatabase = false; + // the database may be stored as an + // fdb file in older versions + bool bIsFdbStored = false; + if (url == "sdbc:embedded:firebird") + { + m_bIsEmbedded = true; + + const PropertyValue* pIter = info.getConstArray(); + const PropertyValue* pEnd = pIter + info.getLength(); + + for (;pIter != pEnd; ++pIter) + { + if ( pIter->Name == "Storage" ) + { + m_xEmbeddedStorage.set(pIter->Value,UNO_QUERY); + } + else if ( pIter->Name == "Document" ) + { + pIter->Value >>= m_xParentDocument; + } + } + + if ( !m_xEmbeddedStorage.is() ) + { + ::connectivity::SharedResources aResources; + const OUString sMessage = aResources.getResourceString(STR_NO_STORAGE); + ::dbtools::throwGenericSQLException(sMessage ,*this); + } + + bIsNewDatabase = !m_xEmbeddedStorage->hasElements(); + + m_pDatabaseFileDir.reset(new ::utl::TempFile(nullptr, true)); + m_pDatabaseFileDir->EnableKillingFile(); + m_sFirebirdURL = m_pDatabaseFileDir->GetFileName() + "/firebird.fdb"; + m_sFBKPath = m_pDatabaseFileDir->GetFileName() + "/firebird.fbk"; + + SAL_INFO("connectivity.firebird", "Temporary .fdb location: " << m_sFirebirdURL); + + if (!bIsNewDatabase) + { + if (m_xEmbeddedStorage->hasByName(our_sFBKLocation) && + m_xEmbeddedStorage->isStreamElement(our_sFBKLocation)) + { + SAL_INFO("connectivity.firebird", "Extracting* .fbk from .odb" ); + loadDatabaseFile(our_sFBKLocation, m_sFBKPath); + } + else if(m_xEmbeddedStorage->hasByName(our_sFDBLocation) && + m_xEmbeddedStorage->isStreamElement(our_sFDBLocation)) + { + SAL_INFO("connectivity.firebird", "Found .fdb instead of .fbk"); + bIsFdbStored = true; + loadDatabaseFile(our_sFDBLocation, m_sFirebirdURL); + } + else + { + // There might be files which are not firebird databases. + // This is not a problem. + bIsNewDatabase = true; + } + } + // TODO: Get DB properties from XML + + } + // External file AND/OR remote connection + else if (url.startsWith("sdbc:firebird:")) + { + m_sFirebirdURL = url.copy(OUString("sdbc:firebird:").getLength()); + if (m_sFirebirdURL.startsWith("file://")) + { + m_bIsFile = true; + uno::Reference< ucb::XSimpleFileAccess > xFileAccess = + ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + if (!xFileAccess->exists(m_sFirebirdURL)) + bIsNewDatabase = true; + + osl::FileBase::getSystemPathFromFileURL(m_sFirebirdURL, m_sFirebirdURL); + } + } + + std::string dpbBuffer; + { + OString userName; + OString userPassword; + + dpbBuffer.push_back(isc_dpb_version1); + dpbBuffer.push_back(isc_dpb_sql_dialect); + dpbBuffer.push_back(1); // 1 byte long + dpbBuffer.push_back(SQL_DIALECT_CURRENT); + + // set UTF8 as default character set of the database + const char sCharset[] = "UTF8"; + dpbBuffer.push_back(isc_dpb_set_db_charset); + dpbBuffer.push_back(sizeof(sCharset) - 1); + dpbBuffer.append(sCharset); + // set UTF8 as default character set of the connection + dpbBuffer.push_back(isc_dpb_lc_ctype); + dpbBuffer.push_back(sizeof(sCharset) - 1); + dpbBuffer.append(sCharset); + + // Do any more dpbBuffer additions here + + if (m_bIsEmbedded || m_bIsFile) + { + userName = "sysdba"; + userPassword = "masterkey"; + } + else + { + for (const auto& rIter : info) + { + if (rIter.Name == "user") + { + if (OUString value; rIter.Value >>= value) + userName = OUStringToOString(value, RTL_TEXTENCODING_UTF8); + } + else if (rIter.Name == "password") + { + if (OUString value; rIter.Value >>= value) + userPassword = OUStringToOString(value, RTL_TEXTENCODING_UTF8); + } + } + } + + if (!userName.isEmpty()) + { + const sal_Int32 nMaxUsername = 255; //max size + int nUsernameLength = std::min(userName.getLength(), nMaxUsername); + dpbBuffer.push_back(isc_dpb_user_name); + dpbBuffer.push_back(nUsernameLength); + dpbBuffer.append(userName.getStr(), nUsernameLength); + } + + if (!userPassword.isEmpty()) + { + const sal_Int32 nMaxPassword = 255; //max size + int nPasswordLength = std::min(userPassword.getLength(), nMaxPassword); + dpbBuffer.push_back(isc_dpb_password); + dpbBuffer.push_back(nPasswordLength); + dpbBuffer.append(userPassword.getStr(), nPasswordLength); + } + } + + // use isc_dpb_utf8_filename to identify encoding of filenames + dpbBuffer.push_back(isc_dpb_utf8_filename); + dpbBuffer.push_back(0); // no filename here, it is passed to functions directly + + ISC_STATUS_ARRAY status; /* status vector */ + ISC_STATUS aErr; + const OString sFirebirdURL = OUStringToOString(m_sFirebirdURL, RTL_TEXTENCODING_UTF8); + if (bIsNewDatabase) + { + aErr = isc_create_database(status, + sFirebirdURL.getLength(), + sFirebirdURL.getStr(), + &m_aDBHandle, + dpbBuffer.size(), + dpbBuffer.c_str(), + 0); + if (aErr) + { + evaluateStatusVector(status, u"isc_create_database", *this); + } + } + else + { + if (m_bIsEmbedded && !bIsFdbStored) // We need to restore the .fbk first + { + runBackupService(isc_action_svc_restore); + } + + aErr = isc_attach_database(status, + sFirebirdURL.getLength(), + sFirebirdURL.getStr(), + &m_aDBHandle, + dpbBuffer.size(), + dpbBuffer.c_str()); + if (aErr) + { + evaluateStatusVector(status, u"isc_attach_database", *this); + } + } + + if (m_bIsEmbedded) // Add DocumentEventListener to save the .fdb as needed + { + // We need to attach as a document listener in order to be able to store + // the temporary db back into the .odb when saving + uno::Reference<XDocumentEventBroadcaster> xBroadcaster(m_xParentDocument, UNO_QUERY); + + if (xBroadcaster.is()) + xBroadcaster->addDocumentEventListener(this); + else + assert(false); + } + } + catch (const Exception&) + { + throw; + } + catch (const std::exception&) + { + throw; + } + catch (...) // const Firebird::Exception& firebird throws this, but doesn't install the fb_exception.h that declares it + + { + throw std::runtime_error("Generic Firebird::Exception"); + } +} + +void Connection::notifyDatabaseModified() +{ + if (m_xParentDocument.is()) // Only true in embedded mode + m_xParentDocument->setModified(true); +} + +//----- XServiceInfo --------------------------------------------------------- +IMPLEMENT_SERVICE_INFO(Connection, "com.sun.star.sdbc.drivers.firebird.Connection", + "com.sun.star.sdbc.Connection") + +Reference< XBlob> Connection::createBlob(ISC_QUAD const * pBlobId) +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + Reference< XBlob > xReturn = new Blob(&m_aDBHandle, + &m_aTransactionHandle, + *pBlobId); + + m_aStatements.push_back(WeakReferenceHelper(xReturn)); + return xReturn; +} + +Reference< XClob> Connection::createClob(ISC_QUAD const * pBlobId) +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + Reference< XClob > xReturn = new Clob(&m_aDBHandle, + &m_aTransactionHandle, + *pBlobId); + + m_aStatements.push_back(WeakReferenceHelper(xReturn)); + return xReturn; +} + +//----- XUnoTunnel ---------------------------------------------------------- +// virtual +sal_Int64 SAL_CALL Connection::getSomething(const css::uno::Sequence<sal_Int8>& rId) +{ + return comphelper::getSomethingImpl(rId, this); +} + +// static +const css::uno::Sequence<sal_Int8> & Connection::getUnoTunnelId() +{ + static const comphelper::UnoIdInit implId; + return implId.getSeq(); +} + +//----- XConnection ---------------------------------------------------------- +Reference< XStatement > SAL_CALL Connection::createStatement( ) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + // the pre + if(m_aTypeInfo.empty()) + buildTypeInfo(); + + // create a statement + // the statement can only be executed once + Reference< XStatement > xReturn = new OStatement(this); + m_aStatements.push_back(WeakReferenceHelper(xReturn)); + return xReturn; +} + +Reference< XPreparedStatement > SAL_CALL Connection::prepareStatement( + const OUString& _sSql) +{ + SAL_INFO("connectivity.firebird", "prepareStatement() " + "called with sql: " << _sSql); + MutexGuard aGuard(m_aMutex); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + if(m_aTypeInfo.empty()) + buildTypeInfo(); + + Reference< XPreparedStatement > xReturn = new OPreparedStatement(this, _sSql); + m_aStatements.push_back(WeakReferenceHelper(xReturn)); + + return xReturn; +} + +Reference< XPreparedStatement > SAL_CALL Connection::prepareCall( + const OUString& _sSql ) +{ + SAL_INFO("connectivity.firebird", "prepareCall(). " + "_sSql: " << _sSql); + + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + // OUString sSqlStatement (transformPreparedStatement( _sSql )); + + // not implemented yet :-) a task to do + return nullptr; +} + +OUString SAL_CALL Connection::nativeSQL( const OUString& _sSql ) +{ + MutexGuard aGuard( m_aMutex ); + // We do not need to adapt the SQL for Firebird atm. + return _sSql; +} + +void SAL_CALL Connection::setAutoCommit( sal_Bool autoCommit ) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + m_bIsAutoCommit = autoCommit; + + if (m_aTransactionHandle) + { + setupTransaction(); + } +} + +sal_Bool SAL_CALL Connection::getAutoCommit() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + return m_bIsAutoCommit; +} + +void Connection::setupTransaction() +{ + MutexGuard aGuard( m_aMutex ); + ISC_STATUS status_vector[20]; + + // TODO: is this sensible? If we have changed parameters then transaction + // is lost... + if (m_aTransactionHandle) + { + disposeStatements(); + isc_rollback_transaction(status_vector, &m_aTransactionHandle); + } + + char aTransactionIsolation = 0; + switch (m_aTransactionIsolation) + { + // TODO: confirm that these are correct. + case TransactionIsolation::READ_UNCOMMITTED: + aTransactionIsolation = isc_tpb_concurrency; + break; + case TransactionIsolation::READ_COMMITTED: + aTransactionIsolation = isc_tpb_read_committed; + break; + case TransactionIsolation::REPEATABLE_READ: + aTransactionIsolation = isc_tpb_consistency; + break; + case TransactionIsolation::SERIALIZABLE: + aTransactionIsolation = isc_tpb_consistency; + break; + default: + assert( false ); // We must have a valid TransactionIsolation. + } + + // You cannot pass an empty tpb parameter so we have to do some pointer + // arithmetic to avoid problems. (i.e. aTPB[x] = 0 is invalid) + char aTPB[5]; + char* pTPB = aTPB; + + *pTPB++ = isc_tpb_version3; + if (m_bIsAutoCommit) + *pTPB++ = isc_tpb_autocommit; + *pTPB++ = (!m_bIsReadOnly ? isc_tpb_write : isc_tpb_read); + *pTPB++ = aTransactionIsolation; + *pTPB++ = isc_tpb_wait; + + isc_start_transaction(status_vector, + &m_aTransactionHandle, + 1, + &m_aDBHandle, + pTPB - aTPB, // bytes used in TPB + aTPB); + + evaluateStatusVector(status_vector, + u"isc_start_transaction", + *this); +} + +isc_tr_handle& Connection::getTransaction() +{ + MutexGuard aGuard( m_aMutex ); + if (!m_aTransactionHandle) + { + setupTransaction(); + } + return m_aTransactionHandle; +} + +void SAL_CALL Connection::commit() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + ISC_STATUS status_vector[20]; + + if (!m_bIsAutoCommit && m_aTransactionHandle) + { + disposeStatements(); + isc_commit_transaction(status_vector, &m_aTransactionHandle); + evaluateStatusVector(status_vector, + u"isc_commit_transaction", + *this); + } +} + +void Connection::loadDatabaseFile(const OUString& srcLocation, const OUString& tmpLocation) +{ + Reference< XStream > xDBStream(m_xEmbeddedStorage->openStreamElement(srcLocation, + ElementModes::READ)); + + uno::Reference< ucb::XSimpleFileAccess2 > xFileAccess = + ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() ); + if ( !xFileAccess.is() ) + { + ::connectivity::SharedResources aResources; + // TODO FIXME: this does _not_ look like the right error message + const OUString sMessage = aResources.getResourceString(STR_ERROR_NEW_VERSION); + ::dbtools::throwGenericSQLException(sMessage ,*this); + } + xFileAccess->writeFile(tmpLocation,xDBStream->getInputStream()); +} + +isc_svc_handle Connection::attachServiceManager() +{ + ISC_STATUS_ARRAY aStatusVector; +#if SAL_TYPES_SIZEOFPOINTER == 8 + isc_svc_handle aServiceHandle = 0; +#else + isc_svc_handle aServiceHandle = nullptr; +#endif + + char aSPBBuffer[256]; + char* pSPB = aSPBBuffer; + *pSPB++ = isc_spb_version; + *pSPB++ = isc_spb_current_version; + *pSPB++ = isc_spb_user_name; + OUString sUserName("SYSDBA"); + char aLength = static_cast<char>(sUserName.getLength()); + *pSPB++ = aLength; + strncpy(pSPB, + OUStringToOString(sUserName, + RTL_TEXTENCODING_UTF8).getStr(), + aLength); + pSPB += aLength; + // TODO: do we need ", isc_dpb_trusted_auth, 1, 1" -- probably not but ... + if (isc_service_attach(aStatusVector, + 0, // Denotes null-terminated string next + "service_mgr", + &aServiceHandle, + pSPB - aSPBBuffer, + aSPBBuffer)) + { + evaluateStatusVector(aStatusVector, + u"isc_service_attach", + *this); + } + + return aServiceHandle; +} + +void Connection::detachServiceManager(isc_svc_handle aServiceHandle) +{ + ISC_STATUS_ARRAY aStatusVector; + if (isc_service_detach(aStatusVector, + &aServiceHandle)) + { + evaluateStatusVector(aStatusVector, + u"isc_service_detach", + *this); + } +} + +void Connection::runBackupService(const short nAction) +{ + assert(nAction == isc_action_svc_backup + || nAction == isc_action_svc_restore); + + ISC_STATUS_ARRAY aStatusVector; + + // convert paths to 8-Bit strings + OString sFDBPath = OUStringToOString(m_sFirebirdURL, RTL_TEXTENCODING_UTF8); + OString sFBKPath = OUStringToOString(m_sFBKPath, RTL_TEXTENCODING_UTF8); + + + OStringBuffer aRequest; // byte array + + + aRequest.append(static_cast<char>(nAction)); + + aRequest.append(char(isc_spb_dbname)); // .fdb + sal_uInt16 nFDBLength = sFDBPath.getLength(); + aRequest.append(static_cast<char>(nFDBLength & 0xFF)); // least significant byte first + aRequest.append(static_cast<char>((nFDBLength >> 8) & 0xFF)); + aRequest.append(sFDBPath); + + aRequest.append(char(isc_spb_bkp_file)); // .fbk + sal_uInt16 nFBKLength = sFBKPath.getLength(); + aRequest.append(static_cast<char>(nFBKLength & 0xFF)); + aRequest.append(static_cast<char>((nFBKLength >> 8) & 0xFF)); + aRequest.append(sFBKPath); + + if (nAction == isc_action_svc_restore) + { + aRequest.append(char(isc_spb_options)); // 4-Byte bitmask + char sOptions[4]; + char * pOptions = sOptions; +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable: 4310) // cast truncates data +#endif + ADD_SPB_NUMERIC(pOptions, isc_spb_res_create); +#ifdef _WIN32 +#pragma warning(pop) +#endif + aRequest.append(sOptions, 4); + } + + isc_svc_handle aServiceHandle; + aServiceHandle = attachServiceManager(); + + if (isc_service_start(aStatusVector, + &aServiceHandle, + nullptr, + aRequest.getLength(), + aRequest.getStr())) + { + evaluateStatusVector(aStatusVector, u"isc_service_start", *this); + } + + char aInfoSPB = isc_info_svc_line; + char aResults[256]; + + // query blocks until success or error + if(isc_service_query(aStatusVector, + &aServiceHandle, + nullptr, // Reserved null + 0,nullptr, // "send" spb -- size and spb -- not needed? + 1, + &aInfoSPB, + sizeof(aResults), + aResults)) + { + evaluateStatusVector(aStatusVector, u"isc_service_query", *this); + } + + detachServiceManager(aServiceHandle); +} + + +void SAL_CALL Connection::rollback() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + ISC_STATUS status_vector[20]; + + if (!m_bIsAutoCommit && m_aTransactionHandle) + { + isc_rollback_transaction(status_vector, &m_aTransactionHandle); + } +} + +sal_Bool SAL_CALL Connection::isClosed( ) +{ + MutexGuard aGuard( m_aMutex ); + + // just simple -> we are close when we are disposed that means someone called dispose(); (XComponent) + return Connection_BASE::rBHelper.bDisposed; +} + +Reference< XDatabaseMetaData > SAL_CALL Connection::getMetaData( ) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + // here we have to create the class with biggest interface + // The answer is 42 :-) + Reference< XDatabaseMetaData > xMetaData = m_xMetaData; + if(!xMetaData.is()) + { + xMetaData = new ODatabaseMetaData(this); // need the connection because it can return it + m_xMetaData = xMetaData; + } + + return xMetaData; +} + +void SAL_CALL Connection::setReadOnly(sal_Bool readOnly) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + m_bIsReadOnly = readOnly; + setupTransaction(); +} + +sal_Bool SAL_CALL Connection::isReadOnly() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + return m_bIsReadOnly; +} + +void SAL_CALL Connection::setCatalog(const OUString& /*catalog*/) +{ + ::dbtools::throwFunctionNotSupportedSQLException("setCatalog", *this); +} + +OUString SAL_CALL Connection::getCatalog() +{ + ::dbtools::throwFunctionNotSupportedSQLException("getCatalog", *this); + return OUString(); +} + +void SAL_CALL Connection::setTransactionIsolation( sal_Int32 level ) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + m_aTransactionIsolation = level; + setupTransaction(); +} + +sal_Int32 SAL_CALL Connection::getTransactionIsolation( ) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + return m_aTransactionIsolation; +} + +Reference< XNameAccess > SAL_CALL Connection::getTypeMap() +{ + ::dbtools::throwFeatureNotImplementedSQLException( "XConnection::getTypeMap", *this ); + return nullptr; +} + +void SAL_CALL Connection::setTypeMap(const Reference< XNameAccess >&) +{ + ::dbtools::throwFeatureNotImplementedSQLException( "XConnection::setTypeMap", *this ); +} + +//----- XCloseable ----------------------------------------------------------- +void SAL_CALL Connection::close( ) +{ + // we just dispose us + { + MutexGuard aGuard( m_aMutex ); + checkDisposed(Connection_BASE::rBHelper.bDisposed); + + } + dispose(); +} + +// XWarningsSupplier +Any SAL_CALL Connection::getWarnings( ) +{ + // when you collected some warnings -> return it + return Any(); +} + +void SAL_CALL Connection::clearWarnings( ) +{ + // you should clear your collected warnings here +} + +// XDocumentEventListener +void SAL_CALL Connection::documentEventOccured( const DocumentEvent& Event ) +{ + MutexGuard aGuard(m_aMutex); + + if (!m_bIsEmbedded) + return; + + if (Event.EventName != "OnSave" && Event.EventName != "OnSaveAs") + return; + + commit(); // Commit and close transaction + if ( !(m_bIsEmbedded && m_xEmbeddedStorage.is()) ) + return; + + SAL_INFO("connectivity.firebird", "Writing .fbk from running db"); + try + { + runBackupService(isc_action_svc_backup); + } + catch (const SQLException& e) + { + auto a = cppu::getCaughtException(); + throw WrappedTargetRuntimeException(e.Message, e.Context, a); + } + + + Reference< XStream > xDBStream(m_xEmbeddedStorage->openStreamElement(our_sFBKLocation, + ElementModes::WRITE)); + + // TODO: verify the backup actually exists -- the backup service + // can fail without giving any sane error messages / telling us + // that it failed. + using namespace ::comphelper; + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + Reference< XInputStream > xInputStream; + if (!xContext.is()) + return; + + xInputStream = + OStorageHelper::GetInputStreamFromURL(m_sFBKPath, xContext); + if (xInputStream.is()) + OStorageHelper::CopyInputToOutput( xInputStream, + xDBStream->getOutputStream()); + + // remove old fdb file if exists + uno::Reference< ucb::XSimpleFileAccess > xFileAccess = + ucb::SimpleFileAccess::create(xContext); + if (xFileAccess->exists(m_sFirebirdURL)) + xFileAccess->kill(m_sFirebirdURL); +} +// XEventListener +void SAL_CALL Connection::disposing(const EventObject& /*rSource*/) +{ + MutexGuard aGuard( m_aMutex ); + + m_xEmbeddedStorage.clear(); +} + +void Connection::buildTypeInfo() +{ + MutexGuard aGuard( m_aMutex ); + + Reference< XResultSet> xRs = getMetaData ()->getTypeInfo (); + Reference< XRow> xRow(xRs,UNO_QUERY); + // Information for a single SQL type + + // Loop on the result set until we reach end of file + + while (xRs->next ()) + { + OTypeInfo aInfo; + aInfo.aTypeName = xRow->getString (1); + aInfo.nType = xRow->getShort (2); + aInfo.nPrecision = xRow->getInt (3); + // aLiteralPrefix = xRow->getString (4); + // aLiteralSuffix = xRow->getString (5); + // aCreateParams = xRow->getString (6); + // bNullable = xRow->getBoolean (7); + // bCaseSensitive = xRow->getBoolean (8); + // nSearchType = xRow->getShort (9); + // bUnsigned = xRow->getBoolean (10); + // bCurrency = xRow->getBoolean (11); + // bAutoIncrement = xRow->getBoolean (12); + aInfo.aLocalTypeName = xRow->getString (13); + // nMinimumScale = xRow->getShort (14); + aInfo.nMaximumScale = xRow->getShort (15); + // nNumPrecRadix = (sal_Int16)xRow->getInt(18); + + + // Now that we have the type info, save it + // in the Hashtable if we don't already have an + // entry for this SQL type. + + m_aTypeInfo.push_back(aInfo); + } + + SAL_INFO("connectivity.firebird", "buildTypeInfo(). " + "Type info built."); + + // Close the result set/statement. + + Reference< XCloseable> xClose(xRs,UNO_QUERY); + xClose->close(); + + SAL_INFO("connectivity.firebird", "buildTypeInfo(). " + "Closed."); +} + +void Connection::disposing() +{ + MutexGuard aGuard(m_aMutex); + + disposeStatements(); + + m_xMetaData = css::uno::WeakReference< css::sdbc::XDatabaseMetaData>(); + + ISC_STATUS_ARRAY status; /* status vector */ + if (m_aTransactionHandle) + { + // TODO: confirm whether we need to ask the user here. + isc_rollback_transaction(status, &m_aTransactionHandle); + } + + if (m_aDBHandle) + { + if (isc_detach_database(status, &m_aDBHandle)) + { + evaluateStatusVector(status, u"isc_detach_database", *this); + } + } + // TODO: write to storage again? + + cppu::WeakComponentImplHelperBase::disposing(); + + m_pDatabaseFileDir.reset(); +} + +void Connection::disposeStatements() +{ + MutexGuard aGuard(m_aMutex); + for (auto const& statement : m_aStatements) + { + Reference< XComponent > xComp(statement.get(), UNO_QUERY); + if (xComp.is()) + xComp->dispose(); + } + m_aStatements.clear(); +} + +uno::Reference< XTablesSupplier > Connection::createCatalog() +{ + MutexGuard aGuard(m_aMutex); + + // m_xCatalog is a weak reference. Reuse it if it still exists. + Reference< XTablesSupplier > xCatalog = m_xCatalog; + if (xCatalog.is()) + { + return xCatalog; + } + else + { + xCatalog = new Catalog(this); + m_xCatalog = xCatalog; + return m_xCatalog; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Connection.hxx b/connectivity/source/drivers/firebird/Connection.hxx new file mode 100644 index 000000000..9fdb0d4d3 --- /dev/null +++ b/connectivity/source/drivers/firebird/Connection.hxx @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include <ibase.h> + +#include <connectivity/CommonTools.hxx> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/weakref.hxx> +#include <memory> +#include <OTypeInfo.hxx> +#include <unotools/tempfile.hxx> + +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/document/DocumentEvent.hpp> +#include <com/sun/star/document/XDocumentEventListener.hpp> +#include <com/sun/star/embed/XStorage.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <com/sun/star/sdbc/XBlob.hpp> +#include <com/sun/star/sdbc/XClob.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XWarningsSupplier.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/util/XModifiable.hpp> + +namespace connectivity::firebird + { + + typedef ::cppu::WeakComponentImplHelper< css::document::XDocumentEventListener, + css::lang::XServiceInfo, + css::lang::XUnoTunnel, + css::sdbc::XConnection, + css::sdbc::XWarningsSupplier + > Connection_BASE; + + class OStatementCommonBase; + class FirebirdDriver; + class ODatabaseMetaData; + + + typedef std::vector< ::connectivity::OTypeInfo> TTypeInfoVector; + typedef std::vector< css::uno::WeakReferenceHelper > OWeakRefArray; + + class Connection final : public Connection_BASE + { + ::osl::Mutex m_aMutex; + + TTypeInfoVector m_aTypeInfo; // vector containing an entry + // for each row returned by + // DatabaseMetaData.getTypeInfo. + + /** The URL passed to us when opening, i.e. of the form sdbc:* */ + OUString m_sConnectionURL; + /** + * The URL passed to firebird, i.e. either a local file (for a + * temporary .fdb extracted from a .odb or a normal local file) or + * a remote url. + */ + OUString m_sFirebirdURL; + + /* EMBEDDED MODE DATA */ + /** Denotes that we have a database stored within a .odb file. */ + bool m_bIsEmbedded; + + /** + * Handle for the parent DatabaseDocument. We need to notify this + * whenever any data is written to our temporary database so that + * the user is able to save this back to the .odb file. + * + * Note that this is ONLY set in embedded mode. + */ + css::uno::Reference< css::util::XModifiable > + m_xParentDocument; + + /** + * Handle for the folder within the .odb where we store our .fbk + * (Only used if m_bIsEmbedded is true). + */ + css::uno::Reference< css::embed::XStorage > + m_xEmbeddedStorage; + /** + * The temporary folder where we extract the .fbk from a .odb, + * and also store the temporary .fdb + * It is only valid if m_bIsEmbedded is true. + * + * The extracted .fbk is written in firebird.fbk, the temporary + * .fdb is stored as firebird.fdb. + */ + std::unique_ptr< ::utl::TempFile > m_pDatabaseFileDir; + /** + * Path for our extracted .fbk file. + * + * (The temporary .fdb is our m_sFirebirdURL.) + */ + OUString m_sFBKPath; + + void loadDatabaseFile(const OUString& pSrcLocation, const OUString& pTmpLocation); + + /** + * Run the backup service, use nAction = + * isc_action_svc_backup to backup, nAction = isc_action_svc_restore + * to restore. + */ + void runBackupService(const short nAction); + + isc_svc_handle attachServiceManager(); + + void detachServiceManager(isc_svc_handle pServiceHandle); + + /** We are using an external (local) file */ + bool m_bIsFile; + + /* CONNECTION PROPERTIES */ + bool m_bIsAutoCommit; + bool m_bIsReadOnly; + sal_Int32 m_aTransactionIsolation; + + isc_db_handle m_aDBHandle; + isc_tr_handle m_aTransactionHandle; + + css::uno::WeakReference< css::sdbcx::XTablesSupplier> + m_xCatalog; + css::uno::WeakReference< css::sdbc::XDatabaseMetaData > + m_xMetaData; + /** Statements owned by this connection. */ + OWeakRefArray m_aStatements; + + /// @throws css::sdbc::SQLException + void buildTypeInfo(); + + /** + * Creates a new transaction with the desired parameters, if + * necessary discarding an existing transaction. This has to be done + * anytime we change the transaction isolation, or autocommitting. + * + * @throws css::sdbc::SQLException + */ + void setupTransaction(); + void disposeStatements(); + + public: + explicit Connection(); + virtual ~Connection() override; + + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + void construct( const OUString& url, + const css::uno::Sequence< css::beans::PropertyValue >& info); + + const OUString& getConnectionURL() const {return m_sConnectionURL;} + bool isEmbedded() const {return m_bIsEmbedded;} + isc_db_handle& getDBHandle() {return m_aDBHandle;} + /// @throws css::sdbc::SQLException + isc_tr_handle& getTransaction(); + + /** + * Must be called anytime the underlying database is likely to have + * changed. + * + * This is used to notify the database document of any changes, so + * that the user is informed of any pending changes needing to be + * saved. + */ + void notifyDatabaseModified(); + + /** + * Create a new Blob tied to this connection. Blobs are tied to a + * transaction and not to a statement, hence the connection should + * deal with their management. + * + * @throws css::sdbc::SQLException + * @throws css::uno::RuntimeException + */ + css::uno::Reference< css::sdbc::XBlob> + createBlob(ISC_QUAD const * pBlobID); + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + css::uno::Reference< css::sdbc::XClob> + createClob(ISC_QUAD const * pBlobID); + + /** + * Create and/or connect to the sdbcx Catalog. This is completely + * unrelated to the SQL "Catalog". + */ + css::uno::Reference< css::sdbcx::XTablesSupplier > + createCatalog(); + + // OComponentHelper + virtual void SAL_CALL disposing() override; + + // XServiceInfo + DECLARE_SERVICE_INFO(); + // XUnoTunnel + virtual sal_Int64 SAL_CALL getSomething(const css::uno::Sequence<sal_Int8>& rId) override; + static const css::uno::Sequence<sal_Int8> & getUnoTunnelId(); + // XConnection + virtual css::uno::Reference< css::sdbc::XStatement > SAL_CALL createStatement( ) override; + virtual css::uno::Reference< css::sdbc::XPreparedStatement > SAL_CALL prepareStatement( const OUString& sql ) override; + virtual css::uno::Reference< css::sdbc::XPreparedStatement > SAL_CALL prepareCall( const OUString& sql ) override; + virtual OUString SAL_CALL nativeSQL( const OUString& sql ) override; + virtual void SAL_CALL setAutoCommit( sal_Bool autoCommit ) override; + virtual sal_Bool SAL_CALL getAutoCommit( ) override; + virtual void SAL_CALL commit( ) override; + virtual void SAL_CALL rollback( ) override; + virtual sal_Bool SAL_CALL isClosed( ) override; + virtual css::uno::Reference< css::sdbc::XDatabaseMetaData > SAL_CALL getMetaData( ) override; + virtual void SAL_CALL setReadOnly( sal_Bool readOnly ) override; + virtual sal_Bool SAL_CALL isReadOnly( ) override; + virtual void SAL_CALL setCatalog( const OUString& catalog ) override; + virtual OUString SAL_CALL getCatalog( ) override; + virtual void SAL_CALL setTransactionIsolation( sal_Int32 level ) override; + virtual sal_Int32 SAL_CALL getTransactionIsolation( ) override; + virtual css::uno::Reference< css::container::XNameAccess > SAL_CALL getTypeMap( ) override; + virtual void SAL_CALL setTypeMap( const css::uno::Reference< css::container::XNameAccess >& typeMap ) override; + // XCloseable + virtual void SAL_CALL close( ) override; + // XWarningsSupplier + virtual css::uno::Any SAL_CALL getWarnings( ) override; + virtual void SAL_CALL clearWarnings( ) override; + // XDocumentEventListener + virtual void SAL_CALL documentEventOccured( const css::document::DocumentEvent& Event ) override; + // css.lang.XEventListener + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/DatabaseMetaData.cxx b/connectivity/source/drivers/firebird/DatabaseMetaData.cxx new file mode 100644 index 000000000..cfee4f41b --- /dev/null +++ b/connectivity/source/drivers/firebird/DatabaseMetaData.cxx @@ -0,0 +1,1803 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "DatabaseMetaData.hxx" +#include "Util.hxx" + +#include <ibase.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <FDatabaseMetaDataResultSet.hxx> + +#include <com/sun/star/sdbc/ColumnSearch.hpp> +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/IndexType.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/ResultSetConcurrency.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/TransactionIsolation.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/KeyRule.hpp> +#include <com/sun/star/sdbc/Deferrability.hpp> + +using namespace connectivity::firebird; +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::sdbc; + +ODatabaseMetaData::ODatabaseMetaData(Connection* _pCon) +: m_pConnection(_pCon) +{ + SAL_WARN_IF(!m_pConnection.is(), "connectivity.firebird", + "ODatabaseMetaData::ODatabaseMetaData: No connection set!"); +} + +ODatabaseMetaData::~ODatabaseMetaData() +{ +} + +//----- Catalog Info -- UNSUPPORTED ------------------------------------------- +OUString SAL_CALL ODatabaseMetaData::getCatalogSeparator() +{ + return OUString(); +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxCatalogNameLength() +{ + return -1; +} + +OUString SAL_CALL ODatabaseMetaData::getCatalogTerm() +{ + return OUString(); +} + +sal_Bool SAL_CALL ODatabaseMetaData::isCatalogAtStart() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCatalogsInTableDefinitions() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCatalogsInIndexDefinitions() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCatalogsInDataManipulation( ) +{ + return false; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getCatalogs() +{ + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eCatalogs); +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCatalogsInProcedureCalls() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCatalogsInPrivilegeDefinitions() +{ + return false; +} + +//----- Schema Info -- UNSUPPORTED -------------------------------------------- +sal_Bool SAL_CALL ODatabaseMetaData::supportsSchemasInProcedureCalls() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSchemasInPrivilegeDefinitions() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSchemasInDataManipulation() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSchemasInIndexDefinitions() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSchemasInTableDefinitions() +{ + return false; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxSchemaNameLength() +{ + return -1; +} + +OUString SAL_CALL ODatabaseMetaData::getSchemaTerm() +{ + return OUString(); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getSchemas() +{ + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eSchemas); +} + +//----- Max Sizes/Lengths ----------------------------------------------------- +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxBinaryLiteralLength() +{ + return 32767; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxRowSize() +{ + return 32767; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxCharLiteralLength() +{ + return 32767; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnNameLength() +{ + return 31; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnsInIndex() +{ + // TODO: No idea. + // See: http://www.firebirdsql.org/en/firebird-technical-specifications/ + return 16; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxCursorNameLength() +{ + return 32; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxConnections() +{ + return 100; // Arbitrary +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnsInTable() +{ + // May however be smaller. + // See: http://www.firebirdsql.org/en/firebird-technical-specifications/ + return 32767; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxStatementLength() +{ + return 32767; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxTableNameLength() +{ + return 31; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxTablesInSelect( ) +{ + return 0; // 0 means no limit +} + + +sal_Bool SAL_CALL ODatabaseMetaData::doesMaxRowSizeIncludeBlobs( ) +{ + return false; +} + +// ---- Identifiers ----------------------------------------------------------- +// Only quoted identifiers are case sensitive, unquoted are case insensitive +OUString SAL_CALL ODatabaseMetaData::getIdentifierQuoteString() +{ + return "\""; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsMixedCaseQuotedIdentifiers( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesLowerCaseQuotedIdentifiers() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesMixedCaseQuotedIdentifiers() +{ + // TODO: confirm this -- the documentation is highly ambiguous + // However it seems this should be true as quoted identifiers ARE + // stored mixed case. + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesUpperCaseQuotedIdentifiers() +{ + return false; +} + +// ---- Unquoted Identifiers ------------------------------------------------- +// All unquoted identifiers are stored upper case. +sal_Bool SAL_CALL ODatabaseMetaData::supportsMixedCaseIdentifiers() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesLowerCaseIdentifiers() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesMixedCaseIdentifiers() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::storesUpperCaseIdentifiers() +{ + return true; +} + +// ---- SQL Feature Support --------------------------------------------------- +sal_Bool SAL_CALL ODatabaseMetaData::supportsCoreSQLGrammar() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsMinimumSQLGrammar() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsAlterTableWithAddColumn() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsAlterTableWithDropColumn() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsPositionedDelete() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsPositionedUpdate() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOuterJoins() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSelectForUpdate() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::allTablesAreSelectable() +{ + // TODO: true if embedded, but unsure about remote server + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsConvert(sal_Int32, + sal_Int32) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsTypeConversion() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsColumnAliasing() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsTableCorrelationNames() +{ + return true; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxIndexLength( ) +{ + return 0; // 0 means no limit +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsNonNullableColumns( ) +{ + return true; +} + +OUString SAL_CALL ODatabaseMetaData::getExtraNameCharacters( ) +{ + return OUString(); +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsDifferentTableCorrelationNames( ) +{ + return false; +} +// ---- Data definition stuff ------------------------------------------------- +sal_Bool SAL_CALL ODatabaseMetaData::dataDefinitionIgnoredInTransactions() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::dataDefinitionCausesTransactionCommit() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsDataManipulationTransactionsOnly() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData:: + supportsDataDefinitionAndDataManipulationTransactions() +{ + return false; +} +//----- Transaction Support -------------------------------------------------- +sal_Bool SAL_CALL ODatabaseMetaData::supportsTransactions() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOpenStatementsAcrossRollback() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOpenStatementsAcrossCommit() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOpenCursorsAcrossCommit() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOpenCursorsAcrossRollback() +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsMultipleTransactions() +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsTransactionIsolationLevel( + sal_Int32 aLevel) +{ + return aLevel == TransactionIsolation::READ_UNCOMMITTED + || aLevel == TransactionIsolation::READ_COMMITTED + || aLevel == TransactionIsolation::REPEATABLE_READ + || aLevel == TransactionIsolation::SERIALIZABLE; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getDefaultTransactionIsolation() +{ + return TransactionIsolation::REPEATABLE_READ; +} + + +sal_Bool SAL_CALL ODatabaseMetaData::supportsANSI92FullSQL( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsANSI92EntryLevelSQL( ) +{ + return true; // should be supported at least +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsIntegrityEnhancementFacility( ) +{ + return true; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxStatements( ) +{ + return 0; // 0 means no limit +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxProcedureNameLength( ) +{ + return 31; // TODO: confirm +} + +sal_Bool SAL_CALL ODatabaseMetaData::allProceduresAreCallable( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsStoredProcedures( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::isReadOnly( ) +{ + return m_pConnection->isReadOnly(); +} + +sal_Bool SAL_CALL ODatabaseMetaData::usesLocalFiles( ) +{ + return m_pConnection->isEmbedded(); +} + +sal_Bool SAL_CALL ODatabaseMetaData::usesLocalFilePerTable( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::nullPlusNonNullIsNull( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsExpressionsInOrderBy( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsGroupBy( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsGroupByBeyondSelect( ) +{ + // Unsure + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsGroupByUnrelated( ) +{ + // Unsure + return false; +} + + +sal_Bool SAL_CALL ODatabaseMetaData::supportsMultipleResultSets( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsLikeEscapeClause( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsOrderByUnrelated( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsUnion( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsUnionAll( ) +{ + return true; +} + +sal_Bool SAL_CALL ODatabaseMetaData::nullsAreSortedAtEnd( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::nullsAreSortedAtStart( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::nullsAreSortedHigh( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::nullsAreSortedLow( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsCorrelatedSubqueries( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSubqueriesInComparisons( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSubqueriesInExists( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSubqueriesInIns( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsSubqueriesInQuantifieds( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsANSI92IntermediateSQL( ) +{ + return false; +} + +OUString SAL_CALL ODatabaseMetaData::getURL() +{ + return m_pConnection->getConnectionURL(); +} + +OUString SAL_CALL ODatabaseMetaData::getUserName( ) +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getDriverName( ) +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getDriverVersion() +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getDatabaseProductVersion( ) +{ + uno::Reference< XStatement > xSelect = m_pConnection->createStatement(); + + uno::Reference< XResultSet > xRs = xSelect->executeQuery("SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') as version from rdb$database"); + (void)xRs->next(); // first and only row + uno::Reference< XRow > xRow( xRs, UNO_QUERY_THROW ); + return xRow->getString(1); +} + +OUString SAL_CALL ODatabaseMetaData::getDatabaseProductName( ) +{ + return "Firebird (engine12)"; +} + +OUString SAL_CALL ODatabaseMetaData::getProcedureTerm( ) +{ + return OUString(); +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getDriverMajorVersion( ) +{ + return 1; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getDriverMinorVersion( ) +{ + return 0; +} + +OUString SAL_CALL ODatabaseMetaData::getSQLKeywords( ) +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getSearchStringEscape( ) +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getStringFunctions( ) +{ + return "ASCII_CHAR,ASCII_VAL,BIT_LENGTH,CHAR_LENGTH,CHAR_TO_UUID,CHARACTER_LENGTH," + "GEN_UUID,HASH,LEFT,LOWER,LPAD,OCTET_LENGTH,OVERLAY,POSITION,REPLACE,REVERSE," + "RIGHT,RPAD,SUBSTRING,TRIM,UPPER,UUID_TO_CHAR"; +} + +OUString SAL_CALL ODatabaseMetaData::getTimeDateFunctions( ) +{ + return "CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,DATEADD, DATEDIFF," + "EXTRACT,'NOW','TODAY','TOMORROW','YESTERDAY'"; +} + +OUString SAL_CALL ODatabaseMetaData::getSystemFunctions( ) +{ + return OUString(); +} + +OUString SAL_CALL ODatabaseMetaData::getNumericFunctions( ) +{ + return "ABS,ACOS,ASIN,ATAN,ATAN2,BIN_AND,BIN_NOT,BIN_OR,BIN_SHL," + "BIN_SHR,BIN_XOR,CEIL,CEILING,COS,COSH,COT,EXP,FLOOR,LN," + "LOG,LOG10,MOD,PI,POWER,RAND,ROUND,SIGN,SIN,SINH,SQRT,TAN,TANH,TRUNC"; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsExtendedSQLGrammar( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsFullOuterJoins( ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsLimitedOuterJoins( ) +{ + return false; +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnsInGroupBy( ) +{ + return 0; // 0 means no limit +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnsInOrderBy( ) +{ + return 0; // 0 means no limit +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxColumnsInSelect( ) +{ + return 0; // 0 means no limit +} + +sal_Int32 SAL_CALL ODatabaseMetaData::getMaxUserNameLength( ) +{ + return 31; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsResultSetType(sal_Int32 setType) +{ + switch (setType) + { + case ResultSetType::FORWARD_ONLY: + return true; + default: + return false; + } +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsResultSetConcurrency( + sal_Int32 aResultSetType, + sal_Int32 aConcurrency) +{ + if (aResultSetType == ResultSetType::FORWARD_ONLY + && aConcurrency == ResultSetConcurrency::READ_ONLY) + return true; + else + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::ownUpdatesAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::ownDeletesAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::ownInsertsAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::othersUpdatesAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::othersDeletesAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::othersInsertsAreVisible( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::updatesAreDetected( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::deletesAreDetected( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::insertsAreDetected( sal_Int32 ) +{ + return false; +} + +sal_Bool SAL_CALL ODatabaseMetaData::supportsBatchUpdates() +{ + // No batch support in firebird + return false; +} + +uno::Reference< XConnection > SAL_CALL ODatabaseMetaData::getConnection() +{ + return m_pConnection; +} + +// here follow all methods which return a resultset +// the first methods is an example implementation how to use this resultset +// of course you could implement it on your and you should do this because +// the general way is more memory expensive + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getTableTypes( ) +{ + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eTableTypes); + + ODatabaseMetaDataResultSet::ORows aResults; + ODatabaseMetaDataResultSet::ORow aRow(2); + + aRow[0] = new ORowSetValueDecorator(); // unused + + // TODO Put these statics to one place + // like postgreSQL's Statics class. + + aRow[1] = new ORowSetValueDecorator(OUString("TABLE")); + aResults.push_back(aRow); + + aRow[1] = new ORowSetValueDecorator(OUString("VIEW")); + aResults.push_back(aRow); + + aRow[1] = new ORowSetValueDecorator(OUString("SYSTEM TABLE")); + aResults.push_back(aRow); + + pResultSet->setRows(std::move(aResults)); + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getTypeInfo() +{ + SAL_INFO("connectivity.firebird", "getTypeInfo()"); + + // this returns an empty resultset where the column-names are already set + // in special the metadata of the resultset already returns the right columns + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = + new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eTypeInfo); + static ODatabaseMetaDataResultSet::ORows aResults = []() + { + ODatabaseMetaDataResultSet::ORows tmp; + ODatabaseMetaDataResultSet::ORow aRow(19); + + // Common data + aRow[4] = ODatabaseMetaDataResultSet::getQuoteValue(); // Literal quote marks + aRow[5] = ODatabaseMetaDataResultSet::getQuoteValue(); // Literal quote marks + aRow[7] = new ORowSetValueDecorator(true); // Nullable + aRow[8] = new ORowSetValueDecorator(true); // Case sensitive + aRow[10] = new ORowSetValueDecorator(false); // Is unsigned + // FIXED_PREC_SCALE: docs state "can it be a money value? " however + // in reality this causes Base to treat all numbers as money formatted + // by default which is wrong (and formatting as money value is still + // possible for all values). + aRow[11] = new ORowSetValueDecorator(false); + // Localised Type Name -- TODO: implement (but can be null): + aRow[13] = new ORowSetValueDecorator(); + aRow[16] = new ORowSetValueDecorator(); // Unused + aRow[17] = new ORowSetValueDecorator(); // Unused + aRow[18] = new ORowSetValueDecorator(sal_Int16(10));// Radix + + // Char + aRow[1] = new ORowSetValueDecorator(OUString("CHAR")); + aRow[2] = new ORowSetValueDecorator(DataType::CHAR); + aRow[3] = new ORowSetValueDecorator(sal_Int16(32765)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(OUString("length")); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // Varchar + aRow[1] = new ORowSetValueDecorator(OUString("VARCHAR")); + aRow[2] = new ORowSetValueDecorator(DataType::VARCHAR); + aRow[3] = new ORowSetValueDecorator(sal_Int16(32765)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(OUString("length")); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // Binary (CHAR); we use the Firebird synonym CHARACTER + // to fool LO into seeing it as different types. + // It is distinguished from Text type by its character set OCTETS; + // that will be added by Tables::createStandardColumnPart + aRow[1] = new ORowSetValueDecorator(OUString("CHARACTER")); + aRow[2] = new ORowSetValueDecorator(DataType::BINARY); + aRow[3] = new ORowSetValueDecorator(sal_Int16(32765)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(OUString("length")); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::NONE)); // Searchable + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // Varbinary (VARCHAR); see comment above about BINARY + aRow[1] = new ORowSetValueDecorator(OUString("CHARACTER VARYING")); + aRow[2] = new ORowSetValueDecorator(DataType::VARBINARY); + aRow[3] = new ORowSetValueDecorator(sal_Int16(32765)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(OUString("length")); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::NONE)); // Searchable + + // Clob (SQL_BLOB) + aRow[1] = new ORowSetValueDecorator(OUString("BLOB SUB_TYPE TEXT")); // BLOB, with subtype 1 + aRow[2] = new ORowSetValueDecorator(DataType::CLOB); + aRow[3] = new ORowSetValueDecorator(sal_Int32(2147483647)); // Precision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // Longvarbinary (SQL_BLOB) + // Distinguished from simple blob with a user-defined subtype. + aRow[1] = new ORowSetValueDecorator(OUString("BLOB SUB_TYPE " + OUString::number(static_cast<short>(BlobSubtype::Image))) ); // BLOB, with subtype 0 + aRow[2] = new ORowSetValueDecorator(DataType::LONGVARBINARY); + tmp.push_back(aRow); + + // Integer Types common + { + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(true); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + } + // Smallint (SQL_SHORT) + aRow[1] = new ORowSetValueDecorator(OUString("SMALLINT")); + aRow[2] = new ORowSetValueDecorator(DataType::SMALLINT); + aRow[3] = new ORowSetValueDecorator(sal_Int16(5)); // Prevision + tmp.push_back(aRow); + // Integer (SQL_LONG) + aRow[1] = new ORowSetValueDecorator(OUString("INTEGER")); + aRow[2] = new ORowSetValueDecorator(DataType::INTEGER); + aRow[3] = new ORowSetValueDecorator(sal_Int16(10)); // Precision + tmp.push_back(aRow); + // Bigint (SQL_INT64) + aRow[1] = new ORowSetValueDecorator(OUString("BIGINT")); + aRow[2] = new ORowSetValueDecorator(DataType::BIGINT); + aRow[3] = new ORowSetValueDecorator(sal_Int16(20)); // Precision + tmp.push_back(aRow); + + // Decimal Types common + { + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(true); // Autoincrement + } + + aRow[6] = new ORowSetValueDecorator(OUString("PRECISION,SCALE")); // Create params + // Numeric + aRow[1] = new ORowSetValueDecorator(OUString("NUMERIC")); + aRow[2] = new ORowSetValueDecorator(DataType::NUMERIC); + aRow[3] = new ORowSetValueDecorator(sal_Int16(18)); // Precision + aRow[14] = new ORowSetValueDecorator(sal_Int16(0)); // Minimum scale + aRow[15] = new ORowSetValueDecorator(sal_Int16(18)); // Max scale + tmp.push_back(aRow); + // Decimal + aRow[1] = new ORowSetValueDecorator(OUString("DECIMAL")); + aRow[2] = new ORowSetValueDecorator(DataType::DECIMAL); + aRow[3] = new ORowSetValueDecorator(sal_Int16(18)); // Precision + aRow[14] = new ORowSetValueDecorator(sal_Int16(0)); // Minimum scale + aRow[15] = new ORowSetValueDecorator(sal_Int16(18)); // Max scale + tmp.push_back(aRow); + + aRow[6] = new ORowSetValueDecorator(); // Create Params + // Float (SQL_FLOAT) + aRow[1] = new ORowSetValueDecorator(OUString("FLOAT")); + aRow[2] = new ORowSetValueDecorator(DataType::FLOAT); + aRow[3] = new ORowSetValueDecorator(sal_Int16(7)); // Precision + aRow[14] = new ORowSetValueDecorator(sal_Int16(1)); // Minimum scale + aRow[15] = new ORowSetValueDecorator(sal_Int16(7)); // Max scale + tmp.push_back(aRow); + // Double (SQL_DOUBLE) + aRow[1] = new ORowSetValueDecorator(OUString("DOUBLE PRECISION")); + aRow[2] = new ORowSetValueDecorator(DataType::DOUBLE); + aRow[3] = new ORowSetValueDecorator(sal_Int16(15)); // Precision + aRow[14] = new ORowSetValueDecorator(sal_Int16(1)); // Minimum scale + aRow[15] = new ORowSetValueDecorator(sal_Int16(15)); // Max scale + tmp.push_back(aRow); + + // TODO: no idea whether D_FLOAT corresponds to an sql type + + // SQL_TIMESTAMP + aRow[1] = new ORowSetValueDecorator(OUString("TIMESTAMP")); + aRow[2] = new ORowSetValueDecorator(DataType::TIMESTAMP); + aRow[3] = new ORowSetValueDecorator(sal_Int32(8)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // SQL_TYPE_TIME + aRow[1] = new ORowSetValueDecorator(OUString("TIME")); + aRow[2] = new ORowSetValueDecorator(DataType::TIME); + aRow[3] = new ORowSetValueDecorator(sal_Int32(8)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // SQL_TYPE_DATE + aRow[1] = new ORowSetValueDecorator(OUString("DATE")); + aRow[2] = new ORowSetValueDecorator(DataType::DATE); + aRow[3] = new ORowSetValueDecorator(sal_Int32(8)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::FULL)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // SQL_BLOB + aRow[1] = new ORowSetValueDecorator(OUString("BLOB SUB_TYPE BINARY")); + aRow[2] = new ORowSetValueDecorator(DataType::BLOB); + aRow[3] = new ORowSetValueDecorator(sal_Int32(0)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::NONE)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + + // SQL_BOOLEAN + aRow[1] = new ORowSetValueDecorator(OUString("BOOLEAN")); + aRow[2] = new ORowSetValueDecorator(DataType::BOOLEAN); + aRow[3] = new ORowSetValueDecorator(sal_Int32(1)); // Prevision = max length + aRow[6] = new ORowSetValueDecorator(); // Create Params + aRow[9] = new ORowSetValueDecorator( + sal_Int16(ColumnSearch::BASIC)); // Searchable + aRow[12] = new ORowSetValueDecorator(false); // Autoincrement + aRow[14] = ODatabaseMetaDataResultSet::get0Value(); // Minimum scale + aRow[15] = ODatabaseMetaDataResultSet::get0Value(); // Max scale + tmp.push_back(aRow); + return tmp; + }(); + // [-loplugin:redundantfcast] false positive: + pResultSet->setRows(ODatabaseMetaDataResultSet::ORows(aResults)); + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getColumnPrivileges( + const Any& /*aCatalog*/, + const OUString& /*sSchema*/, + const OUString& sTable, + const OUString& sColumnNamePattern) +{ + SAL_INFO("connectivity.firebird", "getColumnPrivileges() with " + "Table: " << sTable + << " & ColumnNamePattern: " << sColumnNamePattern); + + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eColumnPrivileges); + uno::Reference< XStatement > statement = m_pConnection->createStatement(); + + static const char wld[] = "%"; + OUStringBuffer queryBuf( + "SELECT " + "priv.RDB$RELATION_NAME, " // 1 Table name + "priv.RDB$GRANTOR," // 2 + "priv.RDB$USER, " // 3 Grantee + "priv.RDB$PRIVILEGE, " // 4 + "priv.RDB$GRANT_OPTION, " // 5 is Grantable + "priv.RDB$FIELD_NAME " // 6 Column name + "FROM RDB$USER_PRIVILEGES priv "); + + { + OUString sAppend = "WHERE priv.RDB$RELATION_NAME = '%' "; + queryBuf.append(sAppend.replaceAll("%", sTable)); + } + if (!sColumnNamePattern.isEmpty()) + { + OUString sAppend; + if (sColumnNamePattern.match(wld)) + sAppend = "AND priv.RDB$FIELD_NAME LIKE '%' "; + else + sAppend = "AND priv.RDB$FIELD_NAME = '%' "; + + queryBuf.append(sAppend.replaceAll(wld, sColumnNamePattern)); + } + + queryBuf.append(" ORDER BY priv.RDB$FIELD, " + "priv.RDB$PRIVILEGE"); + + OUString query = queryBuf.makeStringAndClear(); + + uno::Reference< XResultSet > rs = statement->executeQuery(query); + uno::Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + ODatabaseMetaDataResultSet::ORows aResults; + + ODatabaseMetaDataResultSet::ORow aCurrentRow(9); + aCurrentRow[0] = new ORowSetValueDecorator(); // Unused + aCurrentRow[1] = new ORowSetValueDecorator(); // 1. TABLE_CAT Unsupported + aCurrentRow[2] = new ORowSetValueDecorator(); // 1. TABLE_SCHEM Unsupported + + while( rs->next() ) + { + // 3. TABLE_NAME + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + // 4. COLUMN_NAME + aCurrentRow[4] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(6))); + aCurrentRow[5] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(2))); // 5. GRANTOR + aCurrentRow[6] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(3))); // 6. GRANTEE + aCurrentRow[7] = new ORowSetValueDecorator(xRow->getString(4)); // 7. Privilege + aCurrentRow[8] = new ORowSetValueDecorator( ( xRow->getShort(5) == 1 ) ? + OUString("YES") : OUString("NO")); // 8. Grantable + + aResults.push_back(aCurrentRow); + } + + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getColumns( + const Any& /*catalog*/, + const OUString& /*schemaPattern*/, + const OUString& tableNamePattern, + const OUString& columnNamePattern) +{ + SAL_INFO("connectivity.firebird", "getColumns() with " + "TableNamePattern: " << tableNamePattern << + " & ColumnNamePattern: " << columnNamePattern); + + OUStringBuffer queryBuf("SELECT " + "relfields.RDB$RELATION_NAME, " // 1 + "relfields.RDB$FIELD_NAME, " // 2 + "relfields.RDB$DESCRIPTION," // 3 + "relfields.RDB$DEFAULT_VALUE, " // 4 + "relfields.RDB$FIELD_POSITION, "// 5 + "fields.RDB$FIELD_TYPE, " // 6 + "fields.RDB$FIELD_SUB_TYPE, " // 7 + "fields.RDB$FIELD_LENGTH, " // 8 + "fields.RDB$FIELD_PRECISION, " // 9 + "fields.RDB$FIELD_SCALE, " // 10 + // Specifically use relfields null flag -- the one in fields is used + // for domains, whether a specific field is nullable is set in relfields, + // this is also the one we manually fiddle when changing NULL/NOT NULL + // (see Table.cxx) + "relfields.RDB$NULL_FLAG, " // 11 + "fields.RDB$CHARACTER_LENGTH, " // 12 + "charset.RDB$CHARACTER_SET_NAME " // 13 + "FROM RDB$RELATION_FIELDS relfields " + "JOIN RDB$FIELDS fields " + "on (fields.RDB$FIELD_NAME = relfields.RDB$FIELD_SOURCE) " + "LEFT JOIN RDB$CHARACTER_SETS charset " + "on (fields.RDB$CHARACTER_SET_ID = charset.RDB$CHARACTER_SET_ID) " + "WHERE (1 = 1) "); + + if (!tableNamePattern.isEmpty()) + { + OUString sAppend; + if (tableNamePattern.match("%")) + sAppend = "AND relfields.RDB$RELATION_NAME LIKE '%' "; + else + sAppend = "AND relfields.RDB$RELATION_NAME = '%' "; + + queryBuf.append(sAppend.replaceAll("%", tableNamePattern)); + } + + if (!columnNamePattern.isEmpty()) + { + OUString sAppend; + if (columnNamePattern.match("%")) + sAppend = "AND relfields.RDB$FIELD_NAME LIKE '%' "; + else + sAppend = "AND relfields.RDB$FIELD_NAME = '%' "; + + queryBuf.append(sAppend.replaceAll("%", columnNamePattern)); + } + + OUString query = queryBuf.makeStringAndClear(); + + uno::Reference< XStatement > statement = m_pConnection->createStatement(); + uno::Reference< XResultSet > rs = statement->executeQuery(query); + uno::Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + + ODatabaseMetaDataResultSet::ORows aResults; + ODatabaseMetaDataResultSet::ORow aCurrentRow(19); + + aCurrentRow[0] = new ORowSetValueDecorator(); // Unused -- numbering starts from 0 + aCurrentRow[1] = new ORowSetValueDecorator(); // Catalog - can be null + aCurrentRow[2] = new ORowSetValueDecorator(); // Schema - can be null + aCurrentRow[8] = new ORowSetValueDecorator(); // Unused + aCurrentRow[10] = new ORowSetValueDecorator(sal_Int32(10)); // Radix: fixed in FB + aCurrentRow[14] = new ORowSetValueDecorator(); // Unused + aCurrentRow[15] = new ORowSetValueDecorator(); // Unused + + while( rs->next() ) + { + // 3. TABLE_NAME + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + // 4. Column Name + aCurrentRow[4] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(2))); + // 5. Datatype + short aType = getFBTypeFromBlrType(xRow->getShort(6)); + short aScale = xRow->getShort(10); + OUString sCharsetName = xRow->getString(13); + // result field may be filled with spaces + sCharsetName = sCharsetName.trim(); + ColumnTypeInfo aInfo(aType, xRow->getShort(7), aScale, + sCharsetName); + + aCurrentRow[5] = new ORowSetValueDecorator(aInfo.getSdbcType()); + // 6. Typename (SQL_*) + aCurrentRow[6] = new ORowSetValueDecorator(aInfo.getColumnTypeName()); + + // 7. Column Sizes + { + sal_Int32 aColumnSize = 0; + switch (aType) + { + case SQL_TEXT: + case SQL_VARYING: + aColumnSize = xRow->getShort(12); + break; + case SQL_SHORT: + case SQL_LONG: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_D_FLOAT: + case SQL_INT64: + case SQL_QUAD: + aColumnSize = xRow->getShort(9); + break; + case SQL_TIMESTAMP: + case SQL_BLOB: + case SQL_ARRAY: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + case SQL_NULL: + // TODO: implement. + break; + } + aCurrentRow[7] = new ORowSetValueDecorator(aColumnSize); + } + + // 9. Decimal digits (scale) + // fb stores a negative number + aCurrentRow[9] = new ORowSetValueDecorator( static_cast<sal_Int16>(-aScale) ); + + // 11. Nullable + if (xRow->getShort(11)) + { + aCurrentRow[11] = new ORowSetValueDecorator(ColumnValue::NO_NULLS); + } + else + { + aCurrentRow[11] = new ORowSetValueDecorator(ColumnValue::NULLABLE); + } + // 12. Comments -- may be omitted + { + OUString aDescription; + uno::Reference< XBlob > xBlob = xRow->getBlob(3); + if (xBlob.is()) + { + const sal_Int64 aBlobLength = xBlob->length(); + if (aBlobLength > SAL_MAX_INT32) + { + SAL_WARN("connectivity.firebird", "getBytes can't return " << aBlobLength << " bytes but only max " << SAL_MAX_INT32); + aDescription = OUString(reinterpret_cast<char*>(xBlob->getBytes(1, SAL_MAX_INT32).getArray()), + SAL_MAX_INT32, + RTL_TEXTENCODING_UTF8); + } + else + { + aDescription = OUString(reinterpret_cast<char*>(xBlob->getBytes(1, static_cast<sal_Int32>(aBlobLength)).getArray()), + aBlobLength, + RTL_TEXTENCODING_UTF8); + } + } + aCurrentRow[12] = new ORowSetValueDecorator(aDescription); + } + // 13. Default -- may be omitted. + { + uno::Reference< XBlob > xDefaultValueBlob = xRow->getBlob(4); + if (xDefaultValueBlob.is()) + { + // TODO: Implement + } + aCurrentRow[13] = new ORowSetValueDecorator(); + } + + // 16. Bytes in Column for char + if (aType == SQL_TEXT) + { + aCurrentRow[16] = new ORowSetValueDecorator(xRow->getShort(8)); + } + else if (aType == SQL_VARYING) + { + aCurrentRow[16] = new ORowSetValueDecorator(sal_Int32(32767)); + } + else + { + aCurrentRow[16] = new ORowSetValueDecorator(sal_Int32(0)); + } + // 17. Index of column + { + short nColumnNumber = xRow->getShort(5); + // Firebird stores column numbers beginning with 0 internally + // SDBC expects column numbering to begin with 1. + aCurrentRow[17] = new ORowSetValueDecorator(sal_Int32(nColumnNumber + 1)); + } + // 18. Is nullable + if (xRow->getShort(9)) + { + aCurrentRow[18] = new ORowSetValueDecorator(OUString("NO")); + } + else + { + aCurrentRow[18] = new ORowSetValueDecorator(OUString("YES")); + } + + aResults.push_back(aCurrentRow); + } + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eColumns); + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getTables( + const Any& /*catalog*/, + const OUString& /*schemaPattern*/, + const OUString& tableNamePattern, + const Sequence< OUString >& types) +{ + SAL_INFO("connectivity.firebird", "getTables() with " + "TableNamePattern: " << tableNamePattern); + + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eTables); + uno::Reference< XStatement > statement = m_pConnection->createStatement(); + + static const char wld[] = "%"; + OUStringBuffer queryBuf( + "SELECT " + "RDB$RELATION_NAME, " + "RDB$SYSTEM_FLAG, " + "RDB$RELATION_TYPE, " + "RDB$DESCRIPTION, " + "RDB$VIEW_BLR " + "FROM RDB$RELATIONS " + "WHERE "); + + // TODO: GLOBAL TEMPORARY, LOCAL TEMPORARY, ALIAS, SYNONYM + if (!types.hasElements() || (types.getLength() == 1 && types[0].match(wld))) + { + // from Firebird: src/jrd/constants.h + // rel_persistent = 0, rel_view = 1, rel_external = 2 + // All table types? I.e. includes system tables. + queryBuf.append("(RDB$RELATION_TYPE = 0 OR RDB$RELATION_TYPE = 1 OR RDB$RELATION_TYPE = 2) "); + } + else + { + queryBuf.append("( (0 = 1) "); + for (OUString const & t : types) + { + if (t == "SYSTEM TABLE") + queryBuf.append("OR (RDB$SYSTEM_FLAG = 1 AND RDB$VIEW_BLR IS NULL) "); + else if (t == "TABLE") + queryBuf.append("OR (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0 AND RDB$VIEW_BLR IS NULL) "); + else if (t == "VIEW") + queryBuf.append("OR (RDB$SYSTEM_FLAG IS NULL OR RDB$SYSTEM_FLAG = 0 AND RDB$VIEW_BLR IS NOT NULL) "); + else + throw SQLException(); // TODO: implement other types, see above. + } + queryBuf.append(") "); + } + + if (!tableNamePattern.isEmpty()) + { + OUString sAppend; + if (tableNamePattern.match(wld)) + sAppend = "AND RDB$RELATION_NAME LIKE '%' "; + else + sAppend = "AND RDB$RELATION_NAME = '%' "; + + queryBuf.append(sAppend.replaceAll(wld, tableNamePattern)); + } + + queryBuf.append(" ORDER BY RDB$RELATION_TYPE, RDB$RELATION_NAME"); + + OUString query = queryBuf.makeStringAndClear(); + + uno::Reference< XResultSet > rs = statement->executeQuery(query); + uno::Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + ODatabaseMetaDataResultSet::ORows aResults; + + ODatabaseMetaDataResultSet::ORow aCurrentRow(6); + aCurrentRow[0] = new ORowSetValueDecorator(); // 0. Unused + aCurrentRow[1] = new ORowSetValueDecorator(); // 1. Table_Cat Unsupported + aCurrentRow[2] = new ORowSetValueDecorator(); // 2. Table_Schem Unsupported + + while( rs->next() ) + { + // 3. TABLE_NAME + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + // 4. TABLE_TYPE + { + // TODO: check this as the docs are a bit unclear. + sal_Int16 nSystemFlag = xRow->getShort(2); + sal_Int16 nTableType = xRow->getShort(3); + xRow->getBlob(5); // We have to retrieve a column to verify it is null. + bool aIsView = !xRow->wasNull(); + OUString sTableType; + + if (nSystemFlag == 1) + { + sTableType = "SYSTEM TABLE"; + } + else if (aIsView) + { + sTableType = "VIEW"; + } + else + { + // see above about src/jrd/constants.h + if (nTableType == 0 || nTableType == 2) + sTableType = "TABLE"; + } + + aCurrentRow[4] = new ORowSetValueDecorator(sTableType); + } + // 5. REMARKS + { + uno::Reference< XClob > xClob = xRow->getClob(4); + if (xClob.is()) + { + aCurrentRow[5] = new ORowSetValueDecorator(xClob->getSubString(1, xClob->length())); + } + } + + aResults.push_back(aCurrentRow); + } + + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getProcedureColumns( + const Any&, const OUString&, + const OUString&, const OUString& ) +{ + SAL_WARN("connectivity.firebird", "Not yet implemented"); + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eProcedureColumns); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getProcedures( + const Any&, const OUString&, + const OUString& ) +{ + SAL_WARN("connectivity.firebird", "Not yet implemented"); + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eProcedures); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getVersionColumns( + const Any&, const OUString&, const OUString& ) +{ + SAL_WARN("connectivity.firebird", "Not yet implemented"); + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eVersionColumns); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getExportedKeys( + const Any&, const OUString&, const OUString& table ) +{ + return ODatabaseMetaData::lcl_getKeys(false, table); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getImportedKeys( + const Any&, const OUString&, const OUString& table ) +{ + return ODatabaseMetaData::lcl_getKeys(true, table); +} + +uno::Reference< XResultSet > ODatabaseMetaData::lcl_getKeys(const bool bIsImport, std::u16string_view table ) +{ + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(bIsImport?ODatabaseMetaDataResultSet::eImportedKeys:ODatabaseMetaDataResultSet::eExportedKeys); + + uno::Reference< XStatement > statement = m_pConnection->createStatement(); + + OUString sSQL = "SELECT " + "RDB$REF_CONSTRAINTS.RDB$UPDATE_RULE, " // 1 update rule + "RDB$REF_CONSTRAINTS.RDB$DELETE_RULE, " // 2 delete rule + "RDB$REF_CONSTRAINTS.RDB$CONST_NAME_UQ, " // 3 primary or unique key name + "RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME, " // 4 foreign key name + "PRIM.RDB$DEFERRABLE, " // 5 deferrability + "PRIM.RDB$INITIALLY_DEFERRED, " // 6 deferrability + "PRIM.RDB$RELATION_NAME, " // 7 PK table name + "PRIMARY_INDEX.RDB$FIELD_NAME, " // 8 PK column name + "PRIMARY_INDEX.RDB$FIELD_POSITION, " // 9 PK sequence number + "FOREI.RDB$RELATION_NAME, " // 10 FK table name + "FOREIGN_INDEX.RDB$FIELD_NAME " // 11 FK column name + "FROM RDB$REF_CONSTRAINTS " + "INNER JOIN RDB$RELATION_CONSTRAINTS AS PRIM " + "ON RDB$REF_CONSTRAINTS.RDB$CONST_NAME_UQ = PRIM.RDB$CONSTRAINT_NAME " + "INNER JOIN RDB$RELATION_CONSTRAINTS AS FOREI " + "ON RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME = FOREI.RDB$CONSTRAINT_NAME " + "INNER JOIN RDB$INDEX_SEGMENTS AS PRIMARY_INDEX " + "ON PRIM.RDB$INDEX_NAME = PRIMARY_INDEX.RDB$INDEX_NAME " + "INNER JOIN RDB$INDEX_SEGMENTS AS FOREIGN_INDEX " + "ON FOREI.RDB$INDEX_NAME = FOREIGN_INDEX.RDB$INDEX_NAME " + "WHERE FOREI.RDB$CONSTRAINT_TYPE = 'FOREIGN KEY' "; + if (bIsImport) + sSQL += OUString::Concat("AND FOREI.RDB$RELATION_NAME = '")+ table +"'"; + else + sSQL += OUString::Concat("AND PRIM.RDB$RELATION_NAME = '")+ table +"'"; + + uno::Reference< XResultSet > rs = statement->executeQuery(sSQL); + uno::Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + + ODatabaseMetaDataResultSet::ORows aResults; + ODatabaseMetaDataResultSet::ORow aCurrentRow(15); + + // TODO is it necessary to initialize these? + aCurrentRow[0] = new ORowSetValueDecorator(); // Unused + aCurrentRow[1] = new ORowSetValueDecorator(); // PKTABLE_CAT unsupported + aCurrentRow[2] = new ORowSetValueDecorator(); // PKTABLE_SCHEM unsupported + aCurrentRow[5] = new ORowSetValueDecorator(); // FKTABLE_CAT unsupported + aCurrentRow[6] = new ORowSetValueDecorator(); // FKTABLE_SCHEM unsupported + + std::map< OUString,sal_Int32> aRuleMap; + aRuleMap[ OUString("CASCADE")] = KeyRule::CASCADE; + aRuleMap[ OUString("RESTRICT")] = KeyRule::RESTRICT; + aRuleMap[ OUString("SET NULL")] = KeyRule::SET_NULL; + aRuleMap[ OUString("SET DEFAULT")] = KeyRule::SET_DEFAULT; + aRuleMap[ OUString("NO ACTION")] = KeyRule::NO_ACTION; + + while(rs->next()) + { + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(7))); // PK table + aCurrentRow[4] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(8))); // PK column + aCurrentRow[7] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(10))); // FK table + aCurrentRow[8] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(11))); // FK column + + aCurrentRow[9] = new ORowSetValueDecorator(xRow->getShort(9)); // PK sequence number + aCurrentRow[10] = new ORowSetValueDecorator(aRuleMap[sanitizeIdentifier(xRow->getString(1))]); // update role + aCurrentRow[11] = new ORowSetValueDecorator(aRuleMap[sanitizeIdentifier(xRow->getString(2))]); // delete role + + aCurrentRow[12] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(4))); // FK name + aCurrentRow[13] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(3))); // PK name + + aCurrentRow[14] = new ORowSetValueDecorator(Deferrability::NONE); // deferrability + + // deferrability is currently not supported, but may be supported in the future. + /* + aCurrentRow[14] = (xRow->getString(5) == "NO" ? + new ORowSetValueDecorator(Deferrability::NONE) + : (xRow->getString(6) == "NO" ? + new ORowSetValueDecorator(Deferrability::INITIALLY_IMMEDIATE) + : new ORowSetValueDecorator(Deferrability::INITIALLY_DEFERRED)); + */ + + aResults.push_back(aCurrentRow); + } + + pResultSet->setRows( std::move(aResults) ); + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getPrimaryKeys( + const Any& /*aCatalog*/, + const OUString& /*sSchema*/, + const OUString& sTable) +{ + SAL_INFO("connectivity.firebird", "getPrimaryKeys() with " + "Table: " << sTable); + + OUString sAppend = "WHERE constr.RDB$RELATION_NAME = '%' "; + OUString sQuery = "SELECT " + "constr.RDB$RELATION_NAME, " // 1. Table Name + "inds.RDB$FIELD_NAME, " // 2. Column Name + "inds.RDB$FIELD_POSITION, " // 3. Sequence Number + "constr.RDB$CONSTRAINT_NAME " // 4 Constraint name + "FROM RDB$RELATION_CONSTRAINTS constr " + "JOIN RDB$INDEX_SEGMENTS inds " + "on (constr.RDB$INDEX_NAME = inds.RDB$INDEX_NAME) " + + sAppend.replaceAll("%", sTable) + + "AND constr.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' " + "ORDER BY inds.RDB$FIELD_NAME"; + + uno::Reference< XStatement > xStatement = m_pConnection->createStatement(); + uno::Reference< XResultSet > xRs = xStatement->executeQuery(sQuery); + uno::Reference< XRow > xRow( xRs, UNO_QUERY_THROW ); + + ODatabaseMetaDataResultSet::ORows aResults; + ODatabaseMetaDataResultSet::ORow aCurrentRow(7); + + aCurrentRow[0] = new ORowSetValueDecorator(); // Unused -- numbering starts from 0 + aCurrentRow[1] = new ORowSetValueDecorator(); // Catalog - can be null + aCurrentRow[2] = new ORowSetValueDecorator(); // Schema - can be null + + while(xRs->next()) + { + // 3. Table Name + if (xRs->getRow() == 1) // Table name doesn't change, so only retrieve once + { + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + } + // 4. Column Name + aCurrentRow[4] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(2))); + // 5. KEY_SEQ (which key in the sequence) + aCurrentRow[5] = new ORowSetValueDecorator(xRow->getShort(3)); + // 6. Primary Key Name + aCurrentRow[6] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(4))); + + aResults.push_back(aCurrentRow); + } + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::ePrimaryKeys); + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getIndexInfo( + const Any& /*aCatalog*/, + const OUString& /*sSchema*/, + const OUString& sTable, + sal_Bool bIsUnique, + sal_Bool) // TODO: what is bIsApproximate? + +{ + // Apparently this method can also return a "tableIndexStatistic" + // However this is only mentioned in XDatabaseMetaData.idl (whose comments + // are duplicated in the postgresql driver), and is otherwise undocumented. + SAL_INFO("connectivity.firebird", "getPrimaryKeys() with " + "Table: " << sTable); + + OUStringBuffer aQueryBuf("SELECT " + "indices.RDB$RELATION_NAME, " // 1. Table Name + "index_segments.RDB$FIELD_NAME, " // 2. Column Name + "index_segments.RDB$FIELD_POSITION, " // 3. Sequence Number + "indices.RDB$INDEX_NAME, " // 4. Index name + "indices.RDB$UNIQUE_FLAG, " // 5. Unique Flag + "indices.RDB$INDEX_TYPE " // 6. Index Type + "FROM RDB$INDICES indices " + "JOIN RDB$INDEX_SEGMENTS index_segments " + "on (indices.RDB$INDEX_NAME = index_segments.RDB$INDEX_NAME) " + "WHERE indices.RDB$RELATION_NAME = '" + sTable + "' " + "AND (indices.RDB$SYSTEM_FLAG = 0) "); + // Not sure whether we should exclude system indices, but otoh. we never + // actually deal with system tables (system indices only apply to system + // tables) within the GUI. + + // Only filter if true (according to the docs), i.e.: + // If false we return all indices, if true we return only unique indices + if (bIsUnique) + aQueryBuf.append("AND (indices.RDB$UNIQUE_FLAG = 1) "); + + OUString sQuery = aQueryBuf.makeStringAndClear(); + + uno::Reference< XStatement > xStatement = m_pConnection->createStatement(); + uno::Reference< XResultSet > xRs = xStatement->executeQuery(sQuery); + uno::Reference< XRow > xRow( xRs, UNO_QUERY_THROW ); + + ODatabaseMetaDataResultSet::ORows aResults; + ODatabaseMetaDataResultSet::ORow aCurrentRow(14); + + aCurrentRow[0] = new ORowSetValueDecorator(); // Unused -- numbering starts from 0 + aCurrentRow[1] = new ORowSetValueDecorator(); // Catalog - can be null + aCurrentRow[2] = new ORowSetValueDecorator(); // Schema - can be null + aCurrentRow[5] = new ORowSetValueDecorator(); // Index Catalog -- can be null + // Wikipedia indicates: + // 'Firebird makes all indices of the database behave like well-tuned "clustered indexes" used by other architectures.' + // but it's not "CLUSTERED", neither "STATISTIC" nor "HASHED" (the other specific types from offapi/com/sun/star/sdbc/IndexType.idl) + // According to https://www.ibphoenix.com/resources/documents/design/doc_18, + // it seems another type => OTHER + aCurrentRow[7] = new ORowSetValueDecorator(IndexType::OTHER); // 7. INDEX TYPE + aCurrentRow[13] = new ORowSetValueDecorator(); // Filter Condition -- can be null + + while(xRs->next()) + { + // 3. Table Name + if (xRs->getRow() == 1) // Table name doesn't change, so only retrieve once + { + aCurrentRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + } + + // 4. NON_UNIQUE -- i.e. specifically negate here. + aCurrentRow[4] = new ORowSetValueDecorator(xRow->getShort(5) == 0); + // 6. INDEX NAME + aCurrentRow[6] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(4))); + + // 8. ORDINAL POSITION + aCurrentRow[8] = new ORowSetValueDecorator(xRow->getShort(3)); + // 9. COLUMN NAME + aCurrentRow[9] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(2))); + // 10. ASC(ending)/DESC(ending) + if (xRow->getShort(6) == 1) + aCurrentRow[10] = new ORowSetValueDecorator(OUString("D")); + else + aCurrentRow[10] = new ORowSetValueDecorator(OUString("A")); + // TODO: double check this^^^, doesn't seem to be officially documented anywhere. + // 11. CARDINALITY + aCurrentRow[11] = new ORowSetValueDecorator(sal_Int32(0)); // TODO: determine how to do this + // 12. PAGES + aCurrentRow[12] = new ORowSetValueDecorator(sal_Int32(0)); // TODO: determine how to do this + + aResults.push_back(aCurrentRow); + } + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eIndexInfo); + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getBestRowIdentifier( + const Any&, const OUString&, const OUString&, sal_Int32, + sal_Bool ) +{ + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eBestRowIdentifier); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getTablePrivileges( + const Any& /*aCatalog*/, + const OUString& /*sSchemaPattern*/, + const OUString& sTableNamePattern) +{ + SAL_INFO("connectivity.firebird", "getTablePrivileges() with " + "TableNamePattern: " << sTableNamePattern); + + rtl::Reference<ODatabaseMetaDataResultSet> pResultSet = new + ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eTablePrivileges); + uno::Reference< XStatement > statement = m_pConnection->createStatement(); + + // TODO: column specific privileges are included, we may need + // to have WHERE RDB$FIELD_NAME = NULL or similar. + static const char wld[] = "%"; + OUStringBuffer queryBuf( + "SELECT " + "priv.RDB$RELATION_NAME, " // 1 + "priv.RDB$GRANTOR," // 2 + "priv.RDB$USER, " // 3 Grantee + "priv.RDB$PRIVILEGE, " // 4 + "priv.RDB$GRANT_OPTION " // 5 is Grantable + "FROM RDB$USER_PRIVILEGES priv "); + + if (!sTableNamePattern.isEmpty()) + { + OUString sAppend; + if (sTableNamePattern.match(wld)) + sAppend = "WHERE priv.RDB$RELATION_NAME LIKE '%' "; + else + sAppend = "WHERE priv.RDB$RELATION_NAME = '%' "; + + queryBuf.append(sAppend.replaceAll(wld, sTableNamePattern)); + } + queryBuf.append(" ORDER BY priv.RDB$RELATION_TYPE, " + "priv.RDB$RELATION_NAME, " + "priv.RDB$PRIVILEGE"); + + OUString query = queryBuf.makeStringAndClear(); + + uno::Reference< XResultSet > rs = statement->executeQuery(query); + uno::Reference< XRow > xRow( rs, UNO_QUERY_THROW ); + ODatabaseMetaDataResultSet::ORows aResults; + + ODatabaseMetaDataResultSet::ORow aRow(8); + aRow[0] = new ORowSetValueDecorator(); // Unused + aRow[1] = new ORowSetValueDecorator(); // TABLE_CAT unsupported + aRow[2] = new ORowSetValueDecorator(); // TABLE_SCHEM unsupported. + + while( rs->next() ) + { + // 3. TABLE_NAME + aRow[3] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(1))); + aRow[4] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(2))); // 4. GRANTOR + aRow[5] = new ORowSetValueDecorator(sanitizeIdentifier(xRow->getString(3))); // 5. GRANTEE + aRow[6] = new ORowSetValueDecorator(xRow->getString(4)); // 6. Privilege + aRow[7] = new ORowSetValueDecorator(bool(xRow->getBoolean(5))); // 7. Is Grantable + + aResults.push_back(aRow); + } + + pResultSet->setRows( std::move(aResults) ); + + return pResultSet; +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getCrossReference( + const Any&, const OUString&, + const OUString&, const Any&, + const OUString&, const OUString& ) +{ + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eCrossReference); +} + +uno::Reference< XResultSet > SAL_CALL ODatabaseMetaData::getUDTs( const Any&, const OUString&, const OUString&, const Sequence< sal_Int32 >& ) +{ + OSL_FAIL("Not implemented yet!"); + // TODO implement + return new ODatabaseMetaDataResultSet(ODatabaseMetaDataResultSet::eUDTs); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/DatabaseMetaData.hxx b/connectivity/source/drivers/firebird/DatabaseMetaData.hxx new file mode 100644 index 000000000..c577f594d --- /dev/null +++ b/connectivity/source/drivers/firebird/DatabaseMetaData.hxx @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "Connection.hxx" + +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +namespace connectivity::firebird + { + + //************ Class: ODatabaseMetaData + + + typedef ::cppu::WeakImplHelper< css::sdbc::XDatabaseMetaData> ODatabaseMetaData_BASE; + + class ODatabaseMetaData : public ODatabaseMetaData_BASE + { + ::rtl::Reference<Connection> m_pConnection; + private: + css::uno::Reference< css::sdbc::XResultSet > lcl_getKeys( bool bIsImport, std::u16string_view table ); + public: + + explicit ODatabaseMetaData(Connection* _pCon); + virtual ~ODatabaseMetaData() override; + + // as I mentioned before this interface is really BIG + // XDatabaseMetaData + virtual sal_Bool SAL_CALL allProceduresAreCallable( ) override; + virtual sal_Bool SAL_CALL allTablesAreSelectable( ) override; + virtual OUString SAL_CALL getURL( ) override; + virtual OUString SAL_CALL getUserName( ) override; + virtual sal_Bool SAL_CALL isReadOnly( ) override; + virtual sal_Bool SAL_CALL nullsAreSortedHigh( ) override; + virtual sal_Bool SAL_CALL nullsAreSortedLow( ) override; + virtual sal_Bool SAL_CALL nullsAreSortedAtStart( ) override; + virtual sal_Bool SAL_CALL nullsAreSortedAtEnd( ) override; + virtual OUString SAL_CALL getDatabaseProductName( ) override; + virtual OUString SAL_CALL getDatabaseProductVersion( ) override; + virtual OUString SAL_CALL getDriverName( ) override; + virtual OUString SAL_CALL getDriverVersion( ) override; + virtual sal_Int32 SAL_CALL getDriverMajorVersion( ) override; + virtual sal_Int32 SAL_CALL getDriverMinorVersion( ) override; + virtual sal_Bool SAL_CALL usesLocalFiles( ) override; + virtual sal_Bool SAL_CALL usesLocalFilePerTable( ) override; + virtual sal_Bool SAL_CALL supportsMixedCaseIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesUpperCaseIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesLowerCaseIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesMixedCaseIdentifiers( ) override; + virtual sal_Bool SAL_CALL supportsMixedCaseQuotedIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesUpperCaseQuotedIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesLowerCaseQuotedIdentifiers( ) override; + virtual sal_Bool SAL_CALL storesMixedCaseQuotedIdentifiers( ) override; + virtual OUString SAL_CALL getIdentifierQuoteString( ) override; + virtual OUString SAL_CALL getSQLKeywords( ) override; + virtual OUString SAL_CALL getNumericFunctions( ) override; + virtual OUString SAL_CALL getStringFunctions( ) override; + virtual OUString SAL_CALL getSystemFunctions( ) override; + virtual OUString SAL_CALL getTimeDateFunctions( ) override; + virtual OUString SAL_CALL getSearchStringEscape( ) override; + virtual OUString SAL_CALL getExtraNameCharacters( ) override; + virtual sal_Bool SAL_CALL supportsAlterTableWithAddColumn( ) override; + virtual sal_Bool SAL_CALL supportsAlterTableWithDropColumn( ) override; + virtual sal_Bool SAL_CALL supportsColumnAliasing( ) override; + virtual sal_Bool SAL_CALL nullPlusNonNullIsNull( ) override; + virtual sal_Bool SAL_CALL supportsTypeConversion( ) override; + virtual sal_Bool SAL_CALL supportsConvert( sal_Int32 fromType, sal_Int32 toType ) override; + virtual sal_Bool SAL_CALL supportsTableCorrelationNames( ) override; + virtual sal_Bool SAL_CALL supportsDifferentTableCorrelationNames( ) override; + virtual sal_Bool SAL_CALL supportsExpressionsInOrderBy( ) override; + virtual sal_Bool SAL_CALL supportsOrderByUnrelated( ) override; + virtual sal_Bool SAL_CALL supportsGroupBy( ) override; + virtual sal_Bool SAL_CALL supportsGroupByUnrelated( ) override; + virtual sal_Bool SAL_CALL supportsGroupByBeyondSelect( ) override; + virtual sal_Bool SAL_CALL supportsLikeEscapeClause( ) override; + virtual sal_Bool SAL_CALL supportsMultipleResultSets( ) override; + virtual sal_Bool SAL_CALL supportsMultipleTransactions( ) override; + virtual sal_Bool SAL_CALL supportsNonNullableColumns( ) override; + virtual sal_Bool SAL_CALL supportsMinimumSQLGrammar( ) override; + virtual sal_Bool SAL_CALL supportsCoreSQLGrammar( ) override; + virtual sal_Bool SAL_CALL supportsExtendedSQLGrammar( ) override; + virtual sal_Bool SAL_CALL supportsANSI92EntryLevelSQL( ) override; + virtual sal_Bool SAL_CALL supportsANSI92IntermediateSQL( ) override; + virtual sal_Bool SAL_CALL supportsANSI92FullSQL( ) override; + virtual sal_Bool SAL_CALL supportsIntegrityEnhancementFacility( ) override; + virtual sal_Bool SAL_CALL supportsOuterJoins( ) override; + virtual sal_Bool SAL_CALL supportsFullOuterJoins( ) override; + virtual sal_Bool SAL_CALL supportsLimitedOuterJoins( ) override; + virtual OUString SAL_CALL getSchemaTerm( ) override; + virtual OUString SAL_CALL getProcedureTerm( ) override; + virtual OUString SAL_CALL getCatalogTerm( ) override; + virtual sal_Bool SAL_CALL isCatalogAtStart( ) override; + virtual OUString SAL_CALL getCatalogSeparator( ) override; + virtual sal_Bool SAL_CALL supportsSchemasInDataManipulation( ) override; + virtual sal_Bool SAL_CALL supportsSchemasInProcedureCalls( ) override; + virtual sal_Bool SAL_CALL supportsSchemasInTableDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsSchemasInIndexDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsSchemasInPrivilegeDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsCatalogsInDataManipulation( ) override; + virtual sal_Bool SAL_CALL supportsCatalogsInProcedureCalls( ) override; + virtual sal_Bool SAL_CALL supportsCatalogsInTableDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsCatalogsInIndexDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsCatalogsInPrivilegeDefinitions( ) override; + virtual sal_Bool SAL_CALL supportsPositionedDelete( ) override; + virtual sal_Bool SAL_CALL supportsPositionedUpdate( ) override; + virtual sal_Bool SAL_CALL supportsSelectForUpdate( ) override; + virtual sal_Bool SAL_CALL supportsStoredProcedures( ) override; + virtual sal_Bool SAL_CALL supportsSubqueriesInComparisons( ) override; + virtual sal_Bool SAL_CALL supportsSubqueriesInExists( ) override; + virtual sal_Bool SAL_CALL supportsSubqueriesInIns( ) override; + virtual sal_Bool SAL_CALL supportsSubqueriesInQuantifieds( ) override; + virtual sal_Bool SAL_CALL supportsCorrelatedSubqueries( ) override; + virtual sal_Bool SAL_CALL supportsUnion( ) override; + virtual sal_Bool SAL_CALL supportsUnionAll( ) override; + virtual sal_Bool SAL_CALL supportsOpenCursorsAcrossCommit( ) override; + virtual sal_Bool SAL_CALL supportsOpenCursorsAcrossRollback( ) override; + virtual sal_Bool SAL_CALL supportsOpenStatementsAcrossCommit( ) override; + virtual sal_Bool SAL_CALL supportsOpenStatementsAcrossRollback( ) override; + virtual sal_Int32 SAL_CALL getMaxBinaryLiteralLength( ) override; + virtual sal_Int32 SAL_CALL getMaxCharLiteralLength( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnsInGroupBy( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnsInIndex( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnsInOrderBy( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnsInSelect( ) override; + virtual sal_Int32 SAL_CALL getMaxColumnsInTable( ) override; + virtual sal_Int32 SAL_CALL getMaxConnections( ) override; + virtual sal_Int32 SAL_CALL getMaxCursorNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxIndexLength( ) override; + virtual sal_Int32 SAL_CALL getMaxSchemaNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxProcedureNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxCatalogNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxRowSize( ) override; + virtual sal_Bool SAL_CALL doesMaxRowSizeIncludeBlobs( ) override; + virtual sal_Int32 SAL_CALL getMaxStatementLength( ) override; + virtual sal_Int32 SAL_CALL getMaxStatements( ) override; + virtual sal_Int32 SAL_CALL getMaxTableNameLength( ) override; + virtual sal_Int32 SAL_CALL getMaxTablesInSelect( ) override; + virtual sal_Int32 SAL_CALL getMaxUserNameLength( ) override; + virtual sal_Int32 SAL_CALL getDefaultTransactionIsolation( ) override; + virtual sal_Bool SAL_CALL supportsTransactions( ) override; + virtual sal_Bool SAL_CALL supportsTransactionIsolationLevel( sal_Int32 level ) override; + virtual sal_Bool SAL_CALL supportsDataDefinitionAndDataManipulationTransactions( ) override; + virtual sal_Bool SAL_CALL supportsDataManipulationTransactionsOnly( ) override; + virtual sal_Bool SAL_CALL dataDefinitionCausesTransactionCommit( ) override; + virtual sal_Bool SAL_CALL dataDefinitionIgnoredInTransactions( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getProcedures( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& procedureNamePattern ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getProcedureColumns( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& procedureNamePattern, const OUString& columnNamePattern ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getTables( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& tableNamePattern, const css::uno::Sequence< OUString >& types ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getSchemas( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getCatalogs( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getTableTypes( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getColumns( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& tableNamePattern, const OUString& columnNamePattern ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getColumnPrivileges( const css::uno::Any& catalog, const OUString& schema, const OUString& table, const OUString& columnNamePattern ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getTablePrivileges( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& tableNamePattern ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getBestRowIdentifier( const css::uno::Any& catalog, const OUString& schema, const OUString& table, sal_Int32 scope, sal_Bool nullable ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getVersionColumns( const css::uno::Any& catalog, const OUString& schema, const OUString& table ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getPrimaryKeys( const css::uno::Any& catalog, const OUString& schema, const OUString& table ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getImportedKeys( const css::uno::Any& catalog, const OUString& schema, const OUString& table ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getExportedKeys( const css::uno::Any& catalog, const OUString& schema, const OUString& table ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getCrossReference( const css::uno::Any& primaryCatalog, const OUString& primarySchema, const OUString& primaryTable, const css::uno::Any& foreignCatalog, const OUString& foreignSchema, const OUString& foreignTable ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getTypeInfo( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getIndexInfo( const css::uno::Any& catalog, const OUString& schema, const OUString& table, sal_Bool unique, sal_Bool approximate ) override; + virtual sal_Bool SAL_CALL supportsResultSetType( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL supportsResultSetConcurrency( sal_Int32 setType, sal_Int32 concurrency ) override; + virtual sal_Bool SAL_CALL ownUpdatesAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL ownDeletesAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL ownInsertsAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL othersUpdatesAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL othersDeletesAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL othersInsertsAreVisible( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL updatesAreDetected( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL deletesAreDetected( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL insertsAreDetected( sal_Int32 setType ) override; + virtual sal_Bool SAL_CALL supportsBatchUpdates( ) override; + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getUDTs( const css::uno::Any& catalog, const OUString& schemaPattern, const OUString& typeNamePattern, const css::uno::Sequence< sal_Int32 >& types ) override; + virtual css::uno::Reference< css::sdbc::XConnection > SAL_CALL getConnection( ) override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Driver.cxx b/connectivity/source/drivers/firebird/Driver.cxx new file mode 100644 index 000000000..3a1b80292 --- /dev/null +++ b/connectivity/source/drivers/firebird/Driver.cxx @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "Connection.hxx" +#include "Driver.hxx" +#include "SubComponent.hxx" + +#include <connectivity/dbexception.hxx> +#include <strings.hrc> +#include <resource/sharedresources.hxx> + +#include <comphelper/servicehelper.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::sdbcx; + +using namespace ::osl; + +using namespace connectivity::firebird; + +// Static const variables +namespace { +constexpr OUStringLiteral our_sFirebirdTmpVar = u"FIREBIRD_TMP"; +constexpr OUStringLiteral our_sFirebirdLockVar = u"FIREBIRD_LOCK"; +constexpr OUStringLiteral our_sFirebirdMsgVar = u"FIREBIRD_MSG"; +#ifdef MACOSX +constexpr OUStringLiteral our_sFirebirdLibVar = u"LIBREOFFICE_FIREBIRD_LIB"; +#endif +}; + +FirebirdDriver::FirebirdDriver(const css::uno::Reference< css::uno::XComponentContext >& _rxContext) + : ODriver_BASE(m_aMutex) + , m_aContext(_rxContext) + , m_firebirdTMPDirectory(nullptr, true) + , m_firebirdLockDirectory(nullptr, true) +{ + // ::utl::TempFile uses a unique temporary directory (subdirectory of + // /tmp or other user specific tmp directory) per instance in which + // we can create directories for firebird at will. + m_firebirdTMPDirectory.EnableKillingFile(true); + m_firebirdLockDirectory.EnableKillingFile(true); + + // Overrides firebird's default of /tmp or c:\temp + osl_setEnvironment(OUString(our_sFirebirdTmpVar).pData, m_firebirdTMPDirectory.GetFileName().pData); + + // Overrides firebird's default of /tmp/firebird or c:\temp\firebird + osl_setEnvironment(OUString(our_sFirebirdLockVar).pData, m_firebirdLockDirectory.GetFileName().pData); + +#ifndef SYSTEM_FIREBIRD + // Overrides firebird's hardcoded default of /usr/local/firebird on *nix, + // however on Windows it seems to use the current directory as a default. + OUString sMsgURL("$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/firebird"); + ::rtl::Bootstrap::expandMacros(sMsgURL); + OUString sMsgPath; + ::osl::FileBase::getSystemPathFromFileURL(sMsgURL, sMsgPath); + osl_setEnvironment(OUString(our_sFirebirdMsgVar).pData, sMsgPath.pData); +#ifdef MACOSX + // Set an env. variable to specify library location + // for dlopen used in fbclient. + OUString sLibURL("$LO_LIB_DIR"); + ::rtl::Bootstrap::expandMacros(sLibURL); + OUString sLibPath; + ::osl::FileBase::getSystemPathFromFileURL(sLibURL, sLibPath); + osl_setEnvironment(OUString(our_sFirebirdLibVar).pData, sLibPath.pData); +#endif /*MACOSX*/ +#endif /*!SYSTEM_FIREBIRD*/ +} + +FirebirdDriver::~FirebirdDriver() = default; + +void FirebirdDriver::disposing() +{ + MutexGuard aGuard(m_aMutex); + + for (auto const& elem : m_xConnections) + { + Reference< XComponent > xComp(elem.get(), UNO_QUERY); + if (xComp.is()) + xComp->dispose(); + } + m_xConnections.clear(); + + osl_clearEnvironment(OUString(our_sFirebirdTmpVar).pData); + osl_clearEnvironment(OUString(our_sFirebirdLockVar).pData); + +#ifndef SYSTEM_FIREBIRD + osl_clearEnvironment(OUString(our_sFirebirdMsgVar).pData); +#ifdef MACOSX + osl_clearEnvironment(OUString(our_sFirebirdLibVar).pData); +#endif /*MACOSX*/ +#endif /*!SYSTEM_FIREBIRD*/ + + OSL_VERIFY(fb_shutdown(0, 1)); + + ODriver_BASE::disposing(); +} + +OUString SAL_CALL FirebirdDriver::getImplementationName() +{ + return "com.sun.star.comp.sdbc.firebird.Driver"; +} + +sal_Bool SAL_CALL FirebirdDriver::supportsService(const OUString& _rServiceName) +{ + return cppu::supportsService(this, _rServiceName); +} + +Sequence< OUString > SAL_CALL FirebirdDriver::getSupportedServiceNames() +{ + return { "com.sun.star.sdbc.Driver", "com.sun.star.sdbcx.Driver" }; +} + +// ---- XDriver ------------------------------------------------------------- +Reference< XConnection > SAL_CALL FirebirdDriver::connect( + const OUString& url, const Sequence< PropertyValue >& info ) +{ + SAL_INFO("connectivity.firebird", "connect(), URL: " << url ); + + MutexGuard aGuard( m_aMutex ); + if (ODriver_BASE::rBHelper.bDisposed) + throw DisposedException(); + + if ( ! acceptsURL(url) ) + return nullptr; + + rtl::Reference<Connection> pCon = new Connection(); + pCon->construct(url, info); + + m_xConnections.push_back(WeakReferenceHelper(*pCon)); + + return pCon; +} + +sal_Bool SAL_CALL FirebirdDriver::acceptsURL( const OUString& url ) +{ + return (url == "sdbc:embedded:firebird" || url.startsWith("sdbc:firebird:")); +} + +Sequence< DriverPropertyInfo > SAL_CALL FirebirdDriver::getPropertyInfo( + const OUString& url, const Sequence< PropertyValue >& ) +{ + if ( ! acceptsURL(url) ) + { + ::connectivity::SharedResources aResources; + const OUString sMessage = aResources.getResourceString(STR_URI_SYNTAX_ERROR); + ::dbtools::throwGenericSQLException(sMessage ,*this); + } + + return Sequence< DriverPropertyInfo >(); +} + +sal_Int32 SAL_CALL FirebirdDriver::getMajorVersion( ) +{ + // The major and minor version are sdbc driver specific. Must begin with 1.0 + // as per https://api.libreoffice.org/docs/common/ref/com/sun/star/sdbc/XDriver.html + return 1; +} + +sal_Int32 SAL_CALL FirebirdDriver::getMinorVersion( ) +{ + return 0; +} + +//----- XDataDefinitionSupplier +uno::Reference< XTablesSupplier > SAL_CALL FirebirdDriver::getDataDefinitionByConnection( + const uno::Reference< XConnection >& rConnection) +{ + if (Connection* pConnection = comphelper::getFromUnoTunnel<Connection>(rConnection)) + return pConnection->createCatalog(); + return {}; +} + +uno::Reference< XTablesSupplier > SAL_CALL FirebirdDriver::getDataDefinitionByURL( + const OUString& rURL, + const uno::Sequence< PropertyValue >& rInfo) +{ + uno::Reference< XConnection > xConnection = connect(rURL, rInfo); + return getDataDefinitionByConnection(xConnection); +} + +namespace connectivity::firebird +{ + void checkDisposed(bool _bThrow) + { + if (_bThrow) + throw DisposedException(); + + } + +} // namespace + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +connectivity_FirebirdDriver_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + try { + return cppu::acquire(new FirebirdDriver(context)); + } catch (...) { + return nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Driver.hxx b/connectivity/source/drivers/firebird/Driver.hxx new file mode 100644 index 000000000..06841d937 --- /dev/null +++ b/connectivity/source/drivers/firebird/Driver.hxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include "Connection.hxx" + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/sdbc/XDriver.hpp> +#include <com/sun/star/sdbcx/XDataDefinitionSupplier.hpp> +#include <cppuhelper/compbase.hxx> +#include <unotools/tempfile.hxx> + +namespace connectivity::firebird + { + // The SQL dialect in use + // Has to be used in various isc_* calls. + // 3: Is IB6 -- minimum required for delimited identifiers. + // SQL_DIALECT_V6 = 3, it's the last current version + // SQL_DIALECT_CURRENT is an alias for SQL_DIALECT_V6 + // See src/dsql/sqlda_pub.h for full details + + typedef ::cppu::WeakComponentImplHelper< css::sdbc::XDriver, + css::sdbcx::XDataDefinitionSupplier, + css::lang::XServiceInfo > ODriver_BASE; + + class FirebirdDriver : public ODriver_BASE + { + private: + css::uno::Reference<css::uno::XComponentContext> m_aContext; + ::utl::TempFile m_firebirdTMPDirectory; + ::utl::TempFile m_firebirdLockDirectory; + + protected: + ::osl::Mutex m_aMutex; // mutex is need to control member access + OWeakRefArray m_xConnections; // vector containing a list + // of all the Connection objects + // for this Driver + + public: + + explicit FirebirdDriver(const css::uno::Reference< css::uno::XComponentContext >& _rxContext); + virtual ~FirebirdDriver() override; + const css::uno::Reference<css::uno::XComponentContext>& getContext() const { return m_aContext; } + + // OComponentHelper + virtual void SAL_CALL disposing() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // XDriver + virtual css::uno::Reference< css::sdbc::XConnection > SAL_CALL connect( const OUString& url, const css::uno::Sequence< css::beans::PropertyValue >& info ) override; + virtual sal_Bool SAL_CALL acceptsURL( const OUString& url ) override; + virtual css::uno::Sequence< css::sdbc::DriverPropertyInfo > SAL_CALL getPropertyInfo( const OUString& url, const css::uno::Sequence< css::beans::PropertyValue >& info ) override; + virtual sal_Int32 SAL_CALL getMajorVersion( ) override; + virtual sal_Int32 SAL_CALL getMinorVersion( ) override; + + // XDataDefinitionSupplier + virtual css::uno::Reference< css::sdbcx::XTablesSupplier > + SAL_CALL getDataDefinitionByConnection( + const css::uno::Reference< css::sdbc::XConnection >& rxConnection) override; + virtual css::uno::Reference< css::sdbcx::XTablesSupplier > + SAL_CALL getDataDefinitionByURL( + const OUString& rsURL, + const css::uno::Sequence< css::beans::PropertyValue >& rInfo) override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Indexes.cxx b/connectivity/source/drivers/firebird/Indexes.cxx new file mode 100644 index 000000000..10ec90dc5 --- /dev/null +++ b/connectivity/source/drivers/firebird/Indexes.cxx @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Indexes.hxx" + +using namespace ::connectivity; +using namespace ::connectivity::firebird; + +using namespace ::osl; +using namespace ::std; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdbc; + +Indexes::Indexes(Table* pTable, Mutex& rMutex, const vector<OUString>& rVector) + : OIndexesHelper(pTable, rMutex, rVector) + , m_pTable(pTable) +{ +} + +// XDrop +void Indexes::dropObject(sal_Int32 /*nPosition*/, const OUString& sIndexName) +{ + OUString sSql("DROP INDEX \"" + sIndexName + "\""); + m_pTable->getConnection()->createStatement()->execute(sSql); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Indexes.hxx b/connectivity/source/drivers/firebird/Indexes.hxx new file mode 100644 index 000000000..12d7dd198 --- /dev/null +++ b/connectivity/source/drivers/firebird/Indexes.hxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include "Table.hxx" + +#include <connectivity/TIndexes.hxx> + +namespace connectivity::firebird + { + + /** + * Firebird has a non-standard DROP INDEX statement, hence we need + * to override OIndexesHelper::dropObject + */ + class Indexes: public ::connectivity::OIndexesHelper + { + private: + Table* m_pTable; + protected: + // XDrop + virtual void dropObject(sal_Int32 nPosition, + const OUString& sIndexName) override; + public: + Indexes(Table* pTable, + ::osl::Mutex& rMutex, + const std::vector< OUString>& rVector); + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Keys.cxx b/connectivity/source/drivers/firebird/Keys.cxx new file mode 100644 index 000000000..8de112ec6 --- /dev/null +++ b/connectivity/source/drivers/firebird/Keys.cxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Keys.hxx" +#include "Table.hxx" + +#include <connectivity/dbtools.hxx> + +using namespace ::connectivity; +using namespace ::connectivity::firebird; + +using namespace ::dbtools; +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +Keys::Keys(Table* pTable, Mutex& rMutex, const ::std::vector< OUString>& rNames): + OKeysHelper(pTable, + rMutex, + rNames), + m_pTable(pTable) +{ +} + +//----- XDrop ---------------------------------------------------------------- +void Keys::dropObject(sal_Int32 nPosition, const OUString& sName) +{ + if (m_pTable->isNew()) + return; + + uno::Reference<XPropertySet> xKey(getObject(nPosition), UNO_QUERY); + + if (xKey.is()) + { + const OUString sQuote = m_pTable->getConnection()->getMetaData() + ->getIdentifierQuoteString(); + + OUString sSql("ALTER TABLE " + quoteName(sQuote, m_pTable->getName()) + + " DROP CONSTRAINT " + quoteName(sQuote, sName)); + + m_pTable->getConnection()->createStatement()->execute(sSql); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Keys.hxx b/connectivity/source/drivers/firebird/Keys.hxx new file mode 100644 index 000000000..4e9ba5a7e --- /dev/null +++ b/connectivity/source/drivers/firebird/Keys.hxx @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <connectivity/TKeys.hxx> + +namespace connectivity::firebird + { + + class Table; + + class Keys: public ::connectivity::OKeysHelper + { + private: + Table* m_pTable; + + public: + Keys(Table* pTable, + ::osl::Mutex& rMutex, + const ::std::vector< OUString>& rNames); + + // OKeysHelper / XDrop + void dropObject(sal_Int32 nPosition, const OUString& sName) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/PreparedStatement.cxx b/connectivity/source/drivers/firebird/PreparedStatement.cxx new file mode 100644 index 000000000..816c38d5c --- /dev/null +++ b/connectivity/source/drivers/firebird/PreparedStatement.cxx @@ -0,0 +1,1058 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 <sal/config.h> +#include <cmath> + +#include "Connection.hxx" +#include "PreparedStatement.hxx" +#include "ResultSet.hxx" +#include "ResultSetMetaData.hxx" +#include "Util.hxx" + +#include <comphelper/sequence.hxx> +#include <connectivity/dbexception.hxx> +#include <propertyids.hxx> +#include <connectivity/dbtools.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/sdbc/DataType.hpp> + +using namespace connectivity::firebird; + +using namespace ::comphelper; +using namespace ::osl; + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::container; +using namespace com::sun::star::io; +using namespace com::sun::star::util; + +IMPLEMENT_SERVICE_INFO(OPreparedStatement,"com.sun.star.sdbcx.firebird.PreparedStatement","com.sun.star.sdbc.PreparedStatement"); + +constexpr size_t MAX_SIZE_SEGMENT = 65535; // max value of a segment of CLOB, if we want more than 65535 bytes, we need more segments + + +OPreparedStatement::OPreparedStatement( Connection* _pConnection, + const OUString& sql) + :OStatementCommonBase(_pConnection) + ,m_sSqlStatement(sql) + ,m_pOutSqlda(nullptr) + ,m_pInSqlda(nullptr) +{ + SAL_INFO("connectivity.firebird", "OPreparedStatement(). " + "sql: " << sql); +} + +void OPreparedStatement::ensurePrepared() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + if (m_aStatementHandle) + return; + + ISC_STATUS aErr = 0; + + if (!m_pInSqlda) + { + m_pInSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(10))); + m_pInSqlda->version = SQLDA_VERSION1; + m_pInSqlda->sqln = 10; + } + + prepareAndDescribeStatement(m_sSqlStatement, m_pOutSqlda); + + aErr = isc_dsql_describe_bind(m_statusVector, + &m_aStatementHandle, + 1, + m_pInSqlda); + + if (aErr) + { + SAL_WARN("connectivity.firebird", "isc_dsql_describe_bind failed"); + } + else if (m_pInSqlda->sqld > m_pInSqlda->sqln) // Not large enough + { + short nItems = m_pInSqlda->sqld; + free(m_pInSqlda); + m_pInSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(nItems))); + m_pInSqlda->version = SQLDA_VERSION1; + m_pInSqlda->sqln = nItems; + aErr = isc_dsql_describe_bind(m_statusVector, + &m_aStatementHandle, + 1, + m_pInSqlda); + SAL_WARN_IF(aErr, "connectivity.firebird", "isc_dsql_describe_bind failed"); + } + + if (!aErr) + mallocSQLVAR(m_pInSqlda); + else + evaluateStatusVector(m_statusVector, m_sSqlStatement, *this); +} + +OPreparedStatement::~OPreparedStatement() +{ +} + +void SAL_CALL OPreparedStatement::acquire() noexcept +{ + OStatementCommonBase::acquire(); +} + +void SAL_CALL OPreparedStatement::release() noexcept +{ + OStatementCommonBase::release(); +} + +Any SAL_CALL OPreparedStatement::queryInterface(const Type& rType) +{ + Any aRet = OStatementCommonBase::queryInterface(rType); + if(!aRet.hasValue()) + aRet = OPreparedStatement_Base::queryInterface(rType); + return aRet; +} + +uno::Sequence< Type > SAL_CALL OPreparedStatement::getTypes() +{ + return concatSequences(OPreparedStatement_Base::getTypes(), + OStatementCommonBase::getTypes()); +} + +Reference< XResultSetMetaData > SAL_CALL OPreparedStatement::getMetaData() +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ensurePrepared(); + + if(!m_xMetaData.is()) + m_xMetaData = new OResultSetMetaData(m_pConnection.get() + , m_pOutSqlda); + + return m_xMetaData; +} + +void SAL_CALL OPreparedStatement::close() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + OStatementCommonBase::close(); + if (m_pInSqlda) + { + freeSQLVAR(m_pInSqlda); + free(m_pInSqlda); + m_pInSqlda = nullptr; + } + if (m_pOutSqlda) + { + freeSQLVAR(m_pOutSqlda); + free(m_pOutSqlda); + m_pOutSqlda = nullptr; + } +} + +void SAL_CALL OPreparedStatement::disposing() +{ + close(); +} + +void SAL_CALL OPreparedStatement::setString(sal_Int32 nParameterIndex, + const OUString& sInput) +{ + SAL_INFO("connectivity.firebird", + "setString(" << nParameterIndex << " , " << sInput << ")"); + + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ensurePrepared(); + + checkParameterIndex(nParameterIndex); + setParameterNull(nParameterIndex, false); + + OString str = OUStringToOString(sInput , RTL_TEXTENCODING_UTF8 ); + + XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1); + + int dtype = (pVar->sqltype & ~1); // drop flag bit for now + + if (str.getLength() > pVar->sqllen) + str = str.copy(0, pVar->sqllen); + + switch (dtype) { + case SQL_VARYING: + { + const sal_Int32 max_varchar_len = 0xFFFF; + // First 2 bytes indicate string size + if (str.getLength() > max_varchar_len) + { + str = str.copy(0, max_varchar_len); + } + const sal_uInt16 nLength = str.getLength(); + static_assert(sizeof(nLength) == 2, "must match dest memcpy len"); + memcpy(pVar->sqldata, &nLength, 2); + // Actual data + memcpy(pVar->sqldata + 2, str.getStr(), str.getLength()); + break; + } + case SQL_TEXT: + memcpy(pVar->sqldata, str.getStr(), str.getLength()); + // Fill remainder with spaces + memset(pVar->sqldata + str.getLength(), ' ', pVar->sqllen - str.getLength()); + break; + case SQL_BLOB: // Clob + assert( pVar->sqlsubtype == static_cast<short>(BlobSubtype::Clob) ); + setClob(nParameterIndex, sInput ); + break; + case SQL_SHORT: + { + sal_Int32 int32Value = sInput.toInt32(); + if ( (int32Value < std::numeric_limits<sal_Int16>::min()) || + (int32Value > std::numeric_limits<sal_Int16>::max()) ) + { + ::dbtools::throwSQLException( + "Value out of range for SQL_SHORT type", + ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, + *this); + } + setShort(nParameterIndex, int32Value); + break; + } + case SQL_LONG: + { + sal_Int32 int32Value = sInput.toInt32(); + setInt(nParameterIndex, int32Value); + break; + } + case SQL_INT64: + { + sal_Int64 int64Value = sInput.toInt64(); + setLong(nParameterIndex, int64Value); + break; + } + case SQL_FLOAT: + { + float floatValue = sInput.toFloat(); + setFloat(nParameterIndex, floatValue); + break; + } + case SQL_BOOLEAN: + { + bool boolValue = sInput.toBoolean(); + setBoolean(nParameterIndex, boolValue); + break; + } + case SQL_NULL: + { + // See https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-datatypes-special-sqlnull + pVar->sqldata = nullptr; + break; + } + default: + ::dbtools::throwSQLException( + "Incorrect type for setString", + ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, + *this); + } +} + +Reference< XConnection > SAL_CALL OPreparedStatement::getConnection() +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + return m_pConnection; +} + +sal_Bool SAL_CALL OPreparedStatement::execute() +{ + SAL_INFO("connectivity.firebird", "executeQuery(). " + "Got called with sql: " << m_sSqlStatement); + + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + ensurePrepared(); + + ISC_STATUS aErr; + + if (m_xResultSet.is()) // Checks whether we have already run the statement. + { + disposeResultSet(); + // Closes the cursor from the last run. + // This doesn't actually free the statement -- using DSQL_close closes + // the cursor and keeps the statement, using DSQL_drop frees the statement + // (and associated cursors). + aErr = isc_dsql_free_statement(m_statusVector, + &m_aStatementHandle, + DSQL_close); + if (aErr) + { + // Do not throw error. Trying to close a closed cursor is not a + // critical mistake. + OUString sErrMsg = StatusVectorToString(m_statusVector, + u"isc_dsql_free_statement: close cursor"); + SAL_WARN("connectivity.firebird", sErrMsg); + } + } + + aErr = isc_dsql_execute(m_statusVector, + &m_pConnection->getTransaction(), + &m_aStatementHandle, + 1, + m_pInSqlda); + if (aErr) + { + SAL_WARN("connectivity.firebird", "isc_dsql_execute failed" ); + evaluateStatusVector(m_statusVector, u"isc_dsql_execute", *this); + } + + m_xResultSet = new OResultSet(m_pConnection.get(), + m_aMutex, + uno::Reference< XInterface >(*this), + m_aStatementHandle, + m_pOutSqlda); + + if (getStatementChangeCount() > 0) + m_pConnection->notifyDatabaseModified(); + + return m_xResultSet.is(); + // TODO: implement handling of multiple ResultSets. +} + +sal_Int32 SAL_CALL OPreparedStatement::executeUpdate() +{ + execute(); + return getStatementChangeCount(); +} + +Reference< XResultSet > SAL_CALL OPreparedStatement::executeQuery() +{ + execute(); + return m_xResultSet; +} + +namespace { + +/** + * Take out the number part of a fix point decimal without + * the information of where is the fractional part from a + * string representation of a number. (e.g. 54.654 -> 54654) + */ +sal_Int64 toNumericWithoutDecimalPlace(const OUString& sSource) +{ + OUString sNumber(sSource); + + // cut off leading 0 eventually ( eg. 0.567 -> .567) + (void)sSource.startsWith("0", &sNumber); + + sal_Int32 nDotIndex = sNumber.indexOf('.'); + + if( nDotIndex < 0) + { + return sNumber.toInt64(); // no dot -> it's an integer + } + else + { + // remove dot + OUStringBuffer sBuffer(15); + if(nDotIndex > 0) + { + sBuffer.append(sNumber.subView(0, nDotIndex)); + } + sBuffer.append(sNumber.subView(nDotIndex + 1)); + return sBuffer.makeStringAndClear().toInt64(); + } +} + +} + +//----- XParameters ----------------------------------------------------------- +void SAL_CALL OPreparedStatement::setNull(sal_Int32 nIndex, sal_Int32 /*nSqlType*/) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ensurePrepared(); + + checkParameterIndex(nIndex); + setParameterNull(nIndex); +} + +void SAL_CALL OPreparedStatement::setBoolean(sal_Int32 nIndex, sal_Bool bValue) +{ + setValue< sal_Bool >(nIndex, bValue, SQL_BOOLEAN); +} + +template <typename T> +void OPreparedStatement::setValue(sal_Int32 nIndex, const T& nValue, ISC_SHORT nType) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ensurePrepared(); + + checkParameterIndex(nIndex); + setParameterNull(nIndex, false); + + XSQLVAR* pVar = m_pInSqlda->sqlvar + (nIndex - 1); + + if ((pVar->sqltype & ~1) != nType) + { + ::dbtools::throwSQLException( + "Incorrect type for setValue", + ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, + *this); + } + + memcpy(pVar->sqldata, &nValue, sizeof(nValue)); +} + +void SAL_CALL OPreparedStatement::setByte(sal_Int32 nIndex, sal_Int8 nValue) +{ + // there's no TINYINT or equivalent on Firebird, + // so do the same as setShort + setValue< sal_Int16 >(nIndex, nValue, SQL_SHORT); +} + +void SAL_CALL OPreparedStatement::setShort(sal_Int32 nIndex, sal_Int16 nValue) +{ + setValue< sal_Int16 >(nIndex, nValue, SQL_SHORT); +} + +void SAL_CALL OPreparedStatement::setInt(sal_Int32 nIndex, sal_Int32 nValue) +{ + setValue< sal_Int32 >(nIndex, nValue, SQL_LONG); +} + +void SAL_CALL OPreparedStatement::setLong(sal_Int32 nIndex, sal_Int64 nValue) +{ + setValue< sal_Int64 >(nIndex, nValue, SQL_INT64); +} + +void SAL_CALL OPreparedStatement::setFloat(sal_Int32 nIndex, float nValue) +{ + setValue< float >(nIndex, nValue, SQL_FLOAT); +} + +void SAL_CALL OPreparedStatement::setDouble(sal_Int32 nIndex, double nValue) +{ + MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ensurePrepared(); + + XSQLVAR* pVar = m_pInSqlda->sqlvar + (nIndex - 1); + short dType = (pVar->sqltype & ~1); // drop flag bit for now + short dSubType = pVar->sqlsubtype; + // Assume it is a sub type of a number. + if(dSubType < 0 || dSubType > 2) + { + ::dbtools::throwSQLException( + "Incorrect number sub type", + ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, + *this); + } + // firebird stores scale as a negative number + ColumnTypeInfo columnType{ dType, dSubType, + static_cast<short>(-pVar->sqlscale) }; + + // Caller might try to set an integer type here. It makes sense to convert + // it instead of throwing an error. + switch(columnType.getSdbcType()) + { + case DataType::SMALLINT: + setValue< sal_Int16 >(nIndex, + static_cast<sal_Int16>(nValue), + dType); + break; + case DataType::INTEGER: + setValue< sal_Int32 >(nIndex, + static_cast<sal_Int32>(nValue), + dType); + break; + case DataType::BIGINT: + setValue< sal_Int64 >(nIndex, + static_cast<sal_Int64>(nValue), + dType); + break; + case DataType::NUMERIC: + case DataType::DECIMAL: + // take decimal places into account, later on they are removed in makeNumericString + setObjectWithInfo(nIndex,Any{nValue}, columnType.getSdbcType(), columnType.getScale()); + break; + default: + setValue< double >(nIndex, nValue, SQL_DOUBLE); // TODO: SQL_D_FLOAT? + } +} + +void SAL_CALL OPreparedStatement::setDate(sal_Int32 nIndex, const Date& rDate) +{ + struct tm aCTime; + aCTime.tm_mday = rDate.Day; + aCTime.tm_mon = rDate.Month -1; + aCTime.tm_year = rDate.Year -1900; + + ISC_DATE aISCDate; + isc_encode_sql_date(&aCTime, &aISCDate); + + setValue< ISC_DATE >(nIndex, aISCDate, SQL_TYPE_DATE); +} + +void SAL_CALL OPreparedStatement::setTime( sal_Int32 nIndex, const css::util::Time& rTime) +{ + struct tm aCTime; + aCTime.tm_sec = rTime.Seconds; + aCTime.tm_min = rTime.Minutes; + aCTime.tm_hour = rTime.Hours; + + ISC_TIME aISCTime; + isc_encode_sql_time(&aCTime, &aISCTime); + + // Here we "know" that ISC_TIME is simply in units of seconds/ISC_TIME_SECONDS_PRECISION with no + // other funkiness, so we can simply add the fraction of a second. + aISCTime += rTime.NanoSeconds / (1000000000 / ISC_TIME_SECONDS_PRECISION); + + setValue< ISC_TIME >(nIndex, aISCTime, SQL_TYPE_TIME); +} + +void SAL_CALL OPreparedStatement::setTimestamp(sal_Int32 nIndex, const DateTime& rTimestamp) +{ + struct tm aCTime; + aCTime.tm_sec = rTimestamp.Seconds; + aCTime.tm_min = rTimestamp.Minutes; + aCTime.tm_hour = rTimestamp.Hours; + aCTime.tm_mday = rTimestamp.Day; + aCTime.tm_mon = rTimestamp.Month - 1; + aCTime.tm_year = rTimestamp.Year - 1900; + + ISC_TIMESTAMP aISCTimestamp; + isc_encode_timestamp(&aCTime, &aISCTimestamp); + + // As in previous function + aISCTimestamp.timestamp_time += rTimestamp.NanoSeconds / (1000000000 / ISC_TIME_SECONDS_PRECISION); + + setValue< ISC_TIMESTAMP >(nIndex, aISCTimestamp, SQL_TIMESTAMP); +} + + +// void OPreparedStatement::set +void OPreparedStatement::openBlobForWriting(isc_blob_handle& rBlobHandle, ISC_QUAD& rBlobId) +{ + ISC_STATUS aErr; + + aErr = isc_create_blob2(m_statusVector, + &m_pConnection->getDBHandle(), + &m_pConnection->getTransaction(), + &rBlobHandle, + &rBlobId, + 0, // Blob parameter buffer length + nullptr); // Blob parameter buffer handle + + if (aErr) + { + evaluateStatusVector(m_statusVector, + OUStringConcatenation("setBlob failed on " + m_sSqlStatement), + *this); + assert(false); + } +} + +void OPreparedStatement::closeBlobAfterWriting(isc_blob_handle& rBlobHandle) +{ + ISC_STATUS aErr; + + aErr = isc_close_blob(m_statusVector, + &rBlobHandle); + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_close_blob failed", + *this); + assert(false); + } +} + +void SAL_CALL OPreparedStatement::setClob(sal_Int32 nParameterIndex, const Reference< XClob >& xClob ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + +#if SAL_TYPES_SIZEOFPOINTER == 8 + isc_blob_handle aBlobHandle = 0; +#else + isc_blob_handle aBlobHandle = nullptr; +#endif + ISC_QUAD aBlobId; + + openBlobForWriting(aBlobHandle, aBlobId); + + + // Max segment size is 2^16 == SAL_MAX_UINT16 + // SAL_MAX_UINT16 / 4 is surely enough for UTF-8 + // TODO apply max segment size to character encoding + sal_Int64 nCharWritten = 1; // XClob is indexed from 1 + ISC_STATUS aErr = 0; + sal_Int64 nLen = xClob->length(); + while ( nLen >= nCharWritten ) + { + sal_Int64 nCharRemain = nLen - nCharWritten + 1; + constexpr sal_uInt16 MAX_SIZE = SAL_MAX_UINT16 / 4; + sal_uInt16 nWriteSize = std::min<sal_Int64>(nCharRemain, MAX_SIZE); + OString sData = OUStringToOString( + xClob->getSubString(nCharWritten, nWriteSize), + RTL_TEXTENCODING_UTF8); + aErr = isc_put_segment( m_statusVector, + &aBlobHandle, + sData.getLength(), + sData.getStr() ); + nCharWritten += nWriteSize; + + if (aErr) + break; + } + + // We need to make sure we close the Blob even if there are errors, hence evaluate + // errors after closing. + closeBlobAfterWriting(aBlobHandle); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_put_segment failed", + *this); + assert(false); + } + + setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB); +} + +void OPreparedStatement::setClob( sal_Int32 nParameterIndex, const OUString& rStr ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nParameterIndex); + +#if SAL_TYPES_SIZEOFPOINTER == 8 + isc_blob_handle aBlobHandle = 0; +#else + isc_blob_handle aBlobHandle = nullptr; +#endif + ISC_QUAD aBlobId; + + openBlobForWriting(aBlobHandle, aBlobId); + + OString sData = OUStringToOString( + rStr, + RTL_TEXTENCODING_UTF8); + size_t nDataSize = sData.getLength(); + ISC_STATUS aErr = 0; + // we can't store more than MAX_SIZE_SEGMENT in a segment + if (nDataSize <= MAX_SIZE_SEGMENT) + { + aErr = isc_put_segment( m_statusVector, + &aBlobHandle, + sData.getLength(), + sData.getStr() ); + } + else + { + // if we need more, let's split the input and first let's calculate the nb of entire chunks needed + size_t nNbEntireChunks = nDataSize / MAX_SIZE_SEGMENT; + for (size_t i = 0; i < nNbEntireChunks; ++i) + { + OString strCurrentChunk = sData.copy(i * MAX_SIZE_SEGMENT, MAX_SIZE_SEGMENT); + aErr = isc_put_segment( m_statusVector, + &aBlobHandle, + strCurrentChunk.getLength(), + strCurrentChunk.getStr() ); + if (aErr) + break; + } + size_t nRemainingBytes = nDataSize - (nNbEntireChunks * MAX_SIZE_SEGMENT); + if (nRemainingBytes && !aErr) + { + // then copy the remaining + OString strCurrentChunk = sData.copy(nNbEntireChunks * MAX_SIZE_SEGMENT, nRemainingBytes); + aErr = isc_put_segment( m_statusVector, + &aBlobHandle, + strCurrentChunk.getLength(), + strCurrentChunk.getStr() ); + } + } + + // We need to make sure we close the Blob even if there are errors, hence evaluate + // errors after closing. + closeBlobAfterWriting(aBlobHandle); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_put_segment failed", + *this); + assert(false); + } + + setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB); +} + +void SAL_CALL OPreparedStatement::setBlob(sal_Int32 nParameterIndex, + const Reference< XBlob >& xBlob) +{ + ::osl::MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nParameterIndex); + +#if SAL_TYPES_SIZEOFPOINTER == 8 + isc_blob_handle aBlobHandle = 0; +#else + isc_blob_handle aBlobHandle = nullptr; +#endif + ISC_QUAD aBlobId; + + openBlobForWriting(aBlobHandle, aBlobId); + + ISC_STATUS aErr = 0; + const sal_Int64 nBlobLen = xBlob->length(); + if (nBlobLen > 0) + { + // Max write size is 0xFFFF == SAL_MAX_UINT16 + sal_uInt64 nDataWritten = 0; + while (sal::static_int_cast<sal_uInt64>(nBlobLen) > nDataWritten) + { + sal_uInt64 nDataRemaining = nBlobLen - nDataWritten; + sal_uInt16 nWriteSize = std::min(nDataRemaining, sal_uInt64(SAL_MAX_UINT16)); + aErr = isc_put_segment(m_statusVector, + &aBlobHandle, + nWriteSize, + reinterpret_cast<const char*>(xBlob->getBytes(nDataWritten, nWriteSize).getConstArray())); + nDataWritten += nWriteSize; + + if (aErr) + break; + } + } + + // We need to make sure we close the Blob even if there are errors, hence evaluate + // errors after closing. + closeBlobAfterWriting(aBlobHandle); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_put_segment failed", + *this); + assert(false); + } + + setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB); +} + + +void SAL_CALL OPreparedStatement::setArray( sal_Int32 nIndex, const Reference< XArray >& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + + +void SAL_CALL OPreparedStatement::setRef( sal_Int32 nIndex, const Reference< XRef >& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + + +void SAL_CALL OPreparedStatement::setObjectWithInfo( sal_Int32 parameterIndex, const Any& x, sal_Int32 sqlType, sal_Int32 scale ) +{ + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + ::osl::MutexGuard aGuard( m_aMutex ); + ensurePrepared(); + + checkParameterIndex(parameterIndex); + setParameterNull(parameterIndex, false); + + XSQLVAR* pVar = m_pInSqlda->sqlvar + (parameterIndex - 1); + int dType = (pVar->sqltype & ~1); // drop null flag + + if(sqlType == DataType::DECIMAL || sqlType == DataType::NUMERIC) + { + double dbValue =0.0; + OUString sValue; + if( x >>= dbValue ) + { + // truncate and round to 'scale' number of decimal places + sValue = OUString::number( std::floor((dbValue * pow10Integer(scale)) + .5) / pow10Integer(scale) ); + } + else + { + x >>= sValue; + } + + // fill in the number with nulls in fractional part. + // We need this because e.g. 0.450 != 0.045 despite + // their scale is equal + OUStringBuffer sBuffer(15); + sBuffer.append(sValue); + if(sValue.indexOf('.') != -1) // there is a dot + { + for(sal_Int32 i=sValue.subView(sValue.indexOf('.')+1).size(); i<scale;i++) + { + sBuffer.append('0'); + } + } + else + { + for (sal_Int32 i=0; i<scale; i++) + { + sBuffer.append('0'); + } + } + + sValue = sBuffer.makeStringAndClear(); + switch(dType) + { + case SQL_SHORT: + setValue< sal_Int16 >(parameterIndex, + static_cast<sal_Int16>( toNumericWithoutDecimalPlace(sValue) ), + dType); + break; + case SQL_LONG: + case SQL_DOUBLE: + setValue< sal_Int32 >(parameterIndex, + static_cast<sal_Int32>( toNumericWithoutDecimalPlace(sValue) ), + dType); + break; + case SQL_INT64: + setValue< sal_Int64 >(parameterIndex, + toNumericWithoutDecimalPlace(sValue), + dType); + break; + default: + SAL_WARN("connectivity.firebird", + "No Firebird sql type found for numeric or decimal types"); + ::dbtools::setObjectWithInfo(this,parameterIndex,x,sqlType,scale); + } + } + else + { + ::dbtools::setObjectWithInfo(this,parameterIndex,x,sqlType,scale); + } + +} + + +void SAL_CALL OPreparedStatement::setObjectNull( sal_Int32 nIndex, sal_Int32, const OUString& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + + +void SAL_CALL OPreparedStatement::setObject( sal_Int32 nIndex, const Any& ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + +void SAL_CALL OPreparedStatement::setBytes(sal_Int32 nParameterIndex, + const Sequence< sal_Int8 >& xBytes) +{ + ::osl::MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nParameterIndex); + + XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1); + int dType = (pVar->sqltype & ~1); // drop flag bit for now + + if( dType == SQL_BLOB ) + { +#if SAL_TYPES_SIZEOFPOINTER == 8 + isc_blob_handle aBlobHandle = 0; +#else + isc_blob_handle aBlobHandle = nullptr; +#endif + ISC_QUAD aBlobId; + + openBlobForWriting(aBlobHandle, aBlobId); + + ISC_STATUS aErr = 0; + const sal_Int32 nBytesLen = xBytes.getLength(); + if (nBytesLen > 0) + { + // Max write size is 0xFFFF == SAL_MAX_UINT16 + sal_uInt32 nDataWritten = 0; + while (sal::static_int_cast<sal_uInt32>(nBytesLen) > nDataWritten) + { + sal_uInt32 nDataRemaining = nBytesLen - nDataWritten; + sal_uInt16 nWriteSize = std::min(nDataRemaining, sal_uInt32(SAL_MAX_UINT16)); + aErr = isc_put_segment(m_statusVector, + &aBlobHandle, + nWriteSize, + reinterpret_cast<const char*>(xBytes.getConstArray()) + nDataWritten); + nDataWritten += nWriteSize; + + if (aErr) + break; + } + } + + // We need to make sure we close the Blob even if there are errors, hence evaluate + // errors after closing. + closeBlobAfterWriting(aBlobHandle); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_put_segment failed", + *this); + assert(false); + } + + setValue< ISC_QUAD >(nParameterIndex, aBlobId, SQL_BLOB); + } + else if( dType == SQL_VARYING ) + { + setParameterNull(nParameterIndex, false); + const sal_Int32 nMaxSize = 0xFFFF; + Sequence<sal_Int8> xBytesCopy(xBytes); + if (xBytesCopy.getLength() > nMaxSize) + { + xBytesCopy.realloc( nMaxSize ); + } + const sal_uInt16 nSize = xBytesCopy.getLength(); + // 8000 corresponds to value from lcl_addDefaultParameters + // in dbaccess/source/filter/hsqldb/createparser.cxx + if (nSize > 8000) + { + free(pVar->sqldata); + pVar->sqldata = static_cast<char *>(malloc(sizeof(char) * nSize + 2)); + } + static_assert(sizeof(nSize) == 2, "must match dest memcpy len"); + // First 2 bytes indicate string size + memcpy(pVar->sqldata, &nSize, 2); + // Actual data + memcpy(pVar->sqldata + 2, xBytesCopy.getConstArray(), nSize); + } + else if( dType == SQL_TEXT ) + { + if (pVar->sqllen < xBytes.getLength()) + dbtools::throwSQLException("Data too big for this field", + dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, *this); + setParameterNull(nParameterIndex, false); + memcpy(pVar->sqldata, xBytes.getConstArray(), xBytes.getLength() ); + // Fill remainder with zeroes + memset(pVar->sqldata + xBytes.getLength(), 0, pVar->sqllen - xBytes.getLength()); + } + else + { + ::dbtools::throwSQLException( + "Incorrect type for setBytes", + ::dbtools::StandardSQLState::INVALID_SQL_DATA_TYPE, + *this); + } +} + + +void SAL_CALL OPreparedStatement::setCharacterStream( sal_Int32 nIndex, const Reference< css::io::XInputStream >&, sal_Int32 ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + + +void SAL_CALL OPreparedStatement::setBinaryStream( sal_Int32 nIndex, const Reference< css::io::XInputStream >&, sal_Int32 ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + checkParameterIndex(nIndex); +} + + +void SAL_CALL OPreparedStatement::clearParameters( ) +{ +} + +// ---- Batch methods -- unsupported ----------------------------------------- +void SAL_CALL OPreparedStatement::clearBatch() +{ + // Unsupported +} + +void SAL_CALL OPreparedStatement::addBatch() +{ + // Unsupported by firebird +} + +Sequence< sal_Int32 > SAL_CALL OPreparedStatement::executeBatch() +{ + // Unsupported by firebird + return Sequence< sal_Int32 >(); +} + +void OPreparedStatement::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any& rValue) +{ + switch(nHandle) + { + case PROPERTY_ID_RESULTSETCONCURRENCY: + break; + case PROPERTY_ID_RESULTSETTYPE: + break; + case PROPERTY_ID_FETCHDIRECTION: + break; + case PROPERTY_ID_USEBOOKMARKS: + break; + default: + OStatementCommonBase::setFastPropertyValue_NoBroadcast(nHandle,rValue); + } +} + +void OPreparedStatement::checkParameterIndex(sal_Int32 nParameterIndex) +{ + ensurePrepared(); + if ((nParameterIndex == 0) || (nParameterIndex > m_pInSqlda->sqld)) + { + ::dbtools::throwSQLException( + "No column " + OUString::number(nParameterIndex), + ::dbtools::StandardSQLState::COLUMN_NOT_FOUND, + *this); + } +} + +void OPreparedStatement::setParameterNull(sal_Int32 nParameterIndex, + bool bSetNull) +{ + XSQLVAR* pVar = m_pInSqlda->sqlvar + (nParameterIndex - 1); + if (bSetNull) + { + pVar->sqltype |= 1; + *pVar->sqlind = -1; + } + else + *pVar->sqlind = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/PreparedStatement.hxx b/connectivity/source/drivers/firebird/PreparedStatement.hxx new file mode 100644 index 000000000..3e61436b5 --- /dev/null +++ b/connectivity/source/drivers/firebird/PreparedStatement.hxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include "StatementCommonBase.hxx" + +#include <cppuhelper/implbase5.hxx> + +#include <com/sun/star/sdbc/XPreparedStatement.hpp> +#include <com/sun/star/sdbc/XParameters.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> +#include <com/sun/star/sdbc/XPreparedBatchExecution.hpp> +#include <com/sun/star/io/XInputStream.hpp> + +#include <ibase.h> + +namespace connectivity::firebird + { + + class OBoundParam; + typedef ::cppu::ImplHelper5< css::sdbc::XPreparedStatement, + css::sdbc::XParameters, + css::sdbc::XPreparedBatchExecution, + css::sdbc::XResultSetMetaDataSupplier, + css::lang::XServiceInfo> OPreparedStatement_Base; + + class OPreparedStatement : public OStatementCommonBase, + public OPreparedStatement_Base + { + protected: + OUString m_sSqlStatement; + css::uno::Reference< css::sdbc::XResultSetMetaData > m_xMetaData; + + XSQLDA* m_pOutSqlda; + XSQLDA* m_pInSqlda; + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + void checkParameterIndex(sal_Int32 nParameterIndex); + + /** + * Set a numeric value in the input SQLDA. If the destination + * parameter is not of nType then an Exception will be thrown. + * + * @throws css::sdbc::SQLException + * @throws css::uno::RuntimeException + */ + template <typename T> void setValue(sal_Int32 nIndex, const T& nValue, ISC_SHORT nType); + void setParameterNull(sal_Int32 nParameterIndex, bool bSetNull = true); + + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + void ensurePrepared(); + /** + * Assumes that all necessary mutexes have been taken. + */ + void openBlobForWriting(isc_blob_handle& rBlobHandle, ISC_QUAD& rBlobId); + /** + * Assumes that all necessary mutexes have been taken. + */ + void closeBlobAfterWriting(isc_blob_handle& rBlobHandle); + void setClob(sal_Int32 nParamIndex, const OUString& rStr); + + protected: + virtual void SAL_CALL setFastPropertyValue_NoBroadcast(sal_Int32 nHandle, + const css::uno::Any& rValue) override; + virtual ~OPreparedStatement() override; + public: + DECLARE_SERVICE_INFO(); + // a constructor, which is required for returning objects: + OPreparedStatement( Connection* _pConnection, + const OUString& sql); + + //XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + //XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XPreparedStatement + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL + executeQuery() override; + virtual sal_Int32 SAL_CALL + executeUpdate() override; + virtual sal_Bool SAL_CALL + execute() override; + virtual css::uno::Reference< css::sdbc::XConnection > SAL_CALL + getConnection() override; + + // XParameters + virtual void SAL_CALL setNull(sal_Int32 nIndex, sal_Int32 nValue) override; + virtual void SAL_CALL setObjectNull(sal_Int32 parameterIndex, sal_Int32 sqlType, const OUString& typeName ) override; + virtual void SAL_CALL setBoolean( sal_Int32 nIndex, sal_Bool nValue) override; + virtual void SAL_CALL setByte(sal_Int32 nIndex, sal_Int8 nValue) override; + virtual void SAL_CALL setShort(sal_Int32 nIndex, sal_Int16 nValue) override; + virtual void SAL_CALL setInt(sal_Int32 nIndex, sal_Int32 nValue) override; + virtual void SAL_CALL setLong(sal_Int32 nIndex, sal_Int64 nValue) override; + virtual void SAL_CALL setFloat( sal_Int32 parameterIndex, float x ) override; + virtual void SAL_CALL setDouble( sal_Int32 parameterIndex, double x ) override; + virtual void SAL_CALL setString( sal_Int32 parameterIndex, const OUString& x ) override; + virtual void SAL_CALL setBytes( sal_Int32 parameterIndex, const css::uno::Sequence< sal_Int8 >& x ) override; + virtual void SAL_CALL setDate( sal_Int32 parameterIndex, const css::util::Date& x ) override; + virtual void SAL_CALL setTime( sal_Int32 parameterIndex, const css::util::Time& x ) override; + virtual void SAL_CALL setTimestamp( sal_Int32 parameterIndex, const css::util::DateTime& x ) override; + virtual void SAL_CALL setBinaryStream( sal_Int32 parameterIndex, const css::uno::Reference< css::io::XInputStream >& x, sal_Int32 length ) override; + virtual void SAL_CALL setCharacterStream( sal_Int32 parameterIndex, const css::uno::Reference< css::io::XInputStream >& x, sal_Int32 length ) override; + virtual void SAL_CALL setObject( sal_Int32 parameterIndex, const css::uno::Any& x ) override; + virtual void SAL_CALL setObjectWithInfo( sal_Int32 parameterIndex, const css::uno::Any& x, sal_Int32 targetSqlType, sal_Int32 scale ) override; + virtual void SAL_CALL setRef( sal_Int32 parameterIndex, const css::uno::Reference< css::sdbc::XRef >& x ) override; + virtual void SAL_CALL setBlob( sal_Int32 parameterIndex, const css::uno::Reference< css::sdbc::XBlob >& x ) override; + virtual void SAL_CALL setClob( sal_Int32 parameterIndex, const css::uno::Reference< css::sdbc::XClob >& x ) override; + virtual void SAL_CALL setArray( sal_Int32 parameterIndex, const css::uno::Reference< css::sdbc::XArray >& x ) override; + virtual void SAL_CALL clearParameters( ) override; + + // XPreparedBatchExecution -- UNSUPPORTED by firebird + virtual void SAL_CALL + addBatch() override; + virtual void SAL_CALL + clearBatch() override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL + executeBatch() override; + + // XCloseable + virtual void SAL_CALL close() override; + // OComponentHelper + virtual void SAL_CALL disposing() override; + + // XResultSetMetaDataSupplier + virtual css::uno::Reference< css::sdbc::XResultSetMetaData > SAL_CALL getMetaData( ) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/ResultSet.cxx b/connectivity/source/drivers/firebird/ResultSet.cxx new file mode 100644 index 000000000..ea3ac86ae --- /dev/null +++ b/connectivity/source/drivers/firebird/ResultSet.cxx @@ -0,0 +1,926 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "ResultSet.hxx" +#include "ResultSetMetaData.hxx" +#include "Util.hxx" + +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <connectivity/dbexception.hxx> +#include <propertyids.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <TConnection.hxx> + +#include <com/sun/star/beans/PropertyAttribute.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/FetchDirection.hpp> +#include <com/sun/star/sdbc/ResultSetConcurrency.hpp> +#include <com/sun/star/sdbc/ResultSetType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> + +using namespace ::comphelper; +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::cppu; +using namespace ::dbtools; +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::util; + +OResultSet::OResultSet(Connection* pConnection, + ::osl::Mutex& rMutex, + const uno::Reference< XInterface >& xStatement, + isc_stmt_handle aStatementHandle, + XSQLDA* pSqlda ) + : OResultSet_BASE(rMutex) + , OPropertyContainer(OResultSet_BASE::rBHelper) + , m_bIsBookmarkable(false) + , m_nFetchSize(1) + , m_nResultSetType(css::sdbc::ResultSetType::FORWARD_ONLY) + , m_nFetchDirection(css::sdbc::FetchDirection::FORWARD) + , m_nResultSetConcurrency(css::sdbc::ResultSetConcurrency::READ_ONLY) + , m_pConnection(pConnection) + , m_rMutex(rMutex) + , m_xStatement(xStatement) + , m_pSqlda(pSqlda) + , m_statementHandle(aStatementHandle) + , m_bWasNull(false) + , m_currentRow(0) + , m_bIsAfterLastRow(false) + , m_fieldCount(pSqlda? pSqlda->sqld : 0) +{ + SAL_INFO("connectivity.firebird", "OResultSet()."); + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISBOOKMARKABLE), + PROPERTY_ID_ISBOOKMARKABLE, + PropertyAttribute::READONLY, + &m_bIsBookmarkable, + cppu::UnoType<decltype(m_bIsBookmarkable)>::get()); + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHSIZE), + PROPERTY_ID_FETCHSIZE, + PropertyAttribute::READONLY, + &m_nFetchSize, + cppu::UnoType<decltype(m_nFetchSize)>::get()); + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETTYPE), + PROPERTY_ID_RESULTSETTYPE, + PropertyAttribute::READONLY, + &m_nResultSetType, + cppu::UnoType<decltype(m_nResultSetType)>::get()); + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHDIRECTION), + PROPERTY_ID_FETCHDIRECTION, + PropertyAttribute::READONLY, + &m_nFetchDirection, + cppu::UnoType<decltype(m_nFetchDirection)>::get()); + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETCONCURRENCY), + PROPERTY_ID_RESULTSETCONCURRENCY, + PropertyAttribute::READONLY, + &m_nResultSetConcurrency, + cppu::UnoType<decltype(m_nResultSetConcurrency)>::get()); + + if (!pSqlda) + return; // TODO: what? + +} + +OResultSet::~OResultSet() +{ +} + +// ---- XResultSet -- Row retrieval methods ------------------------------------ +sal_Int32 SAL_CALL OResultSet::getRow() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_currentRow; +} + +sal_Bool SAL_CALL OResultSet::next() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + m_currentRow++; + + ISC_STATUS fetchStat = isc_dsql_fetch(m_statusVector, + &m_statementHandle, + 1, + m_pSqlda); + if (fetchStat == 0) // SUCCESSFUL + { + return true; + } + else if (fetchStat == 100) // END OF DATASET + { + m_bIsAfterLastRow = true; + return false; + } + else + { + SAL_WARN("connectivity.firebird", "Error when fetching data"); + // Throws sql exception as appropriate + evaluateStatusVector(m_statusVector, u"isc_dsql_fetch", *this); + return false; + } +} + +sal_Bool SAL_CALL OResultSet::previous() +{ + ::dbtools::throwFunctionNotSupportedSQLException("previous not supported in firebird", + *this); + return false; +} + +sal_Bool SAL_CALL OResultSet::isLast() +{ + ::dbtools::throwFunctionNotSupportedSQLException("isLast not supported in firebird", + *this); + return false; +} + +sal_Bool SAL_CALL OResultSet::isBeforeFirst() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_currentRow == 0; +} + +sal_Bool SAL_CALL OResultSet::isAfterLast() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_bIsAfterLastRow; +} + +sal_Bool SAL_CALL OResultSet::isFirst() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_currentRow == 1 && !m_bIsAfterLastRow; +} + +void SAL_CALL OResultSet::beforeFirst() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if (m_currentRow != 0) + ::dbtools::throwFunctionNotSupportedSQLException("beforeFirst not supported in firebird", + *this); +} + +void SAL_CALL OResultSet::afterLast() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if (!m_bIsAfterLastRow) + ::dbtools::throwFunctionNotSupportedSQLException("afterLast not supported in firebird", + *this); +} + +sal_Bool SAL_CALL OResultSet::first() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if (m_currentRow == 0) + { + return next(); + } + else if (m_currentRow == 1 && !m_bIsAfterLastRow) + { + return true; + } + else + { + ::dbtools::throwFunctionNotSupportedSQLException("first not supported in firebird", + *this); + return false; + } +} + +sal_Bool SAL_CALL OResultSet::last() +{ + // We need to iterate past the last row to know when we've passed the last + // row, hence we can't actually move to last. + ::dbtools::throwFunctionNotSupportedSQLException("last not supported in firebird", + *this); + return false; +} + +sal_Bool SAL_CALL OResultSet::absolute(sal_Int32 aRow) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if (aRow > m_currentRow) + { + sal_Int32 aIterations = aRow - m_currentRow; + return relative(aIterations); + } + else + { + ::dbtools::throwFunctionNotSupportedSQLException("absolute not supported in firebird", + *this); + return false; + } +} + +sal_Bool SAL_CALL OResultSet::relative(sal_Int32 row) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if (row > 0) + { + while (row--) + { + if (!next()) + return false; + } + return true; + } + else + { + ::dbtools::throwFunctionNotSupportedSQLException("relative not supported in firebird", + *this); + return false; + } +} + +void OResultSet::checkColumnIndex(sal_Int32 nIndex) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if( nIndex < 1 || nIndex > m_fieldCount ) + { + ::dbtools::throwSQLException( + "No column " + OUString::number(nIndex), + ::dbtools::StandardSQLState::COLUMN_NOT_FOUND, + *this); + } +} + +void OResultSet::checkRowIndex() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if((m_currentRow < 1) || m_bIsAfterLastRow) + { + ::dbtools::throwSQLException( + "Invalid Row", + ::dbtools::StandardSQLState::INVALID_CURSOR_POSITION, + *this); + } +} + +Any SAL_CALL OResultSet::queryInterface( const Type & rType ) +{ + Any aRet = OPropertySetHelper::queryInterface(rType); + return aRet.hasValue() ? aRet : OResultSet_BASE::queryInterface(rType); +} + + Sequence< Type > SAL_CALL OResultSet::getTypes() +{ + return concatSequences(OPropertySetHelper::getTypes(), OResultSet_BASE::getTypes()); +} +// ---- XColumnLocate --------------------------------------------------------- +sal_Int32 SAL_CALL OResultSet::findColumn(const OUString& rColumnName) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + uno::Reference< XResultSetMetaData > xMeta = getMetaData(); + sal_Int32 nLen = xMeta->getColumnCount(); + sal_Int32 i; + + for(i = 1; i<=nLen; ++i) + { + // We assume case sensitive, otherwise you'd have to test + // xMeta->isCaseSensitive and use qualsIgnoreAsciiCase as needed. + if (rColumnName == xMeta->getColumnName(i)) + return i; + } + + ::dbtools::throwInvalidColumnException(rColumnName, *this); + assert(false); + return 0; // Never reached +} + +uno::Reference< XInputStream > SAL_CALL OResultSet::getBinaryStream( sal_Int32 ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return nullptr; +} + +uno::Reference< XInputStream > SAL_CALL OResultSet::getCharacterStream( sal_Int32 ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return nullptr; +} + +// ---- Internal Utilities --------------------------------------------------- +bool OResultSet::isNull(const sal_Int32 nColumnIndex) +{ + assert(nColumnIndex <= m_fieldCount); + XSQLVAR* pVar = m_pSqlda->sqlvar; + + if (pVar[nColumnIndex-1].sqltype & 1) // Indicates column may contain null + { + if (*pVar[nColumnIndex-1].sqlind == -1) + return true; + } + return false; +} + +template <typename T> +OUString OResultSet::makeNumericString(const sal_Int32 nColumnIndex) +{ + // minus because firebird stores scale as a negative number + int nDecimalCount = -(m_pSqlda->sqlvar[nColumnIndex-1].sqlscale); + if(nDecimalCount < 0) + { + // scale should be always positive + assert(false); + return OUString(); + } + + OUStringBuffer sRetBuffer; + T nAllDigits = *reinterpret_cast<T*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + sal_Int64 nDecimalCountExp = pow10Integer(nDecimalCount); + + if(nAllDigits < 0) + { + sRetBuffer.append('-'); + nAllDigits = -nAllDigits; // abs + } + + sRetBuffer.append(static_cast<sal_Int64>(nAllDigits / nDecimalCountExp) ); + if( nDecimalCount > 0) + { + sRetBuffer.append('.'); + + sal_Int64 nFractionalPart = nAllDigits % nDecimalCountExp; + + int iCount = 0; // digit count + sal_Int64 nFracTemp = nFractionalPart; + while(nFracTemp>0) + { + nFracTemp /= 10; + iCount++; + } + + int nMissingNulls = nDecimalCount - iCount; + + // append nulls after dot and before nFractionalPart + for(int i=0; i<nMissingNulls; i++) + { + sRetBuffer.append('0'); + } + + // the rest + sRetBuffer.append(nFractionalPart); + } + + return sRetBuffer.makeStringAndClear(); +} + +template <typename T> +T OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType) +{ + m_bWasNull = isNull(nColumnIndex); + if (m_bWasNull) + return T(); + + if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == nType) + return *reinterpret_cast<T*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + else + { + ORowSetValue row = retrieveValue< ORowSetValue >(nColumnIndex, 0); + if constexpr ( std::is_same_v<sal_Int64, T> ) + return row.getLong(); + else if constexpr ( std::is_same_v<sal_Int32, T> ) + return row.getInt32(); + else if constexpr ( std::is_same_v<sal_Int16, T> ) + return row.getInt16(); + else if constexpr ( std::is_same_v<float, T> ) + return row.getFloat(); + else if constexpr ( std::is_same_v<double, T> ) + return row.getDouble(); + else if constexpr ( std::is_same_v<bool, T> ) + return row.getBool(); + else + return row; + } +} + +template <> +ORowSetValue OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/) +{ + // See https://wiki.documentfoundation.org/Documentation/DevGuide/Database_Access#Using_the_getXXX_Methods + // (bottom of page) for a chart of possible conversions, we should allow all + // of these -- Blob/Clob will probably need some specialist handling especially + // w.r.t. to generating Strings for them. + // + // Basically we just have to map to the correct direct request and + // ORowSetValue does the rest for us here. + int nSqlSubType = m_pSqlda->sqlvar[nColumnIndex-1].sqlsubtype; + + // TODO Firebird 3.0 does not set subtype (i.e. set to 0) for computed numeric/decimal value. + // It may change in the future. + // Imply numeric data type when subtype is 0 and scale is negative + if( nSqlSubType == 0 && m_pSqlda->sqlvar[nColumnIndex-1].sqlscale < 0 ) + nSqlSubType = 1; + + switch (m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) + { + case SQL_TEXT: + case SQL_VARYING: + return getString(nColumnIndex); + case SQL_SHORT: + if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal + return getString(nColumnIndex); + return getShort(nColumnIndex); + case SQL_LONG: + if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal + return getString(nColumnIndex); + return getInt(nColumnIndex); + case SQL_FLOAT: + return getFloat(nColumnIndex); + case SQL_DOUBLE: + if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal + return getString(nColumnIndex); + return getDouble(nColumnIndex); + case SQL_D_FLOAT: + return getFloat(nColumnIndex); + case SQL_TIMESTAMP: + return getTimestamp(nColumnIndex); + case SQL_TYPE_TIME: + return getTime(nColumnIndex); + case SQL_TYPE_DATE: + return getDate(nColumnIndex); + case SQL_INT64: + if(nSqlSubType == 1 || nSqlSubType == 2) //numeric or decimal + return getString(nColumnIndex); + return getLong(nColumnIndex); + case SQL_BOOLEAN: + return ORowSetValue(bool(getBoolean(nColumnIndex))); + case SQL_BLOB: + case SQL_NULL: + case SQL_QUAD: + case SQL_ARRAY: + // TODO: these are all invalid conversions, so maybe we should + // throw an exception? + return ORowSetValue(); + default: + assert(false); + return ORowSetValue(); + } +} + +template <> +Date OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/) +{ + if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TYPE_DATE) + { + ISC_DATE aISCDate = *reinterpret_cast<ISC_DATE*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + + struct tm aCTime; + isc_decode_sql_date(&aISCDate, &aCTime); + + return Date(aCTime.tm_mday, aCTime.tm_mon + 1, aCTime.tm_year + 1900); + } + else + { + return retrieveValue< ORowSetValue >(nColumnIndex, 0).getDate(); + } +} + +template <> +Time OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/) +{ + if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TYPE_TIME) + { + ISC_TIME aISCTime = *reinterpret_cast<ISC_TIME*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + + struct tm aCTime; + isc_decode_sql_time(&aISCTime, &aCTime); + + // First field is nanoseconds. + // last field denotes UTC (true) or unknown (false) + // Here we "know" that ISC_TIME is simply in units of seconds/ISC_TIME_SECONDS_PRECISION + // with no other funkiness, so we can get the fractional seconds easily. + return Time((aISCTime % ISC_TIME_SECONDS_PRECISION) * (1000000000 / ISC_TIME_SECONDS_PRECISION), + aCTime.tm_sec, aCTime.tm_min, aCTime.tm_hour, false); + } + else + { + return retrieveValue< ORowSetValue >(nColumnIndex, 0).getTime(); + } +} + +template <> +DateTime OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/) +{ + if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) == SQL_TIMESTAMP) + { + ISC_TIMESTAMP aISCTimestamp = *reinterpret_cast<ISC_TIMESTAMP*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + + struct tm aCTime; + isc_decode_timestamp(&aISCTimestamp, &aCTime); + + // Ditto here, see comment in previous function about ISC_TIME and ISC_TIME_SECONDS_PRECISION. + return DateTime((aISCTimestamp.timestamp_time % ISC_TIME_SECONDS_PRECISION) * (1000000000 / ISC_TIME_SECONDS_PRECISION), //nanoseconds + aCTime.tm_sec, + aCTime.tm_min, + aCTime.tm_hour, + aCTime.tm_mday, + aCTime.tm_mon + 1, // tm is from 0 to 11 + aCTime.tm_year + 1900, //tm_year is the years since 1900 + false); // denotes UTC (true), or unknown (false) + } + else + { + return retrieveValue< ORowSetValue >(nColumnIndex, 0).getDateTime(); + } +} + +template <> +OUString OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT /*nType*/) +{ + // &~1 to remove the "can contain NULL" indicator + int aSqlType = m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1; + int aSqlSubType = m_pSqlda->sqlvar[nColumnIndex-1].sqlsubtype; + if (aSqlType == SQL_TEXT ) + { + return OUString(m_pSqlda->sqlvar[nColumnIndex-1].sqldata, + m_pSqlda->sqlvar[nColumnIndex-1].sqllen, + RTL_TEXTENCODING_UTF8); + } + else if (aSqlType == SQL_VARYING) + { + // First 2 bytes are a short containing the length of the string + // Under unclear conditions, it may be wrong and greater than sqllen. + sal_uInt16 aLength = *reinterpret_cast<sal_uInt16*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); + // Use greater signed type sal_Int32 to get the minimum of two 16-bit values + return OUString(m_pSqlda->sqlvar[nColumnIndex-1].sqldata + 2, + std::min<sal_Int32>(aLength, m_pSqlda->sqlvar[nColumnIndex-1].sqllen), + RTL_TEXTENCODING_UTF8); + } + else if ((aSqlType == SQL_SHORT || aSqlType == SQL_LONG || + aSqlType == SQL_DOUBLE || aSqlType == SQL_INT64) + && (aSqlSubType == 1 || + aSqlSubType == 2 || + (aSqlSubType == 0 && m_pSqlda->sqlvar[nColumnIndex-1].sqlscale < 0) ) ) + { + // decimal and numeric types + switch(aSqlType) + { + case SQL_SHORT: + return makeNumericString<sal_Int16>(nColumnIndex); + case SQL_LONG: + return makeNumericString<sal_Int32>(nColumnIndex); + case SQL_DOUBLE: + // TODO FIXME 64 bits? + case SQL_INT64: + return makeNumericString<sal_Int64>(nColumnIndex); + default: + assert(false); + return OUString(); // never reached + } + } + else if(aSqlType == SQL_BLOB && aSqlSubType == static_cast<short>(BlobSubtype::Clob) ) + { + uno::Reference<XClob> xClob = getClob(nColumnIndex); + return xClob->getSubString( 1, xClob->length() ); + } + else + { + return retrieveValue< ORowSetValue >(nColumnIndex, 0).getString(); + } +} + +template <> +ISC_QUAD* OResultSet::retrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType) +{ + // TODO: this is probably wrong + if ((m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1) != nType) + throw SQLException(); // TODO: better exception (can't convert Blob) + + return reinterpret_cast<ISC_QUAD*>(m_pSqlda->sqlvar[nColumnIndex-1].sqldata); +} + +template <typename T> +T OResultSet::safelyRetrieveValue(const sal_Int32 nColumnIndex, const ISC_SHORT nType) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + checkColumnIndex(nColumnIndex); + checkRowIndex(); + + m_bWasNull = isNull(nColumnIndex); + if (m_bWasNull) + return T(); + + return retrieveValue< T >(nColumnIndex, nType); +} + +// ---- XRow ----------------------------------------------------------------- +sal_Bool SAL_CALL OResultSet::wasNull() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_bWasNull; +} + +// ---- XRow: Simple Numerical types ------------------------------------------ +sal_Bool SAL_CALL OResultSet::getBoolean(sal_Int32 nColumnIndex) +{ + return safelyRetrieveValue< bool >(nColumnIndex, SQL_BOOLEAN); +} + +sal_Int8 SAL_CALL OResultSet::getByte(sal_Int32 nColumnIndex) +{ + // Not a native firebird type hence we always have to convert. + return safelyRetrieveValue< ORowSetValue >(nColumnIndex).getInt8(); +} + +Sequence< sal_Int8 > SAL_CALL OResultSet::getBytes(sal_Int32 nColumnIndex) +{ + // &~1 to remove the "can contain NULL" indicator + int aSqlType = m_pSqlda->sqlvar[nColumnIndex-1].sqltype & ~1; + if ( aSqlType == SQL_BLOB ) + { + Reference< XBlob> xBlob = getBlob(nColumnIndex); + if (xBlob.is()) + { + const sal_Int64 aBlobLength = xBlob->length(); + if (aBlobLength > SAL_MAX_INT32) + { + SAL_WARN("connectivity.firebird", "getBytes can't return " << aBlobLength << " bytes but only max " << SAL_MAX_INT32); + return xBlob->getBytes(1, SAL_MAX_INT32); + } + return xBlob->getBytes(1, static_cast<sal_Int32>(aBlobLength)); + } + else + return Sequence< sal_Int8 >(); + } + // TODO implement SQL_VARYING and SQL_TEXT + // as it's the counterpart as OPreparedStatement::setBytes + else + { + return Sequence< sal_Int8 >(); // TODO: implement + } +} + +sal_Int16 SAL_CALL OResultSet::getShort(sal_Int32 columnIndex) +{ + return safelyRetrieveValue< sal_Int16 >(columnIndex, SQL_SHORT); +} + +sal_Int32 SAL_CALL OResultSet::getInt(sal_Int32 columnIndex) +{ + return safelyRetrieveValue< sal_Int32 >(columnIndex, SQL_LONG); +} + +sal_Int64 SAL_CALL OResultSet::getLong(sal_Int32 columnIndex) +{ + return safelyRetrieveValue< sal_Int64 >(columnIndex, SQL_INT64); +} + +float SAL_CALL OResultSet::getFloat(sal_Int32 columnIndex) +{ + return safelyRetrieveValue< float >(columnIndex, SQL_FLOAT); +} + +double SAL_CALL OResultSet::getDouble(sal_Int32 columnIndex) +{ + return safelyRetrieveValue< double >(columnIndex, SQL_DOUBLE); +} + +// ---- XRow: More complex types ---------------------------------------------- +OUString SAL_CALL OResultSet::getString(sal_Int32 nIndex) +{ + return safelyRetrieveValue< OUString >(nIndex); +} + +Date SAL_CALL OResultSet::getDate(sal_Int32 nIndex) +{ + return safelyRetrieveValue< Date >(nIndex, SQL_TYPE_DATE); +} + +Time SAL_CALL OResultSet::getTime(sal_Int32 nIndex) +{ + return safelyRetrieveValue< css::util::Time >(nIndex, SQL_TYPE_TIME); +} + +DateTime SAL_CALL OResultSet::getTimestamp(sal_Int32 nIndex) +{ + return safelyRetrieveValue< DateTime >(nIndex, SQL_TIMESTAMP); +} + + +uno::Reference< XResultSetMetaData > SAL_CALL OResultSet::getMetaData( ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + if(!m_xMetaData.is()) + m_xMetaData = new OResultSetMetaData(m_pConnection + , m_pSqlda); + return m_xMetaData; +} + +uno::Reference< XArray > SAL_CALL OResultSet::getArray( sal_Int32 ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return nullptr; +} + + +uno::Reference< XClob > SAL_CALL OResultSet::getClob( sal_Int32 columnIndex ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + int aSqlSubType = m_pSqlda->sqlvar[columnIndex-1].sqlsubtype; + + SAL_WARN_IF(aSqlSubType != 1, + "connectivity.firebird", "wrong subtype, not a textual blob"); + + ISC_QUAD* pBlobID = safelyRetrieveValue< ISC_QUAD* >(columnIndex, SQL_BLOB); + if (!pBlobID) + return nullptr; + return m_pConnection->createClob(pBlobID); +} + +uno::Reference< XBlob > SAL_CALL OResultSet::getBlob(sal_Int32 columnIndex) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + // TODO: CLOB etc. should be valid here too, but we probably want some more + // cleverness around this. + ISC_QUAD* pBlobID = safelyRetrieveValue< ISC_QUAD* >(columnIndex, SQL_BLOB); + if (!pBlobID) + return nullptr; + return m_pConnection->createBlob(pBlobID); +} + + +uno::Reference< XRef > SAL_CALL OResultSet::getRef( sal_Int32 ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return nullptr; +} + + +Any SAL_CALL OResultSet::getObject( sal_Int32, const uno::Reference< css::container::XNameAccess >& ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return Any(); +} + + +void SAL_CALL OResultSet::close() +{ + SAL_INFO("connectivity.firebird", "close()."); + + { + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + } + dispose(); +} + + +uno::Reference< XInterface > SAL_CALL OResultSet::getStatement() +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + + return m_xStatement; +} +//----- XResultSet: unsupported change detection methods --------------------- +sal_Bool SAL_CALL OResultSet::rowDeleted() +{ + ::dbtools::throwFunctionNotSupportedSQLException("rowDeleted not supported in firebird", + *this); + return false; +} +sal_Bool SAL_CALL OResultSet::rowInserted() +{ + ::dbtools::throwFunctionNotSupportedSQLException("rowInserted not supported in firebird", + *this); + return false; +} + +sal_Bool SAL_CALL OResultSet::rowUpdated() +{ + ::dbtools::throwFunctionNotSupportedSQLException("rowUpdated not supported in firebird", + *this); + return false; +} + +void SAL_CALL OResultSet::refreshRow() +{ + ::dbtools::throwFunctionNotSupportedSQLException("refreshRow not supported in firebird", + *this); +} + + +void SAL_CALL OResultSet::cancel( ) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(OResultSet_BASE::rBHelper.bDisposed); + +} + +//----- OIdPropertyArrayUsageHelper ------------------------------------------ +IPropertyArrayHelper* OResultSet::createArrayHelper() const +{ + Sequence< Property > aProperties; + describeProperties(aProperties); + return new ::cppu::OPropertyArrayHelper(aProperties); +} + +IPropertyArrayHelper & OResultSet::getInfoHelper() +{ + return *getArrayHelper(); +} + +void SAL_CALL OResultSet::acquire() noexcept +{ + OResultSet_BASE::acquire(); +} + +void SAL_CALL OResultSet::release() noexcept +{ + OResultSet_BASE::release(); +} + +uno::Reference< css::beans::XPropertySetInfo > SAL_CALL OResultSet::getPropertySetInfo( ) +{ + return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); +} + +// ---- XServiceInfo ----------------------------------------------------------- +OUString SAL_CALL OResultSet::getImplementationName() +{ + return "com.sun.star.sdbcx.firebird.ResultSet"; +} + +Sequence< OUString > SAL_CALL OResultSet::getSupportedServiceNames() +{ + return {"com.sun.star.sdbc.ResultSet","com.sun.star.sdbcx.ResultSet"}; +} + +sal_Bool SAL_CALL OResultSet::supportsService(const OUString& _rServiceName) +{ + return cppu::supportsService(this, _rServiceName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/ResultSet.hxx b/connectivity/source/drivers/firebird/ResultSet.hxx new file mode 100644 index 000000000..fdae21dfb --- /dev/null +++ b/connectivity/source/drivers/firebird/ResultSet.hxx @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include "Connection.hxx" + +#include <ibase.h> + +#include <connectivity/FValue.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/proparrhlp.hxx> +#include <comphelper/propertycontainer.hxx> + +#include <com/sun/star/util/XCancellable.hpp> +#include <com/sun/star/sdbc/XCloseable.hpp> +#include <com/sun/star/sdbc/XColumnLocate.hpp> +#include <com/sun/star/sdbc/XResultSet.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> + +namespace connectivity::firebird + { + /* + ** OResultSet + */ + typedef ::cppu::WeakComponentImplHelper< css::sdbc::XResultSet, + css::sdbc::XRow, + css::sdbc::XResultSetMetaDataSupplier, + css::util::XCancellable, + css::sdbc::XCloseable, + css::sdbc::XColumnLocate, + css::lang::XServiceInfo> OResultSet_BASE; + + /** + * This ResultSet does not deal with the management of the SQLDA + * it is supplied with. The owner must mange its SQLDA appropriately + * and ensure that the ResultSet is destroyed before disposing of the + * SQLDA. + */ + class OResultSet: public OResultSet_BASE, + public ::comphelper::OPropertyContainer, + public ::comphelper::OPropertyArrayUsageHelper<OResultSet> + { + private: + bool m_bIsBookmarkable; + sal_Int32 m_nFetchSize; + sal_Int32 m_nResultSetType; + sal_Int32 m_nFetchDirection; + sal_Int32 m_nResultSetConcurrency; + + protected: + // Connection kept alive by m_xStatement + Connection* m_pConnection; + ::osl::Mutex& m_rMutex; + const css::uno::Reference< css::uno::XInterface >& m_xStatement; + + css::uno::Reference< css::sdbc::XResultSetMetaData> m_xMetaData; + + XSQLDA* m_pSqlda; + isc_stmt_handle m_statementHandle; + + bool m_bWasNull; + // Row numbering starts with 0 for "in front of first row" + sal_Int32 m_currentRow; + bool m_bIsAfterLastRow; + + const sal_Int32 m_fieldCount; + ISC_STATUS_ARRAY m_statusVector; + + bool isNull(const sal_Int32 nColumnIndex); + + template <typename T> OUString makeNumericString( + const sal_Int32 nColumnIndex); + + template <typename T> T retrieveValue(const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + + template <typename T> T safelyRetrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType = 0); + + // OIdPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper() const override; + virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override; + + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + void checkColumnIndex( sal_Int32 index ); + /// @throws css::sdbc::SQLException + /// @throws css::uno::RuntimeException + void checkRowIndex(); + + // you can't delete objects of this type + virtual ~OResultSet() override; + public: + DECLARE_SERVICE_INFO(); + + OResultSet(Connection* pConnection, + ::osl::Mutex& rMutex, + const css::uno::Reference< css::uno::XInterface >& xStatement, + isc_stmt_handle aStatementHandle, + XSQLDA* aSqlda + ); + + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( + const css::uno::Type& rType) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + //XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + // XResultSet + virtual sal_Bool SAL_CALL next( ) override; + virtual sal_Bool SAL_CALL isBeforeFirst( ) override; + virtual sal_Bool SAL_CALL isAfterLast( ) override; + virtual sal_Bool SAL_CALL isFirst( ) override; + virtual sal_Bool SAL_CALL isLast( ) override; + virtual void SAL_CALL beforeFirst( ) override; + virtual void SAL_CALL afterLast( ) override; + virtual sal_Bool SAL_CALL first( ) override; + virtual sal_Bool SAL_CALL last( ) override; + virtual sal_Int32 SAL_CALL getRow( ) override; + virtual sal_Bool SAL_CALL absolute( sal_Int32 row ) override; + virtual sal_Bool SAL_CALL relative( sal_Int32 rows ) override; + virtual sal_Bool SAL_CALL previous( ) override; + virtual void SAL_CALL refreshRow( ) override; + virtual sal_Bool SAL_CALL rowUpdated( ) override; + virtual sal_Bool SAL_CALL rowInserted( ) override; + virtual sal_Bool SAL_CALL rowDeleted( ) override; + virtual css::uno::Reference< css::uno::XInterface > SAL_CALL getStatement( ) override; + // XRow + virtual sal_Bool SAL_CALL wasNull( ) override; + virtual OUString SAL_CALL getString( sal_Int32 columnIndex ) override; + virtual sal_Bool SAL_CALL getBoolean( sal_Int32 columnIndex ) override; + virtual sal_Int8 SAL_CALL getByte( sal_Int32 columnIndex ) override; + virtual sal_Int16 SAL_CALL getShort( sal_Int32 columnIndex ) override; + virtual sal_Int32 SAL_CALL getInt( sal_Int32 columnIndex ) override; + virtual sal_Int64 SAL_CALL getLong( sal_Int32 columnIndex ) override; + virtual float SAL_CALL getFloat( sal_Int32 columnIndex ) override; + virtual double SAL_CALL getDouble( sal_Int32 columnIndex ) override; + virtual css::uno::Sequence< sal_Int8 > SAL_CALL getBytes( sal_Int32 columnIndex ) override; + virtual css::util::Date SAL_CALL getDate( sal_Int32 columnIndex ) override; + virtual css::util::Time SAL_CALL getTime( sal_Int32 columnIndex ) override; + virtual css::util::DateTime SAL_CALL getTimestamp( sal_Int32 columnIndex ) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getBinaryStream( sal_Int32 columnIndex ) override; + virtual css::uno::Reference< css::io::XInputStream > SAL_CALL getCharacterStream( sal_Int32 columnIndex ) override; + virtual css::uno::Any SAL_CALL getObject( sal_Int32 columnIndex, const css::uno::Reference< css::container::XNameAccess >& typeMap ) override; + virtual css::uno::Reference< css::sdbc::XRef > SAL_CALL getRef( sal_Int32 columnIndex ) override; + virtual css::uno::Reference< css::sdbc::XBlob > SAL_CALL getBlob( sal_Int32 columnIndex ) override; + virtual css::uno::Reference< css::sdbc::XClob > SAL_CALL getClob( sal_Int32 columnIndex ) override; + virtual css::uno::Reference< css::sdbc::XArray > SAL_CALL getArray( sal_Int32 columnIndex ) override; + // XResultSetMetaDataSupplier + virtual css::uno::Reference< css::sdbc::XResultSetMetaData > SAL_CALL getMetaData( ) override; + // XCancellable + virtual void SAL_CALL cancel( ) override; + // XCloseable + virtual void SAL_CALL close( ) override; + // XWarningsSupplier + + // XColumnLocate + virtual sal_Int32 SAL_CALL findColumn(const OUString& columnName) override; + + }; + + // Specialisations have to be in the namespace and can't be within the class. + template <> css::util::Date + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> ::connectivity::ORowSetValue + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> css::util::Time + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> css::util::DateTime + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> ISC_QUAD* + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> OUString + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + template <> ISC_QUAD* + OResultSet::retrieveValue( + const sal_Int32 nColumnIndex, + const ISC_SHORT nType); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/ResultSetMetaData.cxx b/connectivity/source/drivers/firebird/ResultSetMetaData.cxx new file mode 100644 index 000000000..5653d29c4 --- /dev/null +++ b/connectivity/source/drivers/firebird/ResultSetMetaData.cxx @@ -0,0 +1,301 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "ResultSetMetaData.hxx" +#include "Util.hxx" + +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/DataType.hpp> + +#include <sal/log.hxx> + +using namespace connectivity::firebird; + +using namespace com::sun::star::lang; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::sdbcx; +using namespace com::sun::star::uno; + +OResultSetMetaData::~OResultSetMetaData() +{ +} + +OUString OResultSetMetaData::getCharacterSet( sal_Int32 nIndex ) +{ + OUString sTable = getTableName( nIndex ); + if( !sTable.isEmpty() ) + { + OUString sColumnName = getColumnName( nIndex ); + + OUString sSql = "SELECT charset.RDB$CHARACTER_SET_NAME " + "FROM RDB$CHARACTER_SETS charset " + "JOIN RDB$FIELDS fields " + "ON (fields.RDB$CHARACTER_SET_ID = charset.RDB$CHARACTER_SET_ID) " + "JOIN RDB$RELATION_FIELDS relfields " + "ON (fields.RDB$FIELD_NAME = relfields.RDB$FIELD_SOURCE) " + "WHERE relfields.RDB$RELATION_NAME = '" + + escapeWith(sTable, '\'', '\'') + "' AND " + "relfields.RDB$FIELD_NAME = '"+ escapeWith(sColumnName, '\'', '\'') +"'"; + + Reference<XStatement> xStmt= m_pConnection->createStatement(); + + Reference<XResultSet> xRes = + xStmt->executeQuery(sSql); + Reference<XRow> xRow ( xRes, UNO_QUERY); + if(xRes->next()) + { + OUString sCharset = xRow->getString(1).trim(); + return sCharset; + } + } + return OUString(); + + +} + +void OResultSetMetaData::verifyValidColumn(sal_Int32 column) +{ + if (column>getColumnCount() || column < 1) + throw SQLException("Invalid column specified", *this, OUString(), 0, Any()); +} + +sal_Int32 SAL_CALL OResultSetMetaData::getColumnCount() +{ + return m_pSqlda->sqld; +} + +sal_Int32 SAL_CALL OResultSetMetaData::getColumnDisplaySize( sal_Int32 column ) +{ + verifyValidColumn(column); + return 32; // Hard limit for firebird +} + +sal_Int32 SAL_CALL OResultSetMetaData::getColumnType(sal_Int32 column) +{ + verifyValidColumn(column); + + short aType = m_pSqlda->sqlvar[column-1].sqltype & ~1; + OUString sCharset; + + // do not query the character set unnecessarily + if(aType == SQL_TEXT || aType == SQL_VARYING) + { + sCharset = getCharacterSet(column); + } + + ColumnTypeInfo aInfo( m_pSqlda->sqlvar[column-1].sqltype, + m_pSqlda->sqlvar[column-1].sqlsubtype, + -(m_pSqlda->sqlvar[column-1].sqlscale), + sCharset ); + + return aInfo.getSdbcType(); +} + +sal_Bool SAL_CALL OResultSetMetaData::isCaseSensitive(sal_Int32) +{ + // Firebird is generally case sensitive when using quoted identifiers. + // IF THIS CHANGES make ResultSet::findColumn to be case-insensitive as needed. + // Generally names that are entirely UPPERCASE are case insensitive, however + // there remains some ambiguity if there is another mixed-case-named column + // of the same name. For safety always assume case insensitive. + return true; +} + +OUString SAL_CALL OResultSetMetaData::getSchemaName(sal_Int32) +{ + return OUString(); // Schemas supported by firebird +} + +OUString SAL_CALL OResultSetMetaData::getColumnName(sal_Int32 column) +{ + verifyValidColumn(column); + char* pColumnName = m_pSqlda->sqlvar[column - 1].sqlname; + sal_Int32 nColumnNameLength = m_pSqlda->sqlvar[column - 1].sqlname_length; + // tdf#132924 - return column alias if specified + if (m_pSqlda->sqlvar[column - 1].aliasname_length > 0) + { + pColumnName = m_pSqlda->sqlvar[column - 1].aliasname; + nColumnNameLength = m_pSqlda->sqlvar[column - 1].aliasname_length; + } + OUString sRet(pColumnName, nColumnNameLength, RTL_TEXTENCODING_UTF8); + sanitizeIdentifier(sRet); + return sRet; +} + +OUString SAL_CALL OResultSetMetaData::getTableName(sal_Int32 column) +{ + verifyValidColumn(column); + return OUString(m_pSqlda->sqlvar[column-1].relname, + m_pSqlda->sqlvar[column-1].relname_length, + RTL_TEXTENCODING_UTF8); +} + +OUString SAL_CALL OResultSetMetaData::getCatalogName(sal_Int32) +{ + return OUString(); // Catalogs not supported by firebird +} + +OUString SAL_CALL OResultSetMetaData::getColumnTypeName(sal_Int32 column) +{ + verifyValidColumn(column); + + ColumnTypeInfo aInfo( m_pSqlda->sqlvar[column-1].sqltype, + m_pSqlda->sqlvar[column-1].sqlsubtype, + -(m_pSqlda->sqlvar[column-1].sqlscale) ); + + return aInfo.getColumnTypeName(); +} + +OUString SAL_CALL OResultSetMetaData::getColumnLabel(sal_Int32 column) +{ + // aliasname + verifyValidColumn(column); + OUString sRet(m_pSqlda->sqlvar[column-1].aliasname, + m_pSqlda->sqlvar[column-1].aliasname_length, + RTL_TEXTENCODING_UTF8); + sanitizeIdentifier(sRet); + return sRet; +} + +OUString SAL_CALL OResultSetMetaData::getColumnServiceName(sal_Int32) +{ + // TODO: implement + return OUString(); +} + +sal_Bool SAL_CALL OResultSetMetaData::isCurrency(sal_Int32) +{ + return false; +} + +sal_Bool SAL_CALL OResultSetMetaData::isAutoIncrement(sal_Int32 column) +{ + OUString sTable = getTableName(column); + if( sTable.isEmpty() ) + return false; + + OUString sColumnName = getColumnName( column ); + + OUString sSql = "SELECT RDB$IDENTITY_TYPE FROM RDB$RELATION_FIELDS " + "WHERE RDB$RELATION_NAME = '" + + escapeWith(sTable, '\'', '\'') + "' AND " + "RDB$FIELD_NAME = '"+ escapeWith(sColumnName, '\'', '\'') +"'"; + + Reference<XStatement> xStmt =m_pConnection ->createStatement(); + + Reference<XResultSet> xRes = + xStmt->executeQuery(sSql); + Reference<XRow> xRow ( xRes, UNO_QUERY); + if(xRes->next()) + { + int iType = xRow->getShort(1); + if(iType == 1) // IDENTITY + return true; + } + else + { + SAL_WARN("connectivity.firebird","Column '" + << sColumnName + << "' not found in database"); + + return false; + } + return false; +} + + +sal_Bool SAL_CALL OResultSetMetaData::isSigned(sal_Int32) +{ + // Unsigned values aren't supported in firebird. + return true; +} + +sal_Int32 SAL_CALL OResultSetMetaData::getPrecision(sal_Int32 column) +{ + sal_Int32 nType = getColumnType(column); + if( nType != DataType::NUMERIC && nType != DataType::DECIMAL ) + return 0; + + OUString sColumnName = getColumnName( column ); + + // RDB$FIELD_SOURCE is a unique name of column per database + OUString sSql = "SELECT RDB$FIELD_PRECISION FROM RDB$FIELDS " + " INNER JOIN RDB$RELATION_FIELDS " + " ON RDB$RELATION_FIELDS.RDB$FIELD_SOURCE = RDB$FIELDS.RDB$FIELD_NAME " + "WHERE RDB$RELATION_FIELDS.RDB$RELATION_NAME = '" + + escapeWith(getTableName(column), '\'', '\'') + "' AND " + "RDB$RELATION_FIELDS.RDB$FIELD_NAME = '" + + escapeWith(sColumnName, '\'', '\'') +"'"; + Reference<XStatement> xStmt= m_pConnection->createStatement(); + + Reference<XResultSet> xRes = + xStmt->executeQuery(sSql); + Reference<XRow> xRow ( xRes, UNO_QUERY); + if(xRes->next()) + { + return static_cast<sal_Int32>(xRow->getShort(1)); + } + else + { + SAL_WARN("connectivity.firebird","Column '" + << sColumnName + << "' not found in database"); + return 0; + } + return 0; +} + +sal_Int32 SAL_CALL OResultSetMetaData::getScale(sal_Int32 column) +{ + return -(m_pSqlda->sqlvar[column-1].sqlscale); // fb stores negative number +} + +sal_Int32 SAL_CALL OResultSetMetaData::isNullable(sal_Int32 column) +{ + if (m_pSqlda->sqlvar[column-1].sqltype & 1) + return ColumnValue::NULLABLE; + else + return ColumnValue::NO_NULLS; +} + +sal_Bool SAL_CALL OResultSetMetaData::isSearchable(sal_Int32) +{ + // TODO: Can the column be used as part of a where clause? Assume yes + return true; +} + +sal_Bool SAL_CALL OResultSetMetaData::isReadOnly(sal_Int32) +{ + return m_pConnection->isReadOnly(); // Readonly only available on db level +} + +sal_Bool SAL_CALL OResultSetMetaData::isDefinitelyWritable(sal_Int32) +{ + return !m_pConnection->isReadOnly(); +} + +sal_Bool SAL_CALL OResultSetMetaData::isWritable( sal_Int32 ) +{ + return !m_pConnection->isReadOnly(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/ResultSetMetaData.hxx b/connectivity/source/drivers/firebird/ResultSetMetaData.hxx new file mode 100644 index 000000000..a32c20621 --- /dev/null +++ b/connectivity/source/drivers/firebird/ResultSetMetaData.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include "Connection.hxx" + +#include <ibase.h> + +#include <cppuhelper/implbase.hxx> +#include <rtl/ref.hxx> + +#include <com/sun/star/sdbc/XResultSetMetaData.hpp> + +namespace connectivity::firebird + { + typedef ::cppu::WeakImplHelper< css::sdbc::XResultSetMetaData> + OResultSetMetaData_BASE; + + class OResultSetMetaData : public OResultSetMetaData_BASE + { + protected: + ::rtl::Reference<Connection> m_pConnection; + XSQLDA* m_pSqlda; + + virtual ~OResultSetMetaData() override; + + /// @throws css::sdbc::SQLException + void verifyValidColumn(sal_Int32 column); + OUString getCharacterSet(sal_Int32 nIndex); + public: + // a constructor, which is required for returning objects: + OResultSetMetaData(Connection* pConnection, + XSQLDA* pSqlda) + : m_pConnection(pConnection) + , m_pSqlda(pSqlda) + {} + + virtual sal_Int32 SAL_CALL getColumnCount() override; + virtual sal_Bool SAL_CALL isAutoIncrement(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isCaseSensitive(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isSearchable(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isCurrency(sal_Int32 column) override; + virtual sal_Int32 SAL_CALL isNullable(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isSigned(sal_Int32 column) override; + virtual sal_Int32 SAL_CALL getColumnDisplaySize(sal_Int32 column) override; + virtual OUString SAL_CALL getColumnLabel(sal_Int32 column) override; + virtual OUString SAL_CALL getColumnName(sal_Int32 column) override; + virtual OUString SAL_CALL getSchemaName(sal_Int32 column) override; + virtual sal_Int32 SAL_CALL getPrecision(sal_Int32 column) override; + virtual sal_Int32 SAL_CALL getScale(sal_Int32 column) override; + virtual OUString SAL_CALL getTableName(sal_Int32 column) override; + virtual OUString SAL_CALL getCatalogName(sal_Int32 column) override; + virtual sal_Int32 SAL_CALL getColumnType(sal_Int32 column) override; + virtual OUString SAL_CALL getColumnTypeName(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isReadOnly(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isWritable(sal_Int32 column) override; + virtual sal_Bool SAL_CALL isDefinitelyWritable(sal_Int32 column) override; + virtual OUString SAL_CALL getColumnServiceName(sal_Int32 column) override; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Statement.cxx b/connectivity/source/drivers/firebird/Statement.cxx new file mode 100644 index 000000000..f4faebbf1 --- /dev/null +++ b/connectivity/source/drivers/firebird/Statement.cxx @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "Connection.hxx" +#include "ResultSet.hxx" +#include "Statement.hxx" +#include "Util.hxx" + +#include <comphelper/sequence.hxx> +#include <cppuhelper/queryinterface.hxx> +#include <sal/log.hxx> + +using namespace connectivity::firebird; + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::beans; +using namespace com::sun::star::sdbc; +using namespace com::sun::star::sdbcx; +using namespace com::sun::star::container; +using namespace com::sun::star::io; +using namespace com::sun::star::util; + +using namespace ::comphelper; +using namespace ::osl; +using namespace ::std; + +// ---- XBatchExecution - UNSUPPORTED ---------------------------------------- +void SAL_CALL OStatement::addBatch(const OUString&) +{ +} + +void SAL_CALL OStatement::clearBatch() +{ +} + +Sequence< sal_Int32 > SAL_CALL OStatement::executeBatch() +{ + return Sequence< sal_Int32 >(); +} + +IMPLEMENT_SERVICE_INFO(OStatement,"com.sun.star.sdbcx.OStatement","com.sun.star.sdbc.Statement"); + +void SAL_CALL OStatement::acquire() noexcept +{ + OStatementCommonBase::acquire(); +} + +void SAL_CALL OStatement::release() noexcept +{ + OStatementCommonBase::release(); +} + +void OStatement::disposeResultSet() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + OStatementCommonBase::disposeResultSet(); + + if (m_pSqlda) + { + freeSQLVAR(m_pSqlda); + free(m_pSqlda); + m_pSqlda = nullptr; + } +} + +// ---- XStatement ----------------------------------------------------------- +sal_Int32 SAL_CALL OStatement::executeUpdate(const OUString& sql) +{ + execute(sql); + return getStatementChangeCount(); +} + + +uno::Reference< XResultSet > SAL_CALL OStatement::executeQuery(const OUString& sql) +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + SAL_INFO("connectivity.firebird", "executeQuery(" << sql << ")"); + + ISC_STATUS aErr = 0; + + disposeResultSet(); + + prepareAndDescribeStatement(sql, + m_pSqlda); + + aErr = isc_dsql_execute(m_statusVector, + &m_pConnection->getTransaction(), + &m_aStatementHandle, + 1, + nullptr); + if (aErr) + SAL_WARN("connectivity.firebird", "isc_dsql_execute failed"); + + m_xResultSet = new OResultSet(m_pConnection.get(), + m_aMutex, + uno::Reference< XInterface >(*this), + m_aStatementHandle, + m_pSqlda ); + + // TODO: deal with cleanup + + evaluateStatusVector(m_statusVector, sql, *this); + + if (isDDLStatement()) + { + m_pConnection->commit(); + m_pConnection->notifyDatabaseModified(); + } + else if (getStatementChangeCount() > 0) + { + m_pConnection->notifyDatabaseModified(); + } + + return m_xResultSet; +} + +sal_Bool SAL_CALL OStatement::execute(const OUString& sql) +{ + uno::Reference< XResultSet > xResults = executeQuery(sql); + return xResults.is(); + // TODO: what if we have multiple results? +} + +uno::Reference< XConnection > SAL_CALL OStatement::getConnection() +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + return m_pConnection; +} + +Any SAL_CALL OStatement::queryInterface( const Type & rType ) +{ + Any aRet = OStatement_Base::queryInterface(rType); + if(!aRet.hasValue()) + aRet = ::cppu::queryInterface(rType,static_cast< XBatchExecution*> (this)); + if(!aRet.hasValue()) + aRet = OStatementCommonBase::queryInterface(rType); + return aRet; +} + +uno::Sequence< Type > SAL_CALL OStatement::getTypes() +{ + return concatSequences(OStatement_Base::getTypes(), + OStatementCommonBase::getTypes()); +} + +void SAL_CALL OStatement::disposing() +{ + disposeResultSet(); + close(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Statement.hxx b/connectivity/source/drivers/firebird/Statement.hxx new file mode 100644 index 000000000..d1b967def --- /dev/null +++ b/connectivity/source/drivers/firebird/Statement.hxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include "StatementCommonBase.hxx" + +#include <cppuhelper/implbase1.hxx> +#include <com/sun/star/sdbc/XBatchExecution.hpp> + +namespace connectivity::firebird + { + + typedef ::cppu::ImplHelper1< css::sdbc::XStatement > + OStatement_Base; + + class OStatement : public OStatementCommonBase, + public OStatement_Base, + public css::sdbc::XBatchExecution, + public css::lang::XServiceInfo + { + XSQLDA* m_pSqlda; + protected: + virtual ~OStatement() override {} + + public: + // a constructor, which is required for returning objects: + explicit OStatement( Connection* _pConnection) + : OStatementCommonBase( _pConnection), + m_pSqlda(nullptr) + {} + + virtual void disposeResultSet() override; + + DECLARE_SERVICE_INFO(); + + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + // XStatement + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL + executeQuery(const OUString& sql) override; + virtual sal_Int32 SAL_CALL executeUpdate(const OUString& sqlIn) override; + virtual sal_Bool SAL_CALL + execute(const OUString& sql) override; + virtual css::uno::Reference< css::sdbc::XConnection > SAL_CALL + getConnection() override; + + // XBatchExecution - UNSUPPORTED + virtual void SAL_CALL addBatch( const OUString& sql ) override; + virtual void SAL_CALL clearBatch( ) override; + virtual css::uno::Sequence< sal_Int32 > SAL_CALL executeBatch( ) override; + + // XInterface + virtual css::uno::Any SAL_CALL + queryInterface(const css::uno::Type & rType) override; + + //XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL + getTypes() override; + // OComponentHelper + virtual void SAL_CALL disposing() override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/StatementCommonBase.cxx b/connectivity/source/drivers/firebird/StatementCommonBase.cxx new file mode 100644 index 000000000..df4d971e8 --- /dev/null +++ b/connectivity/source/drivers/firebird/StatementCommonBase.cxx @@ -0,0 +1,486 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 "StatementCommonBase.hxx" +#include "Util.hxx" + +#include <sal/log.hxx> +#include <comphelper/sequence.hxx> +#include <cppuhelper/typeprovider.hxx> +#include <propertyids.hxx> +#include <vcl/svapp.hxx> +#include <TConnection.hxx> + +#include <com/sun/star/sdbc/SQLException.hpp> + +using namespace ::connectivity::firebird; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::util; + +using namespace ::comphelper; +using namespace ::osl; +using namespace ::std; + +OStatementCommonBase::OStatementCommonBase(Connection* _pConnection) + : OStatementCommonBase_Base(m_aMutex), + OPropertySetHelper(OStatementCommonBase_Base::rBHelper), + m_pConnection(_pConnection), +#if SAL_TYPES_SIZEOFPOINTER == 8 + m_aStatementHandle(0) +#else + m_aStatementHandle(nullptr) +#endif +{ +} + +OStatementCommonBase::~OStatementCommonBase() +{ +} + +void OStatementCommonBase::disposeResultSet() +{ + uno::Reference< XComponent > xComp(m_xResultSet, UNO_QUERY); + if (xComp.is()) + xComp->dispose(); + m_xResultSet.clear(); +} + +void OStatementCommonBase::freeStatementHandle() +{ + if (m_aStatementHandle) + { + isc_dsql_free_statement(m_statusVector, + &m_aStatementHandle, + DSQL_drop); + evaluateStatusVector(m_statusVector, + u"isc_dsql_free_statement", + *this); + } +} + + +Any SAL_CALL OStatementCommonBase::queryInterface( const Type & rType ) +{ + Any aRet = OStatementCommonBase_Base::queryInterface(rType); + if(!aRet.hasValue()) + aRet = OPropertySetHelper::queryInterface(rType); + return aRet; +} + +Sequence< Type > SAL_CALL OStatementCommonBase::getTypes( ) +{ + ::cppu::OTypeCollection aTypes( + ::cppu::UnoType<XMultiPropertySet>::get(), + ::cppu::UnoType<XFastPropertySet>::get(), + ::cppu::UnoType<XPropertySet>::get()); + + return concatSequences(aTypes.getTypes(),OStatementCommonBase_Base::getTypes()); +} + + +void SAL_CALL OStatementCommonBase::cancel( ) +{ + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + // cancel the current sql statement +} + +void SAL_CALL OStatementCommonBase::close() +{ + SAL_INFO("connectivity.firebird", "close"); + + { + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + disposeResultSet(); + freeStatementHandle(); + } + + dispose(); +} + +void OStatementCommonBase::prepareAndDescribeStatement(std::u16string_view sql, XSQLDA*& pOutSqlda) +{ + SolarMutexGuard g; // tdf#122129 + + freeStatementHandle(); + + if (!pOutSqlda) + { + pOutSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(10))); + pOutSqlda->version = SQLDA_VERSION1; + pOutSqlda->sqln = 10; + } + + ISC_STATUS aErr = isc_dsql_allocate_statement(m_statusVector, + &m_pConnection->getDBHandle(), + &m_aStatementHandle); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_dsql_allocate_statement", + *this); + } + else + { + aErr = isc_dsql_prepare(m_statusVector, + &m_pConnection->getTransaction(), + &m_aStatementHandle, + 0, + OUStringToOString(sql, RTL_TEXTENCODING_UTF8).getStr(), + SQL_DIALECT_CURRENT, + pOutSqlda); + + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_dsql_prepare", + *this); + } + else + { + // Ensure we have enough space in pOutSqlda + if (pOutSqlda->sqld > pOutSqlda->sqln) + { + int n = pOutSqlda->sqld; + free(pOutSqlda); + pOutSqlda = static_cast<XSQLDA*>(calloc(1, XSQLDA_LENGTH(n))); + pOutSqlda->version = SQLDA_VERSION1; + pOutSqlda->sqln = n; + aErr = isc_dsql_describe(m_statusVector, + &m_aStatementHandle, + 1, + pOutSqlda); + } + + // Process each XSQLVAR parameter structure in the output XSQLDA + if (aErr) + { + evaluateStatusVector(m_statusVector, + u"isc_dsql_describe", + *this); + } + else + { + mallocSQLVAR(pOutSqlda); + } + } + if(aErr) + { + freeStatementHandle(); + } + } + if(aErr) + { + free(pOutSqlda); + pOutSqlda = nullptr; + } +} + +// ---- XMultipleResults - UNSUPPORTED ---------------------------------------- +uno::Reference< XResultSet > SAL_CALL OStatementCommonBase::getResultSet() +{ + // TODO: verify we really can't support this +// return uno::Reference< XResultSet >(); + MutexGuard aGuard(m_aMutex); + checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); + + return m_xResultSet; +} + +sal_Bool SAL_CALL OStatementCommonBase::getMoreResults() +{ + // TODO: verify we really can't support this + return false; +// MutexGuard aGuard( m_aMutex ); +// checkDisposed(OStatementCommonBase_Base::rBHelper.bDisposed); +} + +sal_Int32 SAL_CALL OStatementCommonBase::getUpdateCount() +{ + // TODO: verify we really can't support this + return -1; +} + + +// ---- XWarningsSupplier - UNSUPPORTED ---------------------------------------- +Any SAL_CALL OStatementCommonBase::getWarnings() +{ + return Any(); +} + +void SAL_CALL OStatementCommonBase::clearWarnings() +{ +} + +::cppu::IPropertyArrayHelper* OStatementCommonBase::createArrayHelper( ) const +{ + // this properties are define by the service statement + // they must in alphabetic order + return new ::cppu::OPropertyArrayHelper + { + { + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_CURSORNAME), + PROPERTY_ID_CURSORNAME, + cppu::UnoType<OUString>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ESCAPEPROCESSING), + PROPERTY_ID_ESCAPEPROCESSING, + cppu::UnoType<bool>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHDIRECTION), + PROPERTY_ID_FETCHDIRECTION, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_FETCHSIZE), + PROPERTY_ID_FETCHSIZE, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_MAXFIELDSIZE), + PROPERTY_ID_MAXFIELDSIZE, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_MAXROWS), + PROPERTY_ID_MAXROWS, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_QUERYTIMEOUT), + PROPERTY_ID_QUERYTIMEOUT, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETCONCURRENCY), + PROPERTY_ID_RESULTSETCONCURRENCY, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_RESULTSETTYPE), + PROPERTY_ID_RESULTSETTYPE, + cppu::UnoType<sal_Int32>::get(), + 0 + }, + { + ::connectivity::OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_USEBOOKMARKS), + PROPERTY_ID_USEBOOKMARKS, + cppu::UnoType<bool>::get(), + 0 + } + } + }; +} + + +::cppu::IPropertyArrayHelper & OStatementCommonBase::getInfoHelper() +{ + return *getArrayHelper(); +} + +sal_Bool OStatementCommonBase::convertFastPropertyValue( + Any &, + Any &, + sal_Int32, + const Any& ) +{ + // here we have to try to convert + return false; +} + +void OStatementCommonBase::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any&) +{ + // set the value to whatever is necessary + switch(nHandle) + { + case PROPERTY_ID_QUERYTIMEOUT: + case PROPERTY_ID_MAXFIELDSIZE: + case PROPERTY_ID_MAXROWS: + case PROPERTY_ID_CURSORNAME: + case PROPERTY_ID_RESULTSETCONCURRENCY: + case PROPERTY_ID_RESULTSETTYPE: + case PROPERTY_ID_FETCHDIRECTION: + case PROPERTY_ID_FETCHSIZE: + case PROPERTY_ID_ESCAPEPROCESSING: + case PROPERTY_ID_USEBOOKMARKS: + default: + ; + } +} + +void OStatementCommonBase::getFastPropertyValue(Any&,sal_Int32 nHandle) const +{ + switch(nHandle) + { + case PROPERTY_ID_QUERYTIMEOUT: + case PROPERTY_ID_MAXFIELDSIZE: + case PROPERTY_ID_MAXROWS: + case PROPERTY_ID_CURSORNAME: + case PROPERTY_ID_RESULTSETCONCURRENCY: + case PROPERTY_ID_RESULTSETTYPE: + case PROPERTY_ID_FETCHDIRECTION: + case PROPERTY_ID_FETCHSIZE: + case PROPERTY_ID_ESCAPEPROCESSING: + case PROPERTY_ID_USEBOOKMARKS: + default: + ; + } +} + +void SAL_CALL OStatementCommonBase::acquire() noexcept +{ + OStatementCommonBase_Base::acquire(); +} + +void SAL_CALL OStatementCommonBase::release() noexcept +{ + OStatementCommonBase_Base::release(); +} + +uno::Reference< css::beans::XPropertySetInfo > SAL_CALL OStatementCommonBase::getPropertySetInfo( ) +{ + return ::cppu::OPropertySetHelper::createPropertySetInfo(getInfoHelper()); +} + +short OStatementCommonBase::getSqlInfoItem(char aInfoItem) +{ + ISC_STATUS_ARRAY aStatusVector; + ISC_STATUS aErr; + + char aInfoItems[] = {aInfoItem}; + char aResultsBuffer[8]; + + aErr = isc_dsql_sql_info(aStatusVector, + &m_aStatementHandle, + sizeof(aInfoItems), + aInfoItems, + sizeof(aResultsBuffer), + aResultsBuffer); + + if (!aErr && aResultsBuffer[0] == aInfoItem) + { + const short aBytes = static_cast<short>(isc_vax_integer(aResultsBuffer+1, 2)); + return static_cast<short>(isc_vax_integer(aResultsBuffer+3, aBytes)); + } + + evaluateStatusVector(aStatusVector, + u"isc_dsq_sql_info", + *this); + return 0; +} + +bool OStatementCommonBase::isDDLStatement() +{ + return getSqlInfoItem(isc_info_sql_stmt_type) == isc_info_sql_stmt_ddl; +} + +sal_Int32 OStatementCommonBase::getStatementChangeCount() +{ + const short aStatementType = getSqlInfoItem(isc_info_sql_stmt_type); + + + ISC_STATUS_ARRAY aStatusVector; + ISC_STATUS aErr; + + // This is somewhat undocumented so I'm just guessing and hoping for the best. + char aInfoItems[] = {isc_info_sql_records}; + char aResultsBuffer[1024]; + + aErr = isc_dsql_sql_info(aStatusVector, + &m_aStatementHandle, + sizeof(aInfoItems), + aInfoItems, + sizeof(aResultsBuffer), + aResultsBuffer); + + if (aErr) + { + evaluateStatusVector(aStatusVector, + u"isc_dsq_sql_info", + *this); + return 0; + } + + short aDesiredInfoType = 0; + switch (aStatementType) + { + case isc_info_sql_stmt_select: + aDesiredInfoType = isc_info_req_select_count; + break; + case isc_info_sql_stmt_insert: + aDesiredInfoType = isc_info_req_insert_count; + break; + case isc_info_sql_stmt_update: + aDesiredInfoType = isc_info_req_update_count; + break; + case isc_info_sql_stmt_delete: + aDesiredInfoType = isc_info_req_delete_count; + break; + case isc_info_sql_stmt_exec_procedure: + return 0; // cannot determine + default: + throw SQLException(); // TODO: better error message? + } + + char* pResults = aResultsBuffer; + if (static_cast<short>(*pResults++) != isc_info_sql_records) + return 0; + +// const short aTotalLength = (short) isc_vax_integer(pResults, 2); + pResults += 2; + + // Seems to be of form TOKEN (1 byte), LENGTH (2 bytes), DATA (LENGTH bytes) + while (*pResults != isc_info_rsb_end) + { + const char aToken = *pResults; + const short aLength = static_cast<short>(isc_vax_integer(pResults+1, 2)); + + if (aToken == aDesiredInfoType) + { + return isc_vax_integer(pResults + 3, aLength); + } + + pResults += (3 + aLength); + } + + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/StatementCommonBase.hxx b/connectivity/source/drivers/firebird/StatementCommonBase.hxx new file mode 100644 index 000000000..fa9cd7902 --- /dev/null +++ b/connectivity/source/drivers/firebird/StatementCommonBase.hxx @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "Connection.hxx" +#include "SubComponent.hxx" + +#include <ibase.h> + +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> + +#include <com/sun/star/sdbc/XCloseable.hpp> +#include <com/sun/star/sdbc/XMultipleResults.hpp> +#include <com/sun/star/sdbc/XWarningsSupplier.hpp> +#include <com/sun/star/util/XCancellable.hpp> + +namespace connectivity::firebird + { + + typedef ::cppu::WeakComponentImplHelper< css::sdbc::XWarningsSupplier, + css::util::XCancellable, + css::sdbc::XCloseable, + css::sdbc::XMultipleResults> OStatementCommonBase_Base; + + class OStatementCommonBase : public OStatementCommonBase_Base, + public ::cppu::OPropertySetHelper, + public OPropertyArrayUsageHelper<OStatementCommonBase> + + { + protected: + ::osl::Mutex m_aMutex; + + css::uno::Reference< css::sdbc::XResultSet> m_xResultSet; // The last ResultSet created + // for this Statement + + ::rtl::Reference<Connection> m_pConnection; + + ISC_STATUS_ARRAY m_statusVector; + isc_stmt_handle m_aStatementHandle; + + protected: + virtual void disposeResultSet(); + /// @throws css::sdbc::SQLException + void freeStatementHandle(); + + // OPropertyArrayUsageHelper + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override; + // OPropertySetHelper + using OPropertySetHelper::getFastPropertyValue; + virtual ::cppu::IPropertyArrayHelper & SAL_CALL getInfoHelper() override; + virtual sal_Bool SAL_CALL convertFastPropertyValue( + css::uno::Any & rConvertedValue, + css::uno::Any & rOldValue, + sal_Int32 nHandle, + const css::uno::Any& rValue ) override; + virtual void SAL_CALL setFastPropertyValue_NoBroadcast( + sal_Int32 nHandle, + const css::uno::Any& rValue) override; + virtual void SAL_CALL getFastPropertyValue( + css::uno::Any& rValue, + sal_Int32 nHandle) const override; + virtual ~OStatementCommonBase() override; + + /// @throws css::sdbc::SQLException + void prepareAndDescribeStatement(std::u16string_view sqlIn, XSQLDA*& pOutSqlda); + + /// @throws css::sdbc::SQLException + short getSqlInfoItem(char aInfoItem); + /// @throws css::sdbc::SQLException + bool isDDLStatement(); + /// @throws css::sdbc::SQLException + sal_Int32 getStatementChangeCount(); + + public: + + explicit OStatementCommonBase(Connection* _pConnection); + using OStatementCommonBase_Base::operator css::uno::Reference< css::uno::XInterface >; + + // OComponentHelper + virtual void SAL_CALL disposing() override { + disposeResultSet(); + OStatementCommonBase_Base::disposing(); + } + // XInterface + virtual void SAL_CALL release() noexcept override; + virtual void SAL_CALL acquire() noexcept override; + // XInterface + virtual css::uno::Any SAL_CALL queryInterface( const css::uno::Type & rType ) override; + //XTypeProvider + virtual css::uno::Sequence< css::uno::Type > SAL_CALL getTypes( ) override; + + // XPropertySet + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo( ) override; + + // XWarningsSupplier - UNSUPPORTED + virtual css::uno::Any SAL_CALL getWarnings( ) override; + virtual void SAL_CALL clearWarnings( ) override; + // XMultipleResults - UNSUPPORTED + virtual css::uno::Reference< css::sdbc::XResultSet > SAL_CALL getResultSet( ) override; + virtual sal_Int32 SAL_CALL getUpdateCount( ) override; + virtual sal_Bool SAL_CALL getMoreResults( ) override; + + // XCancellable + virtual void SAL_CALL cancel( ) override; + // XCloseable + virtual void SAL_CALL close( ) override; + + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/SubComponent.hxx b/connectivity/source/drivers/firebird/SubComponent.hxx new file mode 100644 index 000000000..bea5d76d4 --- /dev/null +++ b/connectivity/source/drivers/firebird/SubComponent.hxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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 . + */ + +#pragma once + +#include <cppuhelper/propshlp.hxx> +#include <osl/diagnose.h> +#include <osl/mutex.hxx> + +namespace cppu { class IPropertyArrayHelper; } +namespace com::sun::star::lang { class XComponent; } + +namespace connectivity::firebird + { + /// @throws css::lang::DisposedException + void checkDisposed(bool _bThrow); + + + template <class TYPE> + class OPropertyArrayUsageHelper + { + protected: + static sal_Int32 s_nRefCount; + static ::cppu::IPropertyArrayHelper* s_pProps; + static ::osl::Mutex s_aMutex; + + public: + OPropertyArrayUsageHelper(); + virtual ~OPropertyArrayUsageHelper(); + + /** call this in the getInfoHelper method of your derived class. The method returns the array helper of the + class, which is created if necessary. + */ + ::cppu::IPropertyArrayHelper* getArrayHelper(); + + protected: + /** used to implement the creation of the array helper which is shared amongst all instances of the class. + This method needs to be implemented in derived classes. + <BR> + The method gets called with s_aMutex acquired. + @return a pointer to the newly created array helper. Must not be NULL. + */ + virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const = 0; + }; + + template<class TYPE> + sal_Int32 OPropertyArrayUsageHelper< TYPE >::s_nRefCount = 0; + + template<class TYPE> + ::cppu::IPropertyArrayHelper* OPropertyArrayUsageHelper< TYPE >::s_pProps = nullptr; + + template<class TYPE> + ::osl::Mutex OPropertyArrayUsageHelper< TYPE >::s_aMutex; + + + template <class TYPE> + OPropertyArrayUsageHelper<TYPE>::OPropertyArrayUsageHelper() + { + ::osl::MutexGuard aGuard(s_aMutex); + ++s_nRefCount; + } + + + template <class TYPE> + OPropertyArrayUsageHelper<TYPE>::~OPropertyArrayUsageHelper() + { + ::osl::MutexGuard aGuard(s_aMutex); + OSL_ENSURE(s_nRefCount > 0, "OPropertyArrayUsageHelper::~OPropertyArrayUsageHelper : suspicious call : have a refcount of 0 !"); + if (!--s_nRefCount) + { + delete s_pProps; + s_pProps = nullptr; + } + } + + + template <class TYPE> + ::cppu::IPropertyArrayHelper* OPropertyArrayUsageHelper<TYPE>::getArrayHelper() + { + OSL_ENSURE(s_nRefCount, "OPropertyArrayUsageHelper::getArrayHelper : suspicious call : have a refcount of 0 !"); + if (!s_pProps) + { + ::osl::MutexGuard aGuard(s_aMutex); + if (!s_pProps) + { + s_pProps = createArrayHelper(); + OSL_ENSURE(s_pProps, "OPropertyArrayUsageHelper::getArrayHelper : createArrayHelper returned nonsense !"); + } + } + return s_pProps; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Table.cxx b/connectivity/source/drivers/firebird/Table.cxx new file mode 100644 index 000000000..c32160b99 --- /dev/null +++ b/connectivity/source/drivers/firebird/Table.cxx @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Columns.hxx" +#include "Indexes.hxx" +#include "Keys.hxx" +#include "Table.hxx" + +#include <TConnection.hxx> + +#include <sal/log.hxx> +#include <comphelper/sequence.hxx> +#include <connectivity/dbtools.hxx> + +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/sdbcx/Privilege.hpp> +#include <com/sun/star/beans/PropertyAttribute.hpp> + +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::connectivity::sdbcx; + +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::uno; + +Table::Table(Tables* pTables, + Mutex& rMutex, + const uno::Reference< XConnection >& rConnection): + OTableHelper(pTables, + rConnection, + true), + m_rMutex(rMutex), + m_nPrivileges(0) +{ + construct(); +} + +Table::Table(Tables* pTables, + Mutex& rMutex, + const uno::Reference< XConnection >& rConnection, + const OUString& rName, + const OUString& rType, + const OUString& rDescription): + OTableHelper(pTables, + rConnection, + true, + rName, + rType, + rDescription, + "", + ""), + m_rMutex(rMutex), + m_nPrivileges(0) +{ + construct(); +} + +void Table::construct() +{ + OTableHelper::construct(); + if (isNew()) + return; + + // TODO: get privileges when in non-embedded mode. + m_nPrivileges = Privilege::DROP | + Privilege::REFERENCE | + Privilege::ALTER | + Privilege::CREATE | + Privilege::READ | + Privilege::DELETE | + Privilege::UPDATE | + Privilege::INSERT | + Privilege::SELECT; + registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_PRIVILEGES), + PROPERTY_ID_PRIVILEGES, + PropertyAttribute::READONLY, + &m_nPrivileges, + cppu::UnoType<decltype(m_nPrivileges)>::get()); +} +//----- OTableHelper --------------------------------------------------------- +OCollection* Table::createColumns(const ::std::vector< OUString>& rNames) +{ + return new Columns(*this, + m_rMutex, + rNames); +} + +OCollection* Table::createKeys(const ::std::vector< OUString>& rNames) +{ + return new Keys(this, + m_rMutex, + rNames); +} + +OCollection* Table::createIndexes(const ::std::vector< OUString>& rNames) +{ + return new Indexes(this, + m_rMutex, + rNames); +} + +//----- XAlterTable ----------------------------------------------------------- +void SAL_CALL Table::alterColumnByName(const OUString& rColName, + const uno::Reference< XPropertySet >& rDescriptor) +{ + MutexGuard aGuard(m_rMutex); + checkDisposed(WeakComponentImplHelperBase::rBHelper.bDisposed); + + uno::Reference< XPropertySet > xColumn(m_xColumns->getByName(rColName), UNO_QUERY); + + // sdbcx::Descriptor + const bool bNameChanged = xColumn->getPropertyValue("Name") != rDescriptor->getPropertyValue("Name"); + // sdbcx::ColumnDescriptor + const bool bTypeChanged = xColumn->getPropertyValue("Type") != rDescriptor->getPropertyValue("Type"); + const bool bTypeNameChanged = xColumn->getPropertyValue("TypeName") != rDescriptor->getPropertyValue("TypeName"); + const bool bPrecisionChanged = xColumn->getPropertyValue("Precision") != rDescriptor->getPropertyValue("Precision"); + const bool bScaleChanged = xColumn->getPropertyValue("Scale") != rDescriptor->getPropertyValue("Scale"); + const bool bIsNullableChanged = xColumn->getPropertyValue("IsNullable") != rDescriptor->getPropertyValue("IsNullable"); + const bool bIsAutoIncrementChanged = xColumn->getPropertyValue("IsAutoIncrement") != rDescriptor->getPropertyValue("IsAutoIncrement"); + + // TODO: remainder -- these are all "optional" so have to detect presence and change. + + bool bDefaultChanged = xColumn->getPropertyValue("DefaultValue") + != rDescriptor->getPropertyValue("DefaultValue"); + + if (bTypeChanged || bTypeNameChanged || bPrecisionChanged || bScaleChanged) + { + // If bPrecisionChanged this will only succeed if we have increased the + // precision, otherwise an exception is thrown -- however the base + // gui then offers to delete and recreate the column. + OUString sSql(getAlterTableColumn(rColName) + "TYPE " + + ::dbtools::createStandardTypePart(rDescriptor, getConnection())); + getConnection()->createStatement()->execute(sSql); + // TODO: could cause errors e.g. if incompatible types, deal with them here as appropriate. + // possibly we have to wrap things in Util::evaluateStatusVector. + } + + if (bIsNullableChanged) + { + sal_Int32 nNullable = 0; + rDescriptor->getPropertyValue("IsNullable") >>= nNullable; + + if (nNullable != ColumnValue::NULLABLE_UNKNOWN) + { + + OUString sSql; + // Dirty hack: can't change null directly in sql, we have to fiddle + // the system tables manually. + if (nNullable == ColumnValue::NULLABLE) + { + sSql = "UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = NULL " + "WHERE RDB$FIELD_NAME = '" + rColName + "' " + "AND RDB$RELATION_NAME = '" + getName() + "'"; + } + else if (nNullable == ColumnValue::NO_NULLS) + { + // And if we are making NOT NULL then we have to make sure we have + // no nulls left in the column. + OUString sFillNulls("UPDATE \"" + getName() + "\" SET \"" + + rColName + "\" = 0 " + "WHERE \"" + rColName + "\" IS NULL"); + getConnection()->createStatement()->execute(sFillNulls); + + sSql = "UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = 1 " + "WHERE RDB$FIELD_NAME = '" + rColName + "' " + "AND RDB$RELATION_NAME = '" + getName() + "'"; + } + getConnection()->createStatement()->execute(sSql); + } + else + { + SAL_WARN("connectivity.firebird", "Attempting to set Nullable to NULLABLE_UNKNOWN"); + } + } + + if (bIsAutoIncrementChanged) + { + ::dbtools::throwSQLException( + "Changing autoincrement property of existing column is not supported", + ::dbtools::StandardSQLState::FUNCTION_NOT_SUPPORTED, + *this); + + } + + if (bDefaultChanged) + { + OUString sNewDefault; + rDescriptor->getPropertyValue("DefaultValue") >>= sNewDefault; + + OUString sSql; + if (sNewDefault.isEmpty()) + sSql = getAlterTableColumn(rColName) + "DROP DEFAULT"; + else + sSql = getAlterTableColumn(rColName) + "SET DEFAULT " + sNewDefault; + + getConnection()->createStatement()->execute(sSql); + } + // TODO: quote identifiers as needed. + if (bNameChanged) + { + OUString sNewColName; + rDescriptor->getPropertyValue("Name") >>= sNewColName; + OUString sSql(getAlterTableColumn(rColName) + + " TO \"" + sNewColName + "\""); + + getConnection()->createStatement()->execute(sSql); + } + + + m_xColumns->refresh(); +} + +// ----- XRename -------------------------------------------------------------- +void SAL_CALL Table::rename(const OUString&) +{ + throw RuntimeException("Table renaming not supported by Firebird."); +} + +// ----- XInterface ----------------------------------------------------------- +Any SAL_CALL Table::queryInterface(const Type& rType) +{ + if (rType.getTypeName() == "com.sun.star.sdbcx.XRename") + return Any(); + + return OTableHelper::queryInterface(rType); +} + +OUString Table::getAlterTableColumn(std::u16string_view rColumn) +{ + return ("ALTER TABLE \"" + getName() + "\" ALTER COLUMN \"" + rColumn + "\" "); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Table.hxx b/connectivity/source/drivers/firebird/Table.hxx new file mode 100644 index 000000000..ed638a9c8 --- /dev/null +++ b/connectivity/source/drivers/firebird/Table.hxx @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include "Tables.hxx" + +#include <connectivity/TTableHelper.hxx> + +namespace connectivity::firebird + { + + /** + * Implements sdbcx.Table. We don't support table renaming (XRename) + * hence the appropriate methods are overridden. + */ + class Table: public OTableHelper + { + private: + ::osl::Mutex& m_rMutex; + sal_Int32 m_nPrivileges; + + /** + * Get the ALTER TABLE [TABLE] ALTER [COLUMN] String. + * Includes a trailing space. + */ + OUString getAlterTableColumn(std::u16string_view rColumn); + + protected: + void construct() override; + + public: + Table(Tables* pTables, + ::osl::Mutex& rMutex, + const css::uno::Reference< css::sdbc::XConnection >& _xConnection); + Table(Tables* pTables, + ::osl::Mutex& rMutex, + const css::uno::Reference< css::sdbc::XConnection >& _xConnection, + const OUString& rName, + const OUString& rType, + const OUString& rDescription); + + // OTableHelper + virtual ::connectivity::sdbcx::OCollection* createColumns( + const ::std::vector< OUString>& rNames) override; + virtual ::connectivity::sdbcx::OCollection* createKeys( + const ::std::vector< OUString>& rNames) override; + virtual ::connectivity::sdbcx::OCollection* createIndexes( + const ::std::vector< OUString>& rNames) override; + + // XAlterTable + /** + * See css::sdbcx::ColumnDescriptor for details of + * rDescriptor. + */ + virtual void SAL_CALL alterColumnByName( + const OUString& rColName, + const css::uno::Reference< css::beans::XPropertySet >& rDescriptor) override; + + // XRename -- UNSUPPORTED + virtual void SAL_CALL rename(const OUString& sName) override; + + //XInterface + virtual css::uno::Any + SAL_CALL queryInterface(const css::uno::Type & rType) override; + + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Tables.cxx b/connectivity/source/drivers/firebird/Tables.cxx new file mode 100644 index 000000000..936e1465c --- /dev/null +++ b/connectivity/source/drivers/firebird/Tables.cxx @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Table.hxx" +#include "Tables.hxx" +#include "Views.hxx" +#include "Catalog.hxx" + +#include <TConnection.hxx> + +#include <connectivity/dbtools.hxx> + +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/ColumnValue.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <comphelper/types.hxx> + +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::connectivity::sdbcx; +using namespace ::cppu; +using namespace ::osl; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::sdbcx; +using namespace ::com::sun::star::uno; + + +//----- OCollection ----------------------------------------------------------- +void Tables::impl_refresh() +{ + static_cast<Catalog&>(m_rParent).refreshTables(); +} + +ObjectType Tables::createObject(const OUString& rName) +{ + // Only retrieving a single table, so table type is irrelevant (param 4) + uno::Reference< XResultSet > xTables = m_xMetaData->getTables(Any(), + OUString(), + rName, + uno::Sequence< OUString >()); + + if (!xTables.is()) + throw RuntimeException("Could not acquire table."); + + uno::Reference< XRow > xRow(xTables,UNO_QUERY_THROW); + + if (!xTables->next()) + throw RuntimeException(); + + ObjectType xRet(new Table(this, + m_rMutex, + m_xMetaData->getConnection(), + xRow->getString(3), // Name + xRow->getString(4), // Type + xRow->getString(5))); // Description / Remarks / Comments + + if (xTables->next()) + throw RuntimeException("Found more tables than expected."); + + return xRet; +} + +OUString Tables::createStandardColumnPart(const Reference< XPropertySet >& xColProp,const Reference< XConnection>& _xConnection) +{ + Reference<XDatabaseMetaData> xMetaData = _xConnection->getMetaData(); + + ::dbtools::OPropertyMap& rPropMap = OMetaConnection::getPropMap(); + + bool bIsAutoIncrement = false; + xColProp->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_ISAUTOINCREMENT)) >>= bIsAutoIncrement; + + const OUString sQuoteString = xMetaData->getIdentifierQuoteString(); + OUStringBuffer aSql(::dbtools::quoteName(sQuoteString,::comphelper::getString(xColProp->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_NAME))))); + + // check if the user enter a specific string to create autoincrement values + OUString sAutoIncrementValue; + Reference<XPropertySetInfo> xPropInfo = xColProp->getPropertySetInfo(); + + if ( xPropInfo.is() && xPropInfo->hasPropertyByName(rPropMap.getNameByIndex(PROPERTY_ID_AUTOINCREMENTCREATION)) ) + xColProp->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_AUTOINCREMENTCREATION)) >>= sAutoIncrementValue; + + aSql.append(" "); + + aSql.append(dbtools::createStandardTypePart(xColProp, _xConnection)); + // Add character set for (VAR)BINARY (fix) types: + // (VAR) BINARY is distinguished from other CHAR types by its character set. + // Octets is a special character set for binary data. + if ( xPropInfo.is() && xPropInfo->hasPropertyByName(rPropMap.getNameByIndex( + PROPERTY_ID_TYPE)) ) + { + sal_Int32 aType = 0; + xColProp->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_TYPE)) + >>= aType; + if(aType == DataType::BINARY || aType == DataType::VARBINARY) + { + aSql.append(" "); + aSql.append("CHARACTER SET OCTETS"); + } + } + + if ( bIsAutoIncrement && !sAutoIncrementValue.isEmpty()) + { + aSql.append(" "); + aSql.append(sAutoIncrementValue); + } + // AutoIncrement "IDENTITY" is implicitly "NOT NULL" + else if(::comphelper::getINT32(xColProp->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_ISNULLABLE))) == ColumnValue::NO_NULLS) + aSql.append(" NOT NULL"); + + return aSql.makeStringAndClear(); +} + +uno::Reference< XPropertySet > Tables::createDescriptor() +{ + // There is some internal magic so that the same class can be used as either + // a descriptor or as a normal table. See VTable.cxx for the details. In our + // case we just need to ensure we use the correct constructor. + return new Table(this, m_rMutex, m_xMetaData->getConnection()); +} + +//----- XAppend --------------------------------------------------------------- +ObjectType Tables::appendObject(const OUString& rName, + const uno::Reference< XPropertySet >& rDescriptor) +{ + /* OUString sSql(::dbtools::createSqlCreateTableStatement(rDescriptor, + m_xMetaData->getConnection())); */ + OUStringBuffer aSqlBuffer("CREATE TABLE "); + OUString sCatalog, sSchema, sComposedName, sTable; + const Reference< XConnection>& xConnection = m_xMetaData->getConnection(); + + ::dbtools::OPropertyMap& rPropMap = OMetaConnection::getPropMap(); + + rDescriptor->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_CATALOGNAME)) >>= sCatalog; + rDescriptor->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_SCHEMANAME)) >>= sSchema; + rDescriptor->getPropertyValue(rPropMap.getNameByIndex(PROPERTY_ID_NAME)) >>= sTable; + + sComposedName = ::dbtools::composeTableName(m_xMetaData, sCatalog, sSchema, sTable, true, ::dbtools::EComposeRule::InTableDefinitions ); + if ( sComposedName.isEmpty() ) + ::dbtools::throwFunctionSequenceException(xConnection); + + aSqlBuffer.append(sComposedName); + aSqlBuffer.append(" ("); + + // columns + Reference<XColumnsSupplier> xColumnSup(rDescriptor,UNO_QUERY); + Reference<XIndexAccess> xColumns(xColumnSup->getColumns(),UNO_QUERY); + // check if there are columns + if(!xColumns.is() || !xColumns->getCount()) + ::dbtools::throwFunctionSequenceException(xConnection); + + Reference< XPropertySet > xColProp; + + sal_Int32 nCount = xColumns->getCount(); + for(sal_Int32 i=0;i<nCount;++i) + { + if ( (xColumns->getByIndex(i) >>= xColProp) && xColProp.is() ) + { + aSqlBuffer.append(createStandardColumnPart(xColProp,xConnection)); + aSqlBuffer.append(","); + } + } + OUString sSql = aSqlBuffer.makeStringAndClear(); + + const OUString sKeyStmt = ::dbtools::createStandardKeyStatement(rDescriptor,xConnection); + if ( !sKeyStmt.isEmpty() ) + sSql += sKeyStmt; + else + { + if ( sSql.endsWith(",") ) + sSql = sSql.replaceAt(sSql.getLength()-1, 1, u")"); + else + sSql += ")"; + } + + m_xMetaData->getConnection()->createStatement()->execute(sSql); + + return createObject(rName); +} + +//----- XDrop ----------------------------------------------------------------- +void Tables::dropObject(sal_Int32 nPosition, const OUString& sName) +{ + uno::Reference< XPropertySet > xTable(getObject(nPosition)); + + if (ODescriptor::isNew(xTable)) + return; + + OUString sType; + xTable->getPropertyValue("Type") >>= sType; + + const OUString sQuoteString = m_xMetaData->getIdentifierQuoteString(); + + m_xMetaData->getConnection()->createStatement()->execute( + "DROP " + sType + " " + ::dbtools::quoteName(sQuoteString,sName)); + + if (sType == "VIEW") + { + Views* pViews = static_cast<Views*>(static_cast<Catalog&>(m_rParent).getPrivateViews()); + if ( pViews && pViews->hasByName(sName) ) + pViews->dropByNameImpl(sName); + } +} + +void connectivity::firebird::Tables::appendNew(const OUString& _rsNewTable) +{ + insertElement(_rsNewTable, nullptr); + + // notify our container listeners + css::container::ContainerEvent aEvent(static_cast<XContainer*>(this), + css::uno::Any(_rsNewTable), css::uno::Any(), + css::uno::Any()); + comphelper::OInterfaceIteratorHelper3 aListenerLoop(m_aContainerListeners); + while (aListenerLoop.hasMoreElements()) + aListenerLoop.next()->elementInserted(aEvent); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Tables.hxx b/connectivity/source/drivers/firebird/Tables.hxx new file mode 100644 index 000000000..7d84edb20 --- /dev/null +++ b/connectivity/source/drivers/firebird/Tables.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> + +#include <connectivity/sdbcx/VCollection.hxx> + +namespace connectivity::firebird + { + + /** + * This implements com.sun.star.sdbcx.Container, which seems to be + * also known by the name of Tables and Collection. + */ + class Tables: public ::connectivity::sdbcx::OCollection + { + protected: + css::uno::Reference< css::sdbc::XDatabaseMetaData > + m_xMetaData; + + static OUString createStandardColumnPart(const css::uno::Reference< css::beans::XPropertySet >& xColProp,const css::uno::Reference< com::sun::star::sdbc::XConnection>& _xConnection); + + // OCollection + virtual void impl_refresh() override; + virtual ::connectivity::sdbcx::ObjectType createObject( + const OUString& rName) override; + virtual css::uno::Reference< css::beans::XPropertySet > + createDescriptor() override; + virtual ::connectivity::sdbcx::ObjectType appendObject( + const OUString& rName, + const css::uno::Reference< css::beans::XPropertySet >& rDescriptor) override; + + public: + Tables(const css::uno::Reference< css::sdbc::XDatabaseMetaData >& rMetaData, + ::cppu::OWeakObject& rParent, + ::osl::Mutex& rMutex, + ::std::vector< OUString> const & rNames) : sdbcx::OCollection(rParent, true, rMutex, rNames), m_xMetaData(rMetaData) {} + + // TODO: we should also implement XDataDescriptorFactory, XRefreshable, + // XAppend, etc., but all are optional. + + // XDrop + virtual void dropObject(sal_Int32 nPosition, const OUString& rName) override; + + void appendNew(const OUString& _rsNewTable); + + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/User.cxx b/connectivity/source/drivers/firebird/User.cxx new file mode 100644 index 000000000..9f647713a --- /dev/null +++ b/connectivity/source/drivers/firebird/User.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "User.hxx" + +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::connectivity::sdbcx; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdbc; + +User::User(const css::uno::Reference< css::sdbc::XConnection >& rConnection): + OUser(true) // Case Sensitive + , m_xConnection(rConnection) +{} + +User::User(const css::uno::Reference< css::sdbc::XConnection >& rConnection, const OUString& rName): + OUser(rName, + true) // Case Sensitive + , m_xConnection(rConnection) +{} + +void User::changePassword(const OUString&, const OUString& newPassword) +{ + m_xConnection->createStatement()->execute("ALTER USER " + m_Name + " PASSWORD '" + newPassword + "'"); +} + +sal_Int32 User::getPrivileges(const OUString& , sal_Int32 ) +{ + // TODO: implement. + return 0; +} + +sal_Int32 User::getGrantablePrivileges(const OUString& , sal_Int32 ) +{ + // TODO: implement. + return 0; +} + +//----- IRefreshableGroups ---------------------------------------------------- +void User::refreshGroups() +{ + // TODO: implement. +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/User.hxx b/connectivity/source/drivers/firebird/User.hxx new file mode 100644 index 000000000..e47565f5d --- /dev/null +++ b/connectivity/source/drivers/firebird/User.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <sdbcx/VUser.hxx> +#include <com/sun/star/sdbc/XConnection.hpp> + +namespace connectivity::firebird + { + + /** + * This implements com.sun.star.sdbcx.Container. + */ + class User: public ::connectivity::sdbcx::OUser + { + css::uno::Reference< css::sdbc::XConnection > m_xConnection; + + public: + /** + * Create a "new" descriptor, which isn't yet in the database. + */ + User(const css::uno::Reference< css::sdbc::XConnection >& rConnection); + /** + * For a user that already exists in the db. + */ + User(const css::uno::Reference< css::sdbc::XConnection >& rConnection, const OUString& rName); + + // XAuthorizable + virtual void SAL_CALL changePassword(const OUString&, const OUString& newPassword) override; + virtual sal_Int32 SAL_CALL getPrivileges(const OUString&, sal_Int32) override; + virtual sal_Int32 SAL_CALL getGrantablePrivileges(const OUString&, sal_Int32) override; + + // IRefreshableGroups:: + virtual void refreshGroups() override; + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Users.cxx b/connectivity/source/drivers/firebird/Users.cxx new file mode 100644 index 000000000..50cfef84b --- /dev/null +++ b/connectivity/source/drivers/firebird/Users.cxx @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "User.hxx" +#include "Users.hxx" + +using namespace ::connectivity; +using namespace ::connectivity::firebird; +using namespace ::connectivity::sdbcx; +using namespace ::cppu; +using namespace ::osl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + + +Users::Users(const uno::Reference< XDatabaseMetaData >& rMetaData, + OWeakObject& rParent, + Mutex& rMutex, + ::std::vector< OUString> const & rNames) : + OCollection(rParent, + true, + rMutex, + rNames), + m_xMetaData(rMetaData) +{ +} + +//----- OCollection ----------------------------------------------------------- +void Users::impl_refresh() +{ + // TODO: IMPLEMENT ME +} + +ObjectType Users::createObject(const OUString& rName) +{ + return new User(m_xMetaData->getConnection(), rName); +} + +uno::Reference< XPropertySet > Users::createDescriptor() +{ + // There is some internal magic so that the same class can be used as either + // a descriptor or as a normal user. See VUser.cxx for the details. In our + // case we just need to ensure we use the correct constructor. + return new User(m_xMetaData->getConnection()); +} + +//----- XAppend --------------------------------------------------------------- +ObjectType Users::appendObject(const OUString& rName, + const uno::Reference< XPropertySet >&) +{ + // TODO: set sSql as appropriate + m_xMetaData->getConnection()->createStatement()->execute(OUString()); + + return createObject(rName); +} + +//----- XDrop ----------------------------------------------------------------- +void Users::dropObject(sal_Int32 nPosition, const OUString&) +{ + uno::Reference< XPropertySet > xUser(getObject(nPosition)); + + if (!ODescriptor::isNew(xUser)) + { + // TODO: drop me + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Users.hxx b/connectivity/source/drivers/firebird/Users.hxx new file mode 100644 index 000000000..7e78444d1 --- /dev/null +++ b/connectivity/source/drivers/firebird/Users.hxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <connectivity/sdbcx/VCollection.hxx> +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> + +namespace connectivity::firebird + { + + /** + * This implements com.sun.star.sdbcx.Container. + */ + class Users: public ::connectivity::sdbcx::OCollection + { + css::uno::Reference< css::sdbc::XDatabaseMetaData > + m_xMetaData; + protected: + + // OCollection + virtual void impl_refresh() override; + virtual ::connectivity::sdbcx::ObjectType createObject( + const OUString& rName) override; + virtual css::uno::Reference< css::beans::XPropertySet > + createDescriptor() override; + virtual ::connectivity::sdbcx::ObjectType appendObject( + const OUString& rName, + const css::uno::Reference< css::beans::XPropertySet >& rDescriptor) override; + + public: + Users(const css::uno::Reference< css::sdbc::XDatabaseMetaData >& rMetaData, + ::cppu::OWeakObject& rParent, + ::osl::Mutex& rMutex, + ::std::vector< OUString> const & rNames); + + // TODO: we should also implement XDataDescriptorFactory, XRefreshable, + // XAppend, etc., but all are optional. + + // XDrop + virtual void dropObject(sal_Int32 nPosition, const OUString& rName) override; + + }; + +} // namespace connectivity::firebird + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Util.cxx b/connectivity/source/drivers/firebird/Util.cxx new file mode 100644 index 000000000..c015f2d47 --- /dev/null +++ b/connectivity/source/drivers/firebird/Util.cxx @@ -0,0 +1,440 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include "Util.hxx" +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <o3tl/string_view.hxx> + +using namespace ::connectivity; + +using namespace ::com::sun::star; +using namespace ::com::sun::star::sdbc; +using namespace ::com::sun::star::uno; + +using namespace firebird; + +OUString firebird::sanitizeIdentifier(std::u16string_view rIdentifier) +{ + std::u16string_view sRet = o3tl::trim(rIdentifier); + assert(sRet.size() <= 31); // Firebird identifiers cannot be longer than this. + + return OUString(sRet); +} + +OUString firebird::StatusVectorToString(const ISC_STATUS_ARRAY& rStatusVector, + std::u16string_view rCause) +{ + OUStringBuffer buf; + const ISC_STATUS* pStatus = reinterpret_cast<const ISC_STATUS*>(&rStatusVector); + + buf.append("firebird_sdbc error:"); + try + { + char msg[512]; // Size is based on suggestion in docs. + while(fb_interpret(msg, sizeof(msg), &pStatus)) + { + // TODO: verify encoding + buf.append("\n*"); + buf.append(OUString(msg, strlen(msg), RTL_TEXTENCODING_UTF8)); + } + } + catch (...) + { + SAL_WARN("connectivity.firebird", "ignore fb_interpret exception"); + } + buf.append(OUString::Concat("\ncaused by\n'") + rCause + "'\n"); + + OUString error = buf.makeStringAndClear(); + SAL_WARN("connectivity.firebird", error); + return error; +} + +void firebird::evaluateStatusVector(const ISC_STATUS_ARRAY& rStatusVector, + std::u16string_view rCause, + const uno::Reference< XInterface >& _rxContext) +{ + if (IndicatesError(rStatusVector)) + { + OUString error = StatusVectorToString(rStatusVector, rCause); + throw SQLException(error, _rxContext, OUString(), 1, Any()); + } +} + +static sal_Int32 lcl_getNumberType( short aType, NumberSubType aSubType ) +{ + switch(aSubType) + { + case NumberSubType::Numeric: + return DataType::NUMERIC; + case NumberSubType::Decimal: + return DataType::DECIMAL; + default: + switch(aType) + { + case SQL_SHORT: + return DataType::SMALLINT; + case SQL_LONG: + return DataType::INTEGER; + case SQL_DOUBLE: + return DataType::DOUBLE; + case SQL_INT64: + return DataType::BIGINT; + default: + assert(false); // not a number + return 0; + } + } +} +static sal_Int32 lcl_getCharColumnType( short aType, std::u16string_view sCharset ) +{ + switch(aType) + { + case SQL_TEXT: + if( sCharset == u"OCTETS") + return DataType::BINARY; + else + return DataType::CHAR; + case SQL_VARYING: + if( sCharset == u"OCTETS") + return DataType::VARBINARY; + else + return DataType::VARCHAR; + default: + assert(false); + return 0; + } +} + +sal_Int32 firebird::ColumnTypeInfo::getSdbcType() const +{ + short aType = m_aType & ~1; // Remove last bit -- it is used to denote whether column + // can store Null, not needed for type determination + short aSubType = m_aSubType; + if( m_nScale > 0 ) + { + // numeric / decimal + if(aType == SQL_SHORT || aType == SQL_LONG || aType == SQL_DOUBLE + || aType == SQL_INT64) + { + // if scale is set without subtype then imply numeric + if( static_cast<NumberSubType>(aSubType) == NumberSubType::Other ) + aSubType = static_cast<short>(NumberSubType::Numeric); + } + } + + switch (aType) + { + case SQL_TEXT: + case SQL_VARYING: + return lcl_getCharColumnType(aType, m_sCharsetName); + case SQL_SHORT: + case SQL_LONG: + case SQL_DOUBLE: + case SQL_INT64: + return lcl_getNumberType(aType, static_cast<NumberSubType>(aSubType) ); + case SQL_FLOAT: + return DataType::FLOAT; + case SQL_D_FLOAT: + return DataType::DOUBLE; + case SQL_TIMESTAMP: + return DataType::TIMESTAMP; + case SQL_BLOB: + switch (static_cast<BlobSubtype>(aSubType)) + { + case BlobSubtype::Blob: + return DataType::BLOB; + case BlobSubtype::Clob: + return DataType::CLOB; + case BlobSubtype::Image: + return DataType::LONGVARBINARY; + default: + SAL_WARN("connectivity.firebird", "Unknown subtype for Blob type: " << aSubType); + assert(!"Unknown subtype for Blob type"); // Should never happen + return 0; + } + case SQL_ARRAY: + return DataType::ARRAY; + case SQL_TYPE_TIME: + return DataType::TIME; + case SQL_TYPE_DATE: + return DataType::DATE; + case SQL_NULL: + return DataType::SQLNULL; + case SQL_QUAD: // Is a "Blob ID" according to the docs + return 0; // TODO: verify + case SQL_BOOLEAN: + return DataType::BOOLEAN; + default: + assert(false); // Should never happen + return 0; + } +} + +OUString firebird::ColumnTypeInfo::getColumnTypeName() const +{ + sal_Int32 nDataType = this->getSdbcType(); + switch (nDataType) + { + case DataType::BIT: + return "BIT"; + case DataType::TINYINT: + return "TINYINT"; + case DataType::SMALLINT: + return "SMALLINT"; + case DataType::INTEGER: + return "INTEGER"; + case DataType::BIGINT: + return "BIGINT"; + case DataType::FLOAT: + return "FLOAT"; + case DataType::REAL: + return "REAL"; + case DataType::DOUBLE: + return "DOUBLE"; + case DataType::NUMERIC: + return "NUMERIC"; + case DataType::DECIMAL: + return "DECIMAL"; + case DataType::CHAR: + return "CHAR"; + case DataType::VARCHAR: + return "VARCHAR"; + case DataType::LONGVARCHAR: + return "LONGVARCHAR"; + case DataType::DATE: + return "DATE"; + case DataType::TIME: + return "TIME"; + case DataType::TIMESTAMP: + return "TIMESTAMP"; + case DataType::BINARY: + // in Firebird, that is the same datatype "CHAR" as DataType::CHAR, + // only with CHARACTER SET OCTETS; we use the synonym CHARACTER + // to fool LO into seeing it as different types. + return "CHARACTER"; + case DataType::VARBINARY: + // see above comment about DataType::BINARY. + return "CHARACTER VARYING"; + case DataType::LONGVARBINARY: + return "BLOB SUB_TYPE " + OUString::number(static_cast<short>(BlobSubtype::Image)); + case DataType::ARRAY: + return "ARRAY"; + case DataType::BLOB: + return "BLOB SUB_TYPE BINARY"; + case DataType::CLOB: + return "BLOB SUB_TYPE TEXT"; + case DataType::BOOLEAN: + return "BOOLEAN"; + case DataType::SQLNULL: + return "NULL"; + default: + assert(false); // Should never happen + return OUString(); + } +} + +short firebird::getFBTypeFromBlrType(short blrType) +{ + switch (blrType) + { + case blr_text: + return SQL_TEXT; + case blr_text2: + assert(false); + return 0; // No idea if this should be supported + case blr_varying: + return SQL_VARYING; + case blr_varying2: + assert(false); + return 0; // No idea if this should be supported + case blr_short: + return SQL_SHORT; + case blr_long: + return SQL_LONG; + case blr_float: + return SQL_FLOAT; + case blr_double: + return SQL_DOUBLE; + case blr_d_float: + return SQL_D_FLOAT; + case blr_timestamp: + return SQL_TIMESTAMP; + case blr_blob: + return SQL_BLOB; +// case blr_SQL_ARRAY: +// return OUString("SQL_ARRAY"); + case blr_sql_time: + return SQL_TYPE_TIME; + case blr_sql_date: + return SQL_TYPE_DATE; + case blr_int64: + return SQL_INT64; +// case SQL_NULL: +// return OUString("SQL_NULL"); + case blr_quad: + return SQL_QUAD; + case blr_bool: + return SQL_BOOLEAN; + default: + // If this happens we have hit one of the extra types in ibase.h + // look up blr_* for a list, e.g. blr_domain_name, blr_not_nullable etc. + assert(false); + return 0; + } +} + +void firebird::mallocSQLVAR(XSQLDA* pSqlda) +{ + // TODO: confirm the sizings below. + XSQLVAR* pVar = pSqlda->sqlvar; + for (int i=0; i < pSqlda->sqld; i++, pVar++) + { + int dtype = (pVar->sqltype & ~1); /* drop flag bit for now */ + switch(dtype) { + case SQL_TEXT: + pVar->sqldata = static_cast<char *>(malloc(sizeof(char)*pVar->sqllen)); + break; + case SQL_VARYING: + pVar->sqldata = static_cast<char *>(malloc(sizeof(char)*pVar->sqllen + 2)); + break; + case SQL_SHORT: + pVar->sqldata = static_cast<char*>(malloc(sizeof(sal_Int16))); + break; + case SQL_LONG: + pVar->sqldata = static_cast<char*>(malloc(sizeof(sal_Int32))); + break; + case SQL_FLOAT: + pVar->sqldata = static_cast<char *>(malloc(sizeof(float))); + break; + case SQL_DOUBLE: + pVar->sqldata = static_cast<char *>(malloc(sizeof(double))); + break; + case SQL_D_FLOAT: + pVar->sqldata = static_cast<char *>(malloc(sizeof(double))); + break; + case SQL_TIMESTAMP: + pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_TIMESTAMP))); + break; + // an ARRAY is in fact a BLOB of a specialized type + // See https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html#fblangref25-datatypes-array + case SQL_ARRAY: + case SQL_BLOB: + pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_QUAD))); + break; + case SQL_TYPE_TIME: + pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_TIME))); + break; + case SQL_TYPE_DATE: + pVar->sqldata = static_cast<char*>(malloc(sizeof(ISC_DATE))); + break; + case SQL_INT64: + pVar->sqldata = static_cast<char *>(malloc(sizeof(sal_Int64))); + break; + case SQL_BOOLEAN: + pVar->sqldata = static_cast<char *>(malloc(sizeof(sal_Bool))); + break; + // See https://www.firebirdsql.org/file/documentation/html/en/refdocs/fblangref25/firebird-25-language-reference.html#fblangref25-datatypes-special-sqlnull + case SQL_NULL: + pVar->sqldata = nullptr; + break; + case SQL_QUAD: + assert(false); // TODO: implement + break; + default: + SAL_WARN("connectivity.firebird", "Unknown type: " << dtype); + assert(false); + break; + } + /* allocate variable to hold NULL status */ + pVar->sqlind = static_cast<short *>(malloc(sizeof(short))); + } +} + +void firebird::freeSQLVAR(XSQLDA* pSqlda) +{ + XSQLVAR* pVar = pSqlda->sqlvar; + for (int i=0; i < pSqlda->sqld; i++, pVar++) + { + int dtype = (pVar->sqltype & ~1); /* drop flag bit for now */ + switch(dtype) { + case SQL_TEXT: + case SQL_VARYING: + case SQL_SHORT: + case SQL_LONG: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_D_FLOAT: + case SQL_TIMESTAMP: + // an ARRAY is in fact a BLOB of a specialized type + // See https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-datatypes-bnrytypes.html#fblangref25-datatypes-array + case SQL_ARRAY: + case SQL_BLOB: + case SQL_INT64: + case SQL_TYPE_TIME: + case SQL_TYPE_DATE: + case SQL_BOOLEAN: + if(pVar->sqldata) + { + free(pVar->sqldata); + pVar->sqldata = nullptr; + } + break; + case SQL_NULL: + // See SQL_NULL case in mallocSQLVAR + assert(pVar->sqldata == nullptr); + break; + case SQL_QUAD: + assert(false); // TODO: implement + break; + default: + SAL_WARN("connectivity.firebird", "Unknown type: " << dtype); +// assert(false); + break; + } + + if(pVar->sqlind) + { + free(pVar->sqlind); + pVar->sqlind = nullptr; + } + } +} + + +OUString firebird::escapeWith( const OUString& sText, const char aKey, const char aEscapeChar) +{ + OUString sRet(sText); + sal_Int32 aIndex = 0; + for (;;) + { + aIndex = sRet.indexOf(aKey, aIndex); + if ( aIndex <= 0 || aIndex >= sRet.getLength()) + break; + sRet = sRet.replaceAt(aIndex, 1, rtl::OUStringConcatenation(OUStringChar(aEscapeChar) + OUStringChar(aKey)) ); + aIndex += 2; + } + + return sRet; +} + +sal_Int64 firebird::pow10Integer(int nDecimalCount) +{ + sal_Int64 nRet = 1; + for(int i=0; i< nDecimalCount; i++) + { + nRet *= 10; + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Util.hxx b/connectivity/source/drivers/firebird/Util.hxx new file mode 100644 index 000000000..bf1a172ed --- /dev/null +++ b/connectivity/source/drivers/firebird/Util.hxx @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <ibase.h> + +#include <rtl/ustring.hxx> + +#include <com/sun/star/uno/XInterface.hpp> + +namespace connectivity::firebird + { + // Type Blob has 2 subtypes values + // 0 for BLOB, 1 for CLOB + // see http://www.firebirdfaq.org/faq48/ + // User-defined subtypes are negative. + // Use a number for image which is very unlikely to be defined by a + // user. + enum class BlobSubtype { + Blob = 0, + Clob = 1, + Image = -9546 + }; + + /** + * Numeric and decimal types can be identified by their subtype in + * Firebird API. 1 for NUMERIC, 2 for DECIMAL. + */ + enum class NumberSubType { + Other = 0, + Numeric = 1, + Decimal = 2 + }; + + class ColumnTypeInfo { +private: + short m_aType; + short m_aSubType; + short m_nScale; + OUString m_sCharsetName; +public: + /** + * @param tType SQL type of column defined by Firebird (e.g. + * SQL_DOUBLE) + * @param aSubType SQL sub type as in firebird API. See + * NumberSubType. + * @param scale: Scale of the number. It is ignored in case it's not + * a number. Scale obtained from the Firebird API is negative, so + * that should be negated before passing to this constructor. + * + */ + explicit ColumnTypeInfo( short aType, short aSubType = 0, + short nScale = 0, const OUString& sCharset = OUString() ) + : m_aType(aType) + , m_aSubType(aSubType) + , m_nScale(nScale) + , m_sCharsetName(sCharset) {} + explicit ColumnTypeInfo( short aType, const OUString& sCharset ) + : m_aType(aType) + , m_aSubType(0) + , m_nScale(0) + , m_sCharsetName(sCharset) {} + short getType() const { return m_aType; } + short getSubType() const { return m_aSubType; } + short getScale() const { return m_nScale; } + OUString const & getCharacterSet() const { return m_sCharsetName; } + + sal_Int32 getSdbcType() const; + OUString getColumnTypeName() const; + + }; + + /** + * Make sure an identifier is safe to use within the database. Currently + * firebird seems to return identifiers with 93 character (instead of + * 31), whereby the name is simply padded with trailing whitespace. + * This removes all trailing whitespace (i.e. if necessary so that + * the length is below 31 characters). Firebird automatically compensates + * for such shorter strings, however any trailing padding makes the gui + * editing of such names harder, hence we remove all trailing whitespace. + */ + OUString sanitizeIdentifier(std::u16string_view rIdentifier); + + inline bool IndicatesError(const ISC_STATUS_ARRAY& rStatusVector) + { + return rStatusVector[0]==1 && rStatusVector[1]; // indicates error; + } + + OUString StatusVectorToString(const ISC_STATUS_ARRAY& rStatusVector, + std::u16string_view rCause); + + /** + * Evaluate a firebird status vector and throw exceptions as necessary. + * The content of the status vector is included in the thrown exception. + * + * @throws css::sdbc::SQLException + */ + void evaluateStatusVector(const ISC_STATUS_ARRAY& rStatusVector, + std::u16string_view aCause, + const css::uno::Reference< css::uno::XInterface >& _rxContext); + + /** + * Internally (i.e. in RDB$FIELD_TYPE) firebird stores the data type + * for a column as defined in blr_*, however in the firebird + * api the SQL_* types are used, hence we need to be able to convert + * between the two when retrieving column metadata. + */ + short getFBTypeFromBlrType(short blrType); + + void mallocSQLVAR(XSQLDA* pSqlda); + + void freeSQLVAR(XSQLDA* pSqlda); + + OUString escapeWith( const OUString& sText, const char aKey, const char aEscapeChar); + sal_Int64 pow10Integer( int nDecimalCount ); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/View.cxx b/connectivity/source/drivers/firebird/View.cxx new file mode 100644 index 000000000..6dcbf6bce --- /dev/null +++ b/connectivity/source/drivers/firebird/View.cxx @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#include "View.hxx" + +#include <propertyids.hxx> + +#include <com/sun/star/sdbc/XRow.hpp> + +namespace connectivity::firebird +{ +View::View(const css::uno::Reference<css::sdbc::XConnection>& _rxConnection, bool _bCaseSensitive, + const OUString& _rSchemaName, const OUString& _rName) + : View_Base(_bCaseSensitive, _rName, _rxConnection->getMetaData(), OUString(), _rSchemaName, + OUString()) + , m_xConnection(_rxConnection) +{ +} + +View::~View() {} + +void SAL_CALL View::acquire() noexcept { View_Base::acquire(); }; +void SAL_CALL View::release() noexcept { View_Base::release(); }; +css::uno::Any SAL_CALL View::queryInterface(const css::uno::Type& _rType) +{ + css::uno::Any aReturn = View_Base::queryInterface(_rType); + if (!aReturn.hasValue()) + aReturn = View_IBASE::queryInterface(_rType); + return aReturn; +} + +css::uno::Sequence<css::uno::Type> SAL_CALL View::getTypes() +{ + return ::comphelper::concatSequences(View_Base::getTypes(), View_IBASE::getTypes()); +} + +css::uno::Sequence<sal_Int8> SAL_CALL View::getImplementationId() +{ + return css::uno::Sequence<sal_Int8>(); +} + +void SAL_CALL View::alterCommand(const OUString& _rNewCommand) +{ + OUString aCommand = "ALTER VIEW \"" + m_Name + "\" AS " + _rNewCommand; + m_xMetaData->getConnection()->createStatement()->execute(aCommand); +} + +void SAL_CALL View::getFastPropertyValue(css::uno::Any& _rValue, sal_Int32 _nHandle) const +{ + if (_nHandle == PROPERTY_ID_COMMAND) + { + // retrieve the very current command, don't rely on the base classes cached value + // (which we initialized empty, anyway) + _rValue <<= impl_getCommand(); + return; + } + + View_Base::getFastPropertyValue(_rValue, _nHandle); +} + +OUString View::impl_getCommand() const +{ + OUString aCommand("SELECT RDB$VIEW_SOURCE FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = '" + + m_Name + "'"); + css::uno::Reference<css::sdbc::XStatement> statement = m_xConnection->createStatement(); + css::uno::Reference<css::sdbc::XResultSet> xResult = statement->executeQuery(aCommand); + + css::uno::Reference<css::sdbc::XRow> xRow(xResult, css::uno::UNO_QUERY_THROW); + if (!xResult->next()) + { + // hmm. There is no view the name as we know it. Can only mean some other instance + // dropped this view meanwhile... + std::abort(); + } + + return xRow->getString(1); +} + +} // namespace connectivity::firebird +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/View.hxx b/connectivity/source/drivers/firebird/View.hxx new file mode 100644 index 000000000..2b300a8d0 --- /dev/null +++ b/connectivity/source/drivers/firebird/View.hxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#pragma once + +#include <connectivity/sdbcx/VView.hxx> + +#include <com/sun/star/sdbcx/XAlterView.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> + +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase1.hxx> + +namespace connectivity::firebird +{ +typedef ::connectivity::sdbcx::OView View_Base; +typedef ::cppu::ImplHelper1<css::sdbcx::XAlterView> View_IBASE; + +class View : public View_Base, public View_IBASE +{ +public: + View(const css::uno::Reference<css::sdbc::XConnection>& _rxConnection, bool _bCaseSensitive, + const OUString& _rSchemaName, const OUString& _rName); + + // UNO + virtual css::uno::Any SAL_CALL queryInterface(const css::uno::Type& aType) override; + virtual void SAL_CALL acquire() noexcept override; + virtual void SAL_CALL release() noexcept override; + + virtual css::uno::Sequence<css::uno::Type> SAL_CALL getTypes() override; + virtual css::uno::Sequence<sal_Int8> SAL_CALL getImplementationId() override; + + // XAlterView + virtual void SAL_CALL alterCommand(const OUString& NewCommand) override; + +protected: + virtual ~View() override; + +protected: + // OPropertyContainer + virtual void SAL_CALL getFastPropertyValue(css::uno::Any& _rValue, + sal_Int32 _nHandle) const override; + +private: + /** retrieves the current command of the View */ + OUString impl_getCommand() const; + +private: + css::uno::Reference<css::sdbc::XConnection> m_xConnection; + + using View_Base::getFastPropertyValue; +}; + +} // namespace connectivity::firebird +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Views.cxx b/connectivity/source/drivers/firebird/Views.cxx new file mode 100644 index 000000000..2e5bec42a --- /dev/null +++ b/connectivity/source/drivers/firebird/Views.cxx @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#include "Tables.hxx" +#include "Views.hxx" +#include "View.hxx" +#include "Catalog.hxx" +#include <connectivity/dbtools.hxx> +#include <comphelper/types.hxx> +#include <TConnection.hxx> + +connectivity::firebird::Views::Views( + const css::uno::Reference<css::sdbc::XConnection>& _rxConnection, ::cppu::OWeakObject& _rParent, + ::osl::Mutex& _rMutex, const ::std::vector<OUString>& _rVector) + : sdbcx::OCollection(_rParent, true, _rMutex, _rVector) + , m_xConnection(_rxConnection) + , m_xMetaData(_rxConnection->getMetaData()) + , m_bInDrop(false) +{ +} + +connectivity::sdbcx::ObjectType connectivity::firebird::Views::createObject(const OUString& _rName) +{ + OUString sCatalog, sSchema, sTable; + ::dbtools::qualifiedNameComponents(m_xMetaData, _rName, sCatalog, sSchema, sTable, + ::dbtools::EComposeRule::InDataManipulation); + return new View(m_xConnection, isCaseSensitive(), sSchema, sTable); +} + +void connectivity::firebird::Views::impl_refresh() +{ + static_cast<Catalog&>(m_rParent).refreshViews(); +} + +css::uno::Reference<css::beans::XPropertySet> connectivity::firebird::Views::createDescriptor() +{ + return new connectivity::sdbcx::OView(true, m_xMetaData); +} + +// XAppend +connectivity::sdbcx::ObjectType connectivity::firebird::Views::appendObject( + const OUString& _rForName, const css::uno::Reference<css::beans::XPropertySet>& descriptor) +{ + createView(descriptor); + return createObject(_rForName); +} + +// XDrop +void connectivity::firebird::Views::dropObject(sal_Int32 _nPos, const OUString& /*_sElementName*/) +{ + if (m_bInDrop) + return; + + css::uno::Reference<XInterface> xObject(getObject(_nPos)); + bool bIsNew = connectivity::sdbcx::ODescriptor::isNew(xObject); + if (!bIsNew) + { + OUString aSql("DROP VIEW"); + + css::uno::Reference<css::beans::XPropertySet> xProp(xObject, css::uno::UNO_QUERY); + aSql += ::dbtools::composeTableName(m_xMetaData, xProp, + ::dbtools::EComposeRule::InTableDefinitions, true); + + css::uno::Reference<css::sdbc::XConnection> xConnection = m_xMetaData->getConnection(); + css::uno::Reference<css::sdbc::XStatement> xStmt = xConnection->createStatement(); + xStmt->execute(aSql); + ::comphelper::disposeComponent(xStmt); + } +} + +void connectivity::firebird::Views::dropByNameImpl(const OUString& elementName) +{ + m_bInDrop = true; + connectivity::sdbcx::OCollection::dropByName(elementName); + m_bInDrop = false; +} + +void connectivity::firebird::Views::createView( + const css::uno::Reference<css::beans::XPropertySet>& descriptor) +{ + css::uno::Reference<css::sdbc::XConnection> xConnection = m_xMetaData->getConnection(); + + OUString sCommand; + descriptor->getPropertyValue(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_COMMAND)) + >>= sCommand; + + OUString aSql = "CREATE VIEW " + + ::dbtools::composeTableName(m_xMetaData, descriptor, + ::dbtools::EComposeRule::InTableDefinitions, true) + + " AS " + sCommand; + + css::uno::Reference<css::sdbc::XStatement> xStmt = xConnection->createStatement(); + if (xStmt.is()) + { + xStmt->execute(aSql); + ::comphelper::disposeComponent(xStmt); + } + connectivity::firebird::Tables* pTables = static_cast<connectivity::firebird::Tables*>( + static_cast<connectivity::firebird::Catalog&>(m_rParent).getPrivateTables()); + if (pTables) + { + OUString sName = ::dbtools::composeTableName( + m_xMetaData, descriptor, ::dbtools::EComposeRule::InDataManipulation, false); + pTables->appendNew(sName); + } +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/Views.hxx b/connectivity/source/drivers/firebird/Views.hxx new file mode 100644 index 000000000..6887bdc66 --- /dev/null +++ b/connectivity/source/drivers/firebird/Views.hxx @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ +#pragma once + +#include <connectivity/sdbcx/VCollection.hxx> +#include <com/sun/star/sdbc/XDatabaseMetaData.hpp> +namespace connectivity::firebird +{ +class Views final : public connectivity::sdbcx::OCollection +{ + css::uno::Reference<css::sdbc::XConnection> m_xConnection; + css::uno::Reference<css::sdbc::XDatabaseMetaData> m_xMetaData; + bool m_bInDrop; + + // OCollection + virtual connectivity::sdbcx::ObjectType createObject(const OUString& _rName) override; + virtual void impl_refresh() override; + virtual css::uno::Reference<css::beans::XPropertySet> createDescriptor() override; + virtual sdbcx::ObjectType + appendObject(const OUString& _rForName, + const css::uno::Reference<css::beans::XPropertySet>& descriptor) override; + + void createView(const css::uno::Reference<css::beans::XPropertySet>& descriptor); + +public: + Views(const css::uno::Reference<css::sdbc::XConnection>& _rxConnection, + ::cppu::OWeakObject& _rParent, ::osl::Mutex& _rMutex, + const ::std::vector<OUString>& _rVector); + + // XDrop + virtual void dropObject(sal_Int32 _nPos, const OUString& _sElementName) override; + + void dropByNameImpl(const OUString& elementName); +}; +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/connectivity/source/drivers/firebird/firebird_sdbc.component b/connectivity/source/drivers/firebird/firebird_sdbc.component new file mode 100644 index 000000000..d80755a24 --- /dev/null +++ b/connectivity/source/drivers/firebird/firebird_sdbc.component @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.sdbc.firebird.Driver" + constructor="connectivity_FirebirdDriver_get_implementation"> + <service name="com.sun.star.sdbc.Driver"/> + <service name="com.sun.star.sdbcx.Driver"/> + </implementation> +</component> + |