/* -*- 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 OUString our_sFDBLocation( u"firebird.fdb"_ustr );
/**
 * Older version of LO may store the database in a .fdb file
 */
constexpr OUString our_sFBKLocation( u"firebird.fbk"_ustr );

Connection::Connection()
    : Connection_BASE(m_aMutex)
    , m_bIsEmbedded(false)
    , m_bIsFile(false)
    , m_bIsAutoCommit(true)
    , m_bIsReadOnly(false)
    , m_aTransactionIsolation(TransactionIsolation::READ_COMMITTED)
#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::TempFileNamed(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(strlen("sdbc:firebird:"));
            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"_ostr;
                userPassword = "masterkey"_ostr;
            }
            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");
    }
}

//----- XServiceInfo ---------------------------------------------------------
IMPLEMENT_SERVICE_INFO(Connection, u"com.sun.star.sdbc.drivers.firebird.Connection"_ustr,
                                                    u"com.sun.star.sdbc.Connection"_ustr)

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.emplace_back(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.emplace_back(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.emplace_back(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.emplace_back(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 )
{
    // 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:
        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(u"SYSDBA"_ustr);
    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);


    sal_uInt16 nFDBLength = sFDBPath.getLength();
    sal_uInt16 nFBKLength = sFBKPath.getLength();
    OStringBuffer aRequest( // byte array
        OStringChar(static_cast<char>(nAction))
        + OStringChar(char(isc_spb_dbname)) // .fdb
        + OStringChar(static_cast<char>(nFDBLength & 0xFF)) // least significant byte first
        + OStringChar(static_cast<char>((nFDBLength >> 8) & 0xFF))
        + sFDBPath
        + OStringChar(char(isc_spb_bkp_file)) // .fbk
        + OStringChar(static_cast<char>(nFBKLength & 0xFF))
        + OStringChar(static_cast<char>((nFBKLength >> 8) & 0xFF))
        + 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 :-)
    rtl::Reference< ODatabaseMetaData > xMetaData = m_xMetaData.get();
    if(!xMetaData.is())
    {
        xMetaData = new ODatabaseMetaData(this); // need the connection because it can return it
        m_xMetaData = xMetaData.get();
    }

    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(u"setCatalog"_ustr, *this);
}

OUString SAL_CALL Connection::getCatalog()
{
    ::dbtools::throwFunctionNotSupportedSQLException(u"getCatalog"_ustr, *this);
}

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( u"XConnection::getTypeMap"_ustr, *this );
}

void SAL_CALL Connection::setTypeMap(const Reference< XNameAccess >&)
{
    ::dbtools::throwFeatureNotImplementedSQLException( u"XConnection::setTypeMap"_ustr, *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;

    storeDatabase();
}

// 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.clear();

    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);
        }
    }

    storeDatabase();

    cppu::WeakComponentImplHelperBase::disposing();

    m_pDatabaseFileDir.reset();
}

void Connection::storeDatabase()
{
    MutexGuard aGuard(m_aMutex);
    if (m_bIsEmbedded && m_xEmbeddedStorage.is())
    {
        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));
        using namespace ::comphelper;
        const 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());
    }
}


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.
    rtl::Reference< Catalog > xCatalog = m_xCatalog.get();
    if (!xCatalog.is())
    {
        xCatalog = new Catalog(this);
        m_xCatalog = xCatalog.get();
    }
    return xCatalog;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
