kexi

parser_p.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "parser_p.h"
00021 #include "sqlparser.h"
00022 
00023 #include <kdebug.h>
00024 #include <klocale.h>
00025 
00026 #include <qregexp.h>
00027 
00028 #include <assert.h>
00029 
00030 using namespace KexiDB;
00031 
00032 Parser *parser;
00033 Field *field;
00034 //bool requiresTable;
00035 QPtrList<Field> fieldList;
00036 int current = 0;
00037 QString ctoken = "";
00038 
00039 //-------------------------------------
00040 
00041 ParserPrivate::ParserPrivate()
00042  : reservedKeywords(997, 997, false)
00043  , initialized(false)
00044 {
00045     clear();
00046     table = 0;
00047     select = 0;
00048     db = 0;
00049 }
00050 
00051 ParserPrivate::~ParserPrivate()
00052 {
00053     delete select;
00054     delete table;
00055 }
00056 
00057 void ParserPrivate::clear()
00058 {
00059     operation = Parser::OP_None;
00060     error = ParserError();
00061 }
00062 
00063 //-------------------------------------
00064 
00065 ParseInfo::ParseInfo(KexiDB::QuerySchema *query)
00066  : repeatedTablesAndAliases(997, false)
00067  , querySchema(query)
00068 {
00069     repeatedTablesAndAliases.setAutoDelete(true);
00070 }
00071 
00072 ParseInfo::~ParseInfo()
00073 {
00074 }
00075 
00076 //-------------------------------------
00077 
00078 extern int yyparse();
00079 extern void tokenize(const char *data);
00080 
00081 void yyerror(const char *str)
00082 {
00083     KexiDBDbg << "error: " << str << endl;
00084     KexiDBDbg << "at character " << current << " near tooken " << ctoken << endl;
00085     parser->setOperation(Parser::OP_Error);
00086 
00087     const bool otherError = (qstrnicmp(str, "other error", 11)==0);
00088     
00089     if (parser->error().type().isEmpty() 
00090         && (str==0 || strlen(str)==0 
00091         || qstrnicmp(str, "syntax error", 12)==0 || qstrnicmp(str, "parse error", 11)==0)
00092         || otherError)
00093     {
00094         KexiDBDbg << parser->statement() << endl;
00095         QString ptrline = "";
00096         for(int i=0; i < current; i++)
00097             ptrline += " ";
00098 
00099         ptrline += "^";
00100 
00101         KexiDBDbg << ptrline << endl;
00102 
00103         //lexer may add error messages
00104         QString lexerErr = parser->error().error();
00105 
00106         QString errtypestr(str);
00107         if (lexerErr.isEmpty()) {
00108 #if 0
00109             if (errtypestr.startsWith("parse error, unexpected ")) {
00110                 //something like "parse error, unexpected IDENTIFIER, expecting ',' or ')'"
00111                 QString e = errtypestr.mid(24);
00112                 KexiDBDbg << e <<endl;
00113                 QString token = "IDENTIFIER";
00114                 if (e.startsWith(token)) {
00115                     QRegExp re("'.'");
00116                     int pos=0;
00117                     pos = re.search(e, pos);
00118                     QStringList captured=re.capturedTexts();
00119                     if (captured.count()>=2) {
00120 //                      KexiDBDbg << "**" << captured.at(1) << endl;
00121 //                      KexiDBDbg << "**" << captured.at(2) << endl;
00122                     }
00123                 }
00124                     
00125                     
00126                     
00127 //           IDENTIFIER, expecting '")) {
00128                 e = errtypestr.mid(47);
00129                 KexiDBDbg << e <<endl;
00130 //              ,' or ')'
00131 //      lexerErr i18n("identifier was expected");
00132                 
00133             } else 
00134 #endif
00135             if (errtypestr.startsWith("parse error, expecting `IDENTIFIER'"))
00136                 lexerErr = i18n("identifier was expected");
00137         }
00138         
00139         if (!otherError) {
00140             if (!lexerErr.isEmpty())
00141                 lexerErr.prepend(": ");
00142 
00143             if (parser->isReservedKeyword(ctoken.latin1()))
00144                 parser->setError( ParserError(i18n("Syntax Error"), 
00145                     i18n("\"%1\" is a reserved keyword").arg(ctoken)+lexerErr, ctoken, current) );
00146             else
00147                 parser->setError( ParserError(i18n("Syntax Error"), 
00148                     i18n("Syntax Error near \"%1\"").arg(ctoken)+lexerErr, ctoken, current) );
00149         }
00150     }
00151 }
00152 
00153 void setError(const QString& errName, const QString& errDesc)
00154 {
00155     parser->setError( ParserError(errName, errDesc, ctoken, current) );
00156     yyerror(errName.latin1());
00157 }
00158 
00159 void setError(const QString& errDesc)
00160 {
00161     setError("other error", errDesc);
00162 }
00163 
00164 /* this is better than assert() */
00165 #define IMPL_ERROR(errmsg) setError("Implementation error", errmsg)
00166 
00167 bool parseData(Parser *p, const char *data)
00168 {
00169 /* todo: make this REENTRANT */
00170     parser = p;
00171     parser->clear();
00172     field = 0;
00173     fieldList.clear();
00174 //  requiresTable = false;
00175 
00176     if (!data) {
00177         ParserError err(i18n("Error"), i18n("No query specified"), ctoken, current);
00178         parser->setError(err);
00179         yyerror("");
00180         parser = 0;
00181         return false;
00182     }
00183 
00184     tokenize(data);
00185     if (!parser->error().type().isEmpty()) {
00186         parser = 0;
00187         return false;
00188     }
00189     yyparse();
00190 
00191     bool ok = true;
00192     if(parser->operation() == Parser::OP_Select)
00193     {
00194         KexiDBDbg << "parseData(): ok" << endl;
00195 //          KexiDBDbg << "parseData(): " << tableDict.count() << " loaded tables" << endl;
00196 /*          TableSchema *ts;
00197             for(QDictIterator<TableSchema> it(tableDict); TableSchema *s = tableList.first(); s; s = tableList.next())
00198             {
00199                 KexiDBDbg << "  " << s->name() << endl;
00200             }*/
00201 /*removed
00202             Field::ListIterator it = parser->select()->fieldsIterator();
00203             for(Field *item; (item = it.current()); ++it)
00204             {
00205                 if(tableList.findRef(item->table()) == -1)
00206                 {
00207                     ParserError err(i18n("Field List Error"), i18n("Unknown table '%1' in field list").arg(item->table()->name()), ctoken, current);
00208                     parser->setError(err);
00209 
00210                     yyerror("fieldlisterror");
00211                     ok = false;
00212                 }
00213             }*/
00214             //take the dummy table out of the query
00215 //          parser->select()->removeTable(dummy);
00216     }
00217     else {
00218         ok = false;
00219     }
00220 
00221 //      tableDict.clear();
00222     parser = 0;
00223     return ok;
00224 }
00225 
00226     
00227 /* Adds \a column to \a querySchema. \a column can be in a form of
00228  table.field, tableAlias.field or field
00229 */
00230 bool addColumn( ParseInfo& parseInfo, BaseExpr* columnExpr )
00231 {
00232     if (!columnExpr->validate(parseInfo)) {
00233         setError(parseInfo.errMsg, parseInfo.errDescr);
00234         return false;
00235     }
00236 
00237     VariableExpr *v_e = columnExpr->toVariable();
00238     if (columnExpr->exprClass() == KexiDBExpr_Variable && v_e) {
00239         //it's a variable:
00240         if (v_e->name=="*") {//all tables asterisk
00241             if (parseInfo.querySchema->tables()->isEmpty()) {
00242                 setError(i18n("\"*\" could not be used if no tables are specified"));
00243                 return false;
00244             }
00245             parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
00246         }
00247         else if (v_e->tableForQueryAsterisk) {//one-table asterisk
00248             parseInfo.querySchema->addAsterisk( 
00249                 new QueryAsterisk(parseInfo.querySchema, v_e->tableForQueryAsterisk) );
00250         }
00251         else if (v_e->field) {//"table.field" or "field" (bound to a table or not)
00252             parseInfo.querySchema->addField(v_e->field, v_e->tablePositionForField);
00253         }
00254         else {
00255             IMPL_ERROR("addColumn(): unknown case!");
00256             return false;
00257         }
00258         return true;
00259     }
00260 
00261     //it's complex expression
00262     parseInfo.querySchema->addExpression(columnExpr);
00263 
00264 #if 0
00265     KexiDBDbg << "found variable name: " << varName << endl;
00266     int dotPos = varName.find('.');
00267     QString tableName, fieldName;
00268 //TODO: shall we also support db name?
00269     if (dotPos>0) {
00270         tableName = varName.left(dotPos);
00271         fieldName = varName.mid(dotPos+1);
00272     }
00273     if (tableName.isEmpty()) {//fieldname only
00274         fieldName = varName;
00275         if (fieldName=="*") {
00276             parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
00277         }
00278         else {
00279             //find first table that has this field
00280             Field *firstField = 0;
00281             for (TableSchema::ListIterator it(*parseInfo.querySchema->tables()); it.current(); ++it) {
00282                 Field *f = it.current()->field(fieldName);
00283                 if (f) {
00284                     if (!firstField) {
00285                         firstField = f;
00286                     } else if (f->table()!=firstField->table()) {
00287                         //ambiguous field name
00288                         setError(i18n("Ambiguous field name"), 
00289                             i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. "
00290                                 "Use \"<tableName>.%4\" notation to specify table name.")
00291                                 .arg(firstField->table()->name()).arg(f->table()->name())
00292                                 .arg(fieldName).arg(fieldName));
00293                         return false;
00294                     }
00295                 }
00296             }
00297             if (!firstField) {
00298                     setError(i18n("Field not found"), 
00299                         i18n("Table containing \"%1\" field not found").arg(fieldName));
00300                     return false;
00301             }
00302             //ok
00303             parseInfo.querySchema->addField(firstField);
00304         }
00305     }
00306     else {//table.fieldname or tableAlias.fieldname
00307         tableName = tableName.lower();
00308         TableSchema *ts = parseInfo.querySchema->table( tableName );
00309         if (ts) {//table.fieldname
00310             //check if "table" is covered by an alias
00311             const QValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName);
00312             QValueList<int>::ConstIterator it = tPositions.constBegin();
00313             QCString tableAlias;
00314             bool covered = true;
00315             for (; it!=tPositions.constEnd() && covered; ++it) {
00316                 tableAlias = parseInfo.querySchema->tableAlias(*it);
00317                 if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1())
00318                     covered = false; //uncovered
00319                 KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl;
00320             }
00321             if (covered) {
00322                 setError(i18n("Could not access the table directly using its name"), 
00323                     i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", "
00324                     "you can write \"%3\"").arg(tableName)
00325                     .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1()));
00326                 return false;
00327             }
00328         }
00329         
00330         int tablePosition = -1;
00331         if (!ts) {//try to find tableAlias
00332             tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() );
00333             if (tablePosition>=0) {
00334                 ts = parseInfo.querySchema->tables()->at(tablePosition);
00335                 if (ts) {
00336 //                  KexiDBDbg << " --it's a tableAlias.name" << endl;
00337                 }
00338             }
00339         }
00340 
00341 
00342         if (ts) {
00343             QValueList<int> *positionsList = repeatedTablesAndAliases[ tableName ];
00344             if (!positionsList) {
00345                 IMPL_ERROR(tableName + "." + fieldName + ", !positionsList ");
00346                 return false;
00347             }
00348 
00349             if (fieldName=="*") {
00350                 if (positionsList->count()>1) {
00351                     setError(i18n("Ambiguous \"%1.*\" expression").arg(tableName),
00352                         i18n("More than one \"%1\" table or alias defined").arg(tableName));
00353                     return false;
00354                 }
00355                 parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, ts) );
00356             }
00357             else {
00358 //              KexiDBDbg << " --it's a table.name" << endl;
00359                 Field *realField = ts->field(fieldName);
00360                 if (realField) {
00361                     // check if table or alias is used twice and both have the same column
00362                     // (so the column is ambiguous)
00363                     int numberOfTheSameFields = 0;
00364                     for (QValueList<int>::iterator it = positionsList->begin();
00365                         it!=positionsList->end();++it)
00366                     {
00367                         TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it);
00368                         if (otherTS->field(fieldName))
00369                             numberOfTheSameFields++;
00370                         if (numberOfTheSameFields>1) {
00371                             setError(i18n("Ambiguous \"%1.%2\" expression").arg(tableName).arg(fieldName),
00372                                 i18n("More than one \"%1\" table or alias defined containing \"%2\" field")
00373                                 .arg(tableName).arg(fieldName));
00374                             return false;
00375                         }
00376                     }
00377 
00378                     parseInfo.querySchema->addField(realField, tablePosition);
00379                 }
00380                 else {
00381                     setError(i18n("Field not found"), i18n("Table \"%1\" has no \"%2\" field")
00382                         .arg(tableName).arg(fieldName));
00383                     return false;
00384                 }
00385             }
00386         }
00387         else {
00388             tableNotFoundError(tableName);
00389             return false;
00390         }
00391     }
00392 #endif
00393     return true;
00394 }
00395 
00397 #define CLEANUP \
00398     delete colViews; \
00399     delete tablesList; \
00400     delete options
00401 
00402 QuerySchema* buildSelectQuery( 
00403     QuerySchema* querySchema, NArgExpr* colViews, NArgExpr* tablesList, 
00404     SelectOptionsInternal* options )
00405 {
00406     ParseInfo parseInfo(querySchema);
00407     
00408     //-------tables list
00409 //  assert( tablesList ); //&& tablesList->exprClass() == KexiDBExpr_TableList );
00410 
00411     uint columnNum = 0;
00412 /*TODO: use this later if there are columns that use database fields, 
00413         e.g. "SELECT 1 from table1 t, table2 t") is ok however. */
00414     //used to collect information about first repeated table name or alias:
00415 //  QDict<char> tableNamesAndTableAliases(997, false);
00416 //  QString repeatedTableNameOrTableAlias;
00417     if (tablesList) {
00418         for (int i=0; i<tablesList->args(); i++, columnNum++) {
00419             BaseExpr *e = tablesList->arg(i);
00420             VariableExpr* t_e = 0;
00421             QCString aliasString;
00422             if (e->exprClass() == KexiDBExpr_SpecialBinary) {
00423                 BinaryExpr* t_with_alias = e->toBinary();
00424                 assert(t_with_alias);
00425                 assert(t_with_alias->left()->exprClass() == KexiDBExpr_Variable);
00426                 assert(t_with_alias->right()->exprClass() == KexiDBExpr_Variable
00427                     && (t_with_alias->token()==AS || t_with_alias->token()==0));
00428                 t_e = t_with_alias->left()->toVariable();
00429                 aliasString = t_with_alias->right()->toVariable()->name.latin1();
00430             }
00431             else {
00432                 t_e = e->toVariable();
00433             }
00434             assert(t_e);
00435             QCString tname = t_e->name.latin1();
00436             TableSchema *s = parser->db()->tableSchema(tname);
00437             if(!s) {
00438                 setError(//i18n("Field List Error"), 
00439                     i18n("Table \"%1\" does not exist").arg(tname));
00440     //          yyerror("fieldlisterror");
00441                 CLEANUP;
00442                 return 0;
00443             }
00444             QCString tableOrAliasName;
00445             if (!aliasString.isEmpty()) {
00446                 tableOrAliasName = aliasString;
00447 //              KexiDBDbg << "- add alias for table: " << aliasString << endl;
00448             } else {
00449                 tableOrAliasName = tname;
00450             }
00451             // 1. collect information about first repeated table name or alias
00452             //    (potential ambiguity)
00453             QValueList<int> *list = parseInfo.repeatedTablesAndAliases[tableOrAliasName];
00454             if (list) {
00455                 //another table/alias with the same name
00456                 list->append( i );
00457 //              KexiDBDbg << "- another table/alias with name: " << tableOrAliasName << endl;
00458             }
00459             else {
00460                 list = new QValueList<int>();
00461                 list->append( i );
00462                 parseInfo.repeatedTablesAndAliases.insert( tableOrAliasName, list );
00463 //              KexiDBDbg << "- first table/alias with name: " << tableOrAliasName << endl;
00464             }
00465     /*      if (repeatedTableNameOrTableAlias.isEmpty()) {
00466                 if (tableNamesAndTableAliases[tname])
00467                     repeatedTableNameOrTableAlias=tname;
00468                 else
00469                     tableNamesAndTableAliases.insert(tname, (const char*)1);
00470             }
00471             if (!aliasString.isEmpty()) {
00472                 KexiDBDbg << "- add alias for table: " << aliasString << endl;
00473     //          querySchema->setTableAlias(columnNum, aliasString);
00474                 //2. collect information about first repeated table name or alias
00475                 //   (potential ambiguity)
00476                 if (repeatedTableNameOrTableAlias.isEmpty()) {
00477                     if (tableNamesAndTableAliases[aliasString])
00478                         repeatedTableNameOrTableAlias=aliasString;
00479                     else
00480                         tableNamesAndTableAliases.insert(aliasString, (const char*)1);
00481                 }
00482             }*/
00483 //          KexiDBDbg << "addTable: " << tname << endl;
00484             querySchema->addTable( s, aliasString );
00485         }
00486     }
00487 
00488     /* set parent table if there's only one */
00489 //  if (parser->select()->tables()->count()==1)
00490     if (querySchema->tables()->count()==1)
00491         querySchema->setMasterTable(querySchema->tables()->first());
00492 
00493     //-------add fields
00494     if (colViews) {
00495         BaseExpr *e;
00496         columnNum = 0;
00497         const uint colCount = colViews->list.count();
00498 //      for (BaseExpr::ListIterator it(colViews->list);(e = it.current()); columnNum++)
00499         colViews->list.first();
00500         for (; columnNum<colCount; columnNum++) {
00501             e = colViews->list.current();
00502             bool moveNext = true; //used to avoid ++it when an item is taken from the list
00503             BaseExpr *columnExpr = e;
00504             VariableExpr* aliasVariable = 0;
00505             if (e->exprClass() == KexiDBExpr_SpecialBinary && e->toBinary()
00506                 && (e->token()==AS || e->token()==0))
00507             {
00508                 //KexiDBExpr_SpecialBinary: with alias
00509                 columnExpr = e->toBinary()->left();
00510     //          isFieldWithAlias = true;
00511                 aliasVariable = e->toBinary()->right()->toVariable();
00512                 if (!aliasVariable) {
00513                     setError(i18n("Invalid alias definition for column \"%1\"")
00514                         .arg(columnExpr->toString())); //ok?
00515                     CLEANUP;
00516                     return 0;
00517                 }
00518             }
00519     
00520             const int c = columnExpr->exprClass();
00521             const bool isExpressionField = 
00522                     c == KexiDBExpr_Const
00523                 || c == KexiDBExpr_Unary
00524                 || c == KexiDBExpr_Arithm
00525                 || c == KexiDBExpr_Logical
00526                 || c == KexiDBExpr_Relational
00527                 || c == KexiDBExpr_Const
00528                 || c == KexiDBExpr_Function
00529                 || c == KexiDBExpr_Aggregation;
00530     
00531             if (c == KexiDBExpr_Variable) {
00532                 //just a variable, do nothing, addColumn() will handle this
00533             }
00534             else if (isExpressionField) {
00535                 //expression object will be reused, take, will be owned, do not destroy
00536 //      KexiDBDbg << colViews->list.count() << " " << it.current()->debugString() << endl;
00537                 colViews->list.take(); //take() doesn't work
00538                 moveNext = false;
00539             }
00540             else if (aliasVariable) {
00541                 //take first (left) argument of the special binary expr, will be owned, do not destroy
00542                 e->toBinary()->m_larg = 0;
00543             }
00544             else {
00545                 setError(i18n("Invalid \"%1\" column definition").arg(e->toString())); //ok?
00546                 CLEANUP;
00547                 return 0;
00548             }
00549     
00550             if (!addColumn( parseInfo, columnExpr )) {
00551                 CLEANUP;
00552                 return 0;
00553             }
00554             
00555             if (aliasVariable) {
00556 //              KexiDBDbg << "ALIAS \"" << aliasVariable->name << "\" set for column " 
00557 //                  << columnNum << endl;
00558                 querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
00559             }
00560     /*      if (e->exprClass() == KexiDBExpr_SpecialBinary && dynamic_cast<BinaryExpr*>(e)
00561                 && (e->type()==AS || e->type()==0))
00562             {
00563                 //also add alias
00564                 VariableExpr* aliasVariable =
00565                     dynamic_cast<VariableExpr*>(dynamic_cast<BinaryExpr*>(e)->right());
00566                 if (!aliasVariable) {
00567                     setError(i18n("Invalid column alias definition")); //ok?
00568                     return 0;
00569                 }
00570                 kdDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " 
00571                     << columnNum << endl;
00572                 querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
00573             }*/
00574     
00575             if (moveNext) {
00576                 colViews->list.next();
00577 //              ++it;
00578             }
00579         }
00580     }
00581     //----- SELECT options
00582     if (options) {
00583         //----- WHERE expr.
00584         if (options->whereExpr) {
00585             if (!options->whereExpr->validate(parseInfo)) {
00586                 setError(parseInfo.errMsg, parseInfo.errDescr);
00587                 CLEANUP;
00588                 return false;
00589             }
00590             querySchema->setWhereExpression(options->whereExpr);
00591         }
00592         //----- ORDER BY
00593         if (options->orderByColumns) {
00594             OrderByColumnList &orderByColumnList = querySchema->orderByColumnList();
00595             OrderByColumnInternal::ListConstIterator it = options->orderByColumns->constEnd();
00596             uint count = options->orderByColumns->count();
00597             --it;
00598             for (;count>0; --it, --count) 
00599                 /*opposite direction due to parser specifics*/
00600             {
00601                 //first, try to find a column name or alias (outside of asterisks)
00602                 QueryColumnInfo *columnInfo = querySchema->columnInfo( (*it).aliasOrName, false/*outside of asterisks*/ );
00603                 if (columnInfo) {
00604                     orderByColumnList.appendColumn( *columnInfo, (*it).ascending );
00605                 }
00606                 else {
00607                     //failed, try to find a field name within all the tables
00608                     if ((*it).columnNumber != -1) {
00609                         if (!orderByColumnList.appendColumn( *querySchema,
00610                             (*it).ascending, (*it).columnNumber-1 ))
00611                         {
00612                             setError(i18n("Could not define sorting - no column at position %1")
00613                                 .arg((*it).columnNumber));
00614                             CLEANUP;
00615                             return 0;
00616                         }
00617                     }
00618                     else {
00619                         Field * f = querySchema->findTableField((*it).aliasOrName);
00620                         if (!f) {
00621                             setError(i18n("Could not define sorting - "
00622                                 "column name or alias \"%1\" does not exist").arg((*it).aliasOrName));
00623                             CLEANUP;
00624                             return 0;
00625                         }
00626                         orderByColumnList.appendField( *f, (*it).ascending );
00627                     }
00628                 }
00629             }
00630         }
00631     }
00632 
00633 //  KexiDBDbg << "Select ColViews=" << (colViews ? colViews->debugString() : QString::null)
00634 //      << " Tables=" << (tablesList ? tablesList->debugString() : QString::null) << endl;
00635     
00636     CLEANUP;
00637     return querySchema;
00638 }
00639 
00640 #undef CLEANUP
00641 
KDE Home | KDE Accessibility Home | Description of Access Keys