LCOV - code coverage report
Current view: top level - connectivity/source/drivers/postgresql - pq_statement.cxx (source / functions) Hit Total Coverage
Test: commit 10e77ab3ff6f4314137acd6e2702a6e5c1ce1fae Lines: 0 456 0.0 %
Date: 2014-11-03 Functions: 0 34 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
       2             : /*************************************************************************
       3             :  *
       4             :  *  Effective License of whole file:
       5             :  *
       6             :  *    This library is free software; you can redistribute it and/or
       7             :  *    modify it under the terms of the GNU Lesser General Public
       8             :  *    License version 2.1, as published by the Free Software Foundation.
       9             :  *
      10             :  *    This library is distributed in the hope that it will be useful,
      11             :  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13             :  *    Lesser General Public License for more details.
      14             :  *
      15             :  *    You should have received a copy of the GNU Lesser General Public
      16             :  *    License along with this library; if not, write to the Free Software
      17             :  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
      18             :  *    MA  02111-1307  USA
      19             :  *
      20             :  *  Parts "Copyright by Sun Microsystems, Inc" prior to August 2011:
      21             :  *
      22             :  *    The Contents of this file are made available subject to the terms of
      23             :  *    the GNU Lesser General Public License Version 2.1
      24             :  *
      25             :  *    Copyright: 2000 by Sun Microsystems, Inc.
      26             :  *
      27             :  *    Contributor(s): Joerg Budischewski
      28             :  *
      29             :  *  All parts contributed on or after August 2011:
      30             :  *
      31             :  *    This Source Code Form is subject to the terms of the Mozilla Public
      32             :  *    License, v. 2.0. If a copy of the MPL was not distributed with this
      33             :  *    file, You can obtain one at http://mozilla.org/MPL/2.0/.
      34             :  *
      35             :  ************************************************************************/
      36             : 
      37             : #include "pq_statement.hxx"
      38             : #include "pq_fakedupdateableresultset.hxx"
      39             : #include "pq_updateableresultset.hxx"
      40             : #include "pq_tools.hxx"
      41             : #include "pq_statics.hxx"
      42             : 
      43             : #include <osl/thread.h>
      44             : #include <osl/time.h>
      45             : 
      46             : #include <rtl/ustrbuf.hxx>
      47             : #include <rtl/strbuf.hxx>
      48             : 
      49             : #include <cppuhelper/typeprovider.hxx>
      50             : #include <cppuhelper/queryinterface.hxx>
      51             : 
      52             : #include <com/sun/star/beans/PropertyAttribute.hpp>
      53             : 
      54             : #include <com/sun/star/sdbc/ResultSetConcurrency.hpp>
      55             : #include <com/sun/star/sdbc/ResultSetType.hpp>
      56             : #include <com/sun/star/sdbc/XParameters.hpp>
      57             : 
      58             : #include <com/sun/star/sdbcx/XColumnsSupplier.hpp>
      59             : #include <com/sun/star/sdbcx/KeyType.hpp>
      60             : #include <com/sun/star/sdbcx/XKeysSupplier.hpp>
      61             : 
      62             : #include <com/sun/star/container/XIndexAccess.hpp>
      63             : #include <com/sun/star/container/XEnumerationAccess.hpp>
      64             : 
      65             : #include <string.h>
      66             : 
      67             : using osl::Mutex;
      68             : using osl::MutexGuard;
      69             : 
      70             : 
      71             : using com::sun::star::uno::Any;
      72             : using com::sun::star::uno::makeAny;
      73             : using com::sun::star::uno::Type;
      74             : using com::sun::star::uno::RuntimeException;
      75             : using com::sun::star::uno::Exception;
      76             : using com::sun::star::uno::Sequence;
      77             : using com::sun::star::uno::Reference;
      78             : using com::sun::star::uno::XInterface;
      79             : using com::sun::star::uno::UNO_QUERY;
      80             : 
      81             : using com::sun::star::lang::IllegalArgumentException;
      82             : 
      83             : using com::sun::star::sdbc::XWarningsSupplier;
      84             : using com::sun::star::sdbc::XCloseable;
      85             : using com::sun::star::sdbc::XStatement;
      86             : using com::sun::star::sdbc::XPreparedStatement;
      87             : using com::sun::star::sdbc::XParameters;
      88             : using com::sun::star::sdbc::XRow;
      89             : using com::sun::star::sdbc::XResultSet;
      90             : using com::sun::star::sdbc::XGeneratedResultSet;
      91             : using com::sun::star::sdbc::XConnection;
      92             : using com::sun::star::sdbc::SQLException;
      93             : 
      94             : using com::sun::star::sdbcx::XColumnsSupplier;
      95             : using com::sun::star::sdbcx::XTablesSupplier;
      96             : using com::sun::star::sdbcx::XKeysSupplier;
      97             : 
      98             : using com::sun::star::beans::Property;
      99             : using com::sun::star::beans::XPropertySetInfo;
     100             : using com::sun::star::beans::XPropertySet;
     101             : using com::sun::star::beans::XFastPropertySet;
     102             : using com::sun::star::beans::XMultiPropertySet;
     103             : 
     104             : using com::sun::star::container::XNameAccess;
     105             : using com::sun::star::container::XEnumerationAccess;
     106             : using com::sun::star::container::XEnumeration;
     107             : using com::sun::star::container::XIndexAccess;
     108             : 
     109             : namespace pq_sdbc_driver
     110             : {
     111           0 : static ::cppu::IPropertyArrayHelper & getStatementPropertyArrayHelper()
     112             : {
     113             :     static ::cppu::IPropertyArrayHelper *pArrayHelper;
     114           0 :     if( ! pArrayHelper )
     115             :     {
     116           0 :         MutexGuard guard( Mutex::getGlobalMutex() );
     117           0 :         if( ! pArrayHelper )
     118             :         {
     119             :             static Property aTable[] =
     120             :                 {
     121             :                     Property(
     122             :                         OUString("CursorName"), 0,
     123           0 :                         ::cppu::UnoType<OUString>::get() , 0 ),
     124             :                     Property(
     125             :                         OUString("EscapeProcessing"), 1,
     126           0 :                         ::getBooleanCppuType() , 0 ),
     127             :                     Property(
     128             :                         OUString("FetchDirection"), 2,
     129           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     130             :                     Property(
     131             :                         OUString("FetchSize"), 3,
     132           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     133             :                     Property(
     134             :                         OUString("MaxFieldSize"), 4,
     135           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     136             :                     Property(
     137             :                         OUString("MaxRows"), 5,
     138           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     139             :                     Property(
     140             :                         OUString("QueryTimeOut"), 6,
     141           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     142             :                     Property(
     143             :                         OUString("ResultSetConcurrency"), 7,
     144           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 ),
     145             :                     Property(
     146             :                         OUString("ResultSetType"), 8,
     147           0 :                         ::cppu::UnoType<sal_Int32>::get() , 0 )
     148           0 :                 };
     149             :             OSL_ASSERT( sizeof(aTable)/ sizeof(Property)  == STATEMENT_SIZE );
     150           0 :             static ::cppu::OPropertyArrayHelper arrayHelper( aTable, STATEMENT_SIZE, sal_True );
     151           0 :             pArrayHelper = &arrayHelper;
     152           0 :         }
     153             :     }
     154           0 :     return *pArrayHelper;
     155             : }
     156             : 
     157           0 : Statement::Statement( const ::rtl::Reference< RefCountedMutex > & refMutex,
     158             :                       const Reference< XConnection > & conn,
     159             :                       struct ConnectionSettings *pSettings )
     160           0 :     : OComponentHelper( refMutex->mutex )
     161             :     , OPropertySetHelper( OComponentHelper::rBHelper )
     162             :     , m_connection( conn )
     163             :     , m_pSettings( pSettings )
     164             :     , m_refMutex( refMutex )
     165             :     , m_multipleResultAvailable(false)
     166             :     , m_multipleResultUpdateCount(0)
     167           0 :     , m_lastOidInserted(InvalidOid)
     168             : {
     169           0 :     m_props[STATEMENT_QUERY_TIME_OUT] = makeAny( (sal_Int32)0 );
     170           0 :     m_props[STATEMENT_MAX_ROWS] = makeAny( (sal_Int32)0 );
     171           0 :     m_props[STATEMENT_RESULT_SET_CONCURRENCY] = makeAny(
     172           0 :         com::sun::star::sdbc::ResultSetConcurrency::READ_ONLY );
     173           0 :     m_props[STATEMENT_RESULT_SET_TYPE] = makeAny(
     174           0 :         com::sun::star::sdbc::ResultSetType::SCROLL_INSENSITIVE );
     175           0 : }
     176             : 
     177           0 : Statement::~Statement()
     178             : {
     179             :     POSTGRE_TRACE( "dtor Statement" );
     180           0 : }
     181             : 
     182           0 : void Statement::checkClosed() throw (SQLException, RuntimeException )
     183             : {
     184           0 :     if( ! m_pSettings || ! m_pSettings->pConnection )
     185             :         throw SQLException(
     186             :             "pq_driver: Statement or connection has already been closed !",
     187           0 :             *this, OUString(),1,Any());
     188           0 : }
     189             : 
     190           0 : Any Statement::queryInterface( const Type & reqType ) throw (RuntimeException, std::exception)
     191             : {
     192           0 :     Any ret;
     193             : 
     194           0 :     ret = OComponentHelper::queryInterface( reqType );
     195           0 :     if( ! ret.hasValue() )
     196           0 :         ret = ::cppu::queryInterface( reqType,
     197             :                                     static_cast< XWarningsSupplier * > ( this  ),
     198             :                                     static_cast< XStatement * > ( this ),
     199             :                                     static_cast< com::sun::star::sdbc::XResultSetMetaDataSupplier * > ( this ),
     200             :                                     static_cast< XCloseable * > ( this ),
     201             :                                     static_cast< XPropertySet * > ( this ),
     202             :                                     static_cast< XMultiPropertySet * > ( this ),
     203             :                                     static_cast< XGeneratedResultSet * > ( this ),
     204           0 :                                     static_cast< XFastPropertySet * > ( this ) );
     205           0 :     return ret;
     206             : }
     207             : 
     208             : 
     209           0 : Sequence< Type > Statement::getTypes() throw ( RuntimeException, std::exception )
     210             : {
     211             :     static cppu::OTypeCollection *pCollection;
     212           0 :     if( ! pCollection )
     213             :     {
     214           0 :         MutexGuard guard( osl::Mutex::getGlobalMutex() );
     215           0 :         if( !pCollection )
     216             :         {
     217             :             static cppu::OTypeCollection collection(
     218           0 :                 cppu::UnoType<XWarningsSupplier>::get(),
     219           0 :                 cppu::UnoType<XStatement>::get(),
     220           0 :                 cppu::UnoType<com::sun::star::sdbc::XResultSetMetaDataSupplier>::get(),
     221           0 :                 cppu::UnoType<XCloseable>::get(),
     222           0 :                 cppu::UnoType<XPropertySet>::get(),
     223           0 :                 cppu::UnoType<XFastPropertySet>::get(),
     224           0 :                 cppu::UnoType<XMultiPropertySet>::get(),
     225           0 :                 cppu::UnoType<XGeneratedResultSet>::get(),
     226           0 :                 OComponentHelper::getTypes());
     227           0 :             pCollection = &collection;
     228           0 :         }
     229             :     }
     230           0 :     return pCollection->getTypes();
     231             : }
     232             : 
     233           0 : Sequence< sal_Int8> Statement::getImplementationId() throw ( RuntimeException, std::exception )
     234             : {
     235           0 :     return css::uno::Sequence<sal_Int8>();
     236             : }
     237             : 
     238           0 : void Statement::close(  ) throw (SQLException, RuntimeException, std::exception)
     239             : {
     240             :     // let the connection die without acquired mutex !
     241           0 :     Reference< XConnection > r;
     242           0 :     Reference< XCloseable > resultSet;
     243             :     {
     244           0 :         MutexGuard guard( m_refMutex->mutex );
     245           0 :         m_pSettings = 0;
     246           0 :         r = m_connection;
     247           0 :         m_connection.clear();
     248             : 
     249           0 :         resultSet = m_lastResultset;
     250           0 :         m_lastResultset.clear();
     251             :     }
     252           0 :     if( resultSet.is() )
     253             :     {
     254           0 :         resultSet->close();
     255             :         POSTGRE_TRACE( "statement closed" );
     256           0 :     }
     257             : 
     258           0 : }
     259             : 
     260           0 : void Statement::raiseSQLException(
     261             :     const OUString & sql, const char * errorMsg, const char *errorType )
     262             :     throw( SQLException )
     263             : {
     264           0 :     OUStringBuffer buf(128);
     265           0 :     buf.appendAscii( "pq_driver: ");
     266           0 :     if( errorType )
     267             :     {
     268           0 :         buf.appendAscii( "[" );
     269           0 :         buf.appendAscii( errorType );
     270           0 :         buf.appendAscii( "]" );
     271             :     }
     272             :     buf.append(
     273           0 :         OUString( errorMsg, strlen(errorMsg) , m_pSettings->encoding ) );
     274           0 :     buf.appendAscii( " (caused by statement '" );
     275           0 :     buf.append( sql );
     276           0 :     buf.appendAscii( "')" );
     277           0 :     OUString error = buf.makeStringAndClear();
     278           0 :     log( m_pSettings, LogLevel::ERROR, error );
     279           0 :     throw SQLException( error, *this, OUString(), 1, Any() );
     280             : }
     281             : 
     282           0 : Reference< XResultSet > Statement::executeQuery(const OUString& sql )
     283             :         throw (SQLException, RuntimeException, std::exception)
     284             : {
     285           0 :     Reference< XCloseable > lastResultSetHolder = m_lastResultset;
     286           0 :     if( lastResultSetHolder.is() )
     287           0 :         lastResultSetHolder->close();
     288             : 
     289           0 :     if( ! execute( sql ) )
     290             :     {
     291           0 :         raiseSQLException( sql, "not a query" );
     292             :     }
     293           0 :     return Reference< XResultSet > ( m_lastResultset, com::sun::star::uno::UNO_QUERY );
     294             : }
     295             : 
     296           0 : sal_Int32 Statement::executeUpdate( const OUString& sql )
     297             :         throw (SQLException, RuntimeException, std::exception)
     298             : {
     299           0 :     if( execute( sql ) )
     300             :     {
     301           0 :         raiseSQLException( sql, "not a command" );
     302             :     }
     303           0 :     return m_multipleResultUpdateCount;
     304             : }
     305             : 
     306             : 
     307           0 : static void raiseSQLException(
     308             :     ConnectionSettings *pSettings,
     309             :     const Reference< XInterface> & owner,
     310             :     const OString & sql,
     311             :     const char * errorMsg,
     312             :     const char *errorType = 0 )
     313             :     throw( SQLException )
     314             : {
     315           0 :     OUStringBuffer buf(128);
     316           0 :     buf.appendAscii( "pq_driver: ");
     317           0 :     if( errorType )
     318             :     {
     319           0 :         buf.appendAscii( "[" );
     320           0 :         buf.appendAscii( errorType );
     321           0 :         buf.appendAscii( "]" );
     322             :     }
     323             :     buf.append(
     324           0 :         OUString( errorMsg, strlen(errorMsg) , pSettings->encoding ) );
     325           0 :     buf.appendAscii( " (caused by statement '" );
     326           0 :     buf.append( OStringToOUString( sql, pSettings->encoding ) );
     327           0 :     buf.appendAscii( "')" );
     328           0 :     OUString error = buf.makeStringAndClear();
     329           0 :     log( pSettings, LogLevel::ERROR, error );
     330           0 :     throw SQLException( error, owner, OUString(), 1, Any() );
     331             : }
     332             : 
     333             : 
     334             : // returns the elements of the primary key of the given table
     335             : // static Sequence< Reference< com::sun::star::beans::XPropertySet > > lookupKeys(
     336           0 : static Sequence< OUString > lookupKeys(
     337             :     const Reference< com::sun::star::container::XNameAccess > &tables,
     338             :     const OUString & table,
     339             :     OUString *pSchema,
     340             :     OUString *pTable,
     341             :     ConnectionSettings *pSettings)
     342             : {
     343           0 :     Sequence< OUString  > ret;
     344           0 :     Reference< XKeysSupplier > keySupplier;
     345           0 :     Statics & st = getStatics();
     346             : 
     347           0 :     if( tables->hasByName( table ) )
     348           0 :         tables->getByName( table ) >>= keySupplier;
     349           0 :     else if( -1 == table.indexOf( '.' ) )
     350             :     {
     351             :         // it wasn't a fully qualified name. Now need to skip through all tables.
     352             :         Reference< XEnumerationAccess > enumerationAccess =
     353           0 :             Reference< XEnumerationAccess > ( tables, UNO_QUERY );
     354             : 
     355             :         Reference< com::sun::star::container::XEnumeration > enumeration =
     356           0 :             enumerationAccess->createEnumeration();
     357           0 :         while( enumeration->hasMoreElements() )
     358             :         {
     359           0 :             Reference< XPropertySet > set;
     360           0 :             enumeration->nextElement() >>= set;
     361           0 :             OUString name;
     362             : //             OUString schema;
     363             : 
     364           0 :             if( set->getPropertyValue( st.NAME ) >>= name )
     365             :             {
     366             : //                 printf( "searching %s %s\n",
     367             : //                         OUStringToOString( schema, RTL_TEXTENCODING_ASCII_US ).getStr(),
     368             : //                         OUStringToOString( name, RTL_TEXTENCODING_ASCII_US ).getStr() );
     369           0 :                 if( name == table )
     370             :                 {
     371             : 
     372           0 :                     if( keySupplier.is() )
     373             :                     {
     374             :                         // is ambigous, as I don't know postgresql searchpath,
     375             :                         // I can't continue here, as I may write to a different table
     376           0 :                         keySupplier.clear();
     377           0 :                         if( isLog( pSettings, LogLevel::INFO ) )
     378             :                         {
     379           0 :                             OStringBuffer buf( 128 );
     380           0 :                             buf.append( "Can't offer updateable result set because table " );
     381           0 :                             buf.append( OUStringToOString(name, pSettings->encoding) );
     382           0 :                             buf.append( " is duplicated, add schema to resolve ambiguity" );
     383           0 :                             log( pSettings, LogLevel::INFO, buf.makeStringAndClear().getStr() );
     384             :                         }
     385           0 :                         break;
     386             :                     }
     387           0 :                     keySupplier = Reference< XKeysSupplier > ( set, UNO_QUERY );
     388             :                 }
     389             :             }
     390           0 :         }
     391             :     }
     392             :     else
     393             :     {
     394           0 :         if( isLog( pSettings, LogLevel::INFO ) )
     395             :         {
     396           0 :             OStringBuffer buf( 128 );
     397           0 :             buf.append( "Can't offer updateable result set ( table " );
     398           0 :             buf.append( OUStringToOString(table, pSettings->encoding) );
     399           0 :             buf.append( " is unknown)" );
     400           0 :             log( pSettings, LogLevel::INFO, buf.makeStringAndClear().getStr() );
     401             :         }
     402             :     }
     403             : 
     404           0 :     if( keySupplier.is() )
     405             :     {
     406           0 :         Reference< XPropertySet > set( keySupplier, UNO_QUERY );
     407           0 :         set->getPropertyValue( getStatics().NAME ) >>= (*pTable);
     408           0 :         set->getPropertyValue( getStatics().SCHEMA_NAME ) >>= (*pSchema );
     409           0 :         set.clear();
     410             : 
     411           0 :         Reference< XEnumerationAccess > keys ( keySupplier->getKeys(), UNO_QUERY );
     412           0 :         Reference< XEnumeration > enumeration = keys->createEnumeration();
     413           0 :         while( enumeration->hasMoreElements() )
     414             :         {
     415           0 :             enumeration->nextElement() >>= set;
     416           0 :             sal_Int32 keyType = 0;
     417           0 :             if( (set->getPropertyValue( st.TYPE ) >>= keyType ) &&
     418           0 :                 keyType == com::sun::star::sdbcx::KeyType::PRIMARY )
     419             :             {
     420           0 :                 Reference< XColumnsSupplier > columns( set, UNO_QUERY );
     421             :                 Reference< XIndexAccess > indexAccess =
     422           0 :                     Reference< XIndexAccess > ( columns->getColumns(), UNO_QUERY );
     423             : 
     424           0 :                 int length = indexAccess->getCount();
     425           0 :                 ret.realloc( length );
     426             : //                 printf( "primary key for Table %s is ",
     427             : //                         OUStringToOString( table, RTL_TEXTENCODING_ASCII_US ).getStr() );
     428           0 :                 for( int i = 0 ; i < length ; i ++ )
     429             :                 {
     430           0 :                     indexAccess->getByIndex( i ) >>= set;
     431           0 :                     OUString name;
     432           0 :                     set->getPropertyValue( st.NAME ) >>= name;
     433           0 :                     ret[i] = name;
     434             : //                     printf( "%s," , OUStringToOString( name, RTL_TEXTENCODING_ASCII_US ).getStr() );
     435           0 :                 }
     436             : //                 printf( "\n" );
     437             :             }
     438             :         }
     439           0 :         if( ! ret.getLength() )
     440             :         {
     441           0 :             if( isLog( pSettings, LogLevel::INFO ) )
     442             :             {
     443           0 :                 OStringBuffer buf( 128 );
     444           0 :                 buf.append( "Can't offer updateable result set ( table " );
     445           0 :                 buf.append( OUStringToOString(table, pSettings->encoding) );
     446           0 :                 buf.append( " does not have a primary key)" );
     447           0 :                 log( pSettings, LogLevel::INFO, buf.makeStringAndClear().getStr() );
     448             :             }
     449           0 :         }
     450             :     }
     451           0 :     return ret;
     452             : }
     453             : 
     454           0 : bool executePostgresCommand( const OString & cmd, struct CommandData *data )
     455             : {
     456           0 :     ConnectionSettings *pSettings = *(data->ppSettings);
     457             : 
     458           0 :     sal_Int32 duration = osl_getGlobalTimer();
     459           0 :     PGresult *result = PQexec( pSettings->pConnection, cmd.getStr() );
     460           0 :     duration = osl_getGlobalTimer() - duration;
     461           0 :     if( ! result )
     462             :         raiseSQLException(
     463           0 :             pSettings, data->owner, cmd, PQerrorMessage( pSettings->pConnection ) );
     464             : 
     465           0 :     ExecStatusType state = PQresultStatus( result );
     466           0 :     *(data->pLastOidInserted) = 0;
     467           0 :     *(data->pLastTableInserted) = OUString();
     468           0 :     *(data->pLastQuery) = cmd;
     469             : 
     470           0 :     bool ret = false;
     471           0 :     switch( state )
     472             :     {
     473             :     case PGRES_COMMAND_OK:
     474             :     {
     475           0 :         *(data->pMultipleResultUpdateCount) = atoi( PQcmdTuples( result ) );
     476           0 :         *(data->pMultipleResultAvailable) = false;
     477             : 
     478             :         // in case an oid value is available, we retrieve it
     479           0 :         *(data->pLastOidInserted) = PQoidValue( result );
     480             : 
     481             :         // in case it was a single insert, extract the name of the table,
     482             :         // otherwise the table name is empty
     483           0 :         *(data->pLastTableInserted) =
     484           0 :             extractTableFromInsert( OStringToOUString( cmd, pSettings->encoding ) );
     485           0 :         if( isLog( pSettings, LogLevel::SQL ) )
     486             :         {
     487           0 :             OStringBuffer buf( 128 );
     488           0 :             buf.append( "executed command '" );
     489           0 :             buf.append( cmd.getStr() );
     490           0 :             buf.append( "' successfully (" );
     491           0 :             buf.append( *( data->pMultipleResultUpdateCount ) );
     492           0 :             buf.append( ")" );
     493           0 :             buf.append( ", duration=" );
     494           0 :             buf.append( duration );
     495           0 :             buf.append( "ms" );
     496           0 :             if( *(data->pLastOidInserted) )
     497             :             {
     498           0 :                 buf.append( ", usedOid=" );
     499           0 :                 buf.append( *(data->pLastOidInserted) , 10 );
     500           0 :                 buf.append( ", diagnosedTable=" );
     501             :                 buf.append(
     502           0 :                     OUStringToOString( *data->pLastTableInserted, pSettings->encoding ) );
     503             :             }
     504           0 :             log( pSettings, LogLevel::SQL, buf.makeStringAndClear().getStr() );
     505             :         }
     506           0 :         PQclear( result );
     507           0 :         break;
     508             :     }
     509             :     case PGRES_TUPLES_OK: // success
     510             :     {
     511             :         // In case it is a single table, it has a primary key and all columns
     512             :         // belonging to the primary key are in the result set, allow updateable result sets
     513             :         // otherwise, don't
     514           0 :         OUString table, schema;
     515           0 :         Sequence< OUString > sourceTableKeys;
     516           0 :         OStringVector vec;
     517           0 :         tokenizeSQL( cmd, vec );
     518             :         OUString sourceTable =
     519             :             OStringToOUString(
     520           0 :                 extractSingleTableFromSelect( vec ), pSettings->encoding );
     521             : 
     522           0 :         if( data->concurrency ==
     523             :             com::sun::star::sdbc::ResultSetConcurrency::UPDATABLE )
     524             :         {
     525           0 :             OString aReason;
     526           0 :             if( sourceTable.getLength() )
     527             :             {
     528           0 :                 sourceTableKeys = lookupKeys(
     529           0 :                     pSettings->tables.is() ?
     530           0 :                            pSettings->tables : data->tableSupplier->getTables() ,
     531             :                     sourceTable,
     532             :                     &schema,
     533             :                     &table,
     534           0 :                     pSettings);
     535             : 
     536             :                 // check, whether the columns are in the result set (required !)
     537             :                 int i;
     538           0 :                 for( i = 0 ; i < sourceTableKeys.getLength() ;  i ++ )
     539             :                 {
     540           0 :                     if( -1 == PQfnumber(
     541             :                             result,
     542           0 :                             OUStringToOString( sourceTableKeys[i] ,
     543           0 :                                                pSettings->encoding ).getStr()) )
     544             :                     {
     545           0 :                         break;
     546             :                     }
     547             :                 }
     548             : 
     549           0 :                 if( sourceTableKeys.getLength() && i == sourceTableKeys.getLength() )
     550             :                 {
     551           0 :                     *(data->pLastResultset) =
     552             :                         UpdateableResultSet::createFromPGResultSet(
     553             :                             data->refMutex, data->owner, data->ppSettings, result,
     554           0 :                             schema, table,sourceTableKeys );
     555             :                 }
     556           0 :                 else if( ! table.getLength() )
     557             :                 {
     558           0 :                     OStringBuffer buf( 128 );
     559             :                     buf.append( "can't support updateable resultset, because a single table in the "
     560           0 :                                 "WHERE part of the statement could not be identified (" );
     561           0 :                     buf.append( cmd );
     562           0 :                     buf.append( "." );
     563           0 :                     aReason = buf.makeStringAndClear();
     564             :                 }
     565           0 :                 else if( sourceTableKeys.getLength() )
     566             :                 {
     567           0 :                     OStringBuffer buf( 128 );
     568           0 :                     buf.append( "can't support updateable resultset for table " );
     569           0 :                     buf.append( OUStringToOString( schema, pSettings->encoding ) );
     570           0 :                     buf.append( "." );
     571           0 :                     buf.append( OUStringToOString( table, pSettings->encoding ) );
     572           0 :                     buf.append( ", because resultset does not contain a part of the primary key ( column " );
     573           0 :                     buf.append( OUStringToOString( sourceTableKeys[i], pSettings->encoding ) );
     574           0 :                     buf.append( " is missing )" );
     575           0 :                     aReason = buf.makeStringAndClear();
     576             :                 }
     577             :                 else
     578             :                 {
     579             : 
     580           0 :                     OStringBuffer buf( 128 );
     581           0 :                     buf.append( "can't support updateable resultset for table " );
     582           0 :                     buf.append( OUStringToOString( schema, pSettings->encoding ) );
     583           0 :                     buf.append( "." );
     584           0 :                     buf.append( OUStringToOString( table, pSettings->encoding ) );
     585           0 :                     buf.append( ", because resultset table does not have a primary key " );
     586           0 :                     aReason = buf.makeStringAndClear();
     587             :                 }
     588             :             }
     589             :             else
     590             :             {
     591           0 :                 OStringBuffer buf( 128 );
     592           0 :                 buf.append( "can't support updateable result for selects with multiple tables (" );
     593           0 :                 buf.append( cmd );
     594           0 :                 buf.append( ")" );
     595           0 :                 log( pSettings, LogLevel::SQL, buf.makeStringAndClear().getStr() );
     596             :             }
     597           0 :             if( ! (*(data->pLastResultset)).is() )
     598             :             {
     599           0 :                 if( isLog( pSettings, LogLevel::ERROR ) )
     600             :                 {
     601           0 :                     log( pSettings, LogLevel::ERROR,  aReason.getStr());
     602             :                 }
     603             : 
     604             :                 // TODO: How to react here correctly ?
     605             :                 // remove this piece of code
     606           0 :                 *(data->pLastResultset) =
     607             :                     new FakedUpdateableResultSet(
     608             :                         data->refMutex, data->owner,
     609             :                         data->ppSettings,result, schema, table,
     610           0 :                         OStringToOUString( aReason, pSettings->encoding) );
     611           0 :             }
     612             : 
     613             :         }
     614           0 :         else if( sourceTable.getLength() > 0)
     615             :         {
     616           0 :             splitConcatenatedIdentifier( sourceTable, &schema, &table );
     617             :         }
     618             : 
     619           0 :         sal_Int32 returnedRows = PQntuples( result );
     620           0 :         if( ! data->pLastResultset->is() )
     621           0 :             *(data->pLastResultset) =
     622             :                 Reference< XCloseable > (
     623             :                     new ResultSet(
     624             :                         data->refMutex, data->owner,
     625           0 :                         data->ppSettings,result, schema, table ) );
     626           0 :         *(data->pMultipleResultAvailable) = true;
     627           0 :         ret = true;
     628           0 :         if( isLog( pSettings, LogLevel::SQL ) )
     629             :         {
     630           0 :             OStringBuffer buf( 128 );
     631           0 :             buf.append( "executed query '" );
     632           0 :             buf.append( cmd );
     633           0 :             buf.append( "' successfully" );
     634           0 :             buf.append( ", duration=" );
     635           0 :             buf.append( duration );
     636           0 :             buf.append( "ms, returnedRows=" );
     637           0 :             buf.append( returnedRows );
     638           0 :             buf.append( "." );
     639           0 :             log( pSettings, LogLevel::SQL, buf.makeStringAndClear().getStr() );
     640             :         }
     641           0 :         break;
     642             :     }
     643             :     case PGRES_EMPTY_QUERY:
     644             :     case PGRES_COPY_OUT:
     645             :     case PGRES_COPY_IN:
     646             :     case PGRES_BAD_RESPONSE:
     647             :     case PGRES_NONFATAL_ERROR:
     648             :     case PGRES_FATAL_ERROR:
     649             :     default:
     650             :         raiseSQLException(
     651           0 :             pSettings, data->owner, cmd, PQresultErrorMessage( result ) , PQresStatus( state ) );
     652             :     }
     653           0 :     return ret;
     654             : 
     655             : }
     656             : 
     657           0 : static Sequence< OUString > getPrimaryKeyColumnNames(
     658             :     const Reference< XConnection > & connection, const OUString &schemaName, const OUString &tableName )
     659             : {
     660           0 :     Sequence< OUString > ret;
     661             : 
     662           0 :     Int2StringMap mapIndex2Name;
     663           0 :     fillAttnum2attnameMap( mapIndex2Name, connection, schemaName, tableName );
     664             : 
     665             :     // retrieve the primary key ...
     666           0 :     Reference< XPreparedStatement > stmt = connection->prepareStatement(
     667             :             "SELECT conkey "              // 7
     668             :             "FROM pg_constraint INNER JOIN pg_class ON conrelid = pg_class.oid "
     669             :                       "INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid "
     670             :                       "LEFT JOIN pg_class AS class2 ON confrelid = class2.oid "
     671             :                       "LEFT JOIN pg_namespace AS nmsp2 ON class2.relnamespace=nmsp2.oid "
     672           0 :             "WHERE pg_class.relname = ? AND pg_namespace.nspname = ? AND pg_constraint.contype='p'" );
     673           0 :     DisposeGuard guard( stmt );
     674           0 :     Reference< XParameters > paras( stmt, UNO_QUERY );
     675           0 :     paras->setString( 1 , tableName );
     676           0 :     paras->setString( 2 , schemaName );
     677           0 :     Reference< XResultSet > rs = stmt->executeQuery();
     678           0 :     Reference< XRow > xRow( rs , UNO_QUERY );
     679             : 
     680           0 :     if( rs->next() )
     681             :     {
     682           0 :         ret = convertMappedIntArray2StringArray( mapIndex2Name, string2intarray(xRow->getString( 1 ) ) );
     683             :     }
     684           0 :     return ret;
     685             : }
     686             : 
     687           0 : static void getAutoValues(
     688             :     String2StringMap & result,
     689             :     const Reference< XConnection > & connection,
     690             :     const OUString &schemaName,
     691             :     const OUString & tableName )
     692             : {
     693           0 :     Reference< XPreparedStatement > stmt = connection->prepareStatement(
     694             :                   "SELECT   pg_attribute.attname, pg_attrdef.adsrc "
     695             :                   "FROM pg_class, pg_namespace, pg_attribute "
     696             :                   "LEFT JOIN pg_attrdef ON pg_attribute.attrelid = pg_attrdef.adrelid AND "
     697             :                                         "pg_attribute.attnum = pg_attrdef.adnum "
     698             :                   "WHERE pg_attribute.attrelid = pg_class.oid AND "
     699             :                         "pg_class.relnamespace = pg_namespace.oid AND "
     700             :                         "pg_namespace.nspname = ? AND "
     701             :                   // LEM TODO: this is weird; why "LIKE" and not "="?
     702             :                   // Most probably gives problems if tableName contains '%'
     703             :                         "pg_class.relname LIKE ? AND "
     704             :                         "pg_attrdef.adsrc != ''"
     705           0 :             );
     706           0 :     DisposeGuard guard( stmt );
     707           0 :     Reference< XParameters > paras( stmt, UNO_QUERY );
     708           0 :     paras->setString( 1 , schemaName );
     709           0 :     paras->setString( 2 , tableName );
     710           0 :     Reference< XResultSet > rs = stmt->executeQuery();
     711           0 :     Reference< XRow > xRow( rs , UNO_QUERY );
     712             : 
     713           0 :     while( rs->next() )
     714             :     {
     715           0 :         result[ OUStringToOString( xRow->getString( 1 ), RTL_TEXTENCODING_ASCII_US) ] =
     716           0 :             OUStringToOString( xRow->getString(2), RTL_TEXTENCODING_ASCII_US );
     717           0 :     }
     718           0 : }
     719             : 
     720           0 : Reference< XResultSet > getGeneratedValuesFromLastInsert(
     721             :     ConnectionSettings *pConnectionSettings,
     722             :     const Reference< XConnection > &connection,
     723             :     sal_Int32 nLastOid,
     724             :     const OUString & lastTableInserted,
     725             :     const OString & lastQuery )
     726             : {
     727           0 :     Reference< XResultSet > ret;
     728           0 :     OUString query;
     729           0 :     OUString schemaName, tableName;
     730             :     splitConcatenatedIdentifier(
     731           0 :         lastTableInserted, &schemaName, &tableName );
     732             : 
     733           0 :     if( nLastOid && lastTableInserted.getLength() )
     734             :     {
     735           0 :         OUStringBuffer buf( 128 );
     736           0 :         buf.append( "SELECT * FROM " );
     737           0 :         if( schemaName.getLength() )
     738           0 :             bufferQuoteQualifiedIdentifier(buf, schemaName, tableName, pConnectionSettings );
     739             :         else
     740           0 :             bufferQuoteIdentifier( buf, lastTableInserted, pConnectionSettings );
     741           0 :         buf.append( " WHERE oid = " );
     742           0 :         buf.append( nLastOid , 10 );
     743           0 :         query = buf.makeStringAndClear();
     744             :     }
     745           0 :     else if ( lastTableInserted.getLength() && lastQuery.getLength() )
     746             :     {
     747             :         // extract nameValue Pairs
     748           0 :         String2StringMap namedValues;
     749           0 :         extractNameValuePairsFromInsert( namedValues, lastQuery );
     750             : 
     751             :         // debug ...
     752             : //         OStringBuffer buf( 128);
     753             : //         buf.append( "extracting name/value from '" );
     754             : //         buf.append( lastQuery.getStr() );
     755             : //         buf.append( "' to [" );
     756             : //         for( String2StringMap::iterator ii = namedValues.begin() ; ii != namedValues.end() ; ++ii )
     757             : //         {
     758             : //             buf.append( ii->first.getStr() );
     759             : //             buf.append( "=" );
     760             : //             buf.append( ii->second.getStr() );
     761             : //             buf.append( "," );
     762             : //         }
     763             : //         buf.append( "]\n" );
     764             : //         printf( "%s", buf.makeStringAndClear() );
     765             : 
     766             :         // TODO: make also unqualified tables names work here. Have a look at 2.8.3. The Schema Search Path
     767             :         //       in postgresql doc
     768             : 
     769           0 :         Sequence< OUString > keyColumnNames = getPrimaryKeyColumnNames( connection, schemaName, tableName );
     770           0 :         if( keyColumnNames.getLength() )
     771             :         {
     772           0 :             OUStringBuffer buf( 128 );
     773           0 :             buf.append( "SELECT * FROM " );
     774           0 :             bufferQuoteQualifiedIdentifier(buf, schemaName, tableName, pConnectionSettings );
     775           0 :             buf.append( " WHERE " );
     776           0 :             bool additionalCondition = false;
     777           0 :             String2StringMap autoValues;
     778           0 :             for( int i = 0 ; i < keyColumnNames.getLength() ; i ++ )
     779             :             {
     780           0 :                 OUString value;
     781           0 :                 OString columnName = OUStringToOString( keyColumnNames[i], pConnectionSettings->encoding );
     782           0 :                 String2StringMap::iterator ii = namedValues.begin();
     783           0 :                 for( ; ii != namedValues.end() ; ++ii )
     784             :                 {
     785           0 :                     if( columnName.equalsIgnoreAsciiCase( ii->first ) )
     786             :                     {
     787           0 :                         value = OStringToOUString( ii->second , pConnectionSettings->encoding );
     788           0 :                         break;
     789             :                     }
     790             :                 }
     791             : 
     792             :                 // check, if a column of the primary key was not inserted explicitly,
     793           0 :                 if( ii == namedValues.end() )
     794             :                 {
     795             : 
     796           0 :                     if( autoValues.begin() == autoValues.end() )
     797             :                     {
     798           0 :                         getAutoValues( autoValues, connection, schemaName, tableName );
     799             :                     }
     800             :                     // this could mean, that the column is a default or auto value, check this ...
     801           0 :                     String2StringMap::iterator j = autoValues.begin();
     802           0 :                     for( ; j != autoValues.end() ; ++j )
     803             :                     {
     804           0 :                         if( columnName.equalsIgnoreAsciiCase( j->first ) )
     805             :                         {
     806             :                             // it is indeed an auto value.
     807           0 :                             value = OStringToOUString(j->second, RTL_TEXTENCODING_ASCII_US );
     808             :                             // check, whether it is a sequence
     809             : 
     810           0 :                             if( rtl_str_shortenedCompare_WithLength(
     811           0 :                                     j->second.getStr(), j->second.getLength(),
     812           0 :                                     RTL_CONSTASCII_STRINGPARAM( "nextval(" ), 8 ) == 0 )
     813             :                             {
     814             :                                 // retrieve current sequence value:
     815           0 :                                 OUStringBuffer myBuf(128 );
     816           0 :                                 myBuf.append( "SELECT currval(" );
     817           0 :                                 myBuf.appendAscii( &(j->second.getStr()[8]));
     818           0 :                                 value = querySingleValue( connection, myBuf.makeStringAndClear() );
     819             :                             }
     820           0 :                             break;
     821             :                         }
     822             :                     }
     823           0 :                     if( j == autoValues.end() )
     824             :                     {
     825             :                         // it even was no autovalue, no sense to continue as we can't query the
     826             :                         // inserted row
     827           0 :                         buf = OUStringBuffer();
     828           0 :                         break;
     829             :                     }
     830             :                 }
     831             : 
     832           0 :                 if( additionalCondition )
     833           0 :                     buf.append( " AND " );
     834           0 :                 bufferQuoteIdentifier( buf, keyColumnNames[i], pConnectionSettings );
     835           0 :                 buf.append( " = " );
     836           0 :                 buf.append( value );
     837           0 :                 additionalCondition = true;
     838           0 :             }
     839           0 :             query = buf.makeStringAndClear();
     840           0 :         }
     841             :     }
     842             : 
     843           0 :     if( query.getLength() )
     844             :     {
     845           0 :         Reference< com::sun::star::sdbc::XStatement > stmt = connection->createStatement();
     846           0 :         ret = stmt->executeQuery( query );
     847             :     }
     848             : 
     849           0 :     return ret;
     850             : 
     851             : }
     852             : 
     853           0 : sal_Bool Statement::execute( const OUString& sql )
     854             :         throw (SQLException, RuntimeException, std::exception)
     855             : {
     856           0 :     osl::MutexGuard guard( m_refMutex->mutex );
     857           0 :     checkClosed();
     858           0 :     OString cmd = OUStringToOString( sql, m_pSettings );
     859             : 
     860           0 :     m_lastResultset.clear();
     861           0 :     m_lastTableInserted  = OUString();
     862             : 
     863           0 :     struct CommandData data;
     864           0 :     data.refMutex = m_refMutex;
     865           0 :     data.ppSettings = &m_pSettings;
     866           0 :     data.pLastOidInserted = &m_lastOidInserted;
     867           0 :     data.pLastQuery = &m_lastQuery;
     868           0 :     data.pMultipleResultUpdateCount = &m_multipleResultUpdateCount;
     869           0 :     data.pMultipleResultAvailable = &m_multipleResultAvailable;
     870           0 :     data.pLastTableInserted = &m_lastTableInserted;
     871           0 :     data.pLastResultset = &m_lastResultset;
     872           0 :     data.owner = *this;
     873           0 :     data.tableSupplier = Reference< com::sun::star::sdbcx::XTablesSupplier >( m_connection, UNO_QUERY );
     874             :     data.concurrency =
     875           0 :         extractIntProperty( this, getStatics().RESULT_SET_CONCURRENCY );
     876           0 :     return executePostgresCommand( cmd , &data );
     877             : }
     878             : 
     879           0 : Reference< XConnection > Statement::getConnection(  )
     880             :         throw (SQLException, RuntimeException, std::exception)
     881             : {
     882           0 :     Reference< XConnection > ret;
     883             :     {
     884           0 :         MutexGuard guard( m_refMutex->mutex );
     885           0 :         checkClosed();
     886           0 :         ret = m_connection;
     887             :     }
     888           0 :     return ret;
     889             : }
     890             : 
     891             : 
     892           0 : Any Statement::getWarnings(  )
     893             :         throw (SQLException,RuntimeException, std::exception)
     894             : {
     895           0 :     return Any();
     896             : }
     897             : 
     898           0 : void Statement::clearWarnings(  )
     899             :         throw (SQLException, RuntimeException, std::exception)
     900             : {
     901           0 : }
     902             : 
     903           0 : Reference< ::com::sun::star::sdbc::XResultSetMetaData > Statement::getMetaData()
     904             :             throw (SQLException,RuntimeException, std::exception)
     905             : {
     906           0 :     Reference< com::sun::star::sdbc::XResultSetMetaData > ret;
     907           0 :     Reference< com::sun::star::sdbc::XResultSetMetaDataSupplier > supplier( m_lastResultset, UNO_QUERY );
     908           0 :     if( supplier.is() )
     909           0 :         ret = supplier->getMetaData();
     910           0 :     return ret;
     911             : }
     912             : 
     913             : 
     914           0 : ::cppu::IPropertyArrayHelper & Statement::getInfoHelper()
     915             : {
     916           0 :     return getStatementPropertyArrayHelper();
     917             : }
     918             : 
     919             : 
     920           0 : sal_Bool Statement::convertFastPropertyValue(
     921             :         Any & rConvertedValue, Any & rOldValue, sal_Int32 nHandle, const Any& rValue )
     922             :         throw (IllegalArgumentException)
     923             : {
     924           0 :     rOldValue = m_props[nHandle];
     925             :     bool bRet;
     926           0 :     switch( nHandle )
     927             :     {
     928             :     case STATEMENT_CURSOR_NAME:
     929             :     {
     930           0 :         OUString val;
     931           0 :         bRet = ( rValue >>= val );
     932           0 :         rConvertedValue = makeAny( val );
     933           0 :         break;
     934             :     }
     935             :     case STATEMENT_ESCAPE_PROCESSING:
     936             :     {
     937           0 :         bool val(false);
     938           0 :         bRet = ( rValue >>= val );
     939           0 :         rConvertedValue = makeAny( val );
     940           0 :         break;
     941             :     }
     942             :     case STATEMENT_FETCH_DIRECTION:
     943             :     case STATEMENT_FETCH_SIZE:
     944             :     case STATEMENT_MAX_FIELD_SIZE:
     945             :     case STATEMENT_MAX_ROWS:
     946             :     case STATEMENT_QUERY_TIME_OUT:
     947             :     case STATEMENT_RESULT_SET_CONCURRENCY:
     948             :     case STATEMENT_RESULT_SET_TYPE:
     949             :     {
     950             :         sal_Int32 val;
     951           0 :         bRet = ( rValue >>= val );
     952           0 :         rConvertedValue = makeAny( val );
     953           0 :         break;
     954             :     }
     955             :     default:
     956             :     {
     957           0 :         OUStringBuffer buf(128);
     958           0 :         buf.appendAscii( "pq_statement: Invalid property handle (" );
     959           0 :         buf.append( nHandle );
     960           0 :         buf.appendAscii( ")" );
     961           0 :         throw IllegalArgumentException( buf.makeStringAndClear(), *this, 2 );
     962             :     }
     963             :     }
     964           0 :     return bRet;
     965             : }
     966             : 
     967             : 
     968           0 : void Statement::setFastPropertyValue_NoBroadcast(
     969             :     sal_Int32 nHandle,const Any& rValue ) throw (Exception, std::exception)
     970             : {
     971           0 :     m_props[nHandle] = rValue;
     972           0 : }
     973             : 
     974           0 : void Statement::getFastPropertyValue( Any& rValue, sal_Int32 nHandle ) const
     975             : {
     976           0 :     rValue = m_props[nHandle];
     977           0 : }
     978             : 
     979           0 : Reference < XPropertySetInfo >  Statement::getPropertySetInfo()
     980             :         throw(RuntimeException, std::exception)
     981             : {
     982           0 :     return OPropertySetHelper::createPropertySetInfo( getStatementPropertyArrayHelper() );
     983             : }
     984             : 
     985             : 
     986           0 : Reference< XResultSet > Statement::getResultSet(  )
     987             :     throw (::com::sun::star::sdbc::SQLException, ::com::sun::star::uno::RuntimeException, std::exception)
     988             : {
     989           0 :     return Reference< XResultSet > ( m_lastResultset, com::sun::star::uno::UNO_QUERY );
     990             : }
     991             : 
     992           0 : sal_Int32 Statement::getUpdateCount(  )
     993             :     throw (::com::sun::star::sdbc::SQLException, ::com::sun::star::uno::RuntimeException, std::exception)
     994             : {
     995           0 :     return m_multipleResultUpdateCount;
     996             : }
     997             : 
     998           0 : sal_Bool Statement::getMoreResults(  )
     999             :     throw (::com::sun::star::sdbc::SQLException, ::com::sun::star::uno::RuntimeException, std::exception)
    1000             : {
    1001           0 :     return sal_False;
    1002             : }
    1003             : 
    1004             : 
    1005             : 
    1006           0 : void Statement::disposing()
    1007             : {
    1008           0 :     close();
    1009           0 : }
    1010             : 
    1011           0 : Reference< XResultSet > Statement::getGeneratedValues(  )
    1012             :         throw (SQLException, RuntimeException, std::exception)
    1013             : {
    1014           0 :     osl::MutexGuard guard( m_refMutex->mutex );
    1015             :     return getGeneratedValuesFromLastInsert(
    1016           0 :         m_pSettings, m_connection, m_lastOidInserted, m_lastTableInserted, m_lastQuery );
    1017             : }
    1018             : 
    1019             : }
    1020             : 
    1021             : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Generated by: LCOV version 1.10