1247 lines
37 KiB
C++
1247 lines
37 KiB
C++
/* -*- 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: */
|