1*9b5730f6SAndrew Rist /************************************************************** 2cdf0e10cSrcweir * 3*9b5730f6SAndrew Rist * Licensed to the Apache Software Foundation (ASF) under one 4*9b5730f6SAndrew Rist * or more contributor license agreements. See the NOTICE file 5*9b5730f6SAndrew Rist * distributed with this work for additional information 6*9b5730f6SAndrew Rist * regarding copyright ownership. The ASF licenses this file 7*9b5730f6SAndrew Rist * to you under the Apache License, Version 2.0 (the 8*9b5730f6SAndrew Rist * "License"); you may not use this file except in compliance 9*9b5730f6SAndrew Rist * with the License. You may obtain a copy of the License at 10cdf0e10cSrcweir * 11*9b5730f6SAndrew Rist * http://www.apache.org/licenses/LICENSE-2.0 12cdf0e10cSrcweir * 13*9b5730f6SAndrew Rist * Unless required by applicable law or agreed to in writing, 14*9b5730f6SAndrew Rist * software distributed under the License is distributed on an 15*9b5730f6SAndrew Rist * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*9b5730f6SAndrew Rist * KIND, either express or implied. See the License for the 17*9b5730f6SAndrew Rist * specific language governing permissions and limitations 18*9b5730f6SAndrew Rist * under the License. 19cdf0e10cSrcweir * 20*9b5730f6SAndrew Rist *************************************************************/ 21*9b5730f6SAndrew Rist 22*9b5730f6SAndrew Rist 23cdf0e10cSrcweir 24cdf0e10cSrcweir // MARKER(update_precomp.py): autogen include statement, do not remove 25cdf0e10cSrcweir #include "precompiled_connectivity.hxx" 26cdf0e10cSrcweir #include "file/fanalyzer.hxx" 27cdf0e10cSrcweir #include "connectivity/sqlparse.hxx" 28cdf0e10cSrcweir #include <osl/diagnose.h> 29cdf0e10cSrcweir #include <tools/debug.hxx> 30cdf0e10cSrcweir #include <comphelper/extract.hxx> 31cdf0e10cSrcweir #include "connectivity/sqlnode.hxx" 32cdf0e10cSrcweir #include "connectivity/dbexception.hxx" 33cdf0e10cSrcweir #include "file/FConnection.hxx" 34cdf0e10cSrcweir #include "resource/file_res.hrc" 35cdf0e10cSrcweir 36cdf0e10cSrcweir using namespace ::connectivity; 37cdf0e10cSrcweir using namespace ::connectivity::file; 38cdf0e10cSrcweir using namespace ::com::sun::star::uno; 39cdf0e10cSrcweir using namespace ::com::sun::star::beans; 40cdf0e10cSrcweir using namespace ::com::sun::star::sdbc; 41cdf0e10cSrcweir using namespace ::com::sun::star::container; 42cdf0e10cSrcweir 43cdf0e10cSrcweir DBG_NAME( file_OSQLAnalyzer ) 44cdf0e10cSrcweir //------------------------------------------------------------------ 45cdf0e10cSrcweir OSQLAnalyzer::OSQLAnalyzer(OConnection* _pConnection) 46cdf0e10cSrcweir :m_pConnection(_pConnection) 47cdf0e10cSrcweir ,m_bHasSelectionCode(sal_False) 48cdf0e10cSrcweir ,m_bSelectionFirstTime(sal_True) 49cdf0e10cSrcweir { 50cdf0e10cSrcweir DBG_CTOR( file_OSQLAnalyzer, NULL ); 51cdf0e10cSrcweir m_aCompiler = new OPredicateCompiler(this); 52cdf0e10cSrcweir m_aInterpreter = new OPredicateInterpreter(m_aCompiler); 53cdf0e10cSrcweir } 54cdf0e10cSrcweir 55cdf0e10cSrcweir // ----------------------------------------------------------------------------- 56cdf0e10cSrcweir OSQLAnalyzer::~OSQLAnalyzer() 57cdf0e10cSrcweir { 58cdf0e10cSrcweir DBG_DTOR( file_OSQLAnalyzer, NULL ); 59cdf0e10cSrcweir } 60cdf0e10cSrcweir 61cdf0e10cSrcweir // ----------------------------------------------------------------------------- 62cdf0e10cSrcweir void OSQLAnalyzer::setIndexes(const Reference< XNameAccess>& _xIndexes) 63cdf0e10cSrcweir { 64cdf0e10cSrcweir m_aCompiler->m_xIndexes = _xIndexes; 65cdf0e10cSrcweir } 66cdf0e10cSrcweir //------------------------------------------------------------------ 67cdf0e10cSrcweir void OSQLAnalyzer::start(OSQLParseNode* pSQLParseNode) 68cdf0e10cSrcweir { 69cdf0e10cSrcweir if (SQL_ISRULE(pSQLParseNode,select_statement)) 70cdf0e10cSrcweir { 71cdf0e10cSrcweir DBG_ASSERT(pSQLParseNode->count() >= 4,"OFILECursor: Fehler im Parse Tree"); 72cdf0e10cSrcweir 73cdf0e10cSrcweir // check that we don't use anything other than count(*) as function 74cdf0e10cSrcweir OSQLParseNode* pSelection = pSQLParseNode->getChild(2); 75cdf0e10cSrcweir if ( SQL_ISRULE(pSelection,scalar_exp_commalist) ) 76cdf0e10cSrcweir { 77cdf0e10cSrcweir for (sal_uInt32 i = 0; i < pSelection->count(); i++) 78cdf0e10cSrcweir { 79cdf0e10cSrcweir OSQLParseNode *pColumnRef = pSelection->getChild(i)->getChild(0); 80cdf0e10cSrcweir if ( ( SQL_ISRULE(pColumnRef,set_fct_spec) && pColumnRef->count() == 4 ) 81cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,char_value_fct) 82cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,char_substring_fct) 83cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,position_exp) 84cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,fold) 85cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,length_exp) 86cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,num_value_exp) 87cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,term) 88cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,factor) 89cdf0e10cSrcweir || SQL_ISRULE(pColumnRef,set_fct_spec) ) 90cdf0e10cSrcweir { 91cdf0e10cSrcweir ::vos::ORef<OPredicateCompiler> pCompiler = new OPredicateCompiler(this); 92cdf0e10cSrcweir pCompiler->setOrigColumns(m_aCompiler->getOrigColumns()); 93cdf0e10cSrcweir ::vos::ORef<OPredicateInterpreter> pInterpreter = new OPredicateInterpreter(pCompiler); 94cdf0e10cSrcweir pCompiler->execute( pColumnRef ); 95cdf0e10cSrcweir m_aSelectionEvaluations.push_back( TPredicates(pCompiler,pInterpreter) ); 96cdf0e10cSrcweir } 97cdf0e10cSrcweir else if ( ( SQL_ISRULE(pColumnRef,general_set_fct) && pColumnRef->count() != 4 ) ) 98cdf0e10cSrcweir { 99cdf0e10cSrcweir m_pConnection->throwGenericSQLException(STR_QUERY_COMPLEX_COUNT,NULL); 100cdf0e10cSrcweir } 101cdf0e10cSrcweir else 102cdf0e10cSrcweir { 103cdf0e10cSrcweir if ( SQL_ISPUNCTUATION( pColumnRef, "*" ) 104cdf0e10cSrcweir || ( SQL_ISRULE( pColumnRef, column_ref ) 105cdf0e10cSrcweir && ( pColumnRef->count() == 3 ) 106cdf0e10cSrcweir && ( pColumnRef->getChild(0)->getNodeType() == SQL_NODE_NAME ) 107cdf0e10cSrcweir && SQL_ISPUNCTUATION( pColumnRef->getChild(1), "." ) 108cdf0e10cSrcweir && SQL_ISRULE( pColumnRef->getChild(2), column_val ) 109cdf0e10cSrcweir && SQL_ISPUNCTUATION( pColumnRef->getChild(2)->getChild(0), "*" ) 110cdf0e10cSrcweir ) 111cdf0e10cSrcweir ) 112cdf0e10cSrcweir { 113cdf0e10cSrcweir // push one element for each column of our table 114cdf0e10cSrcweir const Reference< XNameAccess > xColumnNames( m_aCompiler->getOrigColumns() ); 115cdf0e10cSrcweir const Sequence< ::rtl::OUString > aColumnNames( xColumnNames->getElementNames() ); 116cdf0e10cSrcweir for ( sal_Int32 j=0; j<aColumnNames.getLength(); ++j ) 117cdf0e10cSrcweir m_aSelectionEvaluations.push_back( TPredicates() ); 118cdf0e10cSrcweir } 119cdf0e10cSrcweir else 120cdf0e10cSrcweir m_aSelectionEvaluations.push_back( TPredicates() ); 121cdf0e10cSrcweir } 122cdf0e10cSrcweir } 123cdf0e10cSrcweir } 124cdf0e10cSrcweir } 125cdf0e10cSrcweir 126cdf0e10cSrcweir m_aCompiler->start(pSQLParseNode); 127cdf0e10cSrcweir } 128cdf0e10cSrcweir 129cdf0e10cSrcweir //------------------------------------------------------------------ 130cdf0e10cSrcweir void OSQLAnalyzer::bindRow(OCodeList& rCodeList,const OValueRefRow& _pRow,OEvaluateSetList& _rEvaluateSetList) 131cdf0e10cSrcweir { 132cdf0e10cSrcweir // Zaehlen, wieviele Kriterien 133cdf0e10cSrcweir // wenn nur ein Kriterium, und das entsprechende Feld ist indiziert 134cdf0e10cSrcweir // dann wird der Index verwendet 135cdf0e10cSrcweir 136cdf0e10cSrcweir OEvaluateSet* pEvaluateSet = NULL; 137cdf0e10cSrcweir 138cdf0e10cSrcweir for (OCodeList::iterator aIter = rCodeList.begin(); aIter != rCodeList.end(); ++aIter) 139cdf0e10cSrcweir { 140cdf0e10cSrcweir OOperandAttr* pAttr = PTR_CAST(OOperandAttr,(*aIter)); 141cdf0e10cSrcweir if (pAttr) 142cdf0e10cSrcweir { 143cdf0e10cSrcweir if (pAttr->isIndexed() && !m_aCompiler->hasORCondition()) 144cdf0e10cSrcweir { 145cdf0e10cSrcweir OCode* pCode1 = *(aIter + 1); 146cdf0e10cSrcweir OCode* pCode2 = *(aIter + 2); 147cdf0e10cSrcweir 148cdf0e10cSrcweir if (PTR_CAST(OOperand,pCode1)) 149cdf0e10cSrcweir pEvaluateSet = pAttr->preProcess(PTR_CAST(OBoolOperator,pCode2), PTR_CAST(OOperand,pCode1)); 150cdf0e10cSrcweir else 151cdf0e10cSrcweir pEvaluateSet = pAttr->preProcess(PTR_CAST(OBoolOperator,pCode1)); 152cdf0e10cSrcweir } 153cdf0e10cSrcweir 154cdf0e10cSrcweir if (pEvaluateSet) 155cdf0e10cSrcweir { 156cdf0e10cSrcweir _rEvaluateSetList.push_back(pEvaluateSet); 157cdf0e10cSrcweir pEvaluateSet = NULL; 158cdf0e10cSrcweir } 159cdf0e10cSrcweir pAttr->bindValue(_pRow); 160cdf0e10cSrcweir } 161cdf0e10cSrcweir } 162cdf0e10cSrcweir } 163cdf0e10cSrcweir //------------------------------------------------------------------ 164cdf0e10cSrcweir void OSQLAnalyzer::bindSelectRow(const OValueRefRow& _pRow) 165cdf0e10cSrcweir { 166cdf0e10cSrcweir // first the select part 167cdf0e10cSrcweir OEvaluateSetList aEvaluateSetList; 168cdf0e10cSrcweir for ( ::std::vector< TPredicates >::iterator aIter = m_aSelectionEvaluations.begin(); aIter != m_aSelectionEvaluations.end();++aIter) 169cdf0e10cSrcweir { 170cdf0e10cSrcweir if ( aIter->first.isValid() ) 171cdf0e10cSrcweir bindRow( aIter->first->m_aCodeList,_pRow,aEvaluateSetList); 172cdf0e10cSrcweir } 173cdf0e10cSrcweir } 174cdf0e10cSrcweir //------------------------------------------------------------------ 175cdf0e10cSrcweir ::std::vector<sal_Int32>* OSQLAnalyzer::bindEvaluationRow(OValueRefRow& _pRow) 176cdf0e10cSrcweir { 177cdf0e10cSrcweir OEvaluateSetList aEvaluateSetList; 178cdf0e10cSrcweir bindRow( m_aCompiler->m_aCodeList,_pRow,aEvaluateSetList); 179cdf0e10cSrcweir 180cdf0e10cSrcweir ::std::vector<sal_Int32>* pKeySet = NULL; 181cdf0e10cSrcweir OEvaluateSet* pEvaluateSet = NULL; 182cdf0e10cSrcweir 183cdf0e10cSrcweir // Keyset erzeugen mit kleinster Liste 184cdf0e10cSrcweir if(!aEvaluateSetList.empty()) 185cdf0e10cSrcweir { 186cdf0e10cSrcweir // welche Liste hat den kleinsten count ? 187cdf0e10cSrcweir OEvaluateSetList::iterator i = aEvaluateSetList.begin(); 188cdf0e10cSrcweir pEvaluateSet = *(i); 189cdf0e10cSrcweir for(++i; i != aEvaluateSetList.end();++i) 190cdf0e10cSrcweir { 191cdf0e10cSrcweir OEvaluateSet* pEvaluateSetComp = (*i); 192cdf0e10cSrcweir for(OEvaluateSet::reverse_iterator j = pEvaluateSet->rbegin(); j != pEvaluateSet->rend(); ++j) 193cdf0e10cSrcweir { 194cdf0e10cSrcweir if (pEvaluateSetComp->find(j->second) != pEvaluateSetComp->end()) 195cdf0e10cSrcweir pEvaluateSet->erase(j->second); 196cdf0e10cSrcweir } 197cdf0e10cSrcweir } 198cdf0e10cSrcweir pKeySet = new ::std::vector<sal_Int32>(pEvaluateSet->size()); 199cdf0e10cSrcweir sal_Int32 k=0; 200cdf0e10cSrcweir for(OEvaluateSet::iterator j = pEvaluateSet->begin(); j != pEvaluateSet->end(); ++j,++k) 201cdf0e10cSrcweir { 202cdf0e10cSrcweir (*pKeySet)[k] = j->second; 203cdf0e10cSrcweir } 204cdf0e10cSrcweir 205cdf0e10cSrcweir // alle loeschen 206cdf0e10cSrcweir for(i = aEvaluateSetList.begin(); i != aEvaluateSetList.end();++i) 207cdf0e10cSrcweir delete (*i); 208cdf0e10cSrcweir } 209cdf0e10cSrcweir 210cdf0e10cSrcweir return pKeySet; 211cdf0e10cSrcweir } 212cdf0e10cSrcweir 213cdf0e10cSrcweir //------------------------------------------------------------------ 214cdf0e10cSrcweir void OSQLAnalyzer::describeParam(::vos::ORef<OSQLColumns> rParameterColumns) 215cdf0e10cSrcweir { 216cdf0e10cSrcweir OCodeList& rCodeList = m_aCompiler->m_aCodeList; 217cdf0e10cSrcweir OCodeStack aCodeStack; 218cdf0e10cSrcweir 219cdf0e10cSrcweir if (!rCodeList.size()) 220cdf0e10cSrcweir return; // kein Praedikat 221cdf0e10cSrcweir if (!rParameterColumns->get().size()) 222cdf0e10cSrcweir return; // keine Parameter 223cdf0e10cSrcweir 224cdf0e10cSrcweir // Anlegen von Columns, die eine genauere Beschreibung fuer die enthalten 225cdf0e10cSrcweir ::vos::ORef<OSQLColumns> aNewParamColumns = new OSQLColumns(*rParameterColumns); 226cdf0e10cSrcweir 227cdf0e10cSrcweir 228cdf0e10cSrcweir // Anlegen einer Testzeile, wird benoetigt um die Parameter zu beschreiben 229cdf0e10cSrcweir OValueRefRow aParameterRow = new OValueRefVector(rParameterColumns->get().size()); 230cdf0e10cSrcweir bindParameterRow(aParameterRow); 231cdf0e10cSrcweir 232cdf0e10cSrcweir OValueRefRow aTestRow = new OValueRefVector(Reference< XIndexAccess>(m_aCompiler->getOrigColumns(),UNO_QUERY)->getCount()); 233cdf0e10cSrcweir delete bindEvaluationRow(aTestRow); // Binden der Attribute an die Values 234cdf0e10cSrcweir 235cdf0e10cSrcweir for(OCodeList::iterator aIter = rCodeList.begin(); aIter != rCodeList.end(); ++aIter) 236cdf0e10cSrcweir { 237cdf0e10cSrcweir OOperand* pOperand = PTR_CAST(OOperand,(*aIter)); 238cdf0e10cSrcweir OOperator* pOperator = PTR_CAST(OOperator,(*aIter)); 239cdf0e10cSrcweir if (pOperand) 240cdf0e10cSrcweir aCodeStack.push(pOperand); 241cdf0e10cSrcweir else 242cdf0e10cSrcweir { 243cdf0e10cSrcweir if (pOperator->getRequestedOperands() == 2) // bei zwei Operatoren ist es moeglich 244cdf0e10cSrcweir { // einen Parameter weiter zu spezifizieren 245cdf0e10cSrcweir OOperandParam *pParam = PTR_CAST(OOperandParam,aCodeStack.top()); 246cdf0e10cSrcweir if (pParam) // Anpassen des ParameterTyps, wenn der linke Operand ein Attribut ist 247cdf0e10cSrcweir { 248cdf0e10cSrcweir OOperandAttr *pLeft = PTR_CAST(OOperandAttr,*(rCodeList.end() - 2)); 249cdf0e10cSrcweir if (pLeft) 250cdf0e10cSrcweir { 251cdf0e10cSrcweir Reference< XPropertySet> xCol; 252cdf0e10cSrcweir Reference< XIndexAccess>(m_aCompiler->getOrigColumns(),UNO_QUERY)->getByIndex(pLeft->getRowPos()) >>= xCol; 253cdf0e10cSrcweir OSL_ENSURE(xCol.is(), "Ungueltige Struktur"); 254cdf0e10cSrcweir pParam->describe(xCol, aNewParamColumns); 255cdf0e10cSrcweir } 256cdf0e10cSrcweir } 257cdf0e10cSrcweir } 258cdf0e10cSrcweir pOperator->Exec(aCodeStack); 259cdf0e10cSrcweir } 260cdf0e10cSrcweir } 261cdf0e10cSrcweir OOperand* pOperand = aCodeStack.top(); 262cdf0e10cSrcweir aCodeStack.pop(); 263cdf0e10cSrcweir 264cdf0e10cSrcweir OSL_ENSURE(aCodeStack.size() == 0, "StackFehler"); 265cdf0e10cSrcweir OSL_ENSURE(pOperand, "StackFehler"); 266cdf0e10cSrcweir if (IS_TYPE(OOperandResult,pOperand)) 267cdf0e10cSrcweir delete pOperand; 268cdf0e10cSrcweir else 269cdf0e10cSrcweir OSL_ENSURE(0,"Illegal here!"); 270cdf0e10cSrcweir 271cdf0e10cSrcweir rParameterColumns = aNewParamColumns; 272cdf0e10cSrcweir // m_aCompiler->setParameterColumns(rParameterColumns); 273cdf0e10cSrcweir } 274cdf0e10cSrcweir 275cdf0e10cSrcweir // ----------------------------------------------------------------------------- 276cdf0e10cSrcweir OOperandAttr* OSQLAnalyzer::createOperandAttr(sal_Int32 _nPos, 277cdf0e10cSrcweir const Reference< XPropertySet>& _xCol, 278cdf0e10cSrcweir const Reference< XNameAccess>& /*_xIndexes*/) 279cdf0e10cSrcweir { 280cdf0e10cSrcweir return new OOperandAttr(static_cast<sal_uInt16>(_nPos),_xCol); 281cdf0e10cSrcweir } 282cdf0e10cSrcweir // ----------------------------------------------------------------------------- 283cdf0e10cSrcweir sal_Bool OSQLAnalyzer::hasRestriction() const 284cdf0e10cSrcweir { 285cdf0e10cSrcweir return m_aCompiler->hasCode(); 286cdf0e10cSrcweir } 287cdf0e10cSrcweir // ----------------------------------------------------------------------------- 288cdf0e10cSrcweir sal_Bool OSQLAnalyzer::hasFunctions() const 289cdf0e10cSrcweir { 290cdf0e10cSrcweir if ( m_bSelectionFirstTime ) 291cdf0e10cSrcweir { 292cdf0e10cSrcweir m_bSelectionFirstTime = sal_False; 293cdf0e10cSrcweir for ( ::std::vector< TPredicates >::const_iterator aIter = m_aSelectionEvaluations.begin(); aIter != m_aSelectionEvaluations.end() && !m_bHasSelectionCode ;++aIter) 294cdf0e10cSrcweir { 295cdf0e10cSrcweir if ( aIter->first.isValid() ) 296cdf0e10cSrcweir m_bHasSelectionCode = aIter->first->hasCode(); 297cdf0e10cSrcweir } 298cdf0e10cSrcweir } 299cdf0e10cSrcweir return m_bHasSelectionCode;; 300cdf0e10cSrcweir } 301cdf0e10cSrcweir // ----------------------------------------------------------------------------- 302cdf0e10cSrcweir void OSQLAnalyzer::setSelectionEvaluationResult(OValueRefRow& _pRow,const ::std::vector<sal_Int32>& _rColumnMapping) 303cdf0e10cSrcweir { 304cdf0e10cSrcweir sal_Int32 nPos = 1; 305cdf0e10cSrcweir for ( ::std::vector< TPredicates >::iterator aIter = m_aSelectionEvaluations.begin(); aIter != m_aSelectionEvaluations.end();++aIter,++nPos) 306cdf0e10cSrcweir { 307cdf0e10cSrcweir if ( aIter->second.isValid() ) 308cdf0e10cSrcweir { 309cdf0e10cSrcweir // the first column (index 0) is for convenience only. The first real select column is no 1. 310cdf0e10cSrcweir sal_Int32 map = nPos; 311cdf0e10cSrcweir if ( nPos < static_cast< sal_Int32 >( _rColumnMapping.size() ) ) 312cdf0e10cSrcweir map = _rColumnMapping[nPos]; 313cdf0e10cSrcweir if ( map > 0 ) 314cdf0e10cSrcweir aIter->second->startSelection( (_pRow->get())[map] ); 315cdf0e10cSrcweir } 316cdf0e10cSrcweir } 317cdf0e10cSrcweir } 318cdf0e10cSrcweir // ----------------------------------------------------------------------------- 319cdf0e10cSrcweir void OSQLAnalyzer::dispose() 320cdf0e10cSrcweir { 321cdf0e10cSrcweir m_aCompiler->dispose(); 322cdf0e10cSrcweir for ( ::std::vector< TPredicates >::iterator aIter = m_aSelectionEvaluations.begin(); aIter != m_aSelectionEvaluations.end();++aIter) 323cdf0e10cSrcweir { 324cdf0e10cSrcweir if ( aIter->first.isValid() ) 325cdf0e10cSrcweir aIter->first->dispose(); 326cdf0e10cSrcweir } 327cdf0e10cSrcweir } 328cdf0e10cSrcweir // ----------------------------------------------------------------------------- 329cdf0e10cSrcweir void OSQLAnalyzer::setOrigColumns(const OFileColumns& rCols) 330cdf0e10cSrcweir { 331cdf0e10cSrcweir m_aCompiler->setOrigColumns(rCols); 332cdf0e10cSrcweir for ( ::std::vector< TPredicates >::iterator aIter = m_aSelectionEvaluations.begin(); aIter != m_aSelectionEvaluations.end();++aIter) 333cdf0e10cSrcweir { 334cdf0e10cSrcweir if ( aIter->first.isValid() ) 335cdf0e10cSrcweir aIter->first->setOrigColumns(rCols); 336cdf0e10cSrcweir } 337cdf0e10cSrcweir } 338cdf0e10cSrcweir // ----------------------------------------------------------------------------- 339