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