/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "Connection.hxx" #include "DatabaseMetaData.hxx" #include "Driver.hxx" #include "PreparedStatement.hxx" #include "Statement.hxx" #include "Tables.hxx" #include "Util.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "connectivity/dbexception.hxx" #include "resource/common_res.hrc" #include "resource/hsqldb_res.hrc" #include "resource/sharedresources.hxx" #include #include #include #include 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::frame; using namespace ::com::sun::star::io; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::uno; const OUString OConnection::sDBLocation( "firebird.fdb" ); OConnection::OConnection(FirebirdDriver* _pDriver) :OConnection_BASE(m_aMutex), OSubComponent((::cppu::OWeakObject*)_pDriver, this), m_xMetaData(NULL), m_bIsEmbedded(sal_False), m_sConnectionURL(), m_sURL(), m_sUser(), m_pDriver(_pDriver), m_bClosed(sal_False), m_bUseOldDateFormat(sal_False), m_bAutoCommit(sal_False), m_bReadOnly(sal_False), m_aTransactionIsolation(TransactionIsolation::REPEATABLE_READ), m_DBHandler(0), m_transactionHandle(0) { SAL_INFO("connectivity.firebird", "OConnection()."); m_pDriver->acquire(); } OConnection::~OConnection() { SAL_INFO("connectivity.firebird", "~OConnection()."); if(!isClosed()) close(); m_pDriver->release(); m_pDriver = NULL; } void SAL_CALL OConnection::release() throw() { relase_ChildImpl(); } void OConnection::construct(const ::rtl::OUString& url, const Sequence< PropertyValue >& info) throw(SQLException) { SAL_INFO("connectivity.firebird", "construct()."); osl_atomic_increment( &m_refCount ); m_sConnectionURL = url; bool bIsNewDatabase = false; OUString aStorageURL; if (url.equals("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 == "URL" ) { pIter->Value >>= aStorageURL; } } if ( !m_xEmbeddedStorage.is() ) { ::connectivity::SharedResources aResources; const OUString sMessage = aResources.getResourceString(STR_NO_STROAGE); ::dbtools::throwGenericSQLException(sMessage ,*this); } bIsNewDatabase = !m_xEmbeddedStorage->hasElements(); m_sURL = utl::TempFile::CreateTempName() + ".fdb"; SAL_INFO("connectivity.firebird", "Temporary .fdb location: " << OUStringToOString(m_sURL,RTL_TEXTENCODING_UTF8 ).getStr()); if (!bIsNewDatabase) { SAL_INFO("connectivity.firebird", "Extracting .fdb from .odb" ); if (!m_xEmbeddedStorage->isStreamElement(sDBLocation)) { ::connectivity::SharedResources aResources; const OUString sMessage = aResources.getResourceString(STR_ERROR_NEW_VERSION); ::dbtools::throwGenericSQLException(sMessage ,*this); } Reference< XStream > xDBStream(m_xEmbeddedStorage->openStreamElement(sDBLocation, ElementModes::READ)); uno::Reference< ucb::XSimpleFileAccess2 > xFileAccess( ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() ), uno::UNO_QUERY ); if ( !xFileAccess.is() ) { ::connectivity::SharedResources aResources; const OUString sMessage = aResources.getResourceString(STR_ERROR_NEW_VERSION); ::dbtools::throwGenericSQLException(sMessage ,*this); } xFileAccess->writeFile(m_sURL,xDBStream->getInputStream()); } // TOOO: Get DB properties from XML } // External file AND/OR remote connection else if (url.startsWith("sdbc:firebird:")) { m_sURL = url.copy(OUString("sdbc:firebird:").getLength()); if (m_sURL.startsWith("file://")) // TODO: are file urls really like this? { uno::Reference< ucb::XSimpleFileAccess > xFileAccess( ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()), uno::UNO_QUERY); if (!xFileAccess->exists(m_sURL)) bIsNewDatabase = true; } } char dpbBuffer[1 + 3 + 257 + 257 ]; // Expand as needed int dpbLength = 0; { char* dpb; char userName[256] = ""; char userPassword[256] = ""; dpb = dpbBuffer; *dpb++ = isc_dpb_version1; *dpb++ = isc_dpb_sql_dialect; *dpb++ = 1; // 1 byte long *dpb++ = FIREBIRD_SQL_DIALECT; // Do any more dpbBuffer additions here if (m_bIsEmbedded) // TODO: || m_bIsLocalFile { *dpb++ = isc_dpb_trusted_auth; *dpb++ = 1; // Length of data *dpb++ = 1; // TRUE } else { // TODO: parse password from connection string as needed? } if (strlen(userName)) { int nUsernameLength = strlen(userName); *dpb++ = isc_dpb_user_name; *dpb++ = (char) nUsernameLength; strcpy(dpb, userName); dpb+= nUsernameLength; } if (strlen(userPassword)) { int nPasswordLength = strlen(userPassword); *dpb++ = isc_dpb_password; *dpb++ = (char) nPasswordLength; strcpy(dpb, userPassword); dpb+= nPasswordLength; } dpbLength = dpb - dpbBuffer; } ISC_STATUS_ARRAY status; /* status vector */ ISC_STATUS aErr; if (bIsNewDatabase) { aErr = isc_create_database(status, m_sURL.getLength(), OUStringToOString(m_sURL,RTL_TEXTENCODING_UTF8).getStr(), &m_DBHandler, dpbLength, dpbBuffer, 0); if (aErr) { evaluateStatusVector(status, "isc_create_database", *this); } } else { aErr = isc_attach_database(status, m_sURL.getLength(), OUStringToOString(m_sURL, RTL_TEXTENCODING_UTF8).getStr(), &m_DBHandler, dpbLength, dpbBuffer); if (aErr) { evaluateStatusVector(status, "isc_attach_database", *this); } } if (m_bIsEmbedded) // Add DocumentEventListener to save the .fdb as needed { uno::Reference< frame::XDesktop2 > xFramesSupplier = frame::Desktop::create(::comphelper::getProcessComponentContext()); uno::Reference< frame::XFrames > xFrames( xFramesSupplier->getFrames(), uno::UNO_QUERY); uno::Sequence< uno::Reference > xFrameList = xFrames->queryFrames( frame::FrameSearchFlag::ALL ); for (sal_Int32 i = 0; i < xFrameList.getLength(); i++) { uno::Reference< frame::XFrame > xf = xFrameList[i]; uno::Reference< XController > xc; if (xf.is()) xc = xf->getController(); uno::Reference< XModel > xm; if (xc.is()) xm = xc->getModel(); OUString aURL; if (xm.is()) aURL = xm->getURL(); if (aURL == aStorageURL) { uno::Reference xBroadcaster( xm, UNO_QUERY); if (xBroadcaster.is()) xBroadcaster->addDocumentEventListener( this ); //TODO: remove in the disposing? } } } osl_atomic_decrement( &m_refCount ); } //----- XServiceInfo --------------------------------------------------------- IMPLEMENT_SERVICE_INFO(OConnection, "com.sun.star.sdbc.drivers.firebird.OConnection", "com.sun.star.sdbc.Connection") Reference< XBlob> OConnection::createBlob(ISC_QUAD* pBlobId) throw(SQLException) { SAL_INFO("connectivity.firebird", "createBlob()"); MutexGuard aGuard(m_aMutex); checkDisposed(OConnection_BASE::rBHelper.bDisposed); Reference< XBlob > xReturn = new Blob(&m_DBHandler, &m_transactionHandle, *pBlobId); m_aStatements.push_back(WeakReferenceHelper(xReturn)); return xReturn; } //----- XConnection ---------------------------------------------------------- Reference< XStatement > SAL_CALL OConnection::createStatement( ) throw(SQLException, RuntimeException) { SAL_INFO("connectivity.firebird", "createStatement()."); MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); // the pre if(m_aTypeInfo.empty()) buildTypeInfo(); SAL_INFO("connectivity.firebird", "createStatement(). " "Creating statement."); // 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 OConnection::prepareStatement( const OUString& _sSql) throw(SQLException, RuntimeException) { SAL_INFO("connectivity.firebird", "prepareStatement() " "called with sql: " << _sSql); MutexGuard aGuard(m_aMutex); checkDisposed(OConnection_BASE::rBHelper.bDisposed); if(m_aTypeInfo.empty()) buildTypeInfo(); Reference< XPreparedStatement > xReturn = new OPreparedStatement(this, m_aTypeInfo, _sSql); m_aStatements.push_back(WeakReferenceHelper(xReturn)); return xReturn; } Reference< XPreparedStatement > SAL_CALL OConnection::prepareCall( const OUString& _sSql ) throw(SQLException, RuntimeException) { SAL_INFO("connectivity.firebird", "prepareCall(). " "_sSql: " << _sSql); MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); // not implemented yet :-) a task to do return NULL; } OUString SAL_CALL OConnection::nativeSQL( const OUString& _sSql ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); // We do not need to adapt the SQL for Firebird atm. return _sSql; } void SAL_CALL OConnection::setAutoCommit( sal_Bool autoCommit ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); m_bAutoCommit = autoCommit; if (m_transactionHandle) { setupTransaction(); } } sal_Bool SAL_CALL OConnection::getAutoCommit() throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); return m_bAutoCommit; } void OConnection::setupTransaction() throw (SQLException) { MutexGuard aGuard( m_aMutex ); ISC_STATUS status_vector[20]; // TODO: is this sensible? If we have changed parameters then transaction // is lost... if (m_transactionHandle) { clearStatements(); isc_rollback_transaction(status_vector, &m_transactionHandle); } 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_bAutoCommit) *pTPB++ = isc_tpb_autocommit; *pTPB++ = (!m_bReadOnly ? isc_tpb_write : isc_tpb_read); *pTPB++ = aTransactionIsolation; *pTPB++ = isc_tpb_wait; isc_start_transaction(status_vector, &m_transactionHandle, 1, &m_DBHandler, pTPB - aTPB, // bytes used in TPB aTPB); evaluateStatusVector(status_vector, "isc_start_transaction", *this); } isc_tr_handle& OConnection::getTransaction() throw (SQLException) { MutexGuard aGuard( m_aMutex ); if (!m_transactionHandle) { setupTransaction(); } return m_transactionHandle; } void SAL_CALL OConnection::commit() throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); ISC_STATUS status_vector[20]; if (!m_bAutoCommit && m_transactionHandle) { clearStatements(); isc_commit_transaction(status_vector, &m_transactionHandle); evaluateStatusVector(status_vector, "isc_commit_transaction", *this); } } void SAL_CALL OConnection::rollback() throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); ISC_STATUS status_vector[20]; if (!m_bAutoCommit && m_transactionHandle) { isc_rollback_transaction(status_vector, &m_transactionHandle); } } sal_Bool SAL_CALL OConnection::isClosed( ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); // just simple -> we are close when we are disposed taht means someone called dispose(); (XComponent) return OConnection_BASE::rBHelper.bDisposed; } // -------------------------------------------------------------------------------- Reference< XDatabaseMetaData > SAL_CALL OConnection::getMetaData( ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_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 OConnection::setReadOnly(sal_Bool readOnly) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); m_bReadOnly = readOnly; setupTransaction(); } sal_Bool SAL_CALL OConnection::isReadOnly() throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); return m_bReadOnly; } void SAL_CALL OConnection::setCatalog(const OUString& catalog) throw(SQLException, RuntimeException) { ::dbtools::throwFeatureNotImplementedException( "XConnection::setCatalog", *this ); (void) catalog; } OUString SAL_CALL OConnection::getCatalog() throw(SQLException, RuntimeException) { // Unsupported return OUString(); } void SAL_CALL OConnection::setTransactionIsolation( sal_Int32 level ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); m_aTransactionIsolation = level; setupTransaction(); } sal_Int32 SAL_CALL OConnection::getTransactionIsolation( ) throw(SQLException, RuntimeException) { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); return m_aTransactionIsolation; } Reference< XNameAccess > SAL_CALL OConnection::getTypeMap() throw(SQLException, RuntimeException) { ::dbtools::throwFeatureNotImplementedException( "XConnection::getTypeMap", *this ); return 0; } void SAL_CALL OConnection::setTypeMap(const Reference< XNameAccess >& typeMap) throw(SQLException, RuntimeException) { ::dbtools::throwFeatureNotImplementedException( "XConnection::setTypeMap", *this ); (void) typeMap; } //----- XCloseable ----------------------------------------------------------- void SAL_CALL OConnection::close( ) throw(SQLException, RuntimeException) { SAL_INFO("connectivity.firebird", "close()."); // we just dispose us { MutexGuard aGuard( m_aMutex ); checkDisposed(OConnection_BASE::rBHelper.bDisposed); } dispose(); } // -------------------------------------------------------------------------------- // XWarningsSupplier Any SAL_CALL OConnection::getWarnings( ) throw(SQLException, RuntimeException) { // when you collected some warnings -> return it return Any(); } // -------------------------------------------------------------------------------- void SAL_CALL OConnection::clearWarnings( ) throw(SQLException, RuntimeException) { // you should clear your collected warnings here } // -------------------------------------------------------------------------------- // XDocumentEventListener void SAL_CALL OConnection::documentEventOccured( const DocumentEvent& _Event ) throw(RuntimeException) { MutexGuard aGuard(m_aMutex); if (!m_bIsEmbedded) return; if (_Event.EventName == "OnSave" || _Event.EventName == "OnSaveAs") { commit(); // Commit and close transaction if ( m_bIsEmbedded && m_xEmbeddedStorage.is() ) { SAL_INFO("connectivity.firebird", "Writing .fdb into .odb" ); Reference< XStream > xDBStream(m_xEmbeddedStorage->openStreamElement(sDBLocation, ElementModes::WRITE)); using namespace ::comphelper; Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); Reference< XInputStream > xInputStream; if (xContext.is()) xInputStream = OStorageHelper::GetInputStreamFromURL(m_sURL, xContext); if (xInputStream.is()) OStorageHelper::CopyInputToOutput( xInputStream, xDBStream->getOutputStream()); // TODO: ensure db is in safe state } } } // XEventListener void SAL_CALL OConnection::disposing( const EventObject& Source ) throw (RuntimeException) { (void) Source; } //-------------------------------------------------------------------- void OConnection::buildTypeInfo() throw( SQLException) { SAL_INFO("connectivity.firebird", "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); aInfo.aLiteralPrefix = xRow->getString (4); aInfo.aLiteralSuffix = xRow->getString (5); aInfo.aCreateParams = xRow->getString (6); aInfo.bNullable = xRow->getBoolean (7) == ColumnValue::NULLABLE; aInfo.bCaseSensitive = xRow->getBoolean (8); aInfo.nSearchType = xRow->getShort (9); aInfo.bUnsigned = xRow->getBoolean (10); aInfo.bCurrency = xRow->getBoolean (11); aInfo.bAutoIncrement = xRow->getBoolean (12); aInfo.aLocalTypeName = xRow->getString (13); aInfo.nMinimumScale = xRow->getShort (14); aInfo.nMaximumScale = xRow->getShort (15); aInfo.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 OConnection::disposing() { SAL_INFO("connectivity.firebird", "disposing()."); MutexGuard aGuard(m_aMutex); clearStatements(); m_bClosed = sal_True; m_xMetaData = ::com::sun::star::uno::WeakReference< ::com::sun::star::sdbc::XDatabaseMetaData>(); ISC_STATUS_ARRAY status; /* status vector */ if (m_transactionHandle) { // TODO: confirm whether we need to ask the user here. isc_rollback_transaction(status, &m_transactionHandle); } if (isc_detach_database(status, &m_DBHandler)) { evaluateStatusVector(status, "isc_detach_database", *this); } // TODO: write to storage again? if (m_bIsEmbedded) { uno::Reference< ucb::XSimpleFileAccess > xFileAccess( ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext()), uno::UNO_QUERY); if (xFileAccess->exists(m_sURL)) xFileAccess->kill(m_sURL); } dispose_ChildImpl(); cppu::WeakComponentImplHelperBase::disposing(); } void OConnection::clearStatements() { MutexGuard aGuard(m_aMutex); for (OWeakRefArray::iterator i = m_aStatements.begin(); m_aStatements.end() != i; ++i) { Reference< XComponent > xComp(i->get(), UNO_QUERY); if (xComp.is()) xComp->dispose(); } m_aStatements.clear(); } //----- XTablesSupplier ------------------------------------------------------ uno::Reference< XNameAccess > OConnection::getTables() throw (RuntimeException) { // TODO: IMPLEMENT ME PROPERLY //return new Tables(); return 0; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */