tdf#69949 GSoC Firebird implement autoincrement

Change-Id: I6fe08b575f01b986f0a3c96b341f254279427b68
Reviewed-on: https://gerrit.libreoffice.org/28062
Reviewed-by: Lionel Elie Mamane <lionel@mamane.lu>
Tested-by: Jenkins <ci@libreoffice.org>
diff --git a/connectivity/Library_firebird_sdbc.mk b/connectivity/Library_firebird_sdbc.mk
index 4c3dae1..43fa363 100644
--- a/connectivity/Library_firebird_sdbc.mk
+++ b/connectivity/Library_firebird_sdbc.mk
@@ -42,6 +42,7 @@ $(eval $(call gb_Library_set_componentfile,firebird_sdbc,connectivity/source/dri
$(eval $(call gb_Library_add_exception_objects,firebird_sdbc,\
    connectivity/source/drivers/firebird/Blob \
    connectivity/source/drivers/firebird/Catalog \
    connectivity/source/drivers/firebird/Column \
    connectivity/source/drivers/firebird/Columns \
    connectivity/source/drivers/firebird/Connection \
    connectivity/source/drivers/firebird/DatabaseMetaData \
diff --git a/connectivity/source/drivers/firebird/Column.cxx b/connectivity/source/drivers/firebird/Column.cxx
new file mode 100644
index 0000000..28ce9ac
--- /dev/null
+++ b/connectivity/source/drivers/firebird/Column.cxx
@@ -0,0 +1,54 @@
/* -*- 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/.
 */

#include "Columns.hxx"
#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(  ) throw(css::uno::RuntimeException, std::exception)
{
    css::uno::Sequence< OUString > aSupported { "com.sun.star.sdbc.Firebird" };

    return aSupported;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/connectivity/source/drivers/firebird/Column.hxx b/connectivity/source/drivers/firebird/Column.hxx
new file mode 100644
index 0000000..0b1ea67
--- /dev/null
+++ b/connectivity/source/drivers/firebird/Column.hxx
@@ -0,0 +1,37 @@
/* -*- 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/.
 */
#ifndef INCLUDED_CONNECTIVITY_SOURCE_DRIVERS_FIREBIRD_HCOLUMN_HXX
#define INCLUDED_CONNECTIVITY_SOURCE_DRIVERS_FIREBIRD_HCOLUMN_HXX
#include <connectivity/TColumnsHelper.hxx>
#include <connectivity/sdbcx/VColumn.hxx>

namespace connectivity
{
    namespace firebird
    {
        class Column;
        typedef sdbcx::OColumn Column_BASE;
        typedef ::comphelper::OIdPropertyArrayUsageHelper<Column> Column_PROP;
        class Column : public Column_BASE,
                           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(  ) throw(css::uno::RuntimeException, std::exception) override;
        };
    }
}
#endif // INCLUDED_CONNECTIVITY_SOURCE_INC_HSQLDB_HCOLUMNS_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/connectivity/source/drivers/firebird/Columns.cxx b/connectivity/source/drivers/firebird/Columns.cxx
index 6f41dca..d418026 100644
--- a/connectivity/source/drivers/firebird/Columns.cxx
+++ b/connectivity/source/drivers/firebird/Columns.cxx
@@ -8,6 +8,7 @@
 */

#include "Columns.hxx"
#include "Column.hxx"

#include <com/sun/star/sdbc/XRow.hpp>

@@ -33,4 +34,9 @@ Columns::Columns(Table& rTable,
    OColumnsHelper::setParent(&rTable);
}

Reference< css::beans::XPropertySet > Columns::createDescriptor()
{
    return new Column;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/connectivity/source/drivers/firebird/Columns.hxx b/connectivity/source/drivers/firebird/Columns.hxx
index 87c81fb..58c1b33 100644
--- a/connectivity/source/drivers/firebird/Columns.hxx
+++ b/connectivity/source/drivers/firebird/Columns.hxx
@@ -20,11 +20,12 @@ namespace connectivity
    {
        class Columns: public ::connectivity::OColumnsHelper
        {
        protected:
            virtual css::uno::Reference< css::beans::XPropertySet > createDescriptor() override;
        public:
            Columns(Table& rTable,
                    ::osl::Mutex& rMutex,
                    const ::connectivity::TStringVector &_rVector);

        };

    } // namespace firebird
diff --git a/connectivity/source/drivers/firebird/PreparedStatement.cxx b/connectivity/source/drivers/firebird/PreparedStatement.cxx
index 801acd8..13402b8 100644
--- a/connectivity/source/drivers/firebird/PreparedStatement.cxx
+++ b/connectivity/source/drivers/firebird/PreparedStatement.cxx
@@ -56,6 +56,7 @@ OPreparedStatement::OPreparedStatement( Connection* _pConnection,
    ,m_sSqlStatement(sql)
    ,m_pOutSqlda(nullptr)
    ,m_pInSqlda(nullptr)
    ,m_sTableName()
{
    SAL_INFO("connectivity.firebird", "OPreparedStatement(). "
             "sql: " << sql);
@@ -83,6 +84,11 @@ void OPreparedStatement::ensurePrepared()
                               m_pOutSqlda,
                               m_pInSqlda);

    OStringVector vec;
    tokenizeSQL( OUStringToOString(m_sSqlStatement, RTL_TEXTENCODING_UTF8), vec );
    m_sTableName =
            OStringToOUString(
            extractSingleTableFromSelect( vec ), RTL_TEXTENCODING_UTF8);

    aErr = isc_dsql_describe_bind(m_statusVector,
                                  &m_aStatementHandle,
@@ -151,7 +157,9 @@ Reference< XResultSetMetaData > SAL_CALL OPreparedStatement::getMetaData()
    ensurePrepared();

    if(!m_xMetaData.is())
        m_xMetaData = new OResultSetMetaData(m_pConnection.get(), m_pOutSqlda);
        m_xMetaData = new OResultSetMetaData(m_pConnection.get()
                                           , m_pOutSqlda
                                           , m_sTableName);

    return m_xMetaData;
}
@@ -285,7 +293,8 @@ sal_Bool SAL_CALL OPreparedStatement::execute()
                                  m_aMutex,
                                  uno::Reference< XInterface >(*this),
                                  m_aStatementHandle,
                                  m_pOutSqlda);
                                  m_pOutSqlda,
                                  m_sTableName);

    if (getStatementChangeCount() > 0)
        m_pConnection->notifyDatabaseModified();
diff --git a/connectivity/source/drivers/firebird/PreparedStatement.hxx b/connectivity/source/drivers/firebird/PreparedStatement.hxx
index 72e90d5..33e1421 100644
--- a/connectivity/source/drivers/firebird/PreparedStatement.hxx
+++ b/connectivity/source/drivers/firebird/PreparedStatement.hxx
@@ -53,6 +53,7 @@ namespace connectivity

            XSQLDA*         m_pOutSqlda;
            XSQLDA*         m_pInSqlda;
            ::rtl::OUString                                       m_sTableName;
            void checkParameterIndex(sal_Int32 nParameterIndex)
                throw(css::sdbc::SQLException,
                      css::uno::RuntimeException);
diff --git a/connectivity/source/drivers/firebird/ResultSet.cxx b/connectivity/source/drivers/firebird/ResultSet.cxx
index 68517a8..299dafa 100644
--- a/connectivity/source/drivers/firebird/ResultSet.cxx
+++ b/connectivity/source/drivers/firebird/ResultSet.cxx
@@ -57,7 +57,8 @@ OResultSet::OResultSet(Connection* pConnection,
                       ::osl::Mutex& rMutex,
                       const uno::Reference< XInterface >& xStatement,
                       isc_stmt_handle& aStatementHandle,
                       XSQLDA* pSqlda)
                       XSQLDA* pSqlda,
                       const OUString& rTableName)
    : OResultSet_BASE(rMutex)
    , OPropertyContainer(OResultSet_BASE::rBHelper)
    , m_bIsBookmarkable(false)
@@ -75,6 +76,7 @@ OResultSet::OResultSet(Connection* pConnection,
    , m_currentRow(0)
    , m_bIsAfterLastRow(false)
    , m_fieldCount(pSqlda? pSqlda->sqld : 0)
    , m_sTableName(rTableName)
{
    SAL_INFO("connectivity.firebird", "OResultSet().");
    registerProperty(OMetaConnection::getPropMap().getNameByIndex(PROPERTY_ID_ISBOOKMARKABLE),
@@ -640,7 +642,9 @@ uno::Reference< XResultSetMetaData > SAL_CALL OResultSet::getMetaData(  ) throw(
    checkDisposed(OResultSet_BASE::rBHelper.bDisposed);

    if(!m_xMetaData.is())
        m_xMetaData = new OResultSetMetaData(m_pConnection, m_pSqlda);
        m_xMetaData = new OResultSetMetaData(m_pConnection
                                           , m_pSqlda
                                           , m_sTableName);
    return m_xMetaData;
}

diff --git a/connectivity/source/drivers/firebird/ResultSet.hxx b/connectivity/source/drivers/firebird/ResultSet.hxx
index e5ae03a..e0f3e46 100644
--- a/connectivity/source/drivers/firebird/ResultSet.hxx
+++ b/connectivity/source/drivers/firebird/ResultSet.hxx
@@ -97,6 +97,8 @@ namespace connectivity
            const sal_Int32                             m_fieldCount;
            ISC_STATUS_ARRAY                            m_statusVector;

            OUString                                    m_sTableName;

            bool isNull(const sal_Int32 nColumnIndex);

            template <typename T> T     retrieveValue(const sal_Int32 nColumnIndex,
@@ -126,7 +128,8 @@ namespace connectivity
                       ::osl::Mutex& rMutex,
                       const css::uno::Reference< css::uno::XInterface >& xStatement,
                       isc_stmt_handle& aStatementHandle,
                       XSQLDA* aSqlda);
                       XSQLDA* aSqlda,
                       const OUString & rTableName);

            // XInterface
            virtual css::uno::Any SAL_CALL queryInterface(
diff --git a/connectivity/source/drivers/firebird/ResultSetMetaData.cxx b/connectivity/source/drivers/firebird/ResultSetMetaData.cxx
index e76be24..9baadab 100644
--- a/connectivity/source/drivers/firebird/ResultSetMetaData.cxx
+++ b/connectivity/source/drivers/firebird/ResultSetMetaData.cxx
@@ -21,13 +21,19 @@
#include "Util.hxx"

#include <com/sun/star/sdbc/ColumnValue.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
#include <com/sun/star/sdbc/XRow.hpp>

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;

using com::sun::star::beans::XPropertySet;
using com::sun::star::container::XNameAccess;

OResultSetMetaData::~OResultSetMetaData()
{
}
@@ -141,8 +147,35 @@ sal_Bool SAL_CALL OResultSetMetaData::isCurrency(sal_Int32 column)
sal_Bool SAL_CALL OResultSetMetaData::isAutoIncrement(sal_Int32 column)
    throw(SQLException, RuntimeException, std::exception)
{
    // Supported internally but no way of determining this here.
    (void) column;
    if( !m_sTableName.isEmpty() )
    {
        OUString sColumnName = getColumnName( column );

        OUString sSql = "SELECT RDB$IDENTITY_TYPE FROM RDB$RELATION_FIELDS "
                   "WHERE RDB$RELATION_NAME = '"
                   + escapeWith(m_sTableName, '\'', '\'') + "' 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;
}

diff --git a/connectivity/source/drivers/firebird/ResultSetMetaData.hxx b/connectivity/source/drivers/firebird/ResultSetMetaData.hxx
index 4df534a..cac8a01 100644
--- a/connectivity/source/drivers/firebird/ResultSetMetaData.hxx
+++ b/connectivity/source/drivers/firebird/ResultSetMetaData.hxx
@@ -40,6 +40,7 @@ namespace connectivity
        protected:
            ::rtl::Reference<Connection> m_pConnection;
            XSQLDA*         m_pSqlda;
            OUString        m_sTableName;

            virtual ~OResultSetMetaData();

@@ -47,9 +48,11 @@ namespace connectivity
        public:
            // a constructor, which is required for returning objects:
            OResultSetMetaData(Connection* pConnection,
                               XSQLDA* pSqlda)
                               XSQLDA* pSqlda,
                               const OUString & rTableName)
                : m_pConnection(pConnection)
                , m_pSqlda(pSqlda)
                , m_sTableName(rTableName)
            {}

            virtual sal_Int32 SAL_CALL getColumnCount()
diff --git a/connectivity/source/drivers/firebird/Statement.cxx b/connectivity/source/drivers/firebird/Statement.cxx
index 29c195c..f69e3ef5 100644
--- a/connectivity/source/drivers/firebird/Statement.cxx
+++ b/connectivity/source/drivers/firebird/Statement.cxx
@@ -125,11 +125,18 @@ uno::Reference< XResultSet > SAL_CALL OStatement::executeQuery(const OUString& s
    if (aErr)
        SAL_WARN("connectivity.firebird", "isc_dsql_execute failed");

    OStringVector vec;
    tokenizeSQL( OUStringToOString(sql, RTL_TEXTENCODING_UTF8), vec );
    OUString sourceTable =
            OStringToOUString(
            extractSingleTableFromSelect( vec ), RTL_TEXTENCODING_UTF8);

    m_xResultSet = new OResultSet(m_pConnection.get(),
                                  m_aMutex,
                                  uno::Reference< XInterface >(*this),
                                  m_aStatementHandle,
                                  m_pSqlda);
                                  m_pSqlda,
                                  sourceTable);

    // TODO: deal with cleanup

diff --git a/connectivity/source/drivers/firebird/Table.cxx b/connectivity/source/drivers/firebird/Table.cxx
index 8f189d7..fea9046 100644
--- a/connectivity/source/drivers/firebird/Table.cxx
+++ b/connectivity/source/drivers/firebird/Table.cxx
@@ -196,7 +196,11 @@ void SAL_CALL Table::alterColumnByName(const OUString& rColName,

    if (bIsAutoIncrementChanged)
    {
        // TODO: changeType
       ::dbtools::throwSQLException(
            "Changing autoincrement property of existing column is not supported",
            ::dbtools::StandardSQLState::FUNCTION_NOT_SUPPORTED,
            *this);

    }

    if (bDefaultChanged)
diff --git a/connectivity/source/drivers/firebird/Tables.cxx b/connectivity/source/drivers/firebird/Tables.cxx
index df3edb7..bc15f90 100644
--- a/connectivity/source/drivers/firebird/Tables.cxx
+++ b/connectivity/source/drivers/firebird/Tables.cxx
@@ -11,9 +11,13 @@
#include "Tables.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>

using namespace ::connectivity;
using namespace ::connectivity::firebird;
@@ -26,6 +30,7 @@ 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;


@@ -65,6 +70,42 @@ ObjectType Tables::createObject(const OUString& rName)
    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));


    if ( bIsAutoIncrement && !sAutoIncrementValue.isEmpty())
    {
        aSql.append(" ");
        aSql.append(sAutoIncrementValue);
    }
    // AutoIncrementive "IDENTITY" is implizit "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
@@ -77,8 +118,56 @@ uno::Reference< XPropertySet > Tables::createDescriptor()
ObjectType Tables::appendObject(const OUString& rName,
                                const uno::Reference< XPropertySet >& rDescriptor)
{
    OUString sSql(::dbtools::createSqlCreateTableStatement(rDescriptor,
                                                            m_xMetaData->getConnection()));
   /* 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(aSqlBuffer.getLength()-1, 1, ")");
        else
            sSql += ")";
    }

    m_xMetaData->getConnection()->createStatement()->execute(sSql);

    return createObject(rName);
diff --git a/connectivity/source/drivers/firebird/Tables.hxx b/connectivity/source/drivers/firebird/Tables.hxx
index 5fc5397..1a0a358 100644
--- a/connectivity/source/drivers/firebird/Tables.hxx
+++ b/connectivity/source/drivers/firebird/Tables.hxx
@@ -29,6 +29,8 @@ namespace connectivity
            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()
                throw(css::uno::RuntimeException) override;
diff --git a/connectivity/source/drivers/firebird/Util.cxx b/connectivity/source/drivers/firebird/Util.cxx
index 00eb4aa3..ee5a9bd 100644
--- a/connectivity/source/drivers/firebird/Util.cxx
+++ b/connectivity/source/drivers/firebird/Util.cxx
@@ -9,6 +9,7 @@

#include "Util.hxx"
#include <rtl/ustrbuf.hxx>
#include <rtl/strbuf.hxx>

using namespace ::connectivity;

@@ -306,4 +307,237 @@ void firebird::freeSQLVAR(XSQLDA* pSqlda)
        }
    }
}

static bool isWhitespace( sal_Unicode c )
{
    return ' ' == c || 9 == c || 10 == c || 13 == c;
}

static bool isOperator( char c )
{
    bool ret;
    switch(c)
    {
    case '+':
    case '-':
    case '*':
    case '/':
    case '<':
    case '>':
    case '=':
    case '~':
    case '!':
    case '@':
    case '#':
    case '%':
    case '^':
    case '&':
    case '|':
    case '`':
    case '?':
    case '$':
        ret = true;
        break;
    default:
        ret = false;
    }
    return ret;
}

void firebird::tokenizeSQL( const OString & sql, OStringVector &vec  )
{
    int length = sql.getLength();

    int i = 0;
    bool singleQuote = false;
    bool doubleQuote = false;
    int start = 0;
    for( ; i < length ; i ++ )
    {
        char c = sql[i];
        if( doubleQuote  )
        {
            if( '"' == c )
            {
                vec.push_back( OString( &sql.getStr()[start], i-start  ) );
                start = i + 1;
                doubleQuote = false;
            }
        }
        else if( singleQuote )
        {
            if( '\'' == c )
            {
                vec.push_back( OString( &sql.getStr()[start], i - start +1 ) );
                start = i + 1; // leave single quotes !
                singleQuote = false;
            }
        }
        else
        {
            if( '"' == c )
            {
                doubleQuote = true;
                start = i +1; // skip double quotes !
            }
            else if( '\'' == c )
            {
                singleQuote = true;
                start = i; // leave single quotes
            }
            else if( isWhitespace( c ) )
            {
                if( i == start )
                    start ++;   // skip additional whitespace
                else
                {
                    vec.push_back( OString( &sql.getStr()[start], i - start  ) );
                    start = i +1;
                }
            }
            else if( ',' == c || isOperator( c ) || '(' == c || ')' == c )
            {
                if( i - start )
                    vec.push_back( OString( &sql.getStr()[start], i - start ) );
                vec.push_back( OString( &sql.getStr()[i], 1 ) );
                start = i + 1;
            }
            else if( '.' == c )
            {
                if( ( i > start && sql[start] >= '0' && sql[start] <= '9' ) ||
                    ( i == start && i > 1 && isWhitespace( sql[i-1] ) ) )
                {
                    // ignore, is a literal
                }
                else
                {
                    if( i - start )
                        vec.push_back( OString( &sql.getStr()[start], i - start ) );
                    vec.push_back( OString( "." ) );
                    start = i + 1;
                }
            }
        }
    }
    if( start < i )
        vec.push_back( OString( &sql.getStr()[start] , i - start ) );
}

OString firebird::extractSingleTableFromSelect( const OStringVector &vec )
{
    OString ret;

    if( 0 == rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
            vec[0].pData->buffer, vec[0].pData->length, "select" , 6 , 6 ) )
    {
        size_t token = 0;

        for( token = 1; token < vec.size() ; token ++ )
        {
            if( 0 == rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
                    vec[token].getStr(), vec[token].getLength(), "from" , 4 , 4 ) )
            {
                // found from
                break;
            }
        }
        token ++;

        if( token < vec.size() && 0 == rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
                vec[token].pData->buffer, vec[token].pData->length, "only " , 4 , 4 ) )
        {
            token ++;
        }

        if( token < vec.size() && vec[token] != "(" )
        {
            // it is a table or a function name
            OStringBuffer buf(128);
            if( '"' == vec[token][0] )
                buf.append( &(vec[token].getStr()[1]) , vec[token].getLength() -2 );
            else
                buf.append( vec[token] );
            token ++;

            if( token < vec.size() )
            {
                if( vec[token] == "." )
                {
                    buf.append( vec[token] );
                    token ++;
                    if( token < vec.size() )
                    {
                        if( '"' == vec[token][0] )
                            buf.append( &(vec[token].getStr()[1]) , vec[token].getLength() -2 );
                        else
                            buf.append( vec[token] );
                        token ++;
                    }
                }
            }

            ret = buf.makeStringAndClear();
            // now got my table candidate

            if( token < vec.size() && vec[token] == "(" )
            {
                // whoops, it is a function
                ret.clear();
            }
            else
            {
                if( token < vec.size() )
                {
                    if( 0 == rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
                            vec[token].pData->buffer, vec[token].pData->length, "as" , 2, 2 ) )
                    {
                        token += 2; // skip alias
                    }
                }

                if( token < vec.size() )
                {
                    if( vec[token] == "," )
                    {
                        // whoops, multiple tables are used
                        ret.clear();
                    }
                    else
                    {
                        static const char * forbiddenKeywords[] =
                            { "join", "natural", "outer", "inner", "left", "right", "full" , nullptr };
                        for( int i = 0 ; forbiddenKeywords[i] ; i ++ )
                        {
                            size_t nKeywordLen = strlen(forbiddenKeywords[i]);
                            if( 0 == rtl_str_shortenedCompareIgnoreAsciiCase_WithLength(
                                 vec[token].pData->buffer, vec[token].pData->length,
                                 forbiddenKeywords[i], nKeywordLen,
                                 nKeywordLen ) )
                            {
                                // whoops, it is a join
                                ret.clear();
                            }
                        }
                    }
                }
            }
        }
    }
    return ret;

}

OUString firebird::escapeWith( const OUString& sText, const char aKey, const char aEscapeChar)
{
    OUString sRet(sText);
    sal_Int32 aIndex = 0;
    while( (aIndex = sRet.indexOf(aKey, aIndex)) > 0 &&
            aIndex < sRet.getLength())
    {
            sRet = sRet.replaceAt(aIndex, 1, OUString(aEscapeChar) + OUString(aKey)  );
            aIndex+= 2;
    }

    return sRet;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/connectivity/source/drivers/firebird/Util.hxx b/connectivity/source/drivers/firebird/Util.hxx
index 8e66aeb..b957c80 100644
--- a/connectivity/source/drivers/firebird/Util.hxx
+++ b/connectivity/source/drivers/firebird/Util.hxx
@@ -13,15 +13,18 @@
#include <ibase.h>

#include <rtl/ustring.hxx>
#include <rtl/ustrbuf.hxx>

#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>

#include <vector>

namespace connectivity
{
    namespace firebird
    {

        typedef ::std::vector< OString > OStringVector;
        /**
         * Make sure an identifier is safe to use within the databse. Currently
         * firebird seems to return identifiers with 93 character (instead of
@@ -64,6 +67,12 @@ namespace connectivity
        void mallocSQLVAR(XSQLDA* pSqlda);

        void freeSQLVAR(XSQLDA* pSqlda);

        void tokenizeSQL( const OString & sql, OStringVector &vec  );

        OString extractSingleTableFromSelect( const OStringVector &vec );

        OUString escapeWith( const OUString& sText, const char aKey, const char aEscapeChar);
    }
}
#endif // INCLUDED_CONNECTIVITY_SOURCE_DRIVERS_FIREBIRD_UTIL_HXX