1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_dbaccess.hxx"
30 
31 #ifndef DBACCESS_SOURCE_SDBTOOLS_CONNECTION_OBJECTNAMES_HXX
32 #include "objectnames.hxx"
33 #endif
34 
35 #ifndef DBACCESS_MODULE_SDBT_HXX
36 #include "module_sdbt.hxx"
37 #endif
38 #ifndef DBACCESS_SDBT_RESOURCE_HRC
39 #include "sdbt_resource.hrc"
40 #endif
41 
42 /** === begin UNO includes === **/
43 #include <com/sun/star/lang/NullPointerException.hpp>
44 #include <com/sun/star/sdb/CommandType.hpp>
45 #include <com/sun/star/sdbcx/XTablesSupplier.hpp>
46 #include <com/sun/star/sdb/XQueriesSupplier.hpp>
47 #include <com/sun/star/sdb/ErrorCondition.hpp>
48 /** === end UNO includes === **/
49 
50 #include <connectivity/dbmetadata.hxx>
51 #include <connectivity/dbtools.hxx>
52 #include <connectivity/sqlerror.hxx>
53 #include <cppuhelper/exc_hlp.hxx>
54 #include <rtl/ustrbuf.hxx>
55 #include <tools/string.hxx>
56 
57 #include <boost/shared_ptr.hpp>
58 
59 //........................................................................
60 namespace sdbtools
61 {
62 //........................................................................
63 
64 	/** === begin UNO using === **/
65     using ::com::sun::star::uno::Reference;
66     using ::com::sun::star::sdbc::XConnection;
67     using ::com::sun::star::lang::NullPointerException;
68     using ::com::sun::star::lang::IllegalArgumentException;
69     using ::com::sun::star::uno::RuntimeException;
70     using ::com::sun::star::sdbc::SQLException;
71     using ::com::sun::star::sdbc::XDatabaseMetaData;
72     using ::com::sun::star::uno::XInterface;
73     using ::com::sun::star::container::XNameAccess;
74     using ::com::sun::star::sdbc::XDatabaseMetaData;
75     using ::com::sun::star::uno::UNO_QUERY_THROW;
76     using ::com::sun::star::sdbcx::XTablesSupplier;
77     using ::com::sun::star::sdb::XQueriesSupplier;
78     using ::com::sun::star::uno::Exception;
79     using ::com::sun::star::uno::makeAny;
80     using ::com::sun::star::uno::Any;
81 	/** === end UNO using === **/
82 
83     namespace CommandType = ::com::sun::star::sdb::CommandType;
84     namespace ErrorCondition = ::com::sun::star::sdb::ErrorCondition;
85 
86 	//====================================================================
87 	//= INameValidation
88 	//====================================================================
89     class INameValidation
90     {
91     public:
92         virtual bool validateName( const ::rtl::OUString& _rName ) = 0;
93         virtual void validateName_throw( const ::rtl::OUString& _rName ) = 0;
94 
95         virtual ~INameValidation() { }
96     };
97     typedef ::boost::shared_ptr< INameValidation >   PNameValidation;
98 
99 	//====================================================================
100 	//= PlainExistenceCheck
101 	//====================================================================
102     class PlainExistenceCheck : public INameValidation
103     {
104     private:
105         const ::comphelper::ComponentContext    m_aContext;
106         Reference< XConnection >                m_xConnection;
107         Reference< XNameAccess >                m_xContainer;
108 
109     public:
110         PlainExistenceCheck( const ::comphelper::ComponentContext& _rContext, const Reference< XConnection >& _rxConnection, const Reference< XNameAccess >& _rxContainer )
111             :m_aContext( _rContext )
112             ,m_xConnection( _rxConnection )
113             ,m_xContainer( _rxContainer )
114         {
115             OSL_ENSURE( m_xContainer.is(), "PlainExistenceCheck::PlainExistenceCheck: this will crash!" );
116         }
117 
118         // INameValidation
119         virtual bool validateName( const ::rtl::OUString& _rName )
120         {
121             return !m_xContainer->hasByName( _rName );
122         }
123 
124         virtual void validateName_throw( const ::rtl::OUString& _rName )
125         {
126             if ( validateName( _rName ) )
127                 return;
128 
129             ::connectivity::SQLError aErrors( m_aContext );
130             SQLException aError( aErrors.getSQLException( ErrorCondition::DB_OBJECT_NAME_IS_USED, m_xConnection, _rName ) );
131 
132             ::dbtools::DatabaseMetaData aMeta( m_xConnection );
133             if ( aMeta.supportsSubqueriesInFrom() )
134             {
135                 String sNeedDistinctNames( SdbtRes( STR_QUERY_AND_TABLE_DISTINCT_NAMES ) );
136                 aError.NextException <<= SQLException( sNeedDistinctNames, m_xConnection, ::rtl::OUString(), 0, Any() );
137             }
138 
139             throw aError;
140         }
141     };
142 
143 	//====================================================================
144 	//= TableValidityCheck
145 	//====================================================================
146     class TableValidityCheck : public INameValidation
147     {
148         const ::comphelper::ComponentContext  m_aContext;
149         const Reference< XConnection >        m_xConnection;
150 
151     public:
152         TableValidityCheck( const ::comphelper::ComponentContext& _rContext, const Reference< XConnection >& _rxConnection )
153             :m_aContext( _rContext )
154             ,m_xConnection( _rxConnection )
155         {
156         }
157 
158         virtual bool validateName( const ::rtl::OUString& _rName )
159         {
160             ::dbtools::DatabaseMetaData aMeta( m_xConnection );
161             if  ( !aMeta.restrictIdentifiersToSQL92() )
162                 return true;
163 
164             ::rtl::OUString sCatalog, sSchema, sName;
165             ::dbtools::qualifiedNameComponents(
166                 m_xConnection->getMetaData(), _rName, sCatalog, sSchema, sName, ::dbtools::eInTableDefinitions );
167 
168             ::rtl::OUString sExtraNameCharacters( m_xConnection->getMetaData()->getExtraNameCharacters() );
169             if  (   ( sCatalog.getLength() && !::dbtools::isValidSQLName( sCatalog, sExtraNameCharacters ) )
170                 ||  ( sSchema.getLength() && !::dbtools::isValidSQLName( sSchema, sExtraNameCharacters ) )
171                 ||  ( sName.getLength() && !::dbtools::isValidSQLName( sName, sExtraNameCharacters ) )
172                 )
173                 return false;
174 
175             return true;
176         }
177 
178         virtual void validateName_throw( const ::rtl::OUString& _rName )
179         {
180             if ( validateName( _rName ) )
181                 return;
182 
183             ::connectivity::SQLError aErrors( m_aContext );
184             aErrors.raiseException( ErrorCondition::DB_INVALID_SQL_NAME, m_xConnection, _rName );
185         }
186     };
187 
188 	//====================================================================
189 	//= QueryValidityCheck
190 	//====================================================================
191     class QueryValidityCheck : public INameValidation
192     {
193         const ::comphelper::ComponentContext    m_aContext;
194         const Reference< XConnection >          m_xConnection;
195 
196     public:
197         QueryValidityCheck( const ::comphelper::ComponentContext& _rContext, const Reference< XConnection >& _rxConnection )
198             :m_aContext( _rContext )
199             ,m_xConnection( _rxConnection )
200         {
201         }
202 
203         inline ::connectivity::ErrorCondition validateName_getErrorCondition( const ::rtl::OUString& _rName )
204         {
205             if  (   ( _rName.indexOf( (sal_Unicode)34  ) >= 0 )  // "
206                 ||  ( _rName.indexOf( (sal_Unicode)39  ) >= 0 )  // '
207                 ||  ( _rName.indexOf( (sal_Unicode)96  ) >= 0 )  //
208                 ||  ( _rName.indexOf( (sal_Unicode)145 ) >= 0 )  //
209                 ||  ( _rName.indexOf( (sal_Unicode)146 ) >= 0 )  //
210                 ||  ( _rName.indexOf( (sal_Unicode)180 ) >= 0 )  // #86621# removed unparsable chars
211                 )
212                 return ErrorCondition::DB_QUERY_NAME_WITH_QUOTES;
213 
214             if ( _rName.indexOf( '/') >= 0 )
215                 return ErrorCondition::DB_OBJECT_NAME_WITH_SLASHES;
216 
217             return 0;
218         }
219 
220         virtual bool validateName( const ::rtl::OUString& _rName )
221         {
222             if ( validateName_getErrorCondition( _rName ) != 0 )
223                 return false;
224             return true;
225         }
226 
227         virtual void validateName_throw( const ::rtl::OUString& _rName )
228         {
229             ::connectivity::ErrorCondition nErrorCondition = validateName_getErrorCondition( _rName );
230             if ( nErrorCondition != 0 )
231             {
232                 ::connectivity::SQLError aErrors( m_aContext );
233                 aErrors.raiseException( nErrorCondition, m_xConnection );
234             }
235         }
236     };
237 
238 	//====================================================================
239 	//= CombinedNameCheck
240 	//====================================================================
241     class CombinedNameCheck : public INameValidation
242     {
243     private:
244         PNameValidation  m_pPrimary;
245         PNameValidation  m_pSecondary;
246 
247     public:
248         CombinedNameCheck( PNameValidation _pPrimary, PNameValidation _pSecondary )
249             :m_pPrimary( _pPrimary )
250             ,m_pSecondary( _pSecondary )
251         {
252             OSL_ENSURE( m_pPrimary.get() && m_pSecondary.get(), "CombinedNameCheck::CombinedNameCheck: this will crash!" );
253         }
254 
255         // INameValidation
256         virtual bool validateName( const ::rtl::OUString& _rName )
257         {
258             return m_pPrimary->validateName( _rName ) && m_pSecondary->validateName( _rName );
259         }
260 
261         virtual void validateName_throw( const ::rtl::OUString& _rName )
262         {
263             m_pPrimary->validateName_throw( _rName );
264             m_pSecondary->validateName_throw( _rName );
265         }
266     };
267 
268 	//====================================================================
269 	//= NameCheckFactory
270 	//====================================================================
271     class NameCheckFactory
272     {
273     public:
274         /** creates an INameValidation instance which can be used to check the existence of query or table names
275 
276             @param _rContext
277                 the component's context
278 
279             @param  _nCommandType
280                 the type of objects (CommandType::TABLE or CommandType::QUERY) of which names shall be checked for existence
281 
282             @param  _rxConnection
283                 the connection relative to which the names are to be checked. Must be an SDB-level connection
284 
285             @throws IllegalArgumentException
286                 if the given connection is no SDB-level connection
287 
288             @throws IllegalArgumentException
289                 if the given command type is neither CommandType::TABLE or CommandType::QUERY
290         */
291         static  PNameValidation  createExistenceCheck(
292                     const ::comphelper::ComponentContext& _rContext,
293                     sal_Int32 _nCommandType,
294                     const Reference< XConnection >& _rxConnection
295                 );
296 
297         /** creates an INameValidation instance which can be used to check the validity of a query or table name
298 
299             @param _rContext
300                 the component's context
301 
302             @param  _nCommandType
303                 the type of objects (CommandType::TABLE or CommandType::QUERY) of which names shall be validated
304 
305             @param  _rxConnection
306                 the connection relative to which the names are to be checked. Must be an SDB-level connection
307 
308             @throws IllegalArgumentException
309                 if the given connection is no SDB-level connection
310 
311             @throws IllegalArgumentException
312                 if the given command type is neither CommandType::TABLE or CommandType::QUERY
313         */
314         static  PNameValidation  createValidityCheck(
315                     const ::comphelper::ComponentContext& _rContext,
316                     const sal_Int32 _nCommandType,
317                     const Reference< XConnection >& _rxConnection
318                 );
319 
320     private:
321         NameCheckFactory();                                     // never implemented
322 
323     private:
324         static  void    verifyCommandType( sal_Int32 _nCommandType );
325     };
326 
327 	//--------------------------------------------------------------------
328     void NameCheckFactory::verifyCommandType( sal_Int32 _nCommandType )
329     {
330         if  (   ( _nCommandType != CommandType::TABLE )
331             &&  ( _nCommandType != CommandType::QUERY )
332             )
333             throw IllegalArgumentException(
334                 String( SdbtRes( STR_INVALID_COMMAND_TYPE ) ),
335                 NULL,
336                 0
337             );
338     }
339 
340 	//--------------------------------------------------------------------
341     PNameValidation  NameCheckFactory::createExistenceCheck( const ::comphelper::ComponentContext& _rContext, sal_Int32 _nCommandType, const Reference< XConnection >& _rxConnection )
342     {
343         verifyCommandType( _nCommandType );
344 
345         ::dbtools::DatabaseMetaData aMeta( _rxConnection );
346 
347         Reference< XNameAccess > xTables, xQueries;
348         try
349         {
350             Reference< XTablesSupplier > xSuppTables( _rxConnection, UNO_QUERY_THROW );
351             Reference< XQueriesSupplier > xQueriesSupplier( _rxConnection, UNO_QUERY_THROW );
352             xTables.set( xSuppTables->getTables(), UNO_QUERY_THROW );
353             xQueries.set( xQueriesSupplier->getQueries(), UNO_QUERY_THROW );
354         }
355         catch( const Exception& )
356         {
357         	throw IllegalArgumentException(
358                 String( SdbtRes( STR_CONN_WITHOUT_QUERIES_OR_TABLES ) ),
359                 NULL,
360                 0
361             );
362         }
363 
364         PNameValidation pTableCheck( new PlainExistenceCheck( _rContext, _rxConnection, xTables ) );
365         PNameValidation pQueryCheck( new PlainExistenceCheck( _rContext, _rxConnection, xQueries ) );
366         PNameValidation pReturn;
367 
368         if ( aMeta.supportsSubqueriesInFrom() )
369             pReturn.reset( new CombinedNameCheck( pTableCheck, pQueryCheck ) );
370         else if ( _nCommandType == CommandType::TABLE )
371             pReturn = pTableCheck;
372         else
373             pReturn = pQueryCheck;
374         return pReturn;
375     }
376 
377 	//--------------------------------------------------------------------
378     PNameValidation  NameCheckFactory::createValidityCheck( const ::comphelper::ComponentContext& _rContext, sal_Int32 _nCommandType, const Reference< XConnection >& _rxConnection )
379     {
380         verifyCommandType( _nCommandType );
381 
382         Reference< XDatabaseMetaData > xMeta;
383         try
384         {
385             xMeta.set( _rxConnection->getMetaData(), UNO_QUERY_THROW );
386         }
387         catch( const Exception& )
388         {
389         	throw IllegalArgumentException(
390                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "The connection could not provide its database's meta data." ) ),
391                 NULL,
392                 0
393             );
394         }
395 
396         if ( _nCommandType == CommandType::TABLE )
397             return PNameValidation( new TableValidityCheck( _rContext, _rxConnection ) );
398         return PNameValidation( new QueryValidityCheck( _rContext, _rxConnection ) );
399     }
400 
401 	//====================================================================
402 	//= ObjectNames_Impl
403 	//====================================================================
404     struct ObjectNames_Impl
405     {
406         SdbtClient  m_aModuleClient;    // keep the module alive as long as this instance lives
407     };
408 
409 	//====================================================================
410 	//= ObjectNames
411 	//====================================================================
412 	//--------------------------------------------------------------------
413     ObjectNames::ObjectNames( const ::comphelper::ComponentContext& _rContext, const Reference< XConnection >& _rxConnection )
414         :ConnectionDependentComponent( _rContext )
415         ,m_pImpl( new ObjectNames_Impl )
416     {
417         if ( !_rxConnection.is() )
418             throw NullPointerException();
419         setWeakConnection( _rxConnection );
420     }
421 
422 	//--------------------------------------------------------------------
423     ObjectNames::~ObjectNames()
424     {
425     }
426 
427     //--------------------------------------------------------------------
428     ::rtl::OUString SAL_CALL ObjectNames::suggestName( ::sal_Int32 _CommandType, const ::rtl::OUString& _BaseName ) throw (IllegalArgumentException, RuntimeException)
429     {
430         EntryGuard aGuard( *this );
431 
432         PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( getContext(), _CommandType, getConnection() ) );
433 
434         String sBaseName( _BaseName );
435         if ( sBaseName.Len() == 0 )
436         {
437             if ( _CommandType == CommandType::TABLE )
438                 sBaseName = String( SdbtRes( STR_BASENAME_TABLE ) );
439             else
440                 sBaseName = String( SdbtRes( STR_BASENAME_QUERY ) );
441         }
442 
443         ::rtl::OUString sName( sBaseName );
444         sal_Int32 i = 1;
445         while ( !pNameCheck->validateName( sName ) )
446         {
447             ::rtl::OUStringBuffer aNameBuffer;
448             aNameBuffer.append( sBaseName );
449             aNameBuffer.appendAscii( " " );
450             aNameBuffer.append( (sal_Int32)++i );
451             sName = aNameBuffer.makeStringAndClear();
452         }
453 
454         return sName;
455     }
456 
457     //--------------------------------------------------------------------
458     ::rtl::OUString SAL_CALL ObjectNames::convertToSQLName( const ::rtl::OUString& Name ) throw (RuntimeException)
459     {
460         EntryGuard aGuard( *this );
461         Reference< XDatabaseMetaData > xMeta( getConnection()->getMetaData(), UNO_QUERY_THROW );
462         return ::dbtools::convertName2SQLName( Name, xMeta->getExtraNameCharacters() );
463     }
464 
465     //--------------------------------------------------------------------
466     ::sal_Bool SAL_CALL ObjectNames::isNameUsed( ::sal_Int32 _CommandType, const ::rtl::OUString& _Name ) throw (IllegalArgumentException, RuntimeException)
467     {
468         EntryGuard aGuard( *this );
469 
470         PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( getContext(), _CommandType, getConnection()) );
471         return !pNameCheck->validateName( _Name );
472     }
473 
474     //--------------------------------------------------------------------
475     ::sal_Bool SAL_CALL ObjectNames::isNameValid( ::sal_Int32 _CommandType, const ::rtl::OUString& _Name ) throw (IllegalArgumentException, RuntimeException)
476     {
477         EntryGuard aGuard( *this );
478 
479         PNameValidation pNameCheck( NameCheckFactory::createValidityCheck( getContext(), _CommandType, getConnection()) );
480         return pNameCheck->validateName( _Name );
481     }
482 
483     //--------------------------------------------------------------------
484     void SAL_CALL ObjectNames::checkNameForCreate( ::sal_Int32 _CommandType, const ::rtl::OUString& _Name ) throw (SQLException, RuntimeException)
485     {
486         EntryGuard aGuard( *this );
487 
488         PNameValidation pNameCheck( NameCheckFactory::createExistenceCheck( getContext(), _CommandType, getConnection() ) );
489         pNameCheck->validateName_throw( _Name );
490 
491         pNameCheck = NameCheckFactory::createValidityCheck( getContext(), _CommandType, getConnection() );
492         pNameCheck->validateName_throw( _Name );
493     }
494 
495 //........................................................................
496 } // namespace sdbtools
497 //........................................................................
498 
499