/* -*- 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 .
*/
// here we define our functions used in the anonymous namespace to get our header file smaller // please look at the book LargeScale C++ to know why namespace
{ constchar C_AND[] = " AND "; constchar C_OR[] = " OR ";
ScopedVclPtrInstance< OQueryTableConnection > aInfo(pTableView, xInfoData); // Because OQueryTableConnection never takes ownership of the data passed to it, but only remembers the pointer, // this pointer to a local variable is not critical, as xInfoData and aInfo have the same lifetime
pTableView->NotifyTabConnection( *aInfo );
} else
{
OUString aSourceFieldName(_aDragLeft->GetField());
OUString aDestFieldName(_aDragRight->GetField()); // the connection could point on the other side if (pConn->GetSourceWin() == _aDragRight->GetTabWindow())
std::swap(aSourceFieldName, aDestFieldName);
pConn->GetData()->AppendConnLine( aSourceFieldName,aDestFieldName);
pConn->UpdateLineList(); // Modified-Flag // SetModified(); // and redraw
pConn->RecalcLines(); // for the following Invalidate, the new Connection must first be able // to determine its BoundingRect
pConn->InvalidateConnection();
}
}
OUString ParseCondition( OQueryController& rController
,const ::connectivity::OSQLParseNode* pCondition
,const OUString& _sDecimal
,const css::lang::Locale& _rLocale
,sal_uInt32 _nStartIndex)
{
OUString aCondition;
Reference< XConnection> xConnection = rController.getConnection(); if ( xConnection.is() )
{
sal_uInt32 nCount = pCondition->count(); for(sal_uInt32 i = _nStartIndex ; i < nCount ; ++i)
pCondition->getChild(i)->parseNodeToPredicateStr(aCondition,
xConnection,
rController.getNumberFormatter(),
_rLocale,
_sDecimal,
&rController.getParser().getContext());
} return aCondition;
}
SqlParseError FillOuterJoins(OQueryDesignView const * _pView, const ::connectivity::OSQLParseNode* pTableRefList)
{
SqlParseError eErrorCode = eOk;
sal_uInt32 nCount = pTableRefList->count(); bool bError = false; for (sal_uInt32 i=0; !bError && i < nCount; ++i)
{ const ::connectivity::OSQLParseNode* pParseNode = pTableRefList->getChild(i); const ::connectivity::OSQLParseNode* pJoinNode = nullptr;
return aCondition.makeStringAndClear();
} /** JoinCycle looks for a join cycle and append it to the string @param _xConnection the connection @param _pEntryConn the table connection which holds the data @param _pEntryTabTo the corresponding table window @param _rJoin the String which will contain the resulting string
*/ void JoinCycle( const Reference< XConnection>& _xConnection,
OQueryTableConnection* _pEntryConn, const OQueryTableWindow* _pEntryTabTo,
OUString& _rJoin )
{
assert(_pEntryConn && "TableConnection can not be null!");
// if we have a none numeric field, the table alias could be in the name // otherwise we are not allowed to do this (e.g. 0.1 * PRICE ) if ( !field->isOtherFunction() )
{ // we have to look if we have alias.* here but before we have to check if the column doesn't already exist
OTableFieldDescRef aInfo = new OTableFieldDesc(); for (autoconst& table : rTabList)
{
OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
if ( field->isAggregateFunction() )
{
OSL_ENSURE(!field->GetFunction().isEmpty(),"Function name must not be empty! ;-(");
aTmpStr = field->GetFunction() + "(" + aTmpStr.makeStringAndClear() + ")";
}
OUString aFieldName,aCriteria,aWhereStr,aHavingStr,aWork/*,aOrderStr*/; // print line by line joined with AND
sal_uInt16 nMaxCriteria = 0; for (autoconst& field : _rFieldList)
{
nMaxCriteria = std::max<sal_uInt16>(nMaxCriteria,static_cast<sal_uInt16>(field->GetCriteria().size()));
} try
{ const Reference< XDatabaseMetaData > xMetaData = xConnection->getMetaData(); const OUString aQuote = xMetaData->getIdentifierQuoteString(); const IParseContext& rContext = static_cast<OQueryController&>(_pView->getController()).getParser().getContext(); // * must not contain a filter : have I already shown the correct warning ? bool bCritsOnAsteriskWarning = false; // ** TMFS **
for (sal_uInt16 i=0 ; i < nMaxCriteria ; i++)
{
aHavingStr.clear();
aWhereStr.clear();
for (autoconst& field : _rFieldList)
{
aFieldName = field->GetField();
if (aFieldName.isEmpty()) continue;
aCriteria = field->GetCriteria( i ); if ( !aCriteria.isEmpty() )
{ // * is not allowed to contain any filter, only when used in combination an aggregate function if ( aFieldName.toChar() == '*' && field->isNoneFunction() )
{ // only show the messagebox the first time if (!bCritsOnAsteriskWarning)
{
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(_pView->GetFrameWeld(),
VclMessageType::Warning, VclButtonsType::Ok,
DBA_RES(STR_QRY_CRITERIA_ON_ASTERISK)));
xBox->run();
}
bCritsOnAsteriskWarning = true; continue;
}
aWork = quoteTableAlias(bMulti,field->GetAlias(),aQuote);
aWhereStr += " "; // aCriteria could have some German numbers so I have to be sure here
OUString aErrorMsg;
Reference<XPropertySet> xColumn;
std::unique_ptr< ::connectivity::OSQLParseNode> pParseNode( _pView->getPredicateTreeFromEntry(field,aCriteria,aErrorMsg,xColumn)); if (pParseNode)
{ if (bMulti && !(field->isOtherFunction() || (aFieldName.toChar() == '*')))
pParseNode->replaceNodeValue(field->GetAlias(),aFieldName);
OUString aWhere = aWhereStr;
pParseNode->parseNodeToStr( aWhere,
xConnection,
&rContext, false,
!field->isOtherFunction() );
aWhereStr = aWhere;
} else
{
aWhereStr += aWork + "=" + aCriteria;
}
}
} // only once for each field elseif ( !i && field->isCondition() )
{ if (aWhereStr.isEmpty()) // no more criteria
aWhereStr += "("; // bracket else
aWhereStr += C_AND;
aWhereStr += field->GetField();
}
} if (!aWhereStr.isEmpty())
{
aWhereStr += ")"; // close bracket for the AND branch if (!rRetStr.isEmpty()) // are there conditions on the field?
rRetStr.append(C_OR); else// open bracket for the OR branch
rRetStr.append('(');
rRetStr.append(aWhereStr);
} if (!aHavingStr.isEmpty())
{
aHavingStr += ")"; // close bracket for the AND branch if (!rHavingStr.isEmpty()) // are there conditions on the field?
rHavingStr.append(C_OR); else// Open bracket for the OR branch
rHavingStr.append('(');
rHavingStr.append(aHavingStr);
}
}
if (!rRetStr.isEmpty())
rRetStr.append(')'); // close bracket for the OR branch if (!rHavingStr.isEmpty())
rHavingStr.append(')'); // close bracket for the OR branch
} catch(SQLException&)
{
OSL_FAIL("Failure while building where clause!");
} returntrue;
}
SqlParseError GenerateOrder( OQueryDesignView const * _pView,
OTableFields& _rFieldList, bool bMulti,
OUString& _rsRet)
{ const OQueryController& rController = static_cast<OQueryController&>(_pView->getController()); const Reference< XConnection>& xConnection = rController.getConnection(); if ( !xConnection.is() ) return eNoConnection;
SqlParseError eErrorCode = eOk;
OUString aColumnName;
OUString aWorkStr; try
{ constbool bColumnAliasInOrderBy = rController.getSdbMetaData().supportsColumnAliasInOrderBy();
Reference< XDatabaseMetaData > xMetaData = xConnection->getMetaData();
OUString aQuote = xMetaData->getIdentifierQuoteString(); // * must not contain filter - have I already shown the warning? bool bCritsOnAsteriskWarning = false; // ** TMFS ** for (autoconst& field : _rFieldList)
{
EOrderDir eOrder = field->GetOrderDir(); // only create a sort expression when the table name and the sort criteria are defined // otherwise they will be built in GenerateCriteria if ( eOrder != ORDER_NONE )
{
aColumnName = field->GetField(); if(aColumnName.toChar() == '*')
{ // only show the MessageBox the first time if (!bCritsOnAsteriskWarning)
{
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(_pView->GetFrameWeld(),
VclMessageType::Warning, VclButtonsType::Ok,
DBA_RES(STR_QRY_ORDERBY_ON_ASTERISK)));
xBox->run();
}
bCritsOnAsteriskWarning = true; continue;
}
if ( bColumnAliasInOrderBy && !field->GetFieldAlias().isEmpty() )
{
aWorkStr += ::dbtools::quoteName(aQuote, field->GetFieldAlias());
} elseif ( field->isNumericOrAggregateFunction() )
{
OSL_ENSURE(!field->GetFunction().isEmpty(),"Function name cannot be empty! ;-(");
aWorkStr += field->GetFunction() + "("
+ quoteTableAlias(
bMulti, field->GetAlias(), aQuote); // only quote column name when we don't have a numeric if ( field->isNumeric() )
aWorkStr += aColumnName; else
aWorkStr += ::dbtools::quoteName(aQuote, aColumnName);
// and now all inner joins // these are implemented as // "FROM tbl1, tbl2 WHERE tbl1.col1=tlb2.col2" // rather than // "FROM tbl1 INNER JOIN tbl2 ON tbl1.col1=tlb2.col2" for (autoconst& connection : rConnList)
{
OQueryTableConnection* pEntryConn = static_cast<OQueryTableConnection*>(connection.get()); if(!pEntryConn->IsVisited())
{
searchAndAppendName(_xConnection, static_cast<OQueryTableWindow*>(pEntryConn->GetSourceWin()),
aTableNames,
aTableListStr);
searchAndAppendName(_xConnection, static_cast<OQueryTableWindow*>(pEntryConn->GetDestWin()),
aTableNames,
aTableListStr);
}
}
} // all tables that haven't a connection to anyone for (autoconst& table : *pTabList)
{ const OQueryTableWindow* pEntryTab = static_cast<const OQueryTableWindow*>(table.second.get()); if(!pEntryTab->ExistsAConn())
{
aTableListStr += BuildTable(_xConnection,pEntryTab) + ",";
}
}
for (autoconst& field : _rFieldList)
{ if ( field->IsGroupBy() )
{
OSL_ENSURE(!field->GetField().isEmpty(),"No Field Name available!;-(");
OUString sGroupByPart = quoteTableAlias(bMulti,field->GetAlias(),aQuote);
// only quote the field name when it isn't calculated if ( field->isNoneFunction() )
{
sGroupByPart += ::dbtools::quoteName(aQuote, field->GetField());
} else
{
OUString aTmp = field->GetField();
OUString aErrorMsg;
Reference<XPropertySet> xColumn;
std::unique_ptr< ::connectivity::OSQLParseNode> pParseNode(_pView->getPredicateTreeFromEntry(field,aTmp,aErrorMsg,xColumn)); if (pParseNode)
{
OUString sGroupBy;
pParseNode->getChild(0)->parseNodeToStr( sGroupBy,
xConnection,
&rController.getParser().getContext(), false,
!field->isOtherFunction());
sGroupByPart += sGroupBy;
} else
sGroupByPart += field->GetField();
} if ( aGroupByNames.find(sGroupByPart) == aGroupByNames.end() )
{
aGroupByNames.emplace(sGroupByPart,true);
aGroupByStr += sGroupByPart + ",";
}
}
} if ( !aGroupByStr.isEmpty() )
{
aGroupByStr = aGroupByStr.replaceAt(aGroupByStr.getLength()-1,1, u" " );
aGroupByStr = " GROUP BY " + aGroupByStr;
}
} catch(SQLException&)
{
OSL_FAIL("Failure while building group by!");
} return aGroupByStr;
}
SqlParseError GetORCriteria(OQueryDesignView* _pView,
OSelectionBrowseBox* _pSelectionBrw, const ::connectivity::OSQLParseNode * pCondition,
sal_uInt16& nLevel , bool bHaving = false, bool bAddOrOnOneLine = false);
SqlParseError GetSelectionCriteria( OQueryDesignView* _pView,
OSelectionBrowseBox* _pSelectionBrw, const ::connectivity::OSQLParseNode* pNode,
sal_uInt16& rLevel )
{ if (!pNode || !SQL_ISRULE(pNode, select_statement)) return eNoSelectStatement;
// nyi: more checking for the correct structure!
pNode = pNode->getChild(3)->getChild(1); // no where clause found if (!pNode || pNode->isLeaf()) return eOk;
// Next free sentence...
SqlParseError eErrorCode = eOk;
::connectivity::OSQLParseNode * pCondition = pNode->getChild(1); if ( pCondition ) // no where clause
{ // now we have to check the other conditions // first make the logical easier
::connectivity::OSQLParseNode::negateSearchCondition(pCondition);
::connectivity::OSQLParseNode *pNodeTmp = pNode->getChild(1);
// first extract the inner joins conditions
GetInnerJoinCriteria(_pView,pNodeTmp); // now simplify again, join are checked in ComparisonPredicate
::connectivity::OSQLParseNode::absorptions(pNodeTmp);
pNodeTmp = pNode->getChild(1);
// round brackets around the printout if (pCondition->count() == 3 &&
SQL_ISPUNCTUATION(pCondition->getChild(0),"(") &&
SQL_ISPUNCTUATION(pCondition->getChild(2),")"))
{
eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pCondition->getChild(1),nLevel,bHaving,bAddOrOnOneLine);
} // OR condition // a searchcondition can only look like this: search_condition SQL_TOKEN_OR boolean_term elseif (SQL_ISRULE(pCondition,search_condition))
{ for (int i = 0; i < 3 && eErrorCode == eOk ; i+=2)
{ const ::connectivity::OSQLParseNode* pChild = pCondition->getChild(i); if ( SQL_ISRULE(pChild,search_condition) )
eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pChild,nLevel,bHaving,bAddOrOnOneLine); else
{
eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pChild, nLevel,bHaving, i != 0 && bAddOrOnOneLine); if ( !bAddOrOnOneLine)
nLevel++;
}
}
} else
eErrorCode = GetANDCriteria( _pView,_pSelectionBrw,pCondition, nLevel, bHaving,bAddOrOnOneLine );
return eErrorCode;
} bool CheckOrCriteria(const ::connectivity::OSQLParseNode* _pCondition,::connectivity::OSQLParseNode* _pFirstColumnRef)
{ bool bRet = true;
::connectivity::OSQLParseNode* pFirstColumnRef = _pFirstColumnRef; for (size_t i = 0; bRet && i < _pCondition->count(); ++i)
{ const ::connectivity::OSQLParseNode* pChild = _pCondition->getChild(i); if ( pChild->isToken() ) continue; elseif ( SQL_ISRULE(pChild,search_condition) )
bRet = CheckOrCriteria(pChild,pFirstColumnRef); else
{ // this is a simple way to test columns are the same, may be we have to adjust this algo a little bit in future. :-)
::connectivity::OSQLParseNode* pSecondColumnRef = pChild->getByRule(::connectivity::OSQLParseNode::column_ref); if ( pFirstColumnRef && pSecondColumnRef )
bRet = *pFirstColumnRef == *pSecondColumnRef; elseif ( !pFirstColumnRef )
pFirstColumnRef = pSecondColumnRef;
}
} return bRet;
}
SqlParseError GetANDCriteria( OQueryDesignView* _pView,
OSelectionBrowseBox* _pSelectionBrw, const ::connectivity::OSQLParseNode * pCondition,
sal_uInt16& nLevel, bool bHaving, bool bAddOrOnOneLine)
{ const css::lang::Locale aLocale = _pView->getLocale(); const OUString sDecimal = _pView->getDecimalSeparator();
// I will need a cast pointer to my css::sdbcx::Container
OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
SqlParseError eErrorCode = eOk;
// round brackets if (SQL_ISRULE(pCondition,boolean_primary))
{ // check if we have to put the or criteria on one line. const ::connectivity::OSQLParseNode* pSearchCondition = pCondition->getChild(1); bool bMustAddOrOnOneLine = CheckOrCriteria(pSearchCondition,nullptr); if ( SQL_ISRULE( pSearchCondition, search_condition) ) // we have a or
{
_pSelectionBrw->DuplicateConditionLevel( nLevel);
eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition->getChild(0), nLevel,bHaving,bMustAddOrOnOneLine ); if ( eErrorCode == eOk )
{
++nLevel;
eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition->getChild(2), nLevel,bHaving,bMustAddOrOnOneLine );
}
} else
eErrorCode = GetORCriteria(_pView,_pSelectionBrw,pSearchCondition, nLevel,bHaving,bMustAddOrOnOneLine );
} // The first element is (again) an AND condition elseif ( SQL_ISRULE(pCondition,boolean_term) )
{
OSL_ENSURE(pCondition->count() == 3,"Illegal definition of boolean_term");
eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pCondition->getChild(0), nLevel,bHaving,bAddOrOnOneLine ); if ( eErrorCode == eOk )
eErrorCode = GetANDCriteria(_pView,_pSelectionBrw,pCondition->getChild(2), nLevel,bHaving,bAddOrOnOneLine );
} elseif (SQL_ISRULE( pCondition, comparison_predicate))
{
eErrorCode = ComparisonPredicate(_pView,_pSelectionBrw,pCondition,nLevel,bHaving,bAddOrOnOneLine);
} elseif( SQL_ISRULE(pCondition,like_predicate) )
{ const ::connectivity::OSQLParseNode* pValueExp = pCondition->getChild(0); if (SQL_ISRULE(pValueExp, column_ref ) )
{
OUString aCondition;
Reference< XConnection> xConnection = rController.getConnection(); if ( xConnection.is() )
{
OUString aColumnName; // the international doesn't matter I have a string
pCondition->parseNodeToPredicateStr(aCondition,
xConnection,
rController.getNumberFormatter(),
aLocale,
sDecimal,
&rController.getParser().getContext());
pFunction->parseNodeToStr( aColumnName,
xConnection,
&rController.getParser().getContext(), true); // quote is to true because we need quoted elements inside the function // don't display the column name
aCondition = aCondition.copy(aColumnName.getLength());
aCondition = aCondition.trim(); if ( aCondition.startsWith("=") ) // ignore the equal sign
aCondition = aCondition.copy(1);
if ( SQL_ISRULE(pFunction, general_set_fct ) )
{
sal_Int32 nFunctionType = FKT_AGGREGATE;
OSQLParseNode* pParamNode = pFunction->getChild(pFunction->count()-2); if ( pParamNode && pParamNode->getTokenValue().toChar() == '*' )
{
OJoinTableView::OTableWindowMap& rTabList = _pView->getTableView()->GetTabWinMap(); for (autoconst& table : rTabList)
{
OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get()); if (pTabWin->ExistsField( u"*"_ustr, aDragLeft ))
{
aDragLeft->SetAlias(OUString());
aDragLeft->SetTable(OUString()); break;
}
}
} elseif (pParamNode)
{
eErrorCode = FillDragInfo(_pView,pParamNode,aDragLeft); if ( eOk != eErrorCode && SQL_ISRULE(pParamNode,num_value_exp))
{
OUString sParameterValue;
pParamNode->parseNodeToStr( sParameterValue,
xConnection,
&rController.getParser().getContext());
nFunctionType |= FKT_NUMERIC;
aDragLeft->SetField(sParameterValue);
eErrorCode = eOk;
}
}
aDragLeft->SetFunctionType(nFunctionType); if ( bHaving )
aDragLeft->SetGroupBy(true);
aDragLeft->SetFunction(aColumnName.getToken(0, '('));
} else
{ // for an unknown function we write the whole text in the field
aDragLeft->SetField(aColumnName); if(bHaving)
aDragLeft->SetGroupBy(true);
aDragLeft->SetFunctionType(FKT_OTHER|FKT_NUMERIC);
}
_pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
}
sal_Int32 i = static_cast<sal_Int32>(pCondition->count() - 2); switch (pCondition->getChild(i)->getNodeType())
{ case SQLNodeType::Equal: // don't display the equal
i--; break; case SQLNodeType::Less: // take the opposite as we change the order
i--;
aCondition += ">"; break; case SQLNodeType::LessEq: // take the opposite as we change the order
i--;
aCondition += ">="; break; case SQLNodeType::Great: // take the opposite as we change the order
i--;
aCondition += "<"; break; case SQLNodeType::GreatEq: // take the opposite as we change the order
i--;
aCondition += "<="; break; default: break;
}
// go backward
Reference< XConnection> xConnection = rController.getConnection(); if(xConnection.is())
{ for (; i >= 0; i--)
pCondition->getChild(i)->parseNodeToPredicateStr(aCondition,
xConnection,
rController.getNumberFormatter(),
_pView->getLocale(),
_pView->getDecimalSeparator(),
&rController.getParser().getContext());
}
} // else ???
if( eOk == ( eErrorCode = FillDragInfo(_pView,pCondition->getChild(nPos),aDragLeft)))
{ if(bHaving)
aDragLeft->SetGroupBy(true);
_pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
}
} elseif( SQL_ISRULEOR2(pCondition->getChild(0), set_fct_spec , general_set_fct ) )
{
AddFunctionCondition( _pView,
_pSelectionBrw,
pCondition,
nLevel,
bHaving,
bAddOrOnOneLine);
} else// it can only be an Expr
{
OUString aName,aCondition;
OTableFieldDescRef aDragLeft = new OTableFieldDesc();
aDragLeft->SetField(aName);
aDragLeft->SetFunctionType(FKT_OTHER|FKT_NUMERIC); // and add it on
_pSelectionBrw->AddCondition(aDragLeft, aCondition, nLevel,bAddOrOnOneLine);
} return eErrorCode;
}
// Put the table names together
::connectivity::OSQLParseTreeIterator& rParseIter = static_cast<OQueryController&>(_pView->getController()).getParseIterator();
rParseIter.getColumnRange( pColumnRef, aColumnName, aTableRange );
bool bFound(false);
OSL_ENSURE(!aColumnName.isEmpty(),"Column name must not be empty"); if (aTableRange.isEmpty())
{ // SELECT column, ...
bFound = nullptr != lcl_findColumnInTables( aColumnName, *pTabList, _raInfo ); if ( bFound && ( aColumnName.toChar() != '*' ) )
_raInfo->SetFieldAlias(aColumnAlias);
} else
{ // SELECT range.column, ...
OQueryTableWindow* pTabWin = static_cast<OQueryTableView*>(_pView->getTableView())->FindTable(aTableRange);
if (SQL_ISRULE(pNode,joined_table)) return InsertJoin(_pView,pNode->getChild(1));
// first check the left and right side const ::connectivity::OSQLParseNode* pRightTableRef = pNode->getChild(3); // table_ref if ( SQL_ISRULE(pNode, qualified_join) && SQL_ISTOKEN(pNode->getChild(1),NATURAL) )
pRightTableRef = pNode->getChild(4); // table_ref
if ( !checkJoinConditions(_pView,pNode->getChild(0)) || !checkJoinConditions(_pView,pRightTableRef)) returnfalse;
// named column join may be implemented later // SQL_ISRULE(pNode->getChild(4),named_columns_join)
EJoinType eJoinType = INNER_JOIN; bool bNatural = false; if ( SQL_ISRULE(pNode, qualified_join) )
{
::connectivity::OSQLParseNode* pJoinType = pNode->getChild(1); // join_type if ( SQL_ISTOKEN(pJoinType,NATURAL) )
{
bNatural = true;
pJoinType = pNode->getChild(2);
}
if (SQL_ISRULE(pJoinType,join_type) && (!pJoinType->count() || SQL_ISTOKEN(pJoinType->getChild(0),INNER)))
{
eJoinType = INNER_JOIN;
} else
{ if (SQL_ISRULE(pJoinType,join_type)) // one level deeper
pJoinType = pJoinType->getChild(0);
returntrue;
} void insertUnUsedFields(OQueryDesignView const * _pView,OSelectionBrowseBox* _pSelectionBrw)
{ // now we have to insert the fields which aren't in the statement
OQueryController& rController = static_cast<OQueryController&>(_pView->getController());
OTableFields& rUnUsedFields = rController.getUnUsedFields(); for (auto & unusedField : rUnUsedFields) if(_pSelectionBrw->InsertField(unusedField,BROWSER_INVALIDID,false,false).is())
unusedField = nullptr;
OTableFields().swap( rUnUsedFields );
}
// if the alias is the complete (composed) table, then shorten it if ( aKeyComp( sComposedName, elem.first ) )
{
OUString sCatalog, sSchema, sTable;
::dbtools::qualifiedNameComponents( xMetaData, sComposedName, sCatalog, sSchema, sTable, ::dbtools::EComposeRule::InDataManipulation );
sAlias = sTable;
}
}
// find the existent window for this alias
OQueryTableWindow* pExistentWin = pTableView->FindTable( sAlias ); if ( !pExistentWin )
{
pTableView->AddTabWin( sComposedName, sAlias ); // don't create data here
} else
{ // there already exists a window for this alias... if ( !aKeyComp( pExistentWin->GetData()->GetComposedName(), sComposedName ) ) // ... but for another complete table name -> new window
pTableView->AddTabWin(sComposedName, sAlias);
}
}
// now delete the data for which we haven't any tablewindow
OJoinTableView::OTableWindowMap aTableMap(pTableView->GetTabWinMap()); for (autoconst& table : aTableMap)
{ if(aMap.find(table.second->GetComposedName()) == aMap.end() &&
aMap.find(table.first) == aMap.end())
pTableView->RemoveTabWin(table.second);
}
if ( eOk == (eErrorCode = FillOuterJoins(_pView,pTableExp->getChild(0)->getChild(1))) )
{ // check if we have a distinct statement if(SQL_ISTOKEN(pParseTree->getChild(1),DISTINCT))
{
rController.setDistinct(true);
rController.InvalidateFeature(SID_QUERY_DISTINCT_VALUES);
} else
{
rController.setDistinct(false);
}
///check if query has a limit if( pTableExp->getChild(6)->count() >= 2 && pTableExp->getChild(6)->getChild(1) )
{
rController.setLimit(
pTableExp->getChild(6)->getChild(1)->getTokenValue().toInt64() );
} else
{
rController.setLimit(-1);
}
if ( (eErrorCode = InstallFields(_pView, pParseTree, &pTableView->GetTabWinMap())) == eOk )
{ // GetSelectionCriteria must be called before GetHavingCriteria
sal_uInt16 nLevel=0;
// New Undo-Actions were created in the Manager by the regeneration
rController.ClearUndoManager();
_pSelectionBrw->Invalidate(); return eErrorCode;
} /** fillSelectSubList @return <TRUE/> when columns could be inserted otherwise <FALSE/>
*/
SqlParseError fillSelectSubList( OQueryDesignView* _pView,
OJoinTableView::OTableWindowMap* _pTabList)
{
SqlParseError eErrorCode = eOk; bool bFirstField = true; for (autoconst& table : *_pTabList)
{
OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get());
OTableFieldDescRef aInfo = new OTableFieldDesc(); if (pTabWin->ExistsField( u"*"_ustr, aInfo ))
{
eErrorCode = _pView->InsertField(aInfo, bFirstField);
bFirstField = false; if (eErrorCode != eOk) break;
}
} return eErrorCode;
}
SqlParseError InstallFields(OQueryDesignView* _pView, const ::connectivity::OSQLParseNode* pNode,
OJoinTableView::OTableWindowMap* pTabList )
{ if( pNode==nullptr || !SQL_ISRULE(pNode,select_statement)) return eNoSelectStatement;
::connectivity::OSQLParseNode* pParseTree = pNode->getChild(2); // selection bool bFirstField = true; // When initializing, the first field must be reactivated
if ( SQL_ISRULE(pColumnRef,general_set_fct)
&& pParamRef && SQL_ISRULE(pParamRef,column_ref) )
{ // Check the parameters for Column references
InsertColumnRef(_pView,pParamRef,aColumnName,aColumnAlias,aTableRange,aInfo,pTabList);
} elseif ( SQL_ISRULE(pColumnRef,general_set_fct) )
{ if ( pParamRef && pParamRef->getTokenValue().toChar() == '*' )
{ for (autoconst& table : *pTabList)
{
OQueryTableWindow* pTabWin = static_cast<OQueryTableWindow*>(table.second.get()); if (pTabWin->ExistsField( u"*"_ustr, aInfo ))
{
aInfo->SetAlias(OUString());
aInfo->SetTable(OUString()); break;
}
}
} else
{
OUString sFieldName = aColumns; if ( pParamRef )
{ // we got an aggregate function but without column name inside // so we set the whole argument of the function as field name
nFunctionType |= FKT_NUMERIC;
sFieldName.clear();
pParamRef->parseNodeToStr( sFieldName,
xConnection,
&rController.getParser().getContext(), true); // quote is to true because we need quoted elements inside the function
}
aInfo->SetDataType(DataType::DOUBLE);
aInfo->SetFieldType(TAB_NORMAL_FIELD);
aInfo->SetField(sFieldName);
}
aInfo->SetTabWindow(nullptr);
aInfo->SetFieldAlias(aColumnAlias);
} else
{
_pView->fillFunctionInfo(pColumnRef,aColumns,aInfo);
aInfo->SetFieldAlias(aColumnAlias);
}
eErrorCode = _pView->InsertField(aInfo, bFirstField);
bFirstField = false;
} else
{
OUString aColumns;
pColumnRef->parseNodeToStr( aColumns,
xConnection,
&rController.getParser().getContext(), true); // quote is to true because we need quoted elements inside the function
aInfo->SetTabWindow( nullptr );
// since we support queries in queries, the thingie might belong to an existing "table"
OQueryTableWindow* pExistingTable = lcl_findColumnInTables( aColumns, *pTabList, aInfo ); if ( pExistingTable )
{
aInfo->SetTabWindow( pExistingTable );
aInfo->SetTable( pExistingTable->GetTableName() );
aInfo->SetAlias( pExistingTable->GetAliasName() );
}
if(SQL_ISRULE(pArgument,column_ref))
{ if( eOk == FillDragInfo(_pView,pArgument,aDragLeft))
_pSelectionBrw->AddOrder( aDragLeft, eOrderDir, i); else// it could be an alias name for a field
{
OUString aTableRange,aColumnName;
::connectivity::OSQLParseTreeIterator& rParseIter = rController.getParseIterator();
rParseIter.getColumnRange( pArgument, aColumnName, aTableRange );
if ( !m_bInSplitHandler )
{ // the resize is triggered by something else than the split handler // our main focus is to try to preserve the size of the selectionbrowse box
Size aSelBoxSize = m_pSelectionBox->GetSizePixel(); if ( aSelBoxSize.Height() )
{ // keep the size of the sel box constant
nSplitPos = aPlaygroundSize.Height() - m_aSplitter->GetSizePixel().Height() - aSelBoxSize.Height();
// and if the box is smaller than the optimal size, try to do something about it
Size aSelBoxOptSize = m_pSelectionBox->CalcOptimalSize( aPlaygroundSize ); if ( aSelBoxOptSize.Height() > aSelBoxSize.Height() )
{
nSplitPos = aPlaygroundSize.Height() - m_aSplitter->GetSizePixel().Height() - aSelBoxOptSize.Height();
}
// position the table
Size aTableViewSize(aPlaygroundSize.Width(), aSplitPos.Y() - aPlaygroundPos.Y());
m_pScrollWindow->SetPosSizePixel(aPlaygroundPos, aTableViewSize);
// position the selection browse box
Point aPos( aPlaygroundPos.X(), aSplitPos.Y() + aSplitSize.Height() );
m_pSelectionBox->SetPosSizePixel( aPos, Size( aPlaygroundSize.Width(), aPlaygroundSize.Height() - aSplitSize.Height() - aTableViewSize.Height() ));
// set the size of the splitter
m_aSplitter->SetPosSizePixel( aSplitPos, aSplitSize );
m_aSplitter->SetDragRectPixel( _rPlayground );
// just for completeness: there is no space left, we occupied it all ...
_rPlayground.SetPos( _rPlayground.BottomRight() );
_rPlayground.SetSize( Size( 0, 0 ) );
}
void OQueryDesignView::TableDeleted(const OUString& rAliasName)
{ // message that the table was removed from the window
m_pSelectionBox->DeleteFields( rAliasName ); static_cast<OQueryController&>(getController()).InvalidateFeature(ID_BROWSER_ADDTABLE); // inform the view again
}
for (autoconst& field : aFields)
{ if (bAllTables || field.toChar() == '*')
rFieldList.append_text(strCurrentPrefix + field); else
rFieldList.append_text(field);
}
if (!bAllTables) // this means that I came into this block because the table name was exactly what I was looking for so I can end here // (and I prevent that fields get added more than once, if a table is repeated in TabWin) break;
}
}
}
// check if the statement is correct when not returning false bool OQueryDesignView::checkStatement()
{ bool bRet = true; if ( m_pSelectionBox )
bRet = m_pSelectionBox->Save(); // an error occurred so we return no return bRet;
}
OUString OQueryDesignView::getStatement()
{
OQueryController& rController = static_cast<OQueryController&>(getController());
m_rController.clearError(); // used for fields which aren't any longer in the statement
OTableFields& rUnUsedFields = rController.getUnUsedFields();
OTableFields().swap( rUnUsedFields );
// create the select columns
sal_uInt32 nFieldcount = 0;
OTableFields& rFieldList = rController.getTableFieldDesc(); for (autoconst& field : rFieldList)
{ if (!field->GetField().isEmpty() && field->IsVisible() )
++nFieldcount; elseif (!field->GetField().isEmpty() &&
!field->HasCriteria() &&
field->isNoneFunction() &&
field->GetOrderDir() == ORDER_NONE &&
!field->IsGroupBy() &&
field->GetFunction().isEmpty() )
rUnUsedFields.push_back(field);
} if ( !nFieldcount ) // no visible fields so return
{
rUnUsedFields = rFieldList; return OUString();
}
// Exception handling, if no fields have been passed we should not // change the tab page // TabBarSelectHdl will query the SQL-OUString for STATEMENT_NOFIELDS // and trigger an error message // ----------------- Build table list ----------------------
constauto& rConnList = m_pTableView->getTableConnections();
Reference< XConnection> xConnection = rController.getConnection();
OUString aTableListStr(GenerateFromClause(xConnection,&rTabList,rConnList));
OSL_ENSURE(!aTableListStr.isEmpty(), "OQueryDesignView::getStatement() : unexpected : have Fields, but no Tables !"); // if fields exist now, these only can be created by inserting from an already existing table; if on the other hand // a table is deleted, also the belonging fields will be deleted -> therefore it CANNOT occur that fields // exist but no tables exist (and aFieldListStr has its length, I secure this above)
OUStringBuffer aHavingStr,aCriteriaListStr;
// ----------------- build the criteria ---------------------- if (!GenerateCriterias(this,aCriteriaListStr,aHavingStr,rFieldList, nTabcount > 1)) return OUString();
// special handling for functions if ( pEntry->GetFunctionType() & (FKT_OTHER | FKT_AGGREGATE | FKT_NUMERIC) )
{ // we have a function here so we have to distinguish the type of return value
OUString sFunction; if ( pEntry->isNumericOrAggregateFunction() )
sFunction = pEntry->GetFunction().getToken(0, '(');
if ( sFunction.isEmpty() )
sFunction = pEntry->GetField().getToken(0, '(');
sal_Int32 nType = ::connectivity::OSQLParser::getFunctionReturnType(sFunction,&rParser.getContext()); if ( nType == DataType::OTHER || (sFunction.isEmpty() && pEntry->isNumericOrAggregateFunction()) )
{ // first try the international version
OUString sSql = "SELECT * FROM x WHERE " + pEntry->GetField() + _sCriteria;
std::unique_ptr<OSQLParseNode> pParseNode( rParser.parseTree( _rsErrorMessage, sSql, true ) );
nType = DataType::DOUBLE; if (pParseNode)
{
OSQLParseNode* pColumnRef = pParseNode->getByRule(OSQLParseNode::column_ref); if ( pColumnRef )
{
OTableFieldDescRef aField = new OTableFieldDesc(); if ( eOk == FillDragInfo(this,pColumnRef,aField) )
{
nType = aField->GetDataType();
}
}
}
}
// _rxColumn, if it is a "lookup" column, not a computed column, // is guaranteed to be the column taken from the *source* of the column, // that is either a table or another query. // _rxColumn is *not* taken from the columns of the query we are constructing // (and rightfully so, since it may not be part of these columns; SELECT A FROM t WHERE B = foo) // If it is a computed column, we just constructed it above, with same Name and RealName. // In all cases, we should use the "external" name of the column, not the "RealName"; // the latter is the name that the column had in the source of the source query. // An example: we are designing "SELECT A, B FROM q WHERE C='foo'" // q itself is query "SELECT aye AS A, bee as B, cee as C FROM t" // We are currently treating the entry "C='foo'" // Then _rxColumn has Name "C" and RealName "cee". We should *obviously* use "C", not "cee".
std::unique_ptr<OSQLParseNode> pParseNode = rParser.predicateTree( _rsErrorMessage,
_sCriteria, static_cast<OQueryController&>(getController()).getNumberFormatter(),
_rxColumn, false); return pParseNode;
}
void OQueryDesignView::GetFocus()
{
OJoinDesignView::GetFocus(); if ( m_pSelectionBox && !m_pSelectionBox->HasChildPathFocus() )
{ // first we have to deactivate the current cell to refill when necessary
m_pSelectionBox->DeactivateCell();
m_pSelectionBox->ActivateCell(m_pSelectionBox->GetCurRow(), m_pSelectionBox->GetCurColumnId());
m_pSelectionBox->GrabFocus();
}
}
OSL_FAIL("unrecognised character datatype"); return DataType::VARCHAR;
}
}
// Try to guess the type of an expression in simple cases. // Originally meant to be called only on a function call (hence the misnomer), // but now tries to do the best it can also in other cases. // Don't completely rely on fillFunctionInfo, // it won't look at the function's arguments to find the return type // (in particular, in the case of general_set_fct, // the return type is the type of the argument; // if that is (as is typical) a column reference, // it is the type of the column). // TODO: There is similar "guess the expression's type" code in several places: // SelectionBrowseBox.cxx: OSelectionBrowseBox::saveField // QueryDesignView.cxx: InstallFields, GetOrderCriteria, GetGroupCriteria // If possible, they should be factorised into this function // (which should then be renamed...)
void OQueryDesignView::fillFunctionInfo( const ::connectivity::OSQLParseNode* pNode
,const OUString& sFunctionTerm
,OTableFieldDescRef& aInfo)
{ // get the type of the expression, as far as easily possible
OQueryController& rController = static_cast<OQueryController&>(getController());
sal_Int32 nDataType = DataType::DOUBLE; switch(pNode->getNodeType())
{ case SQLNodeType::Concat: case SQLNodeType::String:
nDataType = DataType::VARCHAR; break; case SQLNodeType::IntNum:
nDataType = DataType::INTEGER; break; case SQLNodeType::ApproxNum:
nDataType = DataType::DOUBLE; break; case SQLNodeType::AccessDate:
nDataType = DataType::TIMESTAMP; break; case SQLNodeType::Equal: case SQLNodeType::Less: case SQLNodeType::Great: case SQLNodeType::LessEq: case SQLNodeType::GreatEq: case SQLNodeType::NotEqual:
nDataType = DataType::BOOLEAN; break; case SQLNodeType::Name: case SQLNodeType::ListRule: case SQLNodeType::CommaListRule: case SQLNodeType::Keyword: case SQLNodeType::Punctuation:
OSL_FAIL("Unexpected SQL Node Type"); break; case SQLNodeType::Rule: switch(pNode->getKnownRuleID())
{ case OSQLParseNode::select_statement: case OSQLParseNode::table_exp: case OSQLParseNode::table_ref_commalist: case OSQLParseNode::table_ref: case OSQLParseNode::catalog_name: case OSQLParseNode::schema_name: case OSQLParseNode::table_name: case OSQLParseNode::opt_column_commalist: case OSQLParseNode::column_commalist: case OSQLParseNode::column_ref_commalist: case OSQLParseNode::column_ref: case OSQLParseNode::opt_order_by_clause: case OSQLParseNode::ordering_spec_commalist: case OSQLParseNode::ordering_spec: case OSQLParseNode::opt_asc_desc: case OSQLParseNode::where_clause: case OSQLParseNode::opt_where_clause: case OSQLParseNode::opt_escape: case OSQLParseNode::scalar_exp_commalist: case OSQLParseNode::scalar_exp: // Seems to never be generated? case OSQLParseNode::parameter_ref: case OSQLParseNode::parameter: case OSQLParseNode::range_variable: case OSQLParseNode::delete_statement_positioned: case OSQLParseNode::delete_statement_searched: case OSQLParseNode::update_statement_positioned: case OSQLParseNode::update_statement_searched: case OSQLParseNode::assignment_commalist: case OSQLParseNode::assignment: case OSQLParseNode::insert_statement: case OSQLParseNode::insert_atom_commalist: case OSQLParseNode::insert_atom: case OSQLParseNode::from_clause: case OSQLParseNode::qualified_join: case OSQLParseNode::cross_union: case OSQLParseNode::select_sublist: case OSQLParseNode::join_type: case OSQLParseNode::named_columns_join: case OSQLParseNode::joined_table: case OSQLParseNode::sql_not: case OSQLParseNode::manipulative_statement: case OSQLParseNode::value_exp_commalist: case OSQLParseNode::union_statement: case OSQLParseNode::outer_join_type: case OSQLParseNode::selection: case OSQLParseNode::base_table_def: case OSQLParseNode::base_table_element_commalist: case OSQLParseNode::data_type: case OSQLParseNode::column_def: case OSQLParseNode::table_node: case OSQLParseNode::as_clause: case OSQLParseNode::opt_as: case OSQLParseNode::op_column_commalist: case OSQLParseNode::table_primary_as_range_column: case OSQLParseNode::character_string_type: case OSQLParseNode::comparison:
OSL_FAIL("Unexpected SQL RuleID"); break; case OSQLParseNode::column: case OSQLParseNode::column_val:
OSL_FAIL("Cannot guess column type"); break; case OSQLParseNode::values_or_query_spec:
OSL_FAIL("Cannot guess VALUES type"); break; case OSQLParseNode::derived_column:
OSL_FAIL("Cannot guess computed column type"); break; case OSQLParseNode::subquery:
OSL_FAIL("Cannot guess subquery return type"); break; case OSQLParseNode::search_condition: case OSQLParseNode::comparison_predicate: case OSQLParseNode::between_predicate: case OSQLParseNode::like_predicate: case OSQLParseNode::test_for_null: case OSQLParseNode::boolean_term: case OSQLParseNode::boolean_primary: case OSQLParseNode::in_predicate: case OSQLParseNode::existence_test: case OSQLParseNode::unique_test: case OSQLParseNode::all_or_any_predicate: case OSQLParseNode::join_condition: case OSQLParseNode::boolean_factor: case OSQLParseNode::comparison_predicate_part_2: case OSQLParseNode::parenthesized_boolean_value_expression: case OSQLParseNode::other_like_predicate_part_2: case OSQLParseNode::between_predicate_part_2:
nDataType = DataType::BOOLEAN; break; case OSQLParseNode::num_value_exp: case OSQLParseNode::extract_exp: case OSQLParseNode::term: case OSQLParseNode::factor: // Might by an integer or a float; take the most generic
nDataType = DataType::DOUBLE; break; case OSQLParseNode::value_exp_primary: case OSQLParseNode::value_exp: case OSQLParseNode::odbc_call_spec: // Really, we don't know. Let the default. break; case OSQLParseNode::position_exp: case OSQLParseNode::length_exp:
nDataType = DataType::INTEGER; break; case OSQLParseNode::char_value_exp: case OSQLParseNode::char_value_fct: case OSQLParseNode::fold: case OSQLParseNode::char_substring_fct: case OSQLParseNode::char_factor: case OSQLParseNode::concatenation:
nDataType = DataType::VARCHAR; break; case OSQLParseNode::datetime_primary:
nDataType = DataType::TIMESTAMP; break; case OSQLParseNode::bit_value_fct:
nDataType = DataType::BINARY; break; case OSQLParseNode::general_set_fct: // May depend on argument; ignore that for now case OSQLParseNode::set_fct_spec:
{ if (pNode->count() == 0)
{ // This is not a function call, no sense to continue with a function return type lookup
OSL_FAIL("Got leaf SQL node where non-leaf expected"); break;
} const OSQLParseNode* pFunctionName = pNode->getChild(0); if ( SQL_ISPUNCTUATION(pFunctionName,"{") )
{ if ( pNode->count() == 3 ) return fillFunctionInfo( pNode->getChild(1), sFunctionTerm, aInfo ); else
OSL_FAIL("ODBC escape not in recognised form"); break;
} else
{ if ( SQL_ISRULEOR2(pNode,length_exp,char_value_fct) )
pFunctionName = pFunctionName->getChild(0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.