/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "FieldDescriptions.hxx" #include "TEditControl.hxx" #include "TableController.hxx" #include "TableDesignView.hxx" #include "TableRow.hxx" #include "TypeInfo.hxx" #include "UITools.hxx" #include "browserids.hxx" #include "core_resource.hxx" #include "dbu_reghelper.hxx" #include "stringconstants.hxx" #include "strings.hrc" #include "defaultobjectnamecheck.hxx" #include "dlgsave.hxx" #include "dsmeta.hxx" #include "indexdialog.hxx" #include "sqlmessage.hxx" #include "uiservices.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" void SAL_CALL createRegistryInfo_OTableControl() { static ::dbaui::OMultiInstanceAutoRegistration< ::dbaui::OTableController > aAutoRegistration; } using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::io; using namespace ::com::sun::star::beans; using namespace ::com::sun::star::frame; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::container; using namespace ::com::sun::star::sdbcx; using namespace ::com::sun::star::sdbc; using namespace ::com::sun::star::sdb; using namespace ::com::sun::star::ui; using namespace ::com::sun::star::util; using namespace ::dbtools; using namespace ::dbaui; using namespace ::comphelper; // number of columns when creating it from scratch #define NEWCOLS 128 namespace { void dropTable(const Reference& _rxTable,const OUString& _sTableName) { if ( _rxTable->hasByName(_sTableName) ) { Reference xNameCont(_rxTable,UNO_QUERY); OSL_ENSURE(xNameCont.is(),"No drop interface for tables!"); if ( xNameCont.is() ) xNameCont->dropByName(_sTableName); } } } OUString SAL_CALL OTableController::getImplementationName() { return getImplementationName_Static(); } OUString OTableController::getImplementationName_Static() { return OUString("org.openoffice.comp.dbu.OTableDesign"); } Sequence< OUString> OTableController::getSupportedServiceNames_Static() { Sequence aSupported { "com.sun.star.sdb.TableDesign" }; return aSupported; } Sequence< OUString> SAL_CALL OTableController::getSupportedServiceNames() { return getSupportedServiceNames_Static(); } Reference< XInterface > SAL_CALL OTableController::Create(const Reference& _rxFactory) { return *(new OTableController(comphelper::getComponentContext(_rxFactory))); } OTableController::OTableController(const Reference< XComponentContext >& _rM) : OTableController_BASE(_rM) ,m_sTypeNames(DBA_RES(STR_TABLEDESIGN_DBFIELDTYPES)) ,m_pTypeInfo() ,m_bAllowAutoIncrementValue(false) ,m_bNew(true) { InvalidateAll(); m_pTypeInfo = std::make_shared(); m_pTypeInfo->aUIName = m_sTypeNames.getToken(TYPE_OTHER, ';'); } OTableController::~OTableController() { m_aTypeInfoIndex.clear(); m_aTypeInfo.clear(); } void OTableController::startTableListening() { Reference< XComponent > xComponent(m_xTable, UNO_QUERY); if (xComponent.is()) xComponent->addEventListener(static_cast(this)); } void OTableController::stopTableListening() { Reference< XComponent > xComponent(m_xTable, UNO_QUERY); if (xComponent.is()) xComponent->removeEventListener(static_cast(this)); } void OTableController::disposing() { OTableController_BASE::disposing(); clearView(); m_vRowList.clear(); } FeatureState OTableController::GetState(sal_uInt16 _nId) const { FeatureState aReturn; // disabled automatically switch (_nId) { case ID_BROWSER_CLOSE: aReturn.bEnabled = true; break; case ID_BROWSER_EDITDOC: aReturn.bChecked = isEditable(); aReturn.bEnabled = true; break; case ID_BROWSER_SAVEDOC: aReturn.bEnabled = isEditable() && std::any_of(m_vRowList.begin(),m_vRowList.end(),std::mem_fn(&OTableRow::isValid)); break; case ID_BROWSER_SAVEASDOC: aReturn.bEnabled = isConnected() && isEditable(); if ( aReturn.bEnabled ) { aReturn.bEnabled = std::any_of(m_vRowList.begin(),m_vRowList.end(), std::mem_fn(&OTableRow::isValid)); } break; case ID_BROWSER_CUT: aReturn.bEnabled = isEditable() && m_aCurrentFrame.isActive() && getView() && static_cast(getView())->isCutAllowed(); break; case ID_BROWSER_COPY: aReturn.bEnabled = m_aCurrentFrame.isActive() && getView() && static_cast(getView())->isCopyAllowed(); break; case ID_BROWSER_PASTE: aReturn.bEnabled = isEditable() && m_aCurrentFrame.isActive() && getView() && static_cast(getView())->isPasteAllowed(); break; case SID_INDEXDESIGN: aReturn.bEnabled = ( ( ((!m_bNew && impl_isModified()) || impl_isModified()) || Reference< XIndexesSupplier >(m_xTable, UNO_QUERY).is() ) && isConnected() ); if ( aReturn.bEnabled ) { aReturn.bEnabled = std::any_of(m_vRowList.begin(),m_vRowList.end(), std::mem_fn(&OTableRow::isValid)); } break; default: aReturn = OTableController_BASE::GetState(_nId); } return aReturn; } void OTableController::Execute(sal_uInt16 _nId, const Sequence< PropertyValue >& aArgs) { switch(_nId) { case ID_BROWSER_EDITDOC: setEditable(!isEditable()); static_cast(getView())->setReadOnly(!isEditable()); InvalidateFeature(ID_BROWSER_SAVEDOC); InvalidateFeature(ID_BROWSER_PASTE); InvalidateFeature(SID_BROWSER_CLEAR_QUERY); break; case ID_BROWSER_SAVEASDOC: doSaveDoc(true); break; case ID_BROWSER_SAVEDOC: static_cast(getView())->GetEditorCtrl()->SaveCurRow(); doSaveDoc(false); break; case ID_BROWSER_CUT: static_cast(getView())->cut(); break; case ID_BROWSER_COPY: static_cast(getView())->copy(); break; case ID_BROWSER_PASTE: static_cast(getView())->paste(); break; case SID_INDEXDESIGN: doEditIndexes(); break; default: OTableController_BASE::Execute(_nId,aArgs); } InvalidateFeature(_nId); } bool OTableController::doSaveDoc(bool _bSaveAs) { if (!isConnected()) reconnect(true); // ask the user for a new connection Reference xTablesSup(getConnection(),UNO_QUERY); if (!xTablesSup.is()) { OUString aMessage(DBA_RES(STR_TABLEDESIGN_CONNECTION_MISSING)); ScopedVclPtrInstance(getView(), aMessage )->Execute(); return false; } // check if a column exists // TODO Reference xTables; OUString sCatalog, sSchema; bool bNew = m_sName.isEmpty(); bNew = bNew || m_bNew || _bSaveAs; try { xTables = xTablesSup->getTables(); OSL_ENSURE(xTables.is(),"The tables can't be null!"); bNew = bNew || (xTables.is() && !xTables->hasByName(m_sName)); // first we need a name for our query so ask the user if(bNew) { OUString aDefaultName; if (_bSaveAs && !bNew) aDefaultName = m_sName; else { OUString aName = DBA_RES(STR_TBL_TITLE); aDefaultName = aName.getToken(0,' '); aDefaultName = ::dbtools::createUniqueName(xTables,aDefaultName); } DynamicTableOrQueryNameCheck aNameChecker( getConnection(), CommandType::TABLE ); ScopedVclPtrInstance< OSaveAsDlg > aDlg( getView(), CommandType::TABLE, getORB(), getConnection(), aDefaultName, aNameChecker ); if ( aDlg->Execute() != RET_OK ) return false; m_sName = aDlg->getName(); sCatalog = aDlg->getCatalog(); sSchema = aDlg->getSchema(); } // did we get a name if(m_sName.isEmpty()) return false; } catch(Exception&) { OSL_FAIL("OTableController::doSaveDoc: nothing is expected to happen here!"); } bool bAlter = false; bool bError = false; SQLExceptionInfo aInfo; try { // check the columns for double names if(!checkColumns(bNew || !xTables->hasByName(m_sName))) { return false; } Reference xTable; if(bNew || !xTables->hasByName(m_sName)) // just to make sure the table already exists { dropTable(xTables,m_sName); Reference xFact(xTables,UNO_QUERY); OSL_ENSURE(xFact.is(),"OTableController::doSaveDoc: No XDataDescriptorFactory available!"); xTable = xFact->createDataDescriptor(); OSL_ENSURE(xTable.is(),"OTableController::doSaveDoc: Create query failed!"); // to set the name is only allowed when the query is new xTable->setPropertyValue(PROPERTY_CATALOGNAME,makeAny(sCatalog)); xTable->setPropertyValue(PROPERTY_SCHEMANAME,makeAny(sSchema)); xTable->setPropertyValue(PROPERTY_NAME,makeAny(m_sName)); // now append the columns Reference xColSup(xTable,UNO_QUERY); appendColumns(xColSup,bNew); // now append the primary key Reference xKeySup(xTable,UNO_QUERY); appendPrimaryKey(xKeySup,bNew); } // now set the properties if(bNew) { Reference xAppend(xTables,UNO_QUERY); OSL_ENSURE(xAppend.is(),"OTableController::doSaveDoc: No XAppend Interface!"); xAppend->appendByDescriptor(xTable); assignTable(); if(!m_xTable.is()) // correct name and try again { // it can be that someone inserted new data for us m_sName = ::dbtools::composeTableName( getConnection()->getMetaData(), xTable, ::dbtools::EComposeRule::InDataManipulation, false, false, false ); assignTable(); } // now check if our datasource has set a tablefilter and if append the new table name to it ::dbaui::appendToFilter(getConnection(),m_sName,getORB(),getView()); // we are not interessted in the return value Reference< frame::XTitleChangeListener> xEventListener(impl_getTitleHelper_throw(),UNO_QUERY); if ( xEventListener.is() ) { frame::TitleChangedEvent aEvent; xEventListener->titleChanged(aEvent); } releaseNumberForComponent(); } else if(m_xTable.is()) { bAlter = true; alterColumns(); } reSyncRows(); } catch(const SQLContext& e) { aInfo = SQLExceptionInfo(e); } catch(const SQLWarning& e) { aInfo = SQLExceptionInfo(e); } catch(const SQLException& e) { aInfo = SQLExceptionInfo(e); } catch(const ElementExistException& ) { OUString sText( DBA_RES( STR_NAME_ALREADY_EXISTS ) ); sText = sText.replaceFirst( "#" , m_sName); ScopedVclPtrInstance< OSQLMessageBox > aDlg( getView(), DBA_RES( STR_ERROR_DURING_CREATION ), sText, MessBoxStyle::Ok, OSQLMessageBox::Error ); aDlg->Execute(); bError = true; } catch( const Exception& ) { bError = true; DBG_UNHANDLED_EXCEPTION(); } if ( aInfo.isValid() ) aInfo.prepend( DBA_RES( STR_TABLEDESIGN_SAVE_ERROR ) ); showError(aInfo); if (aInfo.isValid() || bError) { if(!bAlter || bNew) { m_sName.clear(); stopTableListening(); m_xTable = nullptr; } } return ! (aInfo.isValid() || bError); } void OTableController::doEditIndexes() { // table needs to be saved before editing indexes if (m_bNew || isModified()) { ScopedVclPtrInstance< MessageDialog > aAsk(getView(), DBA_RES(STR_QUERY_SAVE_TABLE_EDIT_INDEXES), VclMessageType::Question, VclButtonsType::YesNo); if (RET_YES != aAsk->Execute()) return; if (!doSaveDoc(false)) return; OSL_ENSURE(!m_bNew && !isModified(), "OTableController::doEditIndexes: what the hell did doSaveDoc do?"); } Reference< XNameAccess > xIndexes; // will be the keys of the table Sequence< OUString > aFieldNames; // will be the column names of the table try { // get the keys Reference< XIndexesSupplier > xIndexesSupp(m_xTable, UNO_QUERY); if (xIndexesSupp.is()) { xIndexes = xIndexesSupp->getIndexes(); OSL_ENSURE(xIndexes.is(), "OTableController::doEditIndexes: no keys got from the indexes supplier!"); } else OSL_FAIL("OTableController::doEditIndexes: should never have reached this (no indexes supplier)!"); // get the field names Reference< XColumnsSupplier > xColSupp(m_xTable, UNO_QUERY); OSL_ENSURE(xColSupp.is(), "OTableController::doEditIndexes: no columns supplier!"); if (xColSupp.is()) { Reference< XNameAccess > xCols = xColSupp->getColumns(); OSL_ENSURE(xCols.is(), "OTableController::doEditIndexes: no columns!"); if (xCols.is()) aFieldNames = xCols->getElementNames(); } } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } if (!xIndexes.is()) return; ScopedVclPtrInstance< DbaIndexDialog > aDialog(getView(), aFieldNames, xIndexes, getConnection(), getORB()); if (RET_OK != aDialog->Execute()) return; } void OTableController::impl_initialize() { try { OTableController_BASE::impl_initialize(); const NamedValueCollection& rArguments( getInitParams() ); rArguments.get_ensureType( PROPERTY_CURRENTTABLE, m_sName ); // read autoincrement value set in the datasource ::dbaui::fillAutoIncrementValue(getDataSource(),m_bAllowAutoIncrementValue,m_sAutoIncrementValue); assignTable(); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } try { ::dbaui::fillTypeInfo(getConnection(),m_sTypeNames,m_aTypeInfo,m_aTypeInfoIndex); // fill the needed type information } catch(const SQLException&) { ScopedVclPtrInstance(getView(), DBA_RES( STR_NO_TYPE_INFO_AVAILABLE ))->Execute(); throw; } try { loadData(); // fill the column information from the table getView()->initialize(); // show the windows and fill with our information ClearUndoManager(); setModified(false); // and we are not modified yet } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } } bool OTableController::Construct(vcl::Window* pParent) { setView( VclPtr::Create( pParent, getORB(), *this ) ); OTableController_BASE::Construct(pParent); return true; } sal_Bool SAL_CALL OTableController::suspend(sal_Bool /*_bSuspend*/) { if ( getBroadcastHelper().bInDispose || getBroadcastHelper().bDisposed ) return true; SolarMutexGuard aSolarGuard; ::osl::MutexGuard aGuard( getMutex() ); if ( getView() && getView()->IsInModalMode() ) return false; if ( getView() ) static_cast(getView())->GrabFocus(); bool bCheck = true; if ( isModified() ) { if ( std::any_of(m_vRowList.begin(),m_vRowList.end(), std::mem_fn(&OTableRow::isValid)) ) { ScopedVclPtrInstance aQry(getView(), "TableDesignSaveModifiedDialog", "dbaccess/ui/tabledesignsavemodifieddialog.ui"); switch (aQry->Execute()) { case RET_YES: Execute(ID_BROWSER_SAVEDOC,Sequence()); if ( isModified() ) bCheck = false; // when we save the table this must be false else some press cancel break; case RET_CANCEL: bCheck = false; break; default: break; } } else if ( !m_bNew ) { ScopedVclPtrInstance aQry(getView(), "DeleteAllRowsDialog", "dbaccess/ui/deleteallrowsdialog.ui"); switch (aQry->Execute()) { case RET_YES: { try { Reference xTablesSup(getConnection(),UNO_QUERY); Reference xTables = xTablesSup->getTables(); dropTable(xTables,m_sName); } catch(const Exception&) { OSL_FAIL("OTableController::suspend: nothing is expected to happen here!"); } } break; case RET_CANCEL: bCheck = false; break; default: break; } } } return bCheck; } void OTableController::describeSupportedFeatures() { OSingleDocumentController::describeSupportedFeatures(); implDescribeSupportedFeature( ".uno:Redo", ID_BROWSER_REDO, CommandGroup::EDIT ); implDescribeSupportedFeature( ".uno:Save", ID_BROWSER_SAVEDOC, CommandGroup::EDIT ); implDescribeSupportedFeature( ".uno:Undo", ID_BROWSER_UNDO, CommandGroup::EDIT ); implDescribeSupportedFeature( ".uno:HelpMenu", SID_HELPMENU, CommandGroup::APPLICATION ); implDescribeSupportedFeature( ".uno:NewDoc", SID_NEWDOC, CommandGroup::DOCUMENT ); implDescribeSupportedFeature( ".uno:SaveAs", ID_BROWSER_SAVEASDOC, CommandGroup::DOCUMENT ); implDescribeSupportedFeature( ".uno:DBIndexDesign", SID_INDEXDESIGN, CommandGroup::APPLICATION ); implDescribeSupportedFeature( ".uno:EditDoc", ID_BROWSER_EDITDOC, CommandGroup::EDIT ); } void OTableController::impl_onModifyChanged() { OSingleDocumentController::impl_onModifyChanged(); InvalidateFeature( SID_INDEXDESIGN ); } void SAL_CALL OTableController::disposing( const EventObject& _rSource ) { if ( _rSource.Source == m_xTable ) { // some deleted our table so we have a new one stopTableListening(); m_xTable = nullptr; m_bNew = true; setModified(true); } else OTableController_BASE::disposing( _rSource ); } void OTableController::losingConnection( ) { // let the base class do its reconnect OTableController_BASE::losingConnection( ); // remove from the table Reference< XComponent > xComponent(m_xTable, UNO_QUERY); if (xComponent.is()) { Reference xEvtL( static_cast< ::cppu::OWeakObject*>(this), UNO_QUERY); xComponent->removeEventListener(xEvtL); } stopTableListening(); m_xTable = nullptr; assignTable(); if(!m_xTable.is()) { m_bNew = true; setModified(true); } InvalidateAll(); } TOTypeInfoSP OTableController::getTypeInfoByType(sal_Int32 _nDataType) const { return queryTypeInfoByType(_nDataType,m_aTypeInfo); } void OTableController::appendColumns(Reference const & _rxColSup, bool _bNew, bool _bKeyColumns) { try { // now append the columns OSL_ENSURE(_rxColSup.is(),"No columns supplier"); if(!_rxColSup.is()) return; Reference xColumns = _rxColSup->getColumns(); OSL_ENSURE(xColumns.is(),"No columns"); Reference xColumnFactory(xColumns,UNO_QUERY); Reference xAppend(xColumns,UNO_QUERY); OSL_ENSURE(xAppend.is(),"No XAppend Interface!"); std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); for(;aIter != aEnd;++aIter) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( !pField || (!_bNew && (*aIter)->IsReadOnly() && !_bKeyColumns) ) continue; Reference xColumn; if(pField->IsPrimaryKey() || !_bKeyColumns) xColumn = xColumnFactory->createDataDescriptor(); if(xColumn.is()) { if(!_bKeyColumns) ::dbaui::setColumnProperties(xColumn,pField); else xColumn->setPropertyValue(PROPERTY_NAME,makeAny(pField->GetName())); xAppend->appendByDescriptor(xColumn); xColumn = nullptr; // now only the settings are missing if(xColumns->hasByName(pField->GetName())) { xColumns->getByName(pField->GetName()) >>= xColumn; if(xColumn.is()) pField->copyColumnSettingsTo(xColumn); } else { OSL_FAIL("OTableController::appendColumns: invalid field name!"); } } } } catch(const SQLException& ) { showError( SQLExceptionInfo( ::cppu::getCaughtException() ) ); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } } void OTableController::appendPrimaryKey(Reference const & _rxSup, bool _bNew) { if(!_rxSup.is()) return; // the database doesn't support keys OSL_ENSURE(_rxSup.is(),"No XKeysSupplier!"); Reference xKeys(_rxSup->getKeys(),UNO_QUERY); Reference xProp; if (!xKeys.is()) return; const sal_Int32 nCount = xKeys->getCount(); for(sal_Int32 i=0;i< nCount ;++i) { xKeys->getByIndex(i) >>= xProp; sal_Int32 nKeyType = 0; xProp->getPropertyValue(PROPERTY_TYPE) >>= nKeyType; if(KeyType::PRIMARY == nKeyType) { return; // primary key already exists after appending a column } } Reference xKeyFactory(xKeys,UNO_QUERY); OSL_ENSURE(xKeyFactory.is(),"No XDataDescriptorFactory Interface!"); if ( !xKeyFactory.is() ) return; Reference xAppend(xKeyFactory,UNO_QUERY); OSL_ENSURE(xAppend.is(),"No XAppend Interface!"); Reference xKey = xKeyFactory->createDataDescriptor(); OSL_ENSURE(xKey.is(),"Key is null!"); xKey->setPropertyValue(PROPERTY_TYPE,makeAny(KeyType::PRIMARY)); Reference xColSup(xKey,UNO_QUERY); if(xColSup.is()) { appendColumns(xColSup,_bNew,true); Reference xColumns = xColSup->getColumns(); if(xColumns->hasElements()) xAppend->appendByDescriptor(xKey); } } void OTableController::loadData() { // if the data structure already exists, empty it m_vRowList.clear(); std::shared_ptr pTabEdRow; Reference< XDatabaseMetaData> xMetaData = getMetaData( ); // fill data structure with data from DataDefinitionObject if(m_xTable.is() && xMetaData.is()) { Reference xColSup(m_xTable,UNO_QUERY); OSL_ENSURE(xColSup.is(),"No XColumnsSupplier!"); Reference xColumns = xColSup->getColumns(); // ReadOnly-Flag // For Drop no row may be editable // For Add only the empty rows may be editable // For Add and Drop all rows can be edited // sal_Bool bReadOldRow = xMetaData->supportsAlterTableWithAddColumn() && xMetaData->supportsAlterTableWithDropColumn(); bool bIsAlterAllowed = isAlterAllowed(); Sequence< OUString> aColumns = xColumns->getElementNames(); const OUString* pIter = aColumns.getConstArray(); const OUString* pEnd = pIter + aColumns.getLength(); for(;pIter != pEnd;++pIter) { Reference xColumn; xColumns->getByName(*pIter) >>= xColumn; sal_Int32 nType = 0; sal_Int32 nScale = 0; sal_Int32 nPrecision = 0; sal_Int32 nNullable = 0; sal_Int32 nFormatKey = 0; sal_Int32 nAlign = 0; bool bIsAutoIncrement = false, bIsCurrency = false; OUString sName,sDescription,sTypeName,sHelpText; Any aControlDefault; // get the properties from the column xColumn->getPropertyValue(PROPERTY_NAME) >>= sName; xColumn->getPropertyValue(PROPERTY_TYPENAME) >>= sTypeName; xColumn->getPropertyValue(PROPERTY_ISNULLABLE) >>= nNullable; xColumn->getPropertyValue(PROPERTY_ISAUTOINCREMENT) >>= bIsAutoIncrement; xColumn->getPropertyValue(PROPERTY_ISCURRENCY) >>= bIsCurrency; xColumn->getPropertyValue(PROPERTY_TYPE) >>= nType; xColumn->getPropertyValue(PROPERTY_SCALE) >>= nScale; xColumn->getPropertyValue(PROPERTY_PRECISION) >>= nPrecision; xColumn->getPropertyValue(PROPERTY_DESCRIPTION) >>= sDescription; if(xColumn->getPropertySetInfo()->hasPropertyByName(PROPERTY_HELPTEXT)) xColumn->getPropertyValue(PROPERTY_HELPTEXT) >>= sHelpText; if(xColumn->getPropertySetInfo()->hasPropertyByName(PROPERTY_CONTROLDEFAULT)) aControlDefault = xColumn->getPropertyValue(PROPERTY_CONTROLDEFAULT); if(xColumn->getPropertySetInfo()->hasPropertyByName(PROPERTY_FORMATKEY)) xColumn->getPropertyValue(PROPERTY_FORMATKEY) >>= nFormatKey; if(xColumn->getPropertySetInfo()->hasPropertyByName(PROPERTY_ALIGN)) xColumn->getPropertyValue(PROPERTY_ALIGN) >>= nAlign; pTabEdRow.reset(new OTableRow()); pTabEdRow->SetReadOnly(!bIsAlterAllowed); // search for type bool bForce; OUString const sCreate("x"); TOTypeInfoSP pTypeInfo = ::dbaui::getTypeInfoFromType(m_aTypeInfo,nType,sTypeName,sCreate,nPrecision,nScale,bIsAutoIncrement,bForce); if ( !pTypeInfo.get() ) pTypeInfo = m_pTypeInfo; pTabEdRow->SetFieldType( pTypeInfo, bForce ); OFieldDescription* pActFieldDescr = pTabEdRow->GetActFieldDescr(); OSL_ENSURE(pActFieldDescr, "OTableController::loadData: invalid field description generated by the table row!"); if ( pActFieldDescr ) { pActFieldDescr->SetName(sName); pActFieldDescr->SetFormatKey(nFormatKey); pActFieldDescr->SetDescription(sDescription); pActFieldDescr->SetHelpText(sHelpText); pActFieldDescr->SetAutoIncrement(bIsAutoIncrement); pActFieldDescr->SetHorJustify(dbaui::mapTextJustify(nAlign)); pActFieldDescr->SetCurrency(bIsCurrency); // special data pActFieldDescr->SetIsNullable(nNullable); pActFieldDescr->SetControlDefault(aControlDefault); pActFieldDescr->SetPrecision(nPrecision); pActFieldDescr->SetScale(nScale); } m_vRowList.push_back( pTabEdRow); } // fill the primary key information Reference xKeyColumns = getKeyColumns(); if(xKeyColumns.is()) { Sequence< OUString> aKeyColumns = xKeyColumns->getElementNames(); const OUString* pKeyBegin = aKeyColumns.getConstArray(); const OUString* pKeyEnd = pKeyBegin + aKeyColumns.getLength(); for(;pKeyBegin != pKeyEnd;++pKeyBegin) { std::vector< std::shared_ptr >::const_iterator rowIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator rowEnd = m_vRowList.end(); for(;rowIter != rowEnd;++rowIter) { if((*rowIter)->GetActFieldDescr()->GetName() == *pKeyBegin) { (*rowIter)->SetPrimaryKey(true); break; } } } } } // fill empty rows OTypeInfoMap::const_iterator aTypeIter = m_aTypeInfo.find(DataType::VARCHAR); if(aTypeIter == m_aTypeInfo.end()) aTypeIter = m_aTypeInfo.begin(); OSL_ENSURE(aTypeIter != m_aTypeInfo.end(),"We have no type information!"); bool bReadRow = !isAddAllowed(); for(sal_Int32 i=m_vRowList.size(); i < NEWCOLS; i++ ) { pTabEdRow.reset(new OTableRow()); pTabEdRow->SetReadOnly(bReadRow); m_vRowList.push_back( pTabEdRow); } } Reference OTableController::getKeyColumns() const { return getPrimaryKeyColumns_throw(m_xTable); } bool OTableController::checkColumns(bool _bNew) { bool bOk = true; bool bFoundPKey = false; Reference< XDatabaseMetaData > xMetaData = getMetaData( ); DatabaseMetaData aMetaData( getConnection() ); ::comphelper::UStringMixEqual bCase(!xMetaData.is() || xMetaData->supportsMixedCaseQuotedIdentifiers()); std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); for(;aIter != aEnd;++aIter) { OFieldDescription* pFieldDesc = (*aIter)->GetActFieldDescr(); if (pFieldDesc && !pFieldDesc->GetName().isEmpty()) { bFoundPKey |= (*aIter)->IsPrimaryKey(); // first check for duplicate names std::vector< std::shared_ptr >::const_iterator aIter2 = aIter+1; for(;aIter2 != aEnd;++aIter2) { OFieldDescription* pCompareDesc = (*aIter2)->GetActFieldDescr(); if (pCompareDesc && bCase(pCompareDesc->GetName(),pFieldDesc->GetName())) { OUString strMessage = DBA_RES(STR_TABLEDESIGN_DUPLICATE_NAME); strMessage = strMessage.replaceFirst("$column$", pFieldDesc->GetName()); ScopedVclPtrInstance(getView(), strMessage)->Execute(); return false; } } } } if ( _bNew && !bFoundPKey && aMetaData.supportsPrimaryKeys() ) { OUString sTitle(DBA_RES(STR_TABLEDESIGN_NO_PRIM_KEY_HEAD)); OUString sMsg(DBA_RES(STR_TABLEDESIGN_NO_PRIM_KEY)); ScopedVclPtrInstance< OSQLMessageBox > aBox(getView(), sTitle,sMsg, MessBoxStyle::YesNoCancel | MessBoxStyle::DefaultYes); switch ( aBox->Execute() ) { case RET_YES: { std::shared_ptr pNewRow(new OTableRow()); TOTypeInfoSP pTypeInfo = ::dbaui::queryPrimaryKeyType(m_aTypeInfo); if ( !pTypeInfo.get() ) break; pNewRow->SetFieldType( pTypeInfo ); OFieldDescription* pActFieldDescr = pNewRow->GetActFieldDescr(); pActFieldDescr->SetAutoIncrement(false); pActFieldDescr->SetIsNullable(ColumnValue::NO_NULLS); pActFieldDescr->SetName( createUniqueName("ID" )); pActFieldDescr->SetPrimaryKey( true ); m_vRowList.insert(m_vRowList.begin(),pNewRow); static_cast(getView())->GetEditorCtrl()->Invalidate(); static_cast(getView())->GetEditorCtrl()->RowInserted(0); } break; case RET_CANCEL: bOk = false; break; } } return bOk; } void OTableController::alterColumns() { Reference xColSup(m_xTable,UNO_QUERY_THROW); OSL_ENSURE(xColSup.is(),"What happen here?!"); Reference xColumns = xColSup->getColumns(); Reference xIdxColumns(xColumns,UNO_QUERY_THROW); OSL_ENSURE(xColumns.is(),"No columns"); if ( !xColumns.is() ) return; Reference xAlter(m_xTable,UNO_QUERY); // can be null sal_Int32 nColumnCount = xIdxColumns->getCount(); Reference xDrop(xColumns,UNO_QUERY); // can be null Reference xAppend(xColumns,UNO_QUERY); // can be null Reference xColumnFactory(xColumns,UNO_QUERY); // can be null bool bReload = false; // refresh the data // contains all columns names which are already handled those which are not in the list will be deleted Reference< XDatabaseMetaData> xMetaData = getMetaData( ); std::set aColumns( comphelper::UStringMixLess( !xMetaData.is() || xMetaData->supportsMixedCaseQuotedIdentifiers())); std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); // first look for columns where something other than the name changed sal_Int32 nPos = 0; for(;aIter != aEnd;++aIter,++nPos) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( !pField ) continue; if ( (*aIter)->IsReadOnly() ) { aColumns.insert(pField->GetName()); continue; } Reference xColumn; if ( xColumns->hasByName(pField->GetName()) ) { aColumns.insert(pField->GetName()); xColumns->getByName(pField->GetName()) >>= xColumn; OSL_ENSURE(xColumn.is(),"Column is null!"); sal_Int32 nType=0,nPrecision=0,nScale=0,nNullable=0; bool bAutoIncrement = false; OUString sTypeName,sDescription; xColumn->getPropertyValue(PROPERTY_TYPE) >>= nType; xColumn->getPropertyValue(PROPERTY_PRECISION) >>= nPrecision; xColumn->getPropertyValue(PROPERTY_SCALE) >>= nScale; xColumn->getPropertyValue(PROPERTY_ISNULLABLE) >>= nNullable; xColumn->getPropertyValue(PROPERTY_ISAUTOINCREMENT) >>= bAutoIncrement; xColumn->getPropertyValue(PROPERTY_DESCRIPTION) >>= sDescription; try { xColumn->getPropertyValue(PROPERTY_TYPENAME) >>= sTypeName; } catch( const Exception& ) { OSL_FAIL( "no TypeName property?!" ); // since this is a last minute fix for #i41785#, I want to be on the safe side, // and catch errors here as early as possible (instead of the whole process of altering // the columns failing) // Normally, sdbcx::Column objects are expected to have a TypeName property } // check if something changed if((nType != pField->GetType() || sTypeName != pField->GetTypeName() || (nPrecision != pField->GetPrecision() && nPrecision ) || nScale != pField->GetScale() || nNullable != pField->GetIsNullable() || sDescription != pField->GetDescription() || bAutoIncrement != pField->IsAutoIncrement())&& xColumnFactory.is()) { Reference xNewColumn; xNewColumn = xColumnFactory->createDataDescriptor(); ::dbaui::setColumnProperties(xNewColumn,pField); // first try to alter the column bool bNotOk = false; try { // first try if we can alter the column if(xAlter.is()) xAlter->alterColumnByName(pField->GetName(),xNewColumn); } catch(const SQLException&) { if(xDrop.is() && xAppend.is()) { OUString aMessage( DBA_RES( STR_TABLEDESIGN_ALTER_ERROR ) ); aMessage = aMessage.replaceFirst( "$column$", pField->GetName() ); SQLExceptionInfo aError( ::cppu::getCaughtException() ); ScopedVclPtrInstance< OSQLWarningBox > aMsg( getView(), aMessage, MessBoxStyle::YesNo | MessBoxStyle::DefaultYes , &aError ); bNotOk = aMsg->Execute() == RET_YES; } else throw; } // if something went wrong or we can't alter columns // drop and append a new one if((!xAlter.is() || bNotOk) && xDrop.is() && xAppend.is()) { xDrop->dropByName(pField->GetName()); try { xAppend->appendByDescriptor(xNewColumn); } catch(const SQLException&) { // an error occurred so we try to reactivate the old one xAppend->appendByDescriptor(xColumn); throw; } } // exceptions are caught outside xNewColumn = nullptr; if(xColumns->hasByName(pField->GetName())) xColumns->getByName(pField->GetName()) >>= xColumn; bReload = true; } } else if(xColumnFactory.is() && xAlter.is() && nPos < nColumnCount) { // we can't find the column so we could try it with the index before we drop and append a new column try { Reference xNewColumn; xNewColumn = xColumnFactory->createDataDescriptor(); ::dbaui::setColumnProperties(xNewColumn,pField); xAlter->alterColumnByIndex(nPos,xNewColumn); if(xColumns->hasByName(pField->GetName())) { // ask for the append by name aColumns.insert(pField->GetName()); xColumns->getByName(pField->GetName()) >>= xColumn; if(xColumn.is()) pField->copyColumnSettingsTo(xColumn); } else { OSL_FAIL("OTableController::alterColumns: invalid column (2)!"); } } catch(const SQLException&) { // we couldn't alter the column so we have to add new columns SQLExceptionInfo aError( ::cppu::getCaughtException() ); bReload = true; if(xDrop.is() && xAppend.is()) { OUString aMessage(DBA_RES(STR_TABLEDESIGN_ALTER_ERROR)); aMessage = aMessage.replaceFirst("$column$",pField->GetName()); ScopedVclPtrInstance< OSQLWarningBox > aMsg( getView(), aMessage, MessBoxStyle::YesNo | MessBoxStyle::DefaultYes, &aError); if ( aMsg->Execute() != RET_YES ) { Reference xNewColumn(xIdxColumns->getByIndex(nPos),UNO_QUERY_THROW); OUString sName; xNewColumn->getPropertyValue(PROPERTY_NAME) >>= sName; aColumns.insert(sName); aColumns.insert(pField->GetName()); continue; } } else throw; } } else bReload = true; } // alter column settings aIter = m_vRowList.begin(); // first look for columns where something other than the name changed for(nPos = 0;aIter != aEnd;++aIter,++nPos) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( !pField ) continue; if ( (*aIter)->IsReadOnly() ) { aColumns.insert(pField->GetName()); continue; } Reference xColumn; if ( xColumns->hasByName(pField->GetName()) ) { xColumns->getByName(pField->GetName()) >>= xColumn; Reference xInfo = xColumn->getPropertySetInfo(); if ( xInfo->hasPropertyByName(PROPERTY_HELPTEXT) ) xColumn->setPropertyValue(PROPERTY_HELPTEXT,makeAny(pField->GetHelpText())); if(xInfo->hasPropertyByName(PROPERTY_CONTROLDEFAULT)) xColumn->setPropertyValue(PROPERTY_CONTROLDEFAULT,pField->GetControlDefault()); if(xInfo->hasPropertyByName(PROPERTY_FORMATKEY)) xColumn->setPropertyValue(PROPERTY_FORMATKEY,makeAny(pField->GetFormatKey())); if(xInfo->hasPropertyByName(PROPERTY_ALIGN)) xColumn->setPropertyValue(PROPERTY_ALIGN,makeAny(dbaui::mapTextAllign(pField->GetHorJustify()))); } } // second drop all columns which could be found by name Reference xKeyColumns = getKeyColumns(); // now we have to look for the columns who could be deleted if ( xDrop.is() ) { Sequence< OUString> aColumnNames = xColumns->getElementNames(); const OUString* pIter = aColumnNames.getConstArray(); const OUString* pEnd = pIter + aColumnNames.getLength(); for(;pIter != pEnd;++pIter) { if(aColumns.find(*pIter) == aColumns.end()) // found a column to delete { if(xKeyColumns.is() && xKeyColumns->hasByName(*pIter)) // check if this column is a member of the primary key { OUString aMsgT(DBA_RES(STR_TBL_COLUMN_IS_KEYCOLUMN)); aMsgT = aMsgT.replaceFirst("$column$",*pIter); OUString aTitle(DBA_RES(STR_TBL_COLUMN_IS_KEYCOLUMN_TITLE)); ScopedVclPtrInstance< OSQLMessageBox > aMsg(getView(),aTitle,aMsgT,MessBoxStyle::YesNo| MessBoxStyle::DefaultYes); if(aMsg->Execute() == RET_YES) { xKeyColumns = nullptr; dropPrimaryKey(); } else { bReload = true; continue; } } try { xDrop->dropByName(*pIter); } catch (const SQLException&) { OUString sError( DBA_RES( STR_TABLEDESIGN_COULD_NOT_DROP_COL ) ); sError = sError.replaceFirst( "$column$", *pIter ); SQLException aNewException; aNewException.Message = sError; aNewException.SQLState = "S1000"; aNewException.NextException = ::cppu::getCaughtException(); throw aNewException; } } } } // third append the new columns aIter = m_vRowList.begin(); for(;aIter != aEnd;++aIter) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( !pField || (*aIter)->IsReadOnly() || aColumns.find(pField->GetName()) != aColumns.end() ) continue; Reference xColumn; if(!xColumns->hasByName(pField->GetName())) { if(xColumnFactory.is() && xAppend.is()) {// column not found by its name so we assume it is new // Column is new xColumn = xColumnFactory->createDataDescriptor(); ::dbaui::setColumnProperties(xColumn,pField); xAppend->appendByDescriptor(xColumn); if(xColumns->hasByName(pField->GetName())) { // ask for the append by name aColumns.insert(pField->GetName()); xColumns->getByName(pField->GetName()) >>= xColumn; if(xColumn.is()) pField->copyColumnSettingsTo(xColumn); } else { OSL_FAIL("OTableController::alterColumns: invalid column!"); } } } } // check if we have to do something with the primary key bool bNeedDropKey = false; bool bNeedAppendKey = false; if ( xKeyColumns.is() ) { aIter = m_vRowList.begin(); for(;aIter != aEnd;++aIter) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( !pField ) continue; if ( pField->IsPrimaryKey() && !xKeyColumns->hasByName( pField->GetName() ) ) { // new primary key column inserted which isn't already in the columns selection bNeedDropKey = bNeedAppendKey = true; break; } else if ( !pField->IsPrimaryKey() && xKeyColumns->hasByName( pField->GetName() ) ) { // found a column which currently is in the primary key, but is marked not to be anymore bNeedDropKey = bNeedAppendKey = true; break; } } } else { // no primary key available so we check if we should create one bNeedAppendKey = true; } if ( bNeedDropKey && xKeyColumns.is() && xKeyColumns->getElementNames().getLength() ) dropPrimaryKey(); if ( bNeedAppendKey ) { Reference< XKeysSupplier > xKeySup( m_xTable, UNO_QUERY ); appendPrimaryKey( xKeySup ,false); } reSyncRows(); if ( bReload ) reload(); } void OTableController::dropPrimaryKey() { SQLExceptionInfo aInfo; try { Reference xKeySup(m_xTable,UNO_QUERY); Reference xKeys; if(xKeySup.is()) xKeys = xKeySup->getKeys(); if(xKeys.is()) { Reference xProp; for(sal_Int32 i=0;i< xKeys->getCount();++i) { xProp.set(xKeys->getByIndex(i),UNO_QUERY); sal_Int32 nKeyType = 0; xProp->getPropertyValue(PROPERTY_TYPE) >>= nKeyType; if(KeyType::PRIMARY == nKeyType) { Reference xDrop(xKeys,UNO_QUERY); xDrop->dropByIndex(i); // delete the key break; } } } } catch(const SQLContext& e) { aInfo = SQLExceptionInfo(e); } catch(const SQLWarning& e) { aInfo = SQLExceptionInfo(e); } catch(const SQLException& e) { aInfo = SQLExceptionInfo(e); } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } showError(aInfo); } void OTableController::assignTable() { // get the table if(!m_sName.isEmpty()) { Reference xNameAccess; Reference xSup(getConnection(),UNO_QUERY); if(xSup.is()) { xNameAccess = xSup->getTables(); OSL_ENSURE(xNameAccess.is(),"no nameaccess for the queries!"); if(xNameAccess->hasByName(m_sName)) { Reference xProp(xNameAccess->getByName(m_sName), css::uno::UNO_QUERY); if (xProp.is()) { m_xTable = xProp; startTableListening(); // check if we set the table editable Reference xMeta = getConnection()->getMetaData(); setEditable( xMeta.is() && !xMeta->isReadOnly() && (isAlterAllowed() || isDropAllowed() || isAddAllowed()) ); if(!isEditable()) { for( const auto& rTableRow : m_vRowList ) { rTableRow->SetReadOnly(); } } m_bNew = false; // be notified when the table is in disposing InvalidateAll(); } } } } } bool OTableController::isAddAllowed() const { Reference xColsSup(m_xTable,UNO_QUERY); bool bAddAllowed = !m_xTable.is(); if(xColsSup.is()) bAddAllowed = Reference(xColsSup->getColumns(),UNO_QUERY).is(); try { Reference< XDatabaseMetaData > xMetaData = getMetaData( ); bAddAllowed = bAddAllowed || ( xMetaData.is() && xMetaData->supportsAlterTableWithAddColumn()); } catch(Exception&) { DBG_UNHANDLED_EXCEPTION(); bAddAllowed = false; } return bAddAllowed; } bool OTableController::isDropAllowed() const { Reference xColsSup(m_xTable,UNO_QUERY); bool bDropAllowed = !m_xTable.is(); if(xColsSup.is()) { Reference xNameAccess = xColsSup->getColumns(); bDropAllowed = Reference(xNameAccess,UNO_QUERY).is() && xNameAccess->hasElements(); } Reference< XDatabaseMetaData> xMetaData = getMetaData( ); bDropAllowed = bDropAllowed || ( xMetaData.is() && xMetaData->supportsAlterTableWithDropColumn()); return bDropAllowed; } bool OTableController::isAlterAllowed() const { bool bAllowed(!m_xTable.is() || Reference(m_xTable,UNO_QUERY).is()); return bAllowed; } void OTableController::reSyncRows() { bool bAlterAllowed = isAlterAllowed(); bool bAddAllowed = isAddAllowed(); std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); for(;aIter != aEnd;++aIter) { OSL_ENSURE(*aIter,"OTableRow is null!"); OFieldDescription* pField = (*aIter)->GetActFieldDescr(); if ( pField ) (*aIter)->SetReadOnly(!bAlterAllowed); else (*aIter)->SetReadOnly(!bAddAllowed); } static_cast(getView())->reSync(); // show the windows and fill with our information ClearUndoManager(); setModified(false); // and we are not modified yet } OUString OTableController::createUniqueName(const OUString& _rName) { OUString sName = _rName; Reference< XDatabaseMetaData> xMetaData = getMetaData( ); ::comphelper::UStringMixEqual bCase(!xMetaData.is() || xMetaData->supportsMixedCaseQuotedIdentifiers()); std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); for(sal_Int32 i=0;aIter != aEnd;++aIter) { OFieldDescription* pFieldDesc = (*aIter)->GetActFieldDescr(); if (pFieldDesc && !pFieldDesc->GetName().isEmpty() && bCase(sName,pFieldDesc->GetName())) { // found a second name of _rName so we need another sName = _rName + OUString::number(++i); aIter = m_vRowList.begin(); // and retry } } return sName; } OUString OTableController::getPrivateTitle() const { OUString sTitle; try { // get the table if ( !m_sName.isEmpty() && getConnection().is() ) { if ( m_xTable.is() ) sTitle = ::dbtools::composeTableName( getConnection()->getMetaData(), m_xTable, ::dbtools::EComposeRule::InDataManipulation, false, false, false ); else sTitle = m_sName; } if ( sTitle.isEmpty() ) { OUString aName = DBA_RES(STR_TBL_TITLE); sTitle = aName.getToken(0,' ') + OUString::number(getCurrentStartNumber()); } } catch( const Exception& ) { DBG_UNHANDLED_EXCEPTION(); } return sTitle; } void OTableController::reload() { loadData(); // fill the column information from the table static_cast(getView())->reSync(); // show the windows and fill with our information ClearUndoManager(); setModified(false); // and we are not modified yet static_cast(getView())->Invalidate(); } sal_Int32 OTableController::getFirstEmptyRowPosition() { sal_Int32 nRet = -1; std::vector< std::shared_ptr >::const_iterator aIter = m_vRowList.begin(); std::vector< std::shared_ptr >::const_iterator aEnd = m_vRowList.end(); for(;aIter != aEnd;++aIter) { if ( !*aIter || !(*aIter)->GetActFieldDescr() || (*aIter)->GetActFieldDescr()->GetName().isEmpty() ) { nRet = aIter - m_vRowList.begin(); break; } } if ( nRet == -1 ) { bool bReadRow = !isAddAllowed(); std::shared_ptr pTabEdRow(new OTableRow()); pTabEdRow->SetReadOnly(bReadRow); nRet = m_vRowList.size(); m_vRowList.push_back( pTabEdRow); } return nRet; } bool OTableController::isAutoIncrementPrimaryKey() const { return getSdbMetaData().isAutoIncrementPrimaryKey(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */