1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include "objectnames.hxx" 21 #include <core_resource.hxx> 22 23 #include <strings.hrc> 24 25 #include <com/sun/star/sdb/CommandType.hpp> 26 #include <com/sun/star/sdbcx/XTablesSupplier.hpp> 27 #include <com/sun/star/sdb/XQueriesSupplier.hpp> 28 #include <com/sun/star/sdb/ErrorCondition.hpp> 29 30 #include <connectivity/dbmetadata.hxx> 31 #include <connectivity/dbtools.hxx> 32 #include <connectivity/sqlerror.hxx> 33 #include <cppuhelper/exc_hlp.hxx> 34 #include <rtl/ustrbuf.hxx> 35 #include <osl/diagnose.h> 36 37 #include <memory> 38 39 namespace sdbtools 40 { 41 42 using ::com::sun::star::uno::Reference; 43 using ::com::sun::star::sdbc::XConnection; 44 using ::com::sun::star::lang::IllegalArgumentException; 45 using ::com::sun::star::sdbc::SQLException; 46 using ::com::sun::star::sdbc::XDatabaseMetaData; 47 using ::com::sun::star::container::XNameAccess; 48 using ::com::sun::star::uno::UNO_QUERY_THROW; 49 using ::com::sun::star::sdbcx::XTablesSupplier; 50 using ::com::sun::star::sdb::XQueriesSupplier; 51 using ::com::sun::star::uno::Exception; 52 using ::com::sun::star::uno::Any; 53 using ::com::sun::star::uno::XComponentContext; 54 55 namespace CommandType = ::com::sun::star::sdb::CommandType; 56 namespace ErrorCondition = ::com::sun::star::sdb::ErrorCondition; 57 58 // INameValidation 59 class INameValidation 60 { 61 public: 62 virtual bool validateName( const OUString& _rName ) = 0; 63 virtual void validateName_throw( const OUString& _rName ) = 0; 64 ~INameValidation()65 virtual ~INameValidation() { } 66 }; 67 typedef std::shared_ptr< INameValidation > PNameValidation; 68 69 // PlainExistenceCheck 70 class PlainExistenceCheck : public INameValidation 71 { 72 private: 73 Reference< XConnection > m_xConnection; 74 Reference< XNameAccess > m_xContainer; 75 76 public: PlainExistenceCheck(const Reference<XConnection> & _rxConnection,const Reference<XNameAccess> & _rxContainer)77 PlainExistenceCheck( const Reference< XConnection >& _rxConnection, const Reference< XNameAccess >& _rxContainer ) 78 :m_xConnection( _rxConnection ) 79 ,m_xContainer( _rxContainer ) 80 { 81 OSL_ENSURE( m_xContainer.is(), "PlainExistenceCheck::PlainExistenceCheck: this will crash!" ); 82 } 83 84 // INameValidation validateName(const OUString & _rName)85 virtual bool validateName( const OUString& _rName ) override 86 { 87 return !m_xContainer->hasByName( _rName ); 88 } 89 validateName_throw(const OUString & _rName)90 virtual void validateName_throw( const OUString& _rName ) override 91 { 92 if ( validateName( _rName ) ) 93 return; 94 95 ::connectivity::SQLError aErrors; 96 SQLException aError( aErrors.getSQLException( ErrorCondition::DB_OBJECT_NAME_IS_USED, m_xConnection, _rName ) ); 97 98 ::dbtools::DatabaseMetaData aMeta( m_xConnection ); 99 if ( aMeta.supportsSubqueriesInFrom() ) 100 { 101 OUString sNeedDistinctNames( DBA_RES( STR_QUERY_AND_TABLE_DISTINCT_NAMES ) ); 102 aError.NextException <<= SQLException( sNeedDistinctNames, m_xConnection, OUString(), 0, Any() ); 103 } 104 105 throw aError; 106 } 107 }; 108 109 // TableValidityCheck 110 class TableValidityCheck : public INameValidation 111 { 112 const Reference< XConnection > m_xConnection; 113 114 public: TableValidityCheck(const Reference<XConnection> & _rxConnection)115 TableValidityCheck( const Reference< XConnection >& _rxConnection ) 116 :m_xConnection( _rxConnection ) 117 { 118 } 119 validateName(const OUString & _rName)120 virtual bool validateName( const OUString& _rName ) override 121 { 122 ::dbtools::DatabaseMetaData aMeta( m_xConnection ); 123 if ( !aMeta.restrictIdentifiersToSQL92() ) 124 return true; 125 126 OUString sCatalog, sSchema, sName; 127 ::dbtools::qualifiedNameComponents( 128 m_xConnection->getMetaData(), _rName, sCatalog, sSchema, sName, ::dbtools::EComposeRule::InTableDefinitions ); 129 130 OUString sExtraNameCharacters( m_xConnection->getMetaData()->getExtraNameCharacters() ); 131 return !( ( !sCatalog.isEmpty() && !::dbtools::isValidSQLName( sCatalog, sExtraNameCharacters ) ) 132 || ( !sSchema.isEmpty() && !::dbtools::isValidSQLName( sSchema, sExtraNameCharacters ) ) 133 || ( !sName.isEmpty() && !::dbtools::isValidSQLName( sName, sExtraNameCharacters ) )); 134 } 135 validateName_throw(const OUString & _rName)136 virtual void validateName_throw( const OUString& _rName ) override 137 { 138 if ( validateName( _rName ) ) 139 return; 140 141 ::connectivity::SQLError aErrors; 142 aErrors.raiseException( ErrorCondition::DB_INVALID_SQL_NAME, m_xConnection, _rName ); 143 } 144 }; 145 146 // QueryValidityCheck 147 class QueryValidityCheck : public INameValidation 148 { 149 const Reference< XConnection > m_xConnection; 150 151 public: QueryValidityCheck(const Reference<XConnection> & _rxConnection)152 QueryValidityCheck( const Reference< XConnection >& _rxConnection ) 153 :m_xConnection( _rxConnection ) 154 { 155 } 156 validateName_getErrorCondition(const OUString & _rName)157 static ::connectivity::ErrorCondition validateName_getErrorCondition( const OUString& _rName ) 158 { 159 if ( ( _rName.indexOf( u'"' ) >= 0 ) 160 || ( _rName.indexOf( u'\'' ) >= 0 ) 161 || ( _rName.indexOf( u'`' ) >= 0 ) 162 || ( _rName.indexOf( u'\x0091' ) >= 0 ) 163 || ( _rName.indexOf( u'\x0092' ) >= 0 ) 164 || ( _rName.indexOf( u'\x00B4' ) >= 0 ) // removed unparsable chars 165 ) 166 return ErrorCondition::DB_QUERY_NAME_WITH_QUOTES; 167 168 if ( _rName.indexOf( '/') >= 0 ) 169 return ErrorCondition::DB_OBJECT_NAME_WITH_SLASHES; 170 171 return 0; 172 } 173 validateName(const OUString & _rName)174 virtual bool validateName( const OUString& _rName ) override 175 { 176 return validateName_getErrorCondition( _rName ) == 0; 177 } 178 validateName_throw(const OUString & _rName)179 virtual void validateName_throw( const OUString& _rName ) override 180 { 181 ::connectivity::ErrorCondition nErrorCondition = validateName_getErrorCondition( _rName ); 182 if ( nErrorCondition != 0 ) 183 { 184 ::connectivity::SQLError aErrors; 185 aErrors.raiseException( nErrorCondition, m_xConnection ); 186 } 187 } 188 }; 189 190 // CombinedNameCheck 191 class CombinedNameCheck : public INameValidation 192 { 193 private: 194 PNameValidation m_pPrimary; 195 PNameValidation m_pSecondary; 196 197 public: CombinedNameCheck(const PNameValidation & _pPrimary,const PNameValidation & _pSecondary)198 CombinedNameCheck(const PNameValidation& _pPrimary, const PNameValidation& _pSecondary) 199 :m_pPrimary( _pPrimary ) 200 ,m_pSecondary( _pSecondary ) 201 { 202 OSL_ENSURE( m_pPrimary.get() && m_pSecondary.get(), "CombinedNameCheck::CombinedNameCheck: this will crash!" ); 203 } 204 205 // INameValidation validateName(const OUString & _rName)206 virtual bool validateName( const OUString& _rName ) override 207 { 208 return m_pPrimary->validateName( _rName ) && m_pSecondary->validateName( _rName ); 209 } 210 validateName_throw(const OUString & _rName)211 virtual void validateName_throw( const OUString& _rName ) override 212 { 213 m_pPrimary->validateName_throw( _rName ); 214 m_pSecondary->validateName_throw( _rName ); 215 } 216 }; 217 218 // NameCheckFactory 219 class NameCheckFactory 220 { 221 public: 222 NameCheckFactory(const NameCheckFactory&) = delete; 223 const NameCheckFactory& operator=(const NameCheckFactory&) = delete; 224 /** creates an INameValidation instance which can be used to check the existence of query or table names 225 226 @param _nCommandType 227 the type of objects (CommandType::TABLE or CommandType::QUERY) of which names shall be checked for existence 228 229 @param _rxConnection 230 the connection relative to which the names are to be checked. Must be an SDB-level connection 231 232 @throws IllegalArgumentException 233 if the given connection is no SDB-level connection 234 235 @throws IllegalArgumentException 236 if the given command type is neither CommandType::TABLE or CommandType::QUERY 237 */ 238 static PNameValidation createExistenceCheck( 239 sal_Int32 _nCommandType, 240 const Reference< XConnection >& _rxConnection 241 ); 242 243 /** creates an INameValidation instance which can be used to check the validity of a query or table name 244 245 @param _nCommandType 246 the type of objects (CommandType::TABLE or CommandType::QUERY) of which names shall be validated 247 248 @param _rxConnection 249 the connection relative to which the names are to be checked. Must be an SDB-level connection 250 251 @throws IllegalArgumentException 252 if the given connection is no SDB-level connection 253 254 @throws IllegalArgumentException 255 if the given command type is neither CommandType::TABLE or CommandType::QUERY 256 */ 257 static PNameValidation createValidityCheck( 258 const sal_Int32 _nCommandType, 259 const Reference< XConnection >& _rxConnection 260 ); 261 262 private: 263 static void verifyCommandType( sal_Int32 _nCommandType ); 264 }; 265 verifyCommandType(sal_Int32 _nCommandType)266 void NameCheckFactory::verifyCommandType( sal_Int32 _nCommandType ) 267 { 268 if ( ( _nCommandType != CommandType::TABLE ) 269 && ( _nCommandType != CommandType::QUERY ) 270 ) 271 throw IllegalArgumentException( 272 DBA_RES( STR_INVALID_COMMAND_TYPE ), 273 nullptr, 274 0 275 ); 276 } 277 createExistenceCheck(sal_Int32 _nCommandType,const Reference<XConnection> & _rxConnection)278 PNameValidation NameCheckFactory::createExistenceCheck( sal_Int32 _nCommandType, const Reference< XConnection >& _rxConnection ) 279 { 280 verifyCommandType( _nCommandType ); 281 282 ::dbtools::DatabaseMetaData aMeta( _rxConnection ); 283 284 Reference< XNameAccess > xTables, xQueries; 285 try 286 { 287 Reference< XTablesSupplier > xSuppTables( _rxConnection, UNO_QUERY_THROW ); 288 Reference< XQueriesSupplier > xQueriesSupplier( _rxConnection, UNO_QUERY_THROW ); 289 xTables.set( xSuppTables->getTables(), css::uno::UNO_SET_THROW ); 290 xQueries.set( xQueriesSupplier->getQueries(), css::uno::UNO_SET_THROW ); 291 } 292 catch( const Exception& ) 293 { 294 throw IllegalArgumentException( 295 DBA_RES( STR_CONN_WITHOUT_QUERIES_OR_TABLES ), 296 nullptr, 297 0 298 ); 299 } 300 301 PNameValidation pTableCheck( new PlainExistenceCheck( _rxConnection, xTables ) ); 302 PNameValidation pQueryCheck( new PlainExistenceCheck( _rxConnection, xQueries ) ); 303 PNameValidation pReturn; 304 305 if ( aMeta.supportsSubqueriesInFrom() ) 306 pReturn.reset( new CombinedNameCheck( pTableCheck, pQueryCheck ) ); 307 else if ( _nCommandType == CommandType::TABLE ) 308 pReturn = pTableCheck; 309 else 310 pReturn = pQueryCheck; 311 return pReturn; 312 } 313 createValidityCheck(sal_Int32 _nCommandType,const Reference<XConnection> & _rxConnection)314 PNameValidation NameCheckFactory::createValidityCheck( sal_Int32 _nCommandType, const Reference< XConnection >& _rxConnection ) 315 { 316 verifyCommandType( _nCommandType ); 317 318 Reference< XDatabaseMetaData > xMeta; 319 try 320 { 321 xMeta.set( _rxConnection->getMetaData(), css::uno::UNO_SET_THROW ); 322 } 323 catch( const Exception& ) 324 { 325 throw IllegalArgumentException( 326 "The connection could not provide its database's meta data.", 327 nullptr, 328 0 329 ); 330 } 331 332 if ( _nCommandType == CommandType::TABLE ) 333 return PNameValidation( new TableValidityCheck( _rxConnection ) ); 334 return PNameValidation( new QueryValidityCheck( _rxConnection ) ); 335 } 336 337 // ObjectNames ObjectNames(const Reference<XComponentContext> & _rContext,const Reference<XConnection> & _rxConnection)338 ObjectNames::ObjectNames( const Reference<XComponentContext>& _rContext, const Reference< XConnection >& _rxConnection ) 339 :ConnectionDependentComponent( _rContext ) 340 { 341 setWeakConnection( _rxConnection ); 342 } 343 ~ObjectNames()344 ObjectNames::~ObjectNames() 345 { 346 } 347 suggestName(::sal_Int32 CommandType,const OUString & BaseName)348 OUString SAL_CALL ObjectNames::suggestName( ::sal_Int32 CommandType, const OUString& BaseName ) 349 { 350 EntryGuard aGuard( *this ); 351 352 PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( CommandType, getConnection() ) ); 353 354 OUString sBaseName( BaseName ); 355 if ( sBaseName.isEmpty() ) 356 { 357 if ( CommandType == CommandType::TABLE ) 358 sBaseName = DBA_RES(STR_BASENAME_TABLE); 359 else 360 sBaseName = DBA_RES(STR_BASENAME_QUERY); 361 } 362 else if( CommandType == CommandType::QUERY ) 363 { 364 sBaseName=sBaseName.replace('/', '_'); 365 } 366 367 OUString sName( sBaseName ); 368 sal_Int32 i = 1; 369 while ( !pNameCheck->validateName( sName ) ) 370 { 371 sName = sBaseName + " " + OUString::number(++i); 372 } 373 374 return sName; 375 } 376 convertToSQLName(const OUString & Name)377 OUString SAL_CALL ObjectNames::convertToSQLName( const OUString& Name ) 378 { 379 EntryGuard aGuard( *this ); 380 Reference< XDatabaseMetaData > xMeta( getConnection()->getMetaData(), css::uno::UNO_SET_THROW ); 381 return ::dbtools::convertName2SQLName( Name, xMeta->getExtraNameCharacters() ); 382 } 383 isNameUsed(::sal_Int32 CommandType,const OUString & Name)384 sal_Bool SAL_CALL ObjectNames::isNameUsed( ::sal_Int32 CommandType, const OUString& Name ) 385 { 386 EntryGuard aGuard( *this ); 387 388 PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( CommandType, getConnection()) ); 389 return !pNameCheck->validateName( Name ); 390 } 391 isNameValid(::sal_Int32 CommandType,const OUString & Name)392 sal_Bool SAL_CALL ObjectNames::isNameValid( ::sal_Int32 CommandType, const OUString& Name ) 393 { 394 EntryGuard aGuard( *this ); 395 396 PNameValidation pNameCheck( NameCheckFactory::createValidityCheck( CommandType, getConnection()) ); 397 return pNameCheck->validateName( Name ); 398 } 399 checkNameForCreate(::sal_Int32 CommandType,const OUString & Name)400 void SAL_CALL ObjectNames::checkNameForCreate( ::sal_Int32 CommandType, const OUString& Name ) 401 { 402 EntryGuard aGuard( *this ); 403 404 PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( CommandType, getConnection() ) ); 405 pNameCheck->validateName_throw( Name ); 406 407 pNameCheck = NameCheckFactory::createValidityCheck( CommandType, getConnection() ); 408 pNameCheck->validateName_throw( Name ); 409 } 410 411 } // namespace sdbtools 412 413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 414