/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 *  Effective License of whole file:
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 *  Parts "Copyright by Sun Microsystems, Inc" prior to August 2011:
 *
 *    The Contents of this file are made available subject to the terms of
 *    the GNU Lesser General Public License Version 2.1
 *
 *    Copyright: 2000 by Sun Microsystems, Inc.
 *
 *    Contributor(s): Joerg Budischewski
 *
 *  All parts contributed on or after August 2011:
 *
 *    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 <o3tl/any.hxx>
#include <o3tl/string_view.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>

#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/sdbc/SQLException.hpp>
#include <com/sun/star/sdbc/XRow.hpp>
#include <com/sun/star/sdbc/XParameters.hpp>
#include <com/sun/star/sdbc/DataType.hpp>
#include <com/sun/star/sdbc/KeyRule.hpp>
#include <com/sun/star/sdbcx/KeyType.hpp>
#include <com/sun/star/sdbcx/XColumnsSupplier.hpp>

#include "pq_tools.hxx"
#include "pq_statics.hxx"

#include <libpq-fe.h>
#include <string.h>
#include <string_view>

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

using com::sun::star::lang::XComponent;

using com::sun::star::sdbc::SQLException;
using com::sun::star::sdbc::XStatement;
using com::sun::star::sdbc::XConnection;
using com::sun::star::sdbc::XPreparedStatement;
using com::sun::star::sdbc::XParameters;
using com::sun::star::sdbc::XResultSet;
using com::sun::star::sdbc::XRow;

using com::sun::star::sdbcx::XColumnsSupplier;

using com::sun::star::uno::UNO_QUERY;
using com::sun::star::uno::UNO_QUERY_THROW;
using com::sun::star::uno::Reference;
using com::sun::star::uno::Sequence;
using com::sun::star::uno::XInterface;
using com::sun::star::uno::Any;

using com::sun::star::container::XEnumeration;
using com::sun::star::container::XEnumerationAccess;

namespace pq_sdbc_driver
{

OUString concatQualified( std::u16string_view a, std::u16string_view b)
{
    return OUString::Concat(a) + "." + b;
}

static OString iOUStringToOString( std::u16string_view str, ConnectionSettings const *settings) {
    OSL_ENSURE(settings, "pgsql-sdbc: OUStringToOString got NULL settings");
    return rtl::OUStringToOString( str, ConnectionSettings::encoding );
}

OString OUStringToOString( std::u16string_view str, ConnectionSettings const *settings) {
    return iOUStringToOString( str, settings );
}

void bufferEscapeConstant( OUStringBuffer & buf, std::u16string_view value, ConnectionSettings *settings )
{

    OString y = iOUStringToOString( value, settings );
    OStringBuffer strbuf( y.getLength() * 2 + 2 );
    int error;
    int len = PQescapeStringConn(settings->pConnection, const_cast<char*>(strbuf.getStr()), y.getStr() , y.getLength(), &error );
    if ( error )
    {
        char *errstr = PQerrorMessage(settings->pConnection);
        // As of PostgreSQL 9.1, the only possible errors "involve invalid multibyte encoding"
        // According to https://www2.opengroup.org/ogsys/jsp/publications/PublicationDetails.jsp?publicationid=11216
        // (X/Open SQL CLI, March 1995, ISBN: 1-85912-081-4, X/Open Document Number: C451)
        // 22018 is for "Invalid character value" and seems to be the best match.
        // We have no good XInterface Reference to pass here, so just give NULL
        throw SQLException(OUString(errstr, strlen(errstr), ConnectionSettings::encoding),
                           nullptr,
                           u"22018"_ustr,
                           -1,
                           Any());
    }
    strbuf.setLength( len );
    // Previously here RTL_TEXTENCODING_ASCII_US; as we set the PostgreSQL client_encoding to UTF8,
    // we get UTF8 here, too. I'm not sure why it worked well before...
    buf.append( OStringToOUString( strbuf, RTL_TEXTENCODING_UTF8 ) );
}

static void ibufferQuoteConstant( OUStringBuffer & buf, std::u16string_view value, ConnectionSettings *settings )
{
    buf.append( "'" );
    bufferEscapeConstant( buf, value, settings );
    buf.append( "'" );
}

void bufferQuoteConstant( OUStringBuffer & buf, std::u16string_view value, ConnectionSettings *settings )
{
    return ibufferQuoteConstant( buf, value, settings );
}

void bufferQuoteAnyConstant( OUStringBuffer & buf, const Any &val, ConnectionSettings *settings )
{
    if( val.hasValue() )
    {
        OUString str;
        val >>= str;
        bufferQuoteConstant( buf, str, settings );
    }
    else
        buf.append( "NULL" );
}

static void ibufferQuoteIdentifier( OUStringBuffer & buf, std::u16string_view toQuote, ConnectionSettings *settings )
{
    assert(settings && "pgsql-sdbc: bufferQuoteIdentifier got NULL settings");

    OString y = iOUStringToOString( toQuote, settings );
    char *cstr = PQescapeIdentifier(settings->pConnection, y.getStr(), y.getLength());
    if ( cstr == nullptr )
    {
        char *errstr = PQerrorMessage(settings->pConnection);
        // Implementation-defined SQLACCESS error
        throw SQLException(OUString(errstr, strlen(errstr), ConnectionSettings::encoding),
                           nullptr,
                           u"22018"_ustr,
                           -1,
                           Any());
    }
    buf.append( OStringToOUString( cstr, RTL_TEXTENCODING_UTF8 ) );
    PQfreemem( cstr );
}

void bufferQuoteIdentifier( OUStringBuffer & buf, std::u16string_view toQuote, ConnectionSettings *settings )
{
    return ibufferQuoteIdentifier(buf, toQuote, settings);
}


void bufferQuoteQualifiedIdentifier(
    OUStringBuffer & buf, std::u16string_view schema, std::u16string_view table, ConnectionSettings *settings )
{
    ibufferQuoteIdentifier(buf, schema, settings);
    buf.append( "." );
    ibufferQuoteIdentifier(buf, table, settings);
}

void bufferQuoteQualifiedIdentifier(
    OUStringBuffer & buf,
    std::u16string_view schema,
    std::u16string_view table,
    std::u16string_view col,
    ConnectionSettings *settings)
{
    ibufferQuoteIdentifier(buf, schema, settings);
    buf.append( "." );
    ibufferQuoteIdentifier(buf, table, settings);
    buf.append( "." );
    ibufferQuoteIdentifier(buf, col, settings);
}


OUString extractStringProperty(
    const Reference< XPropertySet > & descriptor, const OUString &name )
{
    OUString value;
    descriptor->getPropertyValue( name ) >>= value;
    return value;
}

bool extractBoolProperty(
    const Reference< XPropertySet > & descriptor, const OUString &name )
{
    bool value = false;
    descriptor->getPropertyValue( name ) >>= value;
    return value;
}

sal_Int32 extractIntProperty(
    const Reference< XPropertySet > & descriptor, const OUString &name )
{
    sal_Int32 ret = 0;
    descriptor->getPropertyValue( name ) >>= ret;
    return ret;
}

void disposeObject( const css::uno::Reference< css::uno::XInterface > & r )
{
    Reference< XComponent > comp( r, UNO_QUERY );
    if( comp.is() )
        comp->dispose();
}

void disposeNoThrow( const css::uno::Reference< css::uno::XInterface > & r )
{
    try
    {
        disposeObject( r );
    }
    catch( SQLException & )
    {
        // ignore this
    }

}

Reference< XConnection > extractConnectionFromStatement( const Reference< XInterface > & stmt )
{
    Reference< XConnection > ret;

    Reference< css::sdbc::XStatement > owner( stmt, UNO_QUERY );
    if( owner.is() )
        ret = owner->getConnection();
    else
    {
        Reference< css::sdbc::XPreparedStatement > myowner( stmt, UNO_QUERY );
        if( myowner.is() )
            ret = myowner->getConnection();
        if( ! ret.is() )
            throw SQLException(
                u"PQSDBC: Couldn't retrieve connection from statement"_ustr,
                Reference< XInterface > () , OUString(), 0 , css::uno::Any()  );
    }

    return ret;

}

DisposeGuard::DisposeGuard( const Reference< XInterface > & r )
    : d( r )
{}

DisposeGuard::~DisposeGuard()
{
    disposeNoThrow( d );
}

TransactionGuard::TransactionGuard( const Reference< XStatement > &stmt )
    : m_stmt( stmt ),
      m_commited( false )
{
    m_stmt->executeUpdate( getStatics().BEGIN );
}

void TransactionGuard::commit()
{
    m_stmt->executeUpdate( getStatics().COMMIT );
    m_commited = true;
}

void TransactionGuard::executeUpdate( const OUString & sql )
{
    m_stmt->executeUpdate( sql );
}

TransactionGuard::~TransactionGuard()
{
    try
    {
        if( ! m_commited )
            m_stmt->executeUpdate( getStatics().ROLLBACK );
    }
    catch( css::uno::Exception & )
    {
        // ignore, we are within a dtor
    }

    disposeNoThrow( m_stmt );
}


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

OUString extractTableFromInsert( std::u16string_view sql )
{
    OUString ret;
    size_t i = 0;
    while (i < sql.size() && isWhitespace(sql[i])) { i++; }

    if( o3tl::matchIgnoreAsciiCase(sql, u"insert", i) )
    {
        i += 6;
        while (i < sql.size() && isWhitespace(sql[i])) { i++; }
        if( o3tl::matchIgnoreAsciiCase(sql, u"into", i) )
        {
            i +=4;
            while (i < sql.size() && isWhitespace(sql[i])) { i++; }
            int start = i;
            bool quote = (sql[i] == '"');
            for( i++ ; i < sql.size() ; i ++ )
            {
                if( quote && sql[i] == '"' )
                {
                    while (i < sql.size() && isWhitespace(sql[i])) { i++; }
                    if( '.' == sql[i] )
                    {
                        while (i < sql.size() && isWhitespace(sql[i])) { i++; }
                        if( '"' == sql[i] )
                        {
                            // the second part of the table name does not use quotes
                            // parse on
                            quote = false;
                        }
                    }
                    else
                    {
                        // end quoted name, ok
                        break;
                    }
                }
                else
                {
                    if( isWhitespace( sql[i] ) )
                    {
                        // found the end of an unquoted name
                        break;
                    }
                }
            }
            ret = o3tl::trim(sql.substr(start, i - start ));
//             printf( "pq_statement: parsed table name %s from insert\n" ,
//                     OUStringToOString( ret, RTL_TEXTENCODING_ASCII_US).getStr() );
        }
    }
    return ret;
}


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 splitSQL( const OString & sql, std::vector< OString > &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.emplace_back( &sql.getStr()[start], i-start+1 );
                start = i + 1;
                doubleQuote = false;
            }
        }
        else if( singleQuote )
        {
            if( '\'' == c && (i+1) < length && '\'' == sql[i+1] )
            {
                // two subsequent single quotes within a quoted string
                // mean a single quote within the string
                i ++;
            }
            else if( '\'' == c )
            {
                vec.emplace_back( &sql.getStr()[start], i - start +1 );
                start = i + 1; // leave single quotes !
                singleQuote = false;
            }
        }
        else
        {
            if( '"' == c )
            {
                vec.emplace_back( &sql.getStr()[start], i - start );
                doubleQuote = true;
                start = i;
            }
            else if( '\'' == c )
            {
                vec.emplace_back( &sql.getStr()[start], i - start );
                singleQuote = true;
                start = i;
            }
        }
    }
    if( start < i )
        vec.emplace_back( &sql.getStr()[start] , i - start );

//     for( i = 0 ; i < vec.size() ; i ++ )
//         printf( "%s!" , vec[i].getStr() );
//     printf( "\n" );

}

void tokenizeSQL( const OString & sql, std::vector< OString > &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.emplace_back( &sql.getStr()[start], i-start );
                start = i + 1;
                doubleQuote = false;
            }
        }
        else if( singleQuote )
        {
            if( '\'' == c )
            {
                vec.emplace_back( &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.emplace_back( &sql.getStr()[start], i - start );
                    start = i +1;
                }
            }
            else if( ',' == c || isOperator( c ) || '(' == c || ')' == c )
            {
                if( i - start )
                    vec.emplace_back( &sql.getStr()[start], i - start );
                vec.emplace_back( &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.emplace_back( &sql.getStr()[start], i - start );
                    vec.emplace_back( "." );
                    start = i + 1;
                }
            }
        }
    }
    if( start < i )
        vec.emplace_back( &sql.getStr()[start] , i - start );

//     for( i = 0 ; i < vec.size() ; i ++ )
//         printf( "%s!" , vec[i].getStr() );
//     printf( "\n" );
}


void splitConcatenatedIdentifier( std::u16string_view source, OUString *first, OUString *second)
{
    std::vector< OString > vec;
    tokenizeSQL( rtl::OUStringToOString( source, RTL_TEXTENCODING_UTF8 ), vec );
    switch (vec.size())
    {
    case 1:
        first->clear();
        *second = OStringToOUString( vec[0], RTL_TEXTENCODING_UTF8 );
        break;
    case 3:
        *first  = OStringToOUString( vec[0], RTL_TEXTENCODING_UTF8 );
        *second = OStringToOUString( vec[2], RTL_TEXTENCODING_UTF8 );
        break;
    default:
         SAL_WARN("connectivity.postgresql",
                  "pq_tools::splitConcatenatedIdentifier unexpected number of tokens in identifier: "
                  << vec.size());
    }
}

OUString array2String( const css::uno::Sequence< Any > &seq )
{
    OUStringBuffer buf(128);
    int len = seq.getLength();
    buf.append( "{" );
    for( int i = 0 ; i < len ; i ++ )
    {
        OUString element;
        seq[i] >>= element;

        if( i > 0 )
            buf.append( "," );
        int strLength = element.getLength();
        buf.append( "\"" );
        for( int j = 0 ; j < strLength ; j ++ )
        {
            sal_Unicode c = element[j];
            if( c == '\\' || c == '"' || c == '{' || c == '}' )
            {
                buf.append( "\\" );
            }
            buf.append( c );
        }
        buf.append( "\"" );
    }
    buf.append( "}" );
    return buf.makeStringAndClear();
}


std::vector< Any > parseArray( std::u16string_view str )
{
    size_t len = str.size();
    bool doubleQuote = false;
    int brackets = 0;
    size_t i = 0;

    OUStringBuffer current;
    std::vector<Any> elements;
    bool doubleQuotedValue = false;
    while( i < len )
    {
        sal_Unicode c = str[i];
        sal_Unicode cnext = str[i+1];
        if( doubleQuote )
        {
            if( '\\' == c )
            {
                i ++;
                current.append( cnext );
            }
            else if( '"' == c )
            {
                doubleQuote = false;
                doubleQuotedValue = true; // signal, that there was an empty element
            }
            else
            {
                current.append( c );
            }
        }
        else if ( '{' == c )
        {
            brackets ++;
        }
        else if( '}' == c )
        {
            brackets --;
            if( brackets < 0 )
            {
                throw SQLException(
                    "error during array parsing, didn't expect a } at position "
                    + OUString::number(i) + " ('" + str + "')",
                    Reference< XInterface > (), OUString(), 1, Any() );
            }
            if( brackets == 0 )
            {
                if( !current.isEmpty() || doubleQuotedValue )
                    elements.emplace_back( current.makeStringAndClear() );
            }
            else
            {
                current.append( c );
            }
        }
        else if( '"' == c )
        {
//                 if( current.getLength() != 0 )
//                 {
//                     OUStringBuffer buf;
//                     buf.appendAscii( "error during array parsing, didn't expect a \" at position " );
//                     buf.append( i );
//                     buf.append( " ('" );
//                     buf.append( str );
//                     buf.append( "')" );
//                     throw SDBCException(
//                         buf.makeStringAndClear(),
//                         Reference< XInterface > (), 1, Any() );
//                 }
//                 else
//                 {
            doubleQuote = true;
//                 }
        }
        else if( ',' == c && brackets == 1)
        {
            doubleQuotedValue = false;
            elements.emplace_back( current.makeStringAndClear() );
        }
        else if( isWhitespace( c ) )
        {
            // ignore whitespace without quotes
        }
        else
        {
            current.append( c );
        }
        i++;
    }
    return elements;
}

std::vector< sal_Int32 > parseIntArray( const OUString & str )
{
    sal_Int32 start = 0;
    std::vector<sal_Int32> vec;
//     printf( ">%s<\n" , OUStringToOString( str, RTL_TEXTENCODING_UTF8 ).getStr() );
    for( sal_Int32 i = str.indexOf( ' ' ) ; i != -1 ; i = str.indexOf( ' ', start) )
    {
        vec.push_back( rtl_ustr_toInt32( &str.pData->buffer[start], 10 ) );
//         printf( "found %d\n" , rtl_ustr_toInt32( &str.pData->buffer[start], 10 ));
        start = i + 1;
    }
    vec.push_back( rtl_ustr_toInt32( &str.pData->buffer[start], 10 ) );
//     printf( "found %d\n" , rtl_ustr_toInt32( &str.pData->buffer[start], 10 ));
    return vec;
}

void fillAttnum2attnameMap(
    Int2StringMap &map,
    const Reference< css::sdbc::XConnection > &conn,
    const OUString &schema,
    const OUString &table )
{
    Reference< XPreparedStatement > prep = conn->prepareStatement(
                   u"SELECT attname,attnum "
                   "FROM pg_attribute "
                         "INNER JOIN pg_class ON attrelid = pg_class.oid "
                         "INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid "
                   "WHERE relname=? AND nspname=?"_ustr );

    Reference< XParameters > paras( prep, UNO_QUERY_THROW );
    paras->setString( 1 , table );
    paras->setString( 2 , schema );
    Reference< XResultSet > rs = prep->executeQuery();

    Reference< XRow > xRow( rs , UNO_QUERY_THROW );
    while( rs->next() )
    {
        map[ xRow->getInt(2) ] = xRow->getString(1);
    }
}

OString extractSingleTableFromSelect( const std::vector< OString > &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 getColExprForDefaultSettingVal(ConnectionSettings const *settings)
{
    return (PQserverVersion( settings->pConnection ) < 80000)?
               u"pg_attrdef.adsrc"_ustr:
               u"pg_get_expr(pg_attrdef.adbin, pg_attrdef.adrelid, true)"_ustr;
}

css::uno::Sequence< sal_Int32 > string2intarray( std::u16string_view str )
{
    css::uno::Sequence< sal_Int32 > ret;
    const sal_Int32 strlen = str.size();
    if( strlen > 1 )
    {
        sal_Int32 start = 0;
        sal_uInt32 c;
        for (;;)
        {
            c = o3tl::iterateCodePoints(str, &start);
            if (!iswspace(c))
                break;
            if ( start == strlen)
                return ret;
        }
        if ( c != L'{' )
            return ret;
        for (;;)
        {
            c = o3tl::iterateCodePoints(str, &start);
            if ( !iswspace(c) )
                break;
            if ( start == strlen)
                return ret;
        }
        if ( c == L'}' )
            return ret;

        std::vector< sal_Int32 > vec;
        do
        {
            OUStringBuffer digits;
            do
            {
                if(!iswspace(c))
                    break;
                if ( start == strlen)
                    return ret;
                c = o3tl::iterateCodePoints(str, &start);
            } while ( c );
            do
            {
                if (!iswdigit(c))
                    break;
                if ( start == strlen)
                    return ret;
                digits.append(OUString(&c, 1));
                c = o3tl::iterateCodePoints(str, &start);
            } while ( c );
            vec.push_back( o3tl::toInt32(digits) );
            do
            {
                if(!iswspace(c))
                    break;
                if ( start == strlen)
                    return ret;
                c = o3tl::iterateCodePoints(str, &start);
            } while ( c );
            if ( c == L'}' )
                break;
            if ( o3tl::iterateCodePoints(str, &start) != L',' )
                return ret;
            if ( start == strlen)
                return ret;
        } while( true );
        // vec is guaranteed non-empty
        assert(vec.size() > 0);
        ret = css::uno::Sequence< sal_Int32 > ( vec.data() , vec.size() );
    }
    return ret;
}


Sequence< OUString > convertMappedIntArray2StringArray(
    const Int2StringMap &map, const Sequence< sal_Int32 > &intArray )
{
    Sequence< OUString > ret( intArray.getLength() );
    auto retRange = asNonConstRange(ret);
    for( int i = 0; i < intArray.getLength() ; i ++ )
    {
        Int2StringMap::const_iterator ii = map.find( intArray[i] );
        if( ii != map.end() )
            retRange[i] = ii->second;
    }
    return ret;
}


OUString sqltype2string( const Reference< XPropertySet > & desc )
{
    OUStringBuffer typeName;
    typeName.append( extractStringProperty( desc, getStatics().TYPE_NAME ) );
    sal_Int32 precision = extractIntProperty( desc, getStatics().PRECISION );

    if( precision )
    {
        switch( extractIntProperty( desc, getStatics().TYPE ) )
        {
        case css::sdbc::DataType::VARBINARY:
        case css::sdbc::DataType::VARCHAR:
        case css::sdbc::DataType::CHAR:
        {
            typeName.append( "(" + OUString::number(precision) + ")" );
            break;
        }
        case css::sdbc::DataType::DECIMAL:
        case css::sdbc::DataType::NUMERIC:
        {
            typeName.append( "("
                + OUString::number(precision)
                + ","
                + OUString::number(extractIntProperty( desc, getStatics().SCALE ))
                + ")" );
            break;
        }
        default:
            ((void)0);
        }
    }
    return typeName.makeStringAndClear();
}


static void keyType2String( OUStringBuffer & buf, sal_Int32 keyType )
{
    if( css::sdbc::KeyRule::CASCADE == keyType )
    {
        buf.append( "CASCADE " );
    }
    else if( css::sdbc::KeyRule::RESTRICT  == keyType )
    {
        buf.append( "RESTRICT " );
    }
    else if( css::sdbc::KeyRule::SET_DEFAULT  == keyType )
    {
        buf.append( "SET DEFAULT " );
    }
    else if( css::sdbc::KeyRule::SET_NULL  == keyType )
    {
        buf.append( "SET NULL " );
    }
    else //if( css::sdbc::KeyRule::NO_ACTION == keyType )
    {
        buf.append( "NO ACTION " );
    }
}

void bufferKey2TableConstraint(
    OUStringBuffer &buf, const Reference< XPropertySet > &key, ConnectionSettings *settings )
{
    Statics &st = getStatics();
    sal_Int32 type = extractIntProperty( key, st.TYPE );
    OUString referencedTable = extractStringProperty( key, st.REFERENCED_TABLE );
    sal_Int32 updateRule = extractIntProperty( key, st.UPDATE_RULE );
    sal_Int32 deleteRule = extractIntProperty( key, st.DELETE_RULE );
    bool foreign = false;
    if( type == css::sdbcx::KeyType::UNIQUE )
    {
        buf.append( "UNIQUE( " );
    }
    else if( type == css::sdbcx::KeyType::PRIMARY )
    {
        buf.append( "PRIMARY KEY( " );
    }
    else if( type == css::sdbcx::KeyType::FOREIGN )
    {
        foreign = true;
        buf.append( "FOREIGN KEY( " );
    }

    Reference< XColumnsSupplier > columns( key, UNO_QUERY );
    if( columns.is() )
    {
        Reference< XEnumerationAccess > colEnumAccess( columns->getColumns(), UNO_QUERY );
        if( colEnumAccess.is() )
        {
            Reference< XEnumeration > colEnum = colEnumAccess->createEnumeration();
            bool first = true;
            while(colEnum.is() && colEnum->hasMoreElements() )
            {
                if( first )
                {
                    first = false;
                }
                else
                {
                    buf.append( ", " );
                }
                Reference< XPropertySet > keyColumn( colEnum->nextElement(), UNO_QUERY_THROW );
                bufferQuoteIdentifier(buf, extractStringProperty( keyColumn, st.NAME ), settings );
            }
        }
    }
    buf.append( ") " );

    if( !foreign )
        return;

    buf.append( "REFERENCES " );
    OUString schema;
    OUString tableName;
    splitConcatenatedIdentifier( referencedTable, &schema, &tableName );
    bufferQuoteQualifiedIdentifier(buf , schema, tableName, settings );
    if(columns.is() )
    {
        Reference< XEnumerationAccess > colEnumAccess( columns->getColumns(), UNO_QUERY);
        if( colEnumAccess.is() )
        {
            buf.append( " (" );
            Reference< XEnumeration > colEnum(colEnumAccess->createEnumeration());
            bool first = true;
            while(colEnum.is() && colEnum->hasMoreElements() )
            {
                if( first )
                {
                    first = false;
                }
                else
                {
                    buf.append( ", " );
                }
                Reference< XPropertySet > keyColumn( colEnum->nextElement(), UNO_QUERY_THROW );
                bufferQuoteIdentifier(
                                      buf, extractStringProperty( keyColumn, st.RELATED_COLUMN ), settings );
            }
            buf.append( ") " );
        }
    }

    buf.append( "ON DELETE " );
    keyType2String( buf, deleteRule );
    buf.append( " ON UPDATE " );
    keyType2String( buf, updateRule );

}

void extractNameValuePairsFromInsert( String2StringMap & map, const OString & lastQuery )
{
    std::vector< OString > vec;
    tokenizeSQL( lastQuery, vec  );

    int nSize = vec.size();
//     printf( "1 %d\n", nSize );
    if( !(nSize > 6  &&
        vec[0].equalsIgnoreAsciiCase( "insert" ) &&
        vec[1].equalsIgnoreAsciiCase( "into" )) )
        return;

    int n = 2;

//         printf( "1a\n" );
    // skip table name
    if( vec[n+1].equalsIgnoreAsciiCase( "." ) )
    {
        n +=2;
    }

    n ++;
    if( !vec[n].equalsIgnoreAsciiCase( "(" ) )
        return;

    std::vector< OString> names;
//             printf( "2\n" );
    // extract names
    n++;
    while( nSize > n && ! vec[n].equalsIgnoreAsciiCase( ")" ) )
    {
        names.push_back( vec[n] );
        if( nSize > n+1 && vec[n+1].equalsIgnoreAsciiCase( "," ) )
        {
            n ++;
        }
        n++;
    }
    n++;

    // now read the values
    if( !(nSize > n +1 && vec[n].equalsIgnoreAsciiCase("VALUES") &&
        vec[n+1].equalsIgnoreAsciiCase( "(" )) )
        return;

    n +=2;
//                 printf( "3\n" );
    for (auto& name : names)
    {
        if (n >= nSize)
            break;

        map[name] = vec[n];
        if( nSize > n+1 && vec[n+1].equalsIgnoreAsciiCase(",") )
        {
            n ++;
        }
        n++;
    }
}

OUString querySingleValue(
    const css::uno::Reference< css::sdbc::XConnection > &connection,
    const OUString &query )
{
    OUString ret;
    Reference< XStatement > stmt = connection->createStatement();
    DisposeGuard guard( stmt );
    Reference< XResultSet > rs = stmt->executeQuery( query );
    Reference< XRow > xRow( rs, UNO_QUERY );
    if( rs->next() )
        ret = xRow->getString( 1 );
    return ret;
}


// copied from connectivity/source/dbtools, can't use the function directly
bool implSetObject( const Reference< XParameters >& _rxParameters,
                        const sal_Int32 _nColumnIndex, const Any& _rValue)
{
    bool bSuccessfullyReRouted = true;
    switch (_rValue.getValueTypeClass())
    {
        case css::uno::TypeClass_HYPER:
        {
            _rxParameters->setLong( _nColumnIndex, sal_Int64(0) );
        }
        break;

        case css::uno::TypeClass_VOID:
            _rxParameters->setNull(_nColumnIndex,css::sdbc::DataType::VARCHAR);
            break;

        case css::uno::TypeClass_STRING:
            _rxParameters->setString(_nColumnIndex, *o3tl::forceAccess<OUString>(_rValue));
            break;

        case css::uno::TypeClass_BOOLEAN:
            _rxParameters->setBoolean(_nColumnIndex, *o3tl::forceAccess<bool>(_rValue));
            break;

        case css::uno::TypeClass_BYTE:
            _rxParameters->setByte(_nColumnIndex, *o3tl::forceAccess<sal_Int8>(_rValue));
            break;

        case css::uno::TypeClass_UNSIGNED_SHORT:
        case css::uno::TypeClass_SHORT:
            _rxParameters->setShort(_nColumnIndex, *o3tl::forceAccess<sal_Int16>(_rValue));
            break;

        case css::uno::TypeClass_CHAR:
            _rxParameters->setString(_nColumnIndex, OUString(*o3tl::forceAccess<sal_Unicode>(_rValue)));
            break;

        case css::uno::TypeClass_UNSIGNED_LONG:
        case css::uno::TypeClass_LONG:
            _rxParameters->setInt(_nColumnIndex, *o3tl::forceAccess<sal_Int32>(_rValue));
            break;

        case css::uno::TypeClass_FLOAT:
            _rxParameters->setFloat(_nColumnIndex, *o3tl::forceAccess<float>(_rValue));
            break;

        case css::uno::TypeClass_DOUBLE:
            _rxParameters->setDouble(_nColumnIndex, *o3tl::forceAccess<double>(_rValue));
            break;

        case css::uno::TypeClass_SEQUENCE:
            if (auto s = o3tl::tryAccess<Sequence< sal_Int8 >>(_rValue))
            {
                _rxParameters->setBytes(_nColumnIndex, *s);
            }
            else
                bSuccessfullyReRouted = false;
            break;
        case css::uno::TypeClass_STRUCT:
            if (auto s1 = o3tl::tryAccess<css::util::DateTime>(_rValue))
                _rxParameters->setTimestamp(_nColumnIndex, *s1);
            else if (auto s2 = o3tl::tryAccess<css::util::Date>(_rValue))
                _rxParameters->setDate(_nColumnIndex, *s2);
            else if (auto s3 = o3tl::tryAccess<css::util::Time>(_rValue))
                _rxParameters->setTime(_nColumnIndex, *s3);
            else
                bSuccessfullyReRouted = false;
            break;

        case css::uno::TypeClass_INTERFACE:
        {
            Reference< css::io::XInputStream >  xStream;
            if (_rValue >>= xStream)
            {
                _rValue >>= xStream;
                _rxParameters->setBinaryStream(_nColumnIndex, xStream, xStream->available());
                break;
            }
            [[fallthrough]];
        }
        default:
            bSuccessfullyReRouted = false;

    }

    return bSuccessfullyReRouted;
}

}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
