xref: /core/dbaccess/source/ui/querydesign/querycontroller.cxx (revision 6884f6cd61b5a047375cf09c338c4055c919794b)
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 <browserids.hxx>
21 #include <core_resource.hxx>
22 #include <strings.hrc>
23 #include <strings.hxx>
24 #include <query.hrc>
25 #include <stringconstants.hxx>
26 #include <defaultobjectnamecheck.hxx>
27 #include <dlgsave.hxx>
28 #include <querycontainerwindow.hxx>
29 #include <querycontroller.hxx>
30 #include <QueryDesignView.hxx>
31 #include <QueryTableView.hxx>
32 #include <sqlmessage.hxx>
33 #include <TableConnectionData.hxx>
34 #include <TableFieldDescription.hxx>
35 #include <UITools.hxx>
36 #include <QueryPropertiesDialog.hxx>
37 
38 #include <com/sun/star/beans/PropertyAttribute.hpp>
39 #include <com/sun/star/container/XNameContainer.hpp>
40 #include <com/sun/star/frame/FrameSearchFlag.hpp>
41 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
42 #include <com/sun/star/sdb/CommandType.hpp>
43 #include <com/sun/star/sdb/SQLContext.hpp>
44 #include <com/sun/star/sdb/XQueriesSupplier.hpp>
45 #include <com/sun/star/sdb/XQueryDefinitionsSupplier.hpp>
46 #include <com/sun/star/sdb/XSQLQueryComposerFactory.hpp>
47 #include <com/sun/star/sdbcx/XAppend.hpp>
48 #include <com/sun/star/sdbcx/XDataDescriptorFactory.hpp>
49 #include <com/sun/star/sdbcx/XDrop.hpp>
50 #include <com/sun/star/sdbcx/XTablesSupplier.hpp>
51 #include <com/sun/star/sdbcx/XViewsSupplier.hpp>
52 #include <com/sun/star/ui/dialogs/XExecutableDialog.hpp>
53 #include <com/sun/star/util/XCloseable.hpp>
54 #include <com/sun/star/util/VetoException.hpp>
55 #include <com/sun/star/ui/XUIElement.hpp>
56 
57 #include <comphelper/propertysequence.hxx>
58 #include <comphelper/property.hxx>
59 #include <comphelper/types.hxx>
60 #include <connectivity/dbexception.hxx>
61 #include <connectivity/dbtools.hxx>
62 #include <cppuhelper/exc_hlp.hxx>
63 #include <svl/undo.hxx>
64 #include <toolkit/helper/vclunohelper.hxx>
65 #include <comphelper/diagnose_ex.hxx>
66 #include <osl/diagnose.h>
67 #include <utility>
68 #include <vcl/stdtext.hxx>
69 #include <vcl/svapp.hxx>
70 #include <vcl/weld.hxx>
71 #include <osl/mutex.hxx>
72 #include <o3tl/string_view.hxx>
73 #include <memory>
74 #include <vector>
75 
76 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
org_openoffice_comp_dbu_OQueryDesign_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)77 org_openoffice_comp_dbu_OQueryDesign_get_implementation(
78     css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
79 {
80     return cppu::acquire(new ::dbaui::OQueryController(context));
81 }
82 
83 namespace dbaui
84 {
85     using namespace ::com::sun::star::uno;
86     using namespace ::com::sun::star::beans;
87     using namespace ::com::sun::star::frame;
88     using namespace ::com::sun::star::util;
89     using namespace ::com::sun::star::lang;
90 
91     namespace {
92 
93     class OViewController : public OQueryController
94     {
getImplementationName()95         virtual OUString SAL_CALL getImplementationName() override
96         {
97             return u"org.openoffice.comp.dbu.OViewDesign"_ustr;
98         }
getSupportedServiceNames()99         virtual Sequence< OUString> SAL_CALL getSupportedServiceNames() override
100         {
101             return { u"com.sun.star.sdb.ViewDesign"_ustr };
102         }
103 
104     public:
OViewController(const Reference<XComponentContext> & _rM)105         explicit OViewController(const Reference< XComponentContext >& _rM) : OQueryController(_rM){}
106     };
107 
108     }
109 }
110 
111 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
org_openoffice_comp_dbu_OViewDesign_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)112 org_openoffice_comp_dbu_OViewDesign_get_implementation(
113     css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& )
114 {
115     return cppu::acquire(new ::dbaui::OViewController(context));
116 }
117 
118 namespace dbaui
119 {
120     using namespace ::connectivity;
121 
122     namespace
123     {
lcl_getObjectResourceString(TranslateId pResId,sal_Int32 _nCommandType)124         OUString lcl_getObjectResourceString(TranslateId pResId, sal_Int32 _nCommandType)
125         {
126             OUString sMessageText = DBA_RES(pResId);
127             OUString sObjectType = DBA_RES(RSC_QUERY_OBJECT_TYPE[_nCommandType]);
128             sMessageText = sMessageText.replaceFirst( "$object$", sObjectType );
129             return sMessageText;
130         }
131     }
132 
133 using namespace ::com::sun::star::container;
134 using namespace ::com::sun::star::sdbcx;
135 using namespace ::com::sun::star::sdbc;
136 using namespace ::com::sun::star::sdb;
137 using namespace ::com::sun::star::ui;
138 using namespace ::com::sun::star::awt;
139 using namespace ::dbtools;
140 
141 using namespace ::comphelper;
142 
143 namespace
144 {
ensureToolbars(OQueryController & _rController,bool _bDesign)145     void ensureToolbars( OQueryController& _rController, bool _bDesign )
146     {
147         Reference< css::frame::XLayoutManager > xLayoutManager = OGenericUnoController::getLayoutManager( _rController.getFrame() );
148         if ( !xLayoutManager.is() )
149             return;
150 
151         xLayoutManager->lock();
152         static constexpr OUString s_sDesignToolbar = u"private:resource/toolbar/designobjectbar"_ustr;
153         static constexpr OUString s_sSqlToolbar = u"private:resource/toolbar/sqlobjectbar"_ustr;
154         if ( _bDesign )
155         {
156             xLayoutManager->destroyElement( s_sSqlToolbar );
157             xLayoutManager->createElement( s_sDesignToolbar );
158         }
159         else
160         {
161             xLayoutManager->destroyElement( s_sDesignToolbar );
162             xLayoutManager->createElement( s_sSqlToolbar );
163         }
164         xLayoutManager->unlock();
165         xLayoutManager->doLayout();
166     }
167 
168     /**
169      * The value of m_nLimit is updated when LimitBox loses its focus
170      * So in those case when execution needs recent data, grab the focus
171      * (e.g. execute SQL statement, change views)
172      */
grabFocusFromLimitBox(OQueryController & _rController)173     void grabFocusFromLimitBox( OQueryController& _rController )
174     {
175         Reference< XLayoutManager > xLayoutManager = OGenericUnoController::getLayoutManager( _rController.getFrame() );
176         Reference< XUIElement > xUIElement = xLayoutManager->getElement(u"private:resource/toolbar/designobjectbar"_ustr);
177         if (xUIElement.is())
178         {
179             Reference< XWindow > xWindow(xUIElement->getRealInterface(), css::uno::UNO_QUERY);
180             VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow( xWindow );
181             if( pWindow && pWindow->HasChildPathFocus() )
182             {
183                 pWindow->GrabFocusToDocument();
184             }
185         }
186     }
187 }
188 
getImplementationName()189 OUString SAL_CALL OQueryController::getImplementationName()
190 {
191     return u"org.openoffice.comp.dbu.OQueryDesign"_ustr;
192 }
193 
getSupportedServiceNames()194 Sequence< OUString> SAL_CALL OQueryController::getSupportedServiceNames()
195 {
196     return { u"com.sun.star.sdb.QueryDesign"_ustr };
197 }
198 
OQueryController(const Reference<XComponentContext> & _rM)199 OQueryController::OQueryController(const Reference< XComponentContext >& _rM)
200     :OJoinController(_rM)
201     ,OQueryController_PBase( getBroadcastHelper() )
202     ,m_pParseContext( new svxform::OSystemParseContext )
203     ,m_aSqlParser( _rM, m_pParseContext.get() )
204     ,m_nLimit(-1)
205     ,m_nVisibleRows(0x400)
206     ,m_nSplitPos(-1)
207     ,m_nCommandType( CommandType::QUERY )
208     ,m_bGraphicalDesign(false)
209     ,m_bDistinct(false)
210     ,m_bEscapeProcessing(true)
211 {
212     InvalidateAll();
213 
214     registerProperty( PROPERTY_ACTIVECOMMAND, PROPERTY_ID_ACTIVECOMMAND, PropertyAttribute::READONLY | PropertyAttribute::BOUND,
215         &m_sStatement, cppu::UnoType<decltype(m_sStatement)>::get() );
216     registerProperty( PROPERTY_ESCAPE_PROCESSING, PROPERTY_ID_ESCAPE_PROCESSING, PropertyAttribute::READONLY | PropertyAttribute::BOUND,
217         &m_bEscapeProcessing, cppu::UnoType<decltype(m_bEscapeProcessing)>::get() );
218 }
219 
~OQueryController()220 OQueryController::~OQueryController()
221 {
222     if ( !getBroadcastHelper().bDisposed && !getBroadcastHelper().bInDispose )
223     {
224         OSL_FAIL("Please check who doesn't dispose this component!");
225         // increment ref count to prevent double call of Dtor
226         osl_atomic_increment( &m_refCount );
227         dispose();
228     }
229 }
230 
IMPLEMENT_FORWARD_XINTERFACE2(OQueryController,OJoinController,OQueryController_PBase)231 IMPLEMENT_FORWARD_XINTERFACE2( OQueryController, OJoinController, OQueryController_PBase )
232 IMPLEMENT_FORWARD_XTYPEPROVIDER2( OQueryController, OJoinController, OQueryController_PBase )
233 
234 Reference< XPropertySetInfo > SAL_CALL OQueryController::getPropertySetInfo()
235 {
236     Reference< XPropertySetInfo > xInfo( createPropertySetInfo( getInfoHelper() ) );
237     return xInfo;
238 }
239 
getFastPropertyValue(Any & o_rValue,sal_Int32 i_nHandle) const240 void SAL_CALL OQueryController::getFastPropertyValue( Any& o_rValue, sal_Int32 i_nHandle ) const
241 {
242     switch ( i_nHandle )
243     {
244     case PROPERTY_ID_CURRENT_QUERY_DESIGN:
245     {
246         ::comphelper::NamedValueCollection aCurrentDesign;
247         aCurrentDesign.put( u"GraphicalDesign"_ustr, isGraphicalDesign() );
248         aCurrentDesign.put( PROPERTY_ESCAPE_PROCESSING, m_bEscapeProcessing );
249 
250         if ( isGraphicalDesign() )
251         {
252             getContainer()->SaveUIConfig();
253             saveViewSettings( aCurrentDesign, true );
254             aCurrentDesign.put( u"Statement"_ustr, m_sStatement );
255         }
256         else
257         {
258             aCurrentDesign.put( u"Statement"_ustr, getContainer()->getStatement() );
259         }
260 
261         o_rValue <<= aCurrentDesign.getPropertyValues();
262     }
263     break;
264 
265     default:
266         OPropertyContainer::getFastPropertyValue( o_rValue, i_nHandle );
267         break;
268     }
269 }
270 
getInfoHelper()271 ::cppu::IPropertyArrayHelper& OQueryController::getInfoHelper()
272 {
273     return *getArrayHelper();
274 }
275 
createArrayHelper() const276 ::cppu::IPropertyArrayHelper* OQueryController::createArrayHelper( ) const
277 {
278     Sequence< Property > aProps;
279     describeProperties( aProps );
280 
281     // one additional property:
282     const sal_Int32 nLength = aProps.getLength();
283     aProps.realloc( nLength + 1 );
284     auto pProps = aProps.getArray();
285     pProps[ nLength ] = Property(
286         u"CurrentQueryDesign"_ustr,
287         PROPERTY_ID_CURRENT_QUERY_DESIGN,
288         ::cppu::UnoType< Sequence< PropertyValue > >::get(),
289         PropertyAttribute::READONLY
290     );
291 
292     std::sort(
293         pProps,
294         pProps + aProps.getLength(),
295         ::comphelper::PropertyCompareByName()
296     );
297 
298     return new ::cppu::OPropertyArrayHelper(aProps);
299 }
300 
deleteIterator()301 void OQueryController::deleteIterator()
302 {
303     if(m_pSqlIterator)
304     {
305         delete m_pSqlIterator->getParseTree();
306         m_pSqlIterator->dispose();
307         m_pSqlIterator.reset();
308     }
309 }
310 
disposing()311 void OQueryController::disposing()
312 {
313     OQueryController_PBase::disposing();
314 
315     deleteIterator();
316 
317     m_pParseContext.reset();
318 
319     clearFields();
320     OTableFields().swap(m_vUnUsedFieldsDesc);
321 
322     ::comphelper::disposeComponent(m_xComposer);
323     OJoinController::disposing();
324     OQueryController_PBase::disposing();
325 }
326 
clearFields()327 void OQueryController::clearFields()
328 {
329     OTableFields().swap(m_vTableFieldDesc);
330 }
331 
GetState(sal_uInt16 _nId) const332 FeatureState OQueryController::GetState(sal_uInt16 _nId) const
333 {
334     FeatureState aReturn;
335     aReturn.bEnabled = true;
336         // (disabled automatically)
337 
338     switch (_nId)
339     {
340         case ID_BROWSER_EDITDOC:
341             if ( editingCommand() )
342                 aReturn.bEnabled = false;
343             else if ( editingView() && !m_xAlterView.is() )
344                 aReturn.bEnabled = false;
345             else
346                 aReturn = OJoinController::GetState( _nId );
347             break;
348 
349         case ID_BROWSER_ESCAPEPROCESSING:
350             aReturn.bChecked = !m_bEscapeProcessing;
351             aReturn.bEnabled = ( m_pSqlIterator != nullptr ) && !m_bGraphicalDesign;
352             break;
353         case SID_RELATION_ADD_RELATION:
354             aReturn.bEnabled = isEditable() && m_bGraphicalDesign && m_vTableData.size() > 1;
355             break;
356         case ID_BROWSER_SAVEASDOC:
357             aReturn.bEnabled = !editingCommand() && (!m_bGraphicalDesign || !(m_vTableFieldDesc.empty() || m_vTableData.empty()));
358             break;
359         case ID_BROWSER_SAVEDOC:
360             aReturn.bEnabled = isEditable() && (!m_bGraphicalDesign || !(m_vTableFieldDesc.empty() || m_vTableData.empty()));
361             break;
362         case SID_PRINTDOCDIRECT:
363             break;
364         case ID_BROWSER_CUT:
365             aReturn.bEnabled = isEditable() && getContainer() && getContainer()->isCutAllowed();
366             break;
367         case ID_BROWSER_COPY:
368             aReturn.bEnabled = getContainer() && getContainer()->isCopyAllowed();
369             break;
370         case ID_BROWSER_PASTE:
371             aReturn.bEnabled = isEditable() && getContainer() && getContainer()->isPasteAllowed();
372             break;
373         case ID_BROWSER_SQL:
374             aReturn.bEnabled = m_bEscapeProcessing && m_pSqlIterator;
375             aReturn.bChecked = m_bGraphicalDesign;
376             break;
377         case SID_BROWSER_CLEAR_QUERY:
378             aReturn.bEnabled = isEditable() && (!m_sStatement.isEmpty() || !m_vTableData.empty());
379             break;
380         case SID_QUERY_VIEW_FUNCTIONS:
381         case SID_QUERY_VIEW_TABLES:
382         case SID_QUERY_VIEW_ALIASES:
383             aReturn.bChecked = getContainer() && getContainer()->isSlotEnabled(_nId);
384             aReturn.bEnabled = m_bGraphicalDesign;
385             break;
386         case SID_QUERY_DISTINCT_VALUES:
387             aReturn.bEnabled = m_bGraphicalDesign && isEditable();
388             aReturn.bChecked = m_bDistinct;
389             break;
390         case SID_QUERY_LIMIT:
391             aReturn.bEnabled = m_bGraphicalDesign;
392             if( aReturn.bEnabled )
393                 aReturn.aValue <<= m_nLimit;
394             break;
395         case SID_QUERY_PROP_DLG:
396             aReturn.bEnabled = m_bGraphicalDesign;
397             break;
398         case ID_BROWSER_QUERY_EXECUTE:
399             aReturn.bEnabled = true;
400             break;
401         case SID_DB_QUERY_PREVIEW:
402             aReturn.bEnabled = true;
403             aReturn.bChecked = getContainer() && getContainer()->getPreviewFrame().is();
404             break;
405 #if OSL_DEBUG_LEVEL > 0
406         case ID_EDIT_QUERY_SQL:
407             break;
408         case ID_EDIT_QUERY_DESIGN:
409             break;
410 #endif
411         case ID_BROWSER_ADDTABLE:
412             if ( !m_bGraphicalDesign )
413             {
414                 aReturn.bEnabled = false;
415                 break;
416             }
417             [[fallthrough]];
418         default:
419             aReturn = OJoinController::GetState(_nId);
420             break;
421     }
422     return aReturn;
423 }
424 
Execute(sal_uInt16 _nId,const Sequence<PropertyValue> & aArgs)425 void OQueryController::Execute(sal_uInt16 _nId, const Sequence< PropertyValue >& aArgs)
426 {
427     switch(_nId)
428     {
429         case ID_BROWSER_ESCAPEPROCESSING:
430             setEscapeProcessing_fireEvent( !m_bEscapeProcessing );
431             if ( !editingView() )
432                 setModified(true);
433             InvalidateFeature(ID_BROWSER_SQL);
434             break;
435         case ID_BROWSER_SAVEASDOC:
436         case ID_BROWSER_SAVEDOC:
437             grabFocusFromLimitBox(*this);
438             doSaveAsDoc(ID_BROWSER_SAVEASDOC == _nId);
439             break;
440         case SID_RELATION_ADD_RELATION:
441             {
442                 OJoinDesignView* pView = getJoinView();
443                 if( pView )
444                     static_cast<OQueryTableView*>(pView->getTableView())->createNewConnection();
445             }
446             break;
447         case SID_PRINTDOCDIRECT:
448             break;
449         case ID_BROWSER_CUT:
450             getContainer()->cut();
451             break;
452         case ID_BROWSER_COPY:
453             getContainer()->copy();
454             break;
455         case ID_BROWSER_PASTE:
456             getContainer()->paste();
457             break;
458         case ID_BROWSER_SQL:
459         {
460             grabFocusFromLimitBox(*this);
461             if ( !getContainer()->checkStatement() )
462                 break;
463             SQLExceptionInfo aError;
464             try
465             {
466                 setStatement_fireEvent( getContainer()->getStatement() );
467                 if(m_sStatement.isEmpty() && m_pSqlIterator)
468                 {
469                     // change the view of the data
470                     delete m_pSqlIterator->getParseTree();
471                     m_pSqlIterator->setParseTree(nullptr);
472                     m_bGraphicalDesign = !m_bGraphicalDesign;
473                     impl_setViewMode( &aError );
474                 }
475                 else
476                 {
477                     OUString aErrorMsg;
478                     std::unique_ptr<::connectivity::OSQLParseNode> pNode = m_aSqlParser.parseTree(aErrorMsg,m_sStatement,m_bGraphicalDesign);
479                     if ( pNode )
480                     {
481                         assert(m_pSqlIterator && "SqlIterator must exist");
482                         delete m_pSqlIterator->getParseTree();
483                         m_pSqlIterator->setParseTree(pNode.release());
484                         m_pSqlIterator->traverseAll();
485 
486                         if ( m_pSqlIterator->hasErrors() )
487                         {
488                             aError = m_pSqlIterator->getErrors();
489                         }
490                         else
491                         {
492                             const OSQLTables& rTabs = m_pSqlIterator->getTables();
493                             if ( m_pSqlIterator->getStatementType() != OSQLStatementType::Select || rTabs.empty() )
494                             {
495                                 aError = SQLException(
496                                     DBA_RES(STR_QRY_NOSELECT),
497                                     nullptr,
498                                     u"S1000"_ustr,
499                                     1000,
500                                     Any()
501                                 );
502                             }
503                             else
504                             {
505                                 // change the view of the data
506                                 m_bGraphicalDesign = !m_bGraphicalDesign;
507                                 OUString sNewStatement;
508                                 m_pSqlIterator->getParseTree()->parseNodeToStr( sNewStatement, getConnection() );
509                                 setStatement_fireEvent( sNewStatement );
510                                 getContainer()->SaveUIConfig();
511                                 m_vTableConnectionData.clear();
512                                 impl_setViewMode( &aError );
513                             }
514                         }
515                     }
516                     else
517                     {
518                         aError = SQLException(
519                             DBA_RES(STR_QRY_SYNTAX),
520                             nullptr,
521                             u"S1000"_ustr,
522                             1000,
523                             Any()
524                         );
525                     }
526                 }
527             }
528             catch(const SQLException&)
529             {
530                 aError = ::cppu::getCaughtException();
531             }
532             catch(const Exception&)
533             {
534                 DBG_UNHANDLED_EXCEPTION("dbaccess");
535             }
536 
537             if ( aError.isValid() )
538                 showError( aError );
539 
540             if(m_bGraphicalDesign)
541             {
542                 InvalidateFeature(ID_BROWSER_ADDTABLE);
543                 InvalidateFeature(SID_RELATION_ADD_RELATION);
544             }
545         }
546         break;
547         case SID_BROWSER_CLEAR_QUERY:
548             {
549                 GetUndoManager().EnterListAction(DBA_RES(STR_QUERY_UNDO_TABWINDELETE), OUString(), 0, ViewShellId(-1) );
550                 getContainer()->clear();
551                 GetUndoManager().LeaveListAction();
552 
553                 setStatement_fireEvent( OUString() );
554                 if(m_bGraphicalDesign)
555                     InvalidateFeature(ID_BROWSER_ADDTABLE);
556             }
557             break;
558         case SID_QUERY_VIEW_FUNCTIONS:
559         case SID_QUERY_VIEW_TABLES:
560         case SID_QUERY_VIEW_ALIASES:
561             getContainer()->setSlotEnabled(_nId,!getContainer()->isSlotEnabled(_nId));
562             setModified(true);
563             break;
564         case SID_QUERY_DISTINCT_VALUES:
565             m_bDistinct = !m_bDistinct;
566             setModified(true);
567             break;
568         case SID_QUERY_LIMIT:
569             if ( aArgs.hasElements() && aArgs[0].Name == "DBLimit.Value" )
570             {
571                 aArgs[0].Value >>= m_nLimit;
572                 setModified(true);
573             }
574             break;
575         case SID_QUERY_PROP_DLG:
576             grabFocusFromLimitBox(*this);
577             execute_QueryPropDlg();
578             break;
579         case ID_BROWSER_QUERY_EXECUTE:
580             grabFocusFromLimitBox(*this);
581             if ( getContainer()->checkStatement() )
582                 executeQuery();
583             break;
584         case SID_DB_QUERY_PREVIEW:
585             try
586             {
587                 Reference< css::util::XCloseable > xCloseFrame( getContainer()->getPreviewFrame(), UNO_QUERY );
588                 if ( xCloseFrame.is() )
589                 {
590                     try
591                     {
592                         xCloseFrame->close( true );
593                     }
594                     catch(const Exception&)
595                     {
596                         OSL_FAIL( "OQueryController::Execute(SID_DB_QUERY_PREVIEW): *nobody* is expected to veto closing the preview frame!" );
597                     }
598                 }
599                 else
600                     Execute(ID_BROWSER_QUERY_EXECUTE,Sequence< PropertyValue >());
601             }
602             catch(const Exception&)
603             {
604             }
605             break;
606         default:
607             OJoinController::Execute(_nId,aArgs);
608             return; // else we would invalidate twice
609     }
610     InvalidateFeature(_nId);
611 }
612 
impl_showAutoSQLViewError(const css::uno::Any & _rErrorDetails)613 void OQueryController::impl_showAutoSQLViewError( const css::uno::Any& _rErrorDetails )
614 {
615     SQLContext aErrorContext(
616         lcl_getObjectResourceString(STR_ERROR_PARSING_STATEMENT, m_nCommandType), *this, {}, 0,
617         _rErrorDetails, lcl_getObjectResourceString(STR_INFO_OPENING_IN_SQL_VIEW, m_nCommandType));
618     showError( aErrorContext );
619 }
620 
impl_setViewMode(::dbtools::SQLExceptionInfo * _pErrorInfo)621 void OQueryController::impl_setViewMode( ::dbtools::SQLExceptionInfo* _pErrorInfo )
622 {
623     OSL_PRECOND( getContainer(), "OQueryController::impl_setViewMode: illegal call!" );
624 
625     bool wasModified = isModified();
626 
627     SQLExceptionInfo aError;
628     bool bSuccess = getContainer()->switchView( &aError );
629     if ( !bSuccess )
630     {
631         m_bGraphicalDesign = !m_bGraphicalDesign;
632         // restore old state
633         getContainer()->switchView( nullptr );
634             // don't pass &aError here, this would overwrite the error which the first switchView call
635             // returned in this location.
636         if ( _pErrorInfo )
637             *_pErrorInfo = std::move(aError);
638         else
639             showError( aError );
640     }
641     else
642     {
643         ensureToolbars( *this, m_bGraphicalDesign );
644     }
645 
646     setModified( wasModified );
647 }
648 
impl_initialize(const::comphelper::NamedValueCollection & rArguments)649 void OQueryController::impl_initialize(const ::comphelper::NamedValueCollection& rArguments)
650 {
651     OJoinController::impl_initialize(rArguments);
652 
653     OUString sCommand;
654     m_nCommandType = CommandType::QUERY;
655 
656     // reading parameters:
657 
658     // legacy parameters first (later overwritten by regular parameters)
659     OUString sIndependentSQLCommand;
660     if ( rArguments.get_ensureType( u"IndependentSQLCommand"_ustr, sIndependentSQLCommand ) )
661     {
662         OSL_FAIL( "OQueryController::impl_initialize: IndependentSQLCommand is regognized for compatibility only!" );
663         sCommand = sIndependentSQLCommand;
664         m_nCommandType = CommandType::COMMAND;
665     }
666 
667     OUString sCurrentQuery;
668     if ( rArguments.get_ensureType( u"CurrentQuery"_ustr, sCurrentQuery ) )
669     {
670         OSL_FAIL( "OQueryController::impl_initialize: CurrentQuery is regognized for compatibility only!" );
671         sCommand = sCurrentQuery;
672         m_nCommandType = CommandType::QUERY;
673     }
674 
675     bool bCreateView( false );
676     if ( rArguments.get_ensureType( u"CreateView"_ustr, bCreateView ) && bCreateView )
677     {
678         OSL_FAIL( "OQueryController::impl_initialize: CurrentQuery is regognized for compatibility only!" );
679         m_nCommandType = CommandType::TABLE;
680     }
681 
682     // non-legacy parameters which overwrite the legacy parameters
683     rArguments.get_ensureType( PROPERTY_COMMAND, sCommand );
684     rArguments.get_ensureType( PROPERTY_COMMAND_TYPE, m_nCommandType );
685 
686     // translate Command/Type into proper members
687     // TODO/Later: all this (including those members) should be hidden behind some abstract interface,
688     // which is implemented for all the three commands
689     switch ( m_nCommandType )
690     {
691     case CommandType::QUERY:
692     case CommandType::TABLE:
693         m_sName = sCommand;
694         break;
695     case CommandType::COMMAND:
696         setStatement_fireEvent( sCommand );
697         m_sName.clear();
698         break;
699     default:
700         OSL_FAIL( "OQueryController::impl_initialize: logic error in code!" );
701         throw RuntimeException();
702     }
703 
704     // more legacy parameters
705     bool bGraphicalDesign( true );
706     if ( rArguments.get_ensureType( PROPERTY_QUERYDESIGNVIEW, bGraphicalDesign ) )
707     {
708         OSL_FAIL( "OQueryController::impl_initialize: QueryDesignView is regognized for compatibility only!" );
709         m_bGraphicalDesign = bGraphicalDesign;
710     }
711 
712     // more non-legacy
713     rArguments.get_ensureType( PROPERTY_GRAPHICAL_DESIGN, m_bGraphicalDesign );
714 
715     bool bEscapeProcessing( true );
716     if ( rArguments.get_ensureType( PROPERTY_ESCAPE_PROCESSING, bEscapeProcessing ) )
717     {
718         setEscapeProcessing_fireEvent( bEscapeProcessing );
719 
720         OSL_ENSURE( m_bEscapeProcessing || !m_bGraphicalDesign, "OQueryController::impl_initialize: can't do the graphical design without escape processing!" );
721         if ( !m_bEscapeProcessing )
722             m_bGraphicalDesign = false;
723     }
724 
725     // initial design
726     bool bForceInitialDesign = false;
727     Sequence< PropertyValue > aCurrentQueryDesignProps;
728     aCurrentQueryDesignProps = rArguments.getOrDefault( u"CurrentQueryDesign"_ustr, aCurrentQueryDesignProps );
729 
730     if ( aCurrentQueryDesignProps.hasElements() )
731     {
732         ::comphelper::NamedValueCollection aCurrentQueryDesign( aCurrentQueryDesignProps );
733         if ( aCurrentQueryDesign.has( PROPERTY_GRAPHICAL_DESIGN ) )
734         {
735             aCurrentQueryDesign.get_ensureType( PROPERTY_GRAPHICAL_DESIGN, m_bGraphicalDesign );
736         }
737         if ( aCurrentQueryDesign.has( PROPERTY_ESCAPE_PROCESSING ) )
738         {
739             aCurrentQueryDesign.get_ensureType( PROPERTY_ESCAPE_PROCESSING, m_bEscapeProcessing );
740         }
741         if ( aCurrentQueryDesign.has( u"Statement"_ustr ) )
742         {
743             OUString sStatement;
744             aCurrentQueryDesign.get_ensureType( u"Statement"_ustr, sStatement );
745             aCurrentQueryDesign.remove( u"Statement"_ustr );
746             setStatement_fireEvent( sStatement );
747         }
748 
749         loadViewSettings( aCurrentQueryDesign );
750 
751         bForceInitialDesign = true;
752     }
753 
754     if ( !ensureConnected() )
755     {   // we have no connection so what else should we do
756         m_bGraphicalDesign = false;
757         if ( editingView() )
758         {
759             connectionLostMessage();
760             throw SQLException();
761         }
762     }
763 
764     // check the view capabilities
765     if ( isConnected() && editingView() )
766     {
767         Reference< XViewsSupplier > xViewsSup( getConnection(), UNO_QUERY );
768         Reference< XNameAccess > xViews;
769         if ( xViewsSup.is() )
770             xViews = xViewsSup->getViews();
771 
772         if ( !xViews.is() )
773         {   // we can't create views so we ask if the user wants to create a query instead
774             m_nCommandType = CommandType::QUERY;
775             bool bClose = false;
776             {
777                 OUString aTitle(DBA_RES(STR_QUERYDESIGN_NO_VIEW_SUPPORT));
778                 OUString aMessage(DBA_RES(STR_QUERYDESIGN_NO_VIEW_ASK));
779                 OSQLMessageBox aDlg(getFrameWeld(), aTitle, aMessage, MessBoxStyle::YesNo | MessBoxStyle::DefaultYes, MessageType::Query);
780                 bClose = aDlg.run() == RET_NO;
781             }
782             if ( bClose )
783                 throw VetoException();
784         }
785 
786         // now if we are to edit an existing view, check whether this is possible
787         if ( !m_sName.isEmpty() )
788         {
789             Any aView( xViews->getByName( m_sName ) );
790                 // will throw if there is no such view
791             if ( !( aView >>= m_xAlterView ) )
792             {
793                 throw IllegalArgumentException(
794                     DBA_RES(STR_NO_ALTER_VIEW_SUPPORT),
795                     *this,
796                     1
797                 );
798             }
799         }
800     }
801 
802     OSL_ENSURE(getDataSource().is(),"OQueryController::impl_initialize: need a datasource!");
803 
804     try
805     {
806         getContainer()->initialize();
807         impl_reset( bForceInitialDesign );
808 
809         SQLExceptionInfo aError;
810         const bool bAttemptedGraphicalDesign = m_bGraphicalDesign;
811 
812         if ( bForceInitialDesign )
813         {
814             getContainer()->forceInitialView();
815         }
816         else
817         {
818             impl_setViewMode( &aError );
819         }
820 
821         if ( aError.isValid() && bAttemptedGraphicalDesign && !m_bGraphicalDesign )
822         {
823             // we tried initializing the graphical view, this failed, and we were automatically switched to SQL
824             // view => tell this to the user
825             if ( !editingView() )
826             {
827                 impl_showAutoSQLViewError( aError.get() );
828             }
829         }
830 
831         ClearUndoManager();
832 
833         if  (  m_bGraphicalDesign
834             && (  ( m_sName.isEmpty() && !editingCommand() )
835                || ( m_sStatement.isEmpty() && editingCommand() )
836                )
837             )
838         {
839             Application::PostUserEvent( LINK( this, OQueryController, OnExecuteAddTable ) );
840         }
841 
842         setModified(false);
843     }
844     catch(const SQLException& e)
845     {
846         DBG_UNHANDLED_EXCEPTION("dbaccess");
847         // we caught an exception so we switch to text only mode
848         {
849             m_bGraphicalDesign = false;
850             getContainer()->initialize();
851             OSQLMessageBox aBox(getFrameWeld(), e);
852             aBox.run();
853         }
854         throw;
855     }
856 }
857 
onLoadedMenu(const Reference<css::frame::XLayoutManager> &)858 void OQueryController::onLoadedMenu(const Reference< css::frame::XLayoutManager >& /*_xLayoutManager*/)
859 {
860     ensureToolbars( *this, m_bGraphicalDesign );
861 }
862 
getPrivateTitle() const863 OUString OQueryController::getPrivateTitle( ) const
864 {
865     if ( m_sName.isEmpty() )
866     {
867         if ( !editingCommand() )
868         {
869             SolarMutexGuard aSolarGuard;
870             ::osl::MutexGuard aGuard( getMutex() );
871             OUString aDefaultName = DBA_RES(editingView() ? STR_VIEW_TITLE : STR_QRY_TITLE);
872             return o3tl::getToken(aDefaultName, 0, ' ') + OUString::number(getCurrentStartNumber());
873         }
874     }
875     return m_sName;
876 }
877 
setQueryComposer()878 void OQueryController::setQueryComposer()
879 {
880     if(!isConnected())
881         return;
882 
883     Reference< XSQLQueryComposerFactory >  xFactory(getConnection(), UNO_QUERY);
884     OSL_ENSURE(xFactory.is(),"Connection doesn't support a querycomposer");
885     if ( !(xFactory.is() && getContainer()) )
886         return;
887 
888     try
889     {
890         m_xComposer = xFactory->createQueryComposer();
891         getContainer()->setStatement(m_sStatement);
892     }
893     catch(const Exception&)
894     {
895         m_xComposer = nullptr;
896     }
897     OSL_ENSURE(m_xComposer.is(),"No querycomposer available!");
898     Reference<XTablesSupplier> xTablesSup(getConnection(), UNO_QUERY);
899     deleteIterator();
900     m_pSqlIterator.reset(new ::connectivity::OSQLParseTreeIterator( getConnection(), xTablesSup->getTables(), m_aSqlParser ));
901 }
902 
Construct(vcl::Window * pParent)903 bool OQueryController::Construct(vcl::Window* pParent)
904 {
905     // TODO: we have to check if we should create the text view or the design view
906 
907     setView( VclPtr<OQueryContainerWindow>::Create( pParent, *this, getORB() ) );
908 
909     return OJoinController::Construct(pParent);
910 }
911 
getJoinView()912 OJoinDesignView* OQueryController::getJoinView()
913 {
914     return getContainer()->getDesignView();
915 }
916 
describeSupportedFeatures()917 void OQueryController::describeSupportedFeatures()
918 {
919     OJoinController::describeSupportedFeatures();
920     implDescribeSupportedFeature( u".uno:SaveAs"_ustr,            ID_BROWSER_SAVEASDOC,       CommandGroup::DOCUMENT );
921     implDescribeSupportedFeature( u".uno:SbaNativeSql"_ustr,      ID_BROWSER_ESCAPEPROCESSING,CommandGroup::FORMAT );
922     implDescribeSupportedFeature( u".uno:DBViewFunctions"_ustr,   SID_QUERY_VIEW_FUNCTIONS,   CommandGroup::VIEW );
923     implDescribeSupportedFeature( u".uno:DBViewTableNames"_ustr,  SID_QUERY_VIEW_TABLES,      CommandGroup::VIEW );
924     implDescribeSupportedFeature( u".uno:DBViewAliases"_ustr,     SID_QUERY_VIEW_ALIASES,     CommandGroup::VIEW );
925     implDescribeSupportedFeature( u".uno:DBDistinctValues"_ustr,  SID_QUERY_DISTINCT_VALUES,  CommandGroup::FORMAT );
926     implDescribeSupportedFeature( u".uno:DBChangeDesignMode"_ustr,ID_BROWSER_SQL,             CommandGroup::VIEW );
927     implDescribeSupportedFeature( u".uno:DBClearQuery"_ustr,      SID_BROWSER_CLEAR_QUERY,    CommandGroup::EDIT );
928     implDescribeSupportedFeature( u".uno:SbaExecuteSql"_ustr,     ID_BROWSER_QUERY_EXECUTE,   CommandGroup::VIEW );
929     implDescribeSupportedFeature( u".uno:DBAddRelation"_ustr,     SID_RELATION_ADD_RELATION,  CommandGroup::EDIT );
930     implDescribeSupportedFeature( u".uno:DBQueryPreview"_ustr,    SID_DB_QUERY_PREVIEW,       CommandGroup::VIEW );
931     implDescribeSupportedFeature( u".uno:DBLimit"_ustr,           SID_QUERY_LIMIT,            CommandGroup::FORMAT );
932     implDescribeSupportedFeature( u".uno:DBQueryPropertiesDialog"_ustr, SID_QUERY_PROP_DLG,         CommandGroup::FORMAT );
933 
934 #if OSL_DEBUG_LEVEL > 0
935     implDescribeSupportedFeature( u".uno:DBShowParseTree"_ustr,   ID_EDIT_QUERY_SQL );
936     implDescribeSupportedFeature( u".uno:DBMakeDisjunct"_ustr,    ID_EDIT_QUERY_DESIGN );
937 #endif
938 }
939 
impl_onModifyChanged()940 void OQueryController::impl_onModifyChanged()
941 {
942     OJoinController::impl_onModifyChanged();
943     InvalidateFeature(SID_BROWSER_CLEAR_QUERY);
944     InvalidateFeature(ID_BROWSER_SAVEASDOC);
945     InvalidateFeature(ID_BROWSER_QUERY_EXECUTE);
946 }
947 
disposing(const EventObject & Source)948 void SAL_CALL OQueryController::disposing( const EventObject& Source )
949 {
950     SolarMutexGuard aGuard;
951 
952     if ( getContainer() && Source.Source.is() )
953     {
954         if ( Source.Source == m_aCurrentFrame.getFrame() )
955         {   // our frame is being disposed -> close the preview window (if we have one)
956             Reference< XFrame2 > xPreviewFrame( getContainer()->getPreviewFrame() );
957             ::comphelper::disposeComponent( xPreviewFrame );
958         }
959         else if ( Source.Source == getContainer()->getPreviewFrame() )
960         {
961             getContainer()->disposingPreview();
962         }
963     }
964 
965     OJoinController_BASE::disposing(Source);
966 }
967 
reconnect(bool _bUI)968 void OQueryController::reconnect(bool _bUI)
969 {
970     deleteIterator();
971     ::comphelper::disposeComponent(m_xComposer);
972 
973     OJoinController::reconnect( _bUI );
974 
975     if (isConnected())
976     {
977         setQueryComposer();
978     }
979     else
980     {
981         if(m_bGraphicalDesign)
982         {
983             m_bGraphicalDesign = false;
984             // don't call Execute(SQL) because this changes the sql statement
985             impl_setViewMode( nullptr );
986         }
987         InvalidateAll();
988     }
989 }
990 
saveViewSettings(::comphelper::NamedValueCollection & o_rViewSettings,const bool i_includingCriteria) const991 void OQueryController::saveViewSettings( ::comphelper::NamedValueCollection& o_rViewSettings, const bool i_includingCriteria ) const
992 {
993     saveTableWindows( o_rViewSettings );
994 
995     ::comphelper::NamedValueCollection aAllFieldsData;
996     ::comphelper::NamedValueCollection aFieldData;
997     sal_Int32 i = 1;
998     for (auto const& fieldDesc : m_vTableFieldDesc)
999     {
1000         if ( !fieldDesc->IsEmpty() )
1001         {
1002             aFieldData.clear();
1003             fieldDesc->Save( aFieldData, i_includingCriteria );
1004 
1005             const OUString sFieldSettingName = "Field" + OUString::number( i );
1006             aAllFieldsData.put( sFieldSettingName, aFieldData.getPropertyValues() );
1007         }
1008         ++i;
1009     }
1010 
1011     o_rViewSettings.put( u"Fields"_ustr, aAllFieldsData.getPropertyValues() );
1012     o_rViewSettings.put( u"SplitterPosition"_ustr, m_nSplitPos );
1013     o_rViewSettings.put( u"VisibleRows"_ustr, m_nVisibleRows );
1014 }
1015 
loadViewSettings(const::comphelper::NamedValueCollection & o_rViewSettings)1016 void OQueryController::loadViewSettings( const ::comphelper::NamedValueCollection& o_rViewSettings )
1017 {
1018     loadTableWindows( o_rViewSettings );
1019 
1020     m_nSplitPos = o_rViewSettings.getOrDefault( u"SplitterPosition"_ustr, m_nSplitPos );
1021     m_nVisibleRows = o_rViewSettings.getOrDefault( u"VisibleRows"_ustr, m_nVisibleRows );
1022     m_aFieldInformation = o_rViewSettings.getOrDefault( u"Fields"_ustr, m_aFieldInformation );
1023 }
1024 
execute_QueryPropDlg()1025 void OQueryController::execute_QueryPropDlg()
1026 {
1027     QueryPropertiesDialog aQueryPropDlg(getContainer()->GetFrameWeld(), m_bDistinct, m_nLimit);
1028 
1029     if (aQueryPropDlg.run() == RET_OK)
1030     {
1031         m_bDistinct = aQueryPropDlg.getDistinct();
1032         m_nLimit = aQueryPropDlg.getLimit();
1033         InvalidateFeature( SID_QUERY_DISTINCT_VALUES );
1034         InvalidateFeature( SID_QUERY_LIMIT, nullptr, true );
1035     }
1036 }
1037 
getColWidth(sal_uInt16 _nColPos) const1038 sal_Int32 OQueryController::getColWidth(sal_uInt16 _nColPos)  const
1039 {
1040     if ( _nColPos < m_aFieldInformation.getLength() )
1041     {
1042         rtl::Reference<OTableFieldDesc> pField( new OTableFieldDesc());
1043         pField->Load( m_aFieldInformation[ _nColPos ], false );
1044         return pField->GetColWidth();
1045     }
1046     return 0;
1047 }
1048 
getObjectContainer() const1049 Reference<XNameAccess> OQueryController::getObjectContainer()  const
1050 {
1051     Reference< XNameAccess > xElements;
1052     if ( editingView() )
1053     {
1054         Reference< XViewsSupplier > xViewsSupp( getConnection(), UNO_QUERY );
1055         if ( xViewsSupp.is() )
1056             xElements = xViewsSupp->getViews();
1057     }
1058     else
1059     {
1060         Reference< XQueriesSupplier > xQueriesSupp( getConnection(), UNO_QUERY );
1061         if ( xQueriesSupp.is() )
1062             xElements = xQueriesSupp->getQueries();
1063         else
1064         {
1065             Reference< XQueryDefinitionsSupplier > xQueryDefsSupp( getDataSource(), UNO_QUERY );
1066             if ( xQueryDefsSupp.is() )
1067                 xElements = xQueryDefsSupp->getQueryDefinitions();
1068         }
1069     }
1070 
1071     OSL_ENSURE( xElements.is(), "OQueryController::getObjectContainer: unable to obtain the container!" );
1072     return xElements;
1073 }
1074 
executeQuery()1075 void OQueryController::executeQuery()
1076 {
1077     // we don't need to check the connection here because we already check the composer
1078     // which can't live without his connection
1079     OUString sTranslatedStmt = translateStatement( false );
1080 
1081     OUString sDataSourceName = getDataSourceName();
1082     if ( sDataSourceName.isEmpty() || sTranslatedStmt.isEmpty() )
1083         return;
1084 
1085     try
1086     {
1087         getContainer()->showPreview( getFrame() );
1088         InvalidateFeature(SID_DB_QUERY_PREVIEW);
1089 
1090         URL aWantToDispatch;
1091         aWantToDispatch.Complete = ".component:DB/DataSourceBrowser";
1092 
1093         OUString sFrameName( FRAME_NAME_QUERY_PREVIEW );
1094         sal_Int32 nSearchFlags = FrameSearchFlag::CHILDREN;
1095 
1096         Reference< XDispatch> xDisp;
1097         Reference< XDispatchProvider> xProv( getFrame()->findFrame( sFrameName, nSearchFlags ), UNO_QUERY );
1098         if(!xProv.is())
1099         {
1100             xProv.set( getFrame(), UNO_QUERY );
1101             if (xProv.is())
1102                 xDisp = xProv->queryDispatch(aWantToDispatch, sFrameName, nSearchFlags);
1103         }
1104         else
1105         {
1106             xDisp = xProv->queryDispatch(aWantToDispatch, sFrameName, FrameSearchFlag::SELF);
1107         }
1108         if (xDisp.is())
1109         {
1110             auto aProps(::comphelper::InitPropertySequence(
1111                 {
1112                     { PROPERTY_DATASOURCENAME, Any(sDataSourceName) },
1113                     { PROPERTY_COMMAND_TYPE, Any(CommandType::COMMAND) },
1114                     { PROPERTY_COMMAND, Any(sTranslatedStmt) },
1115                     { PROPERTY_ENABLE_BROWSER, Any(false) },
1116                     { PROPERTY_ACTIVE_CONNECTION, Any(getConnection()) },
1117                     { PROPERTY_UPDATE_CATALOGNAME, Any(m_sUpdateCatalogName) },
1118                     { PROPERTY_UPDATE_SCHEMANAME, Any(m_sUpdateSchemaName) },
1119                     { PROPERTY_UPDATE_TABLENAME, Any(OUString()) },
1120                     { PROPERTY_ESCAPE_PROCESSING, Any(m_bEscapeProcessing) }
1121                 }));
1122 
1123             xDisp->dispatch(aWantToDispatch, aProps);
1124             // check the state of the beamer
1125             // be notified when the beamer frame is closed
1126             Reference< XComponent >  xComponent = getFrame()->findFrame( sFrameName, nSearchFlags );
1127             if (xComponent.is())
1128             {
1129                 OSL_ENSURE(Reference< XFrame >(xComponent, UNO_QUERY).get() == getContainer()->getPreviewFrame().get(),
1130                     "OQueryController::executeQuery: oops ... which window do I have here?");
1131                 Reference< XEventListener> xEvtL(static_cast<cppu::OWeakObject*>(this),UNO_QUERY);
1132                 xComponent->addEventListener(xEvtL);
1133             }
1134         }
1135         else
1136         {
1137             OSL_FAIL("Couldn't create a beamer window!");
1138         }
1139     }
1140     catch(const Exception&)
1141     {
1142         OSL_FAIL("Couldn't create a beamer window!");
1143     }
1144 }
1145 
askForNewName(const Reference<XNameAccess> & _xElements,bool _bSaveAs)1146 bool OQueryController::askForNewName(const Reference<XNameAccess>& _xElements, bool _bSaveAs)
1147 {
1148     OSL_ENSURE( !editingCommand(), "OQueryController::askForNewName: not to be called when designing an independent statement!" );
1149     if ( editingCommand() )
1150         return false;
1151 
1152     OSL_PRECOND( _xElements.is(), "OQueryController::askForNewName: invalid container!" );
1153     if  ( !_xElements.is() )
1154         return false;
1155 
1156     bool bRet = true;
1157     bool bNew = _bSaveAs || !_xElements->hasByName( m_sName );
1158     if(bNew)
1159     {
1160         OUString aDefaultName;
1161         if (!m_sName.isEmpty())
1162             aDefaultName = m_sName;
1163         else
1164         {
1165             OUString sName = DBA_RES(editingView() ? STR_VIEW_TITLE : STR_QRY_TITLE);
1166             aDefaultName = ::dbtools::createUniqueName(_xElements, sName.getToken(0, ' '));
1167         }
1168 
1169         DynamicTableOrQueryNameCheck aNameChecker( getConnection(), CommandType::QUERY );
1170         OSaveAsDlg aDlg(
1171                 getFrameWeld(),
1172                 m_nCommandType,
1173                 getORB(),
1174                 getConnection(),
1175                 aDefaultName,
1176                 aNameChecker,
1177                 SADFlags::NONE );
1178 
1179         bRet = ( aDlg.run() == RET_OK );
1180         if ( bRet )
1181         {
1182             m_sName = aDlg.getName();
1183             if ( editingView() )
1184             {
1185                 m_sUpdateCatalogName    = aDlg.getCatalog();
1186                 m_sUpdateSchemaName     = aDlg.getSchema();
1187             }
1188         }
1189     }
1190     return bRet;
1191 }
1192 
doSaveAsDoc(bool _bSaveAs)1193 bool OQueryController::doSaveAsDoc(bool _bSaveAs)
1194 {
1195     OSL_ENSURE(isEditable(),"Slot ID_BROWSER_SAVEDOC should not be enabled!");
1196     if ( !editingCommand() && !haveDataSource() )
1197     {
1198         OUString aMessage(DBA_RES(STR_DATASOURCE_DELETED));
1199         OSQLWarningBox aBox(getFrameWeld(), aMessage);
1200         aBox.run();
1201         return false;
1202     }
1203 
1204     Reference< XNameAccess > xElements = getObjectContainer();
1205     if ( !xElements.is() )
1206         return false;
1207 
1208     if ( !getContainer()->checkStatement() )
1209         return false;
1210 
1211     OUString sTranslatedStmt = translateStatement();
1212     if ( editingCommand() )
1213     {
1214         setModified( false );
1215         // this is all we need to do here. translateStatement implicitly set our m_sStatement, and
1216         // notified it, and that's all
1217         return true;
1218     }
1219 
1220     if ( sTranslatedStmt.isEmpty() )
1221         return false;
1222 
1223     // first we need a name for our query so ask the user
1224     // did we get a name
1225     OUString sOriginalName( m_sName );
1226     if ( !askForNewName( xElements, _bSaveAs ) || m_sName.isEmpty() )
1227         return false;
1228 
1229     SQLExceptionInfo aInfo;
1230     bool bSuccess = false;
1231     bool bNew = false;
1232     try
1233     {
1234         bNew =  _bSaveAs
1235             || ( !xElements->hasByName( m_sName ) );
1236 
1237         Reference<XPropertySet> xQuery;
1238         if ( bNew ) // just to make sure the query already exists
1239         {
1240             // drop the query, in case it already exists
1241             if ( xElements->hasByName( m_sName ) )
1242             {
1243                 Reference< XDrop > xNameCont( xElements, UNO_QUERY );
1244                 if ( xNameCont.is() )
1245                     xNameCont->dropByName( m_sName );
1246                 else
1247                 {
1248                     Reference< XNameContainer > xCont( xElements, UNO_QUERY );
1249                     if ( xCont.is() )
1250                         xCont->removeByName( m_sName );
1251                 }
1252             }
1253 
1254             // create a new (empty, uninitialized) query resp. view
1255             Reference< XDataDescriptorFactory > xFact( xElements, UNO_QUERY );
1256             if ( xFact.is() )
1257             {
1258                 xQuery = xFact->createDataDescriptor();
1259                 // to set the name is only allowed when the query is new
1260                 xQuery->setPropertyValue( PROPERTY_NAME, Any( m_sName ) );
1261             }
1262             else
1263             {
1264                 Reference< XSingleServiceFactory > xSingleFac( xElements, UNO_QUERY );
1265                 if ( xSingleFac.is() )
1266                     xQuery.set(xSingleFac->createInstance(), css::uno::UNO_QUERY);
1267             }
1268         }
1269         else
1270         {
1271             xElements->getByName( m_sName ) >>= xQuery;
1272         }
1273         if ( !xQuery.is() )
1274             throw RuntimeException();
1275 
1276         // the new commands
1277         if ( editingView() && !bNew )
1278         {
1279             OSL_ENSURE( xQuery == m_xAlterView, "OQueryController::doSaveAsDoc: already have another alterable view ...!?" );
1280             m_xAlterView.set( xQuery, UNO_QUERY_THROW );
1281             m_xAlterView->alterCommand( sTranslatedStmt );
1282         }
1283         else
1284         {   // we're creating a query, or a *new* view
1285             xQuery->setPropertyValue( PROPERTY_COMMAND, Any( sTranslatedStmt ) );
1286 
1287             if ( editingView() )
1288             {
1289                 xQuery->setPropertyValue( PROPERTY_CATALOGNAME, Any( m_sUpdateCatalogName ) );
1290                 xQuery->setPropertyValue( PROPERTY_SCHEMANAME, Any( m_sUpdateSchemaName ) );
1291             }
1292 
1293             if ( editingQuery() )
1294             {
1295                 xQuery->setPropertyValue( PROPERTY_UPDATE_TABLENAME, Any( OUString() ) );
1296                 xQuery->setPropertyValue( PROPERTY_ESCAPE_PROCESSING, css::uno::Any( m_bEscapeProcessing ) );
1297 
1298                 xQuery->setPropertyValue( PROPERTY_LAYOUTINFORMATION, getViewData() );
1299             }
1300         }
1301 
1302         if ( bNew )
1303         {
1304             Reference< XAppend > xAppend( xElements, UNO_QUERY );
1305             if ( xAppend.is() )
1306             {
1307                 xAppend->appendByDescriptor( xQuery );
1308             }
1309             else
1310             {
1311                 Reference< XNameContainer > xCont( xElements, UNO_QUERY );
1312                 if ( xCont.is() )
1313                     xCont->insertByName( m_sName, Any( xQuery ) );
1314             }
1315 
1316             if ( editingView() )
1317             {
1318                 Reference< XPropertySet > xViewProps;
1319                 if ( xElements->hasByName( m_sName ) )
1320                     xViewProps.set( xElements->getByName( m_sName ), UNO_QUERY );
1321 
1322                 if ( !xViewProps.is() ) // correct name and try again
1323                     m_sName = ::dbtools::composeTableName( getMetaData(), xQuery, ::dbtools::EComposeRule::InDataManipulation, false );
1324 
1325                 OSL_ENSURE( xElements->hasByName( m_sName ), "OQueryController::doSaveAsDoc: newly created view does not exist!" );
1326 
1327                 if ( xElements->hasByName( m_sName ) )
1328                     m_xAlterView.set( xElements->getByName( m_sName ), UNO_QUERY );
1329 
1330                 // now check if our datasource has set a tablefilter and if so, append the new table name to it
1331                 ::dbaui::appendToFilter(getConnection(), m_sName, getORB(), getFrameWeld());
1332             }
1333             Reference< XTitleChangeListener> xEventListener(impl_getTitleHelper_throw(),UNO_QUERY);
1334             if ( xEventListener.is() )
1335             {
1336                 TitleChangedEvent aEvent;
1337                 xEventListener->titleChanged(aEvent);
1338             }
1339             releaseNumberForComponent();
1340         }
1341 
1342         setModified( false );
1343         bSuccess = true;
1344 
1345     }
1346     catch(const SQLException&)
1347     {
1348         if ( !bNew )
1349             m_sName = sOriginalName;
1350         aInfo = SQLExceptionInfo( ::cppu::getCaughtException() );
1351     }
1352     catch(const Exception&)
1353     {
1354         DBG_UNHANDLED_EXCEPTION("dbaccess");
1355         if ( !bNew )
1356             m_sName = sOriginalName;
1357     }
1358 
1359     showError( aInfo );
1360 
1361     // if we successfully saved a view we were creating, then close the designer
1362     if ( bSuccess && editingView() && !m_xAlterView.is() )
1363     {
1364         closeTask();
1365     }
1366 
1367     if ( bSuccess && editingView() )
1368         InvalidateFeature( ID_BROWSER_EDITDOC );
1369 
1370     return bSuccess;
1371 }
1372 
1373 namespace {
1374 struct CommentStrip
1375 {
1376     OUString maComment;
1377     bool            mbLastOnLine;
CommentStripdbaui::__anon078316030411::CommentStrip1378     CommentStrip( OUString sComment, bool bLastOnLine )
1379         : maComment(std::move( sComment)), mbLastOnLine( bLastOnLine) {}
1380 };
1381 
1382 }
1383 
1384 /** Obtain all comments in a query.
1385 
1386     See also delComment() implementation for OSQLParser::parseTree().
1387  */
getComment(const OUString & rQuery)1388 static std::vector< CommentStrip > getComment( const OUString& rQuery )
1389 {
1390     std::vector< CommentStrip > aRet;
1391     // First a quick search if there is any "--" or "//" or "/*", if not then
1392     // the whole copying loop is pointless.
1393     if (rQuery.indexOf( "--" ) < 0 && rQuery.indexOf( "//" ) < 0 &&
1394             rQuery.indexOf( "/*" ) < 0)
1395         return aRet;
1396 
1397     const sal_Unicode* pCopy = rQuery.getStr();
1398     const sal_Int32 nQueryLen = rQuery.getLength();
1399     bool bIsText1  = false;     // "text"
1400     bool bIsText2  = false;     // 'text'
1401     bool bComment2 = false;     // /* comment */
1402     bool bComment  = false;     // -- or // comment
1403     OUStringBuffer aBuf;
1404     for (sal_Int32 i=0; i < nQueryLen; ++i)
1405     {
1406         if (bComment2)
1407         {
1408             aBuf.append( &pCopy[i], 1);
1409             if ((i+1) < nQueryLen)
1410             {
1411                 if (pCopy[i]=='*' && pCopy[i+1]=='/')
1412                 {
1413                     bComment2 = false;
1414                     aBuf.append( &pCopy[++i], 1);
1415                     aRet.emplace_back( aBuf.makeStringAndClear(), false);
1416                 }
1417             }
1418             else
1419             {
1420                 // comment can't close anymore, actually an error, but...
1421                 aRet.emplace_back( aBuf.makeStringAndClear(), false);
1422             }
1423             continue;
1424         }
1425         if (pCopy[i] == '\n' || i == nQueryLen-1)
1426         {
1427             if (bComment)
1428             {
1429                 if (i == nQueryLen-1 && pCopy[i] != '\n')
1430                     aBuf.append( &pCopy[i], 1);
1431                 aRet.emplace_back( aBuf.makeStringAndClear(), true);
1432                 bComment = false;
1433             }
1434             else if (!aRet.empty())
1435                 aRet.back().mbLastOnLine = true;
1436         }
1437         else if (!bComment)
1438         {
1439             if (pCopy[i] == '\"' && !bIsText2)
1440                 bIsText1 = !bIsText1;
1441             else if (pCopy[i] == '\'' && !bIsText1)
1442                 bIsText2 = !bIsText2;
1443             if (!bIsText1 && !bIsText2 && (i+1) < nQueryLen)
1444             {
1445                 if ((pCopy[i]=='-' && pCopy[i+1]=='-') || (pCopy[i]=='/' && pCopy[i+1]=='/'))
1446                     bComment = true;
1447                 else if (pCopy[i]=='/' && pCopy[i+1]=='*')
1448                     bComment2 = true;
1449             }
1450         }
1451         if (bComment || bComment2)
1452             aBuf.append( &pCopy[i], 1);
1453     }
1454     return aRet;
1455 }
1456 
1457 /** Concat/insert comments that were previously obtained with getComment().
1458 
1459     NOTE: The current parser implementation does not preserve newlines, so all
1460     comments are always appended to the entire query, also inline comments
1461     that would need positioning anyway that can't be obtained after
1462     recomposition. This is ugly but at least allows commented queries while
1463     preserving the comments _somehow_.
1464  */
concatComment(const OUString & rQuery,const std::vector<CommentStrip> & rComments)1465 static OUString concatComment( const OUString& rQuery, const std::vector< CommentStrip >& rComments )
1466 {
1467     // No comments => return query.
1468     if (rComments.empty())
1469         return rQuery;
1470 
1471     const sal_Unicode* pBeg = rQuery.getStr();
1472     const sal_Int32 nLen = rQuery.getLength();
1473     const size_t nComments = rComments.size();
1474     // Obtaining the needed size once should be faster than reallocating.
1475     // Also add a blank or linefeed for each comment.
1476     sal_Int32 nBufSize = nLen + nComments;
1477     for (auto const& comment : rComments)
1478         nBufSize += comment.maComment.getLength();
1479     OUStringBuffer aBuf( nBufSize );
1480     sal_Int32 nIndBeg = 0;
1481     sal_Int32 nIndLF = rQuery.indexOf('\n');
1482     size_t i = 0;
1483     while (nIndLF >= 0 && i < nComments)
1484     {
1485         aBuf.append( pBeg + nIndBeg, nIndLF - nIndBeg);
1486         do
1487         {
1488             aBuf.append( rComments[i].maComment);
1489         } while (!rComments[i++].mbLastOnLine && i < nComments);
1490         aBuf.append( pBeg + nIndLF, 1);     // the LF
1491         nIndBeg = nIndLF + 1;
1492         nIndLF = (nIndBeg < nLen ? rQuery.indexOf( '\n', nIndBeg) : -1);
1493     }
1494     // Append remainder of query.
1495     if (nIndBeg < nLen)
1496         aBuf.append( pBeg + nIndBeg, nLen - nIndBeg);
1497     // Append all remaining comments, preserve lines.
1498     bool bNewLine = false;
1499     for ( ; i < nComments; ++i)
1500     {
1501         if (!bNewLine)
1502             aBuf.append( ' ');
1503         aBuf.append( rComments[i].maComment);
1504         if (rComments[i].mbLastOnLine)
1505         {
1506             aBuf.append( '\n');
1507             bNewLine = true;
1508         }
1509         else
1510             bNewLine = false;
1511     }
1512     return aBuf.makeStringAndClear();
1513 }
1514 
translateStatement(bool _bFireStatementChange)1515 OUString OQueryController::translateStatement( bool _bFireStatementChange )
1516 {
1517     // now set the properties
1518     setStatement_fireEvent( getContainer()->getStatement(), _bFireStatementChange );
1519     OUString sTranslatedStmt;
1520     if(!m_sStatement.isEmpty() && m_xComposer.is() && m_bEscapeProcessing)
1521     {
1522         try
1523         {
1524             OUString aErrorMsg;
1525 
1526             std::vector< CommentStrip > aComments = getComment( m_sStatement);
1527 
1528             std::unique_ptr<::connectivity::OSQLParseNode> pNode = m_aSqlParser.parseTree( aErrorMsg, m_sStatement, m_bGraphicalDesign );
1529             if(pNode)
1530             {
1531                 pNode->parseNodeToStr( sTranslatedStmt, getConnection() );
1532             }
1533 
1534             m_xComposer->setQuery(sTranslatedStmt);
1535             sTranslatedStmt = m_xComposer->getComposedQuery();
1536             sTranslatedStmt = concatComment( sTranslatedStmt, aComments);
1537         }
1538         catch(const SQLException& e)
1539         {
1540             ::dbtools::SQLExceptionInfo aInfo(e);
1541             showError(aInfo);
1542             // an error occurred so we clear the statement
1543             sTranslatedStmt.clear();
1544         }
1545     }
1546     else if(m_sStatement.isEmpty())
1547     {
1548         showError(SQLException(DBA_RES(STR_QRY_NOSELECT), nullptr, u"S1000"_ustr, 1000, Any()));
1549     }
1550     else
1551         sTranslatedStmt = m_sStatement;
1552 
1553     return sTranslatedStmt;
1554 }
1555 
saveModified()1556 short OQueryController::saveModified()
1557 {
1558     SolarMutexGuard aSolarGuard;
1559     ::osl::MutexGuard aGuard( getMutex() );
1560     short nRet = RET_YES;
1561     if ( !isConnected() || !isModified() )
1562         return nRet;
1563 
1564     if  (  !m_bGraphicalDesign
1565         || (  !m_vTableFieldDesc.empty()
1566            && !m_vTableData.empty()
1567            )
1568         )
1569     {
1570         OUString sMessageText( lcl_getObjectResourceString( STR_QUERY_SAVEMODIFIED, m_nCommandType ) );
1571 
1572         std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(getFrameWeld(),
1573                                                        VclMessageType::Question, VclButtonsType::YesNo,
1574                                                        sMessageText));
1575         xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL);
1576         xQueryBox->set_default_response(RET_YES);
1577 
1578         nRet = xQueryBox->run();
1579         if  (   ( nRet == RET_YES )
1580             &&  !doSaveAsDoc( false )
1581             )
1582         {
1583             nRet = RET_CANCEL;
1584         }
1585     }
1586     return nRet;
1587 }
1588 
impl_reset(const bool i_bForceCurrentControllerSettings)1589 void OQueryController::impl_reset( const bool i_bForceCurrentControllerSettings )
1590 {
1591     bool bValid = false;
1592 
1593     Sequence< PropertyValue > aLayoutInformation;
1594     // get command from the query if a query name was supplied
1595     if ( !i_bForceCurrentControllerSettings && !editingCommand() )
1596     {
1597         if ( !m_sName.isEmpty() )
1598         {
1599             Reference< XNameAccess > xQueries = getObjectContainer();
1600             if ( xQueries.is() )
1601             {
1602                 Reference< XPropertySet > xProp;
1603                 if( xQueries->hasByName( m_sName ) && ( xQueries->getByName( m_sName ) >>= xProp ) && xProp.is() )
1604                 {
1605                     OUString sNewStatement;
1606                     xProp->getPropertyValue( PROPERTY_COMMAND ) >>= sNewStatement;
1607                     setStatement_fireEvent( sNewStatement );
1608 
1609                     if ( editingQuery() )
1610                     {
1611                         bool bNewEscapeProcessing( true );
1612                         xProp->getPropertyValue( PROPERTY_ESCAPE_PROCESSING ) >>= bNewEscapeProcessing;
1613                         setEscapeProcessing_fireEvent( bNewEscapeProcessing );
1614                     }
1615 
1616                     m_bGraphicalDesign = m_bGraphicalDesign && m_bEscapeProcessing;
1617                     bValid = true;
1618 
1619                     try
1620                     {
1621                         if ( editingQuery() )
1622                             xProp->getPropertyValue( PROPERTY_LAYOUTINFORMATION ) >>= aLayoutInformation;
1623                     }
1624                     catch( const Exception& )
1625                     {
1626                         OSL_FAIL( "OQueryController::impl_reset: could not retrieve the layout information from the query!" );
1627                     }
1628                 }
1629             }
1630         }
1631     }
1632     else
1633     {
1634         bValid = true;
1635         // assume that we got all necessary information during initialization
1636     }
1637 
1638     if ( bValid )
1639     {
1640         // load the layoutInformation
1641         if ( aLayoutInformation.hasElements() )
1642         {
1643             try
1644             {
1645                 loadViewSettings( aLayoutInformation );
1646             }
1647             catch( const Exception& )
1648             {
1649                 DBG_UNHANDLED_EXCEPTION("dbaccess");
1650             }
1651         }
1652 
1653         if ( !m_sStatement.isEmpty() )
1654         {
1655             setQueryComposer();
1656 
1657             bool bError( false );
1658 
1659             if ( !m_pSqlIterator )
1660             {
1661                 bError = true;
1662             }
1663             else if ( m_bEscapeProcessing )
1664             {
1665                 OUString aErrorMsg;
1666                 std::unique_ptr< ::connectivity::OSQLParseNode > pNode(
1667                     m_aSqlParser.parseTree( aErrorMsg, m_sStatement, m_bGraphicalDesign ) );
1668 
1669                 if (pNode)
1670                 {
1671                     delete m_pSqlIterator->getParseTree();
1672                     m_pSqlIterator->setParseTree( pNode.release() );
1673                     m_pSqlIterator->traverseAll();
1674                     if ( m_pSqlIterator->hasErrors() )
1675                     {
1676                         if ( !i_bForceCurrentControllerSettings && m_bGraphicalDesign && !editingView() )
1677                         {
1678                             impl_showAutoSQLViewError( Any( m_pSqlIterator->getErrors() ) );
1679                         }
1680                         bError = true;
1681                     }
1682                 }
1683                 else
1684                 {
1685                     if ( !i_bForceCurrentControllerSettings && !editingView() )
1686                     {
1687                         OUString aTitle(DBA_RES(STR_SVT_SQL_SYNTAX_ERROR));
1688                         OSQLMessageBox aDlg(getFrameWeld(), aTitle, aErrorMsg);
1689                         aDlg.run();
1690                     }
1691                     bError = true;
1692                 }
1693             }
1694 
1695             if ( bError )
1696             {
1697                 m_bGraphicalDesign = false;
1698                 if ( editingView() )
1699                     // if we're editing a view whose statement could not be parsed, default to "no escape processing"
1700                     setEscapeProcessing_fireEvent( false );
1701             }
1702         }
1703     }
1704 
1705     if(!m_pSqlIterator)
1706         setQueryComposer();
1707     OSL_ENSURE(m_pSqlIterator,"No SQLIterator set!");
1708 
1709     getContainer()->setNoneVisibleRow(m_nVisibleRows);
1710 }
1711 
reset()1712 void OQueryController::reset()
1713 {
1714     impl_reset();
1715     getContainer()->reset();
1716     ClearUndoManager();
1717 }
1718 
setStatement_fireEvent(const OUString & _rNewStatement,bool _bFireStatementChange)1719 void OQueryController::setStatement_fireEvent( const OUString& _rNewStatement, bool _bFireStatementChange )
1720 {
1721     Any aOldValue( m_sStatement );
1722     m_sStatement = _rNewStatement;
1723     Any aNewValue( m_sStatement );
1724 
1725     sal_Int32 nHandle = PROPERTY_ID_ACTIVECOMMAND;
1726     if ( _bFireStatementChange )
1727         fire( &nHandle, &aNewValue, &aOldValue, 1, false );
1728 }
1729 
setEscapeProcessing_fireEvent(const bool _bEscapeProcessing)1730 void OQueryController::setEscapeProcessing_fireEvent( const bool _bEscapeProcessing )
1731 {
1732     if ( _bEscapeProcessing == m_bEscapeProcessing )
1733         return;
1734 
1735     Any aOldValue( m_bEscapeProcessing );
1736     m_bEscapeProcessing = _bEscapeProcessing;
1737     Any aNewValue( m_bEscapeProcessing );
1738 
1739     sal_Int32 nHandle = PROPERTY_ID_ESCAPE_PROCESSING;
1740     fire( &nHandle, &aNewValue, &aOldValue, 1, false );
1741 }
1742 
IMPL_LINK_NOARG(OQueryController,OnExecuteAddTable,void *,void)1743 IMPL_LINK_NOARG( OQueryController, OnExecuteAddTable, void*, void )
1744 {
1745     Execute( ID_BROWSER_ADDTABLE,Sequence<PropertyValue>() );
1746 }
1747 
allowViews() const1748 bool OQueryController::allowViews() const
1749 {
1750     return true;
1751 }
1752 
allowQueries() const1753 bool OQueryController::allowQueries() const
1754 {
1755     OSL_ENSURE( getSdbMetaData().isConnected(), "OQueryController::allowQueries: illegal call!" );
1756     if ( !getSdbMetaData().supportsSubqueriesInFrom() )
1757         return false;
1758 
1759     bool bCreatingView = ( m_nCommandType == CommandType::TABLE );
1760     return !bCreatingView;
1761 }
1762 
getViewData()1763 Any SAL_CALL OQueryController::getViewData()
1764 {
1765     ::osl::MutexGuard aGuard( getMutex() );
1766 
1767     getContainer()->SaveUIConfig();
1768 
1769     ::comphelper::NamedValueCollection aViewSettings;
1770     saveViewSettings( aViewSettings, false );
1771 
1772     return Any( aViewSettings.getPropertyValues() );
1773 }
1774 
restoreViewData(const Any &)1775 void SAL_CALL OQueryController::restoreViewData(const Any& /*Data*/)
1776 {
1777     // TODO
1778 }
1779 
1780 } // namespace dbaui
1781 
1782 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1783