/* This file is part of the KDE project Copyright (C) 2004, 2006 Jaroslaw Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "parser_p.h" #include "sqlparser.h" #include #include #include #include using namespace KexiDB; Parser *parser = 0; Field *field = 0; //bool requiresTable; TQPtrList fieldList; int current = 0; TQString ctoken = ""; //------------------------------------- ParserPrivate::ParserPrivate() : reservedKeywords(997, 997, false) , initialized(false) { clear(); table = 0; select = 0; db = 0; } ParserPrivate::~ParserPrivate() { delete select; delete table; } void ParserPrivate::clear() { operation = Parser::OP_None; error = ParserError(); } //------------------------------------- ParseInfo::ParseInfo(KexiDB::QuerySchema *query) : repeatedTablesAndAliases(997, false) , querySchema(query) { repeatedTablesAndAliases.setAutoDelete(true); } ParseInfo::~ParseInfo() { } //------------------------------------- extern int yyparse(); extern void tokenize(const char *data); void yyerror(const char *str) { KexiDBDbg << "error: " << str << endl; KexiDBDbg << "at character " << current << " near tooken " << ctoken << endl; parser->setOperation(Parser::OP_Error); const bool otherError = (tqstrnicmp(str, "other error", 11)==0); if (parser->error().type().isEmpty() && (str==0 || strlen(str)==0 || tqstrnicmp(str, "syntax error", 12)==0 || tqstrnicmp(str, "parse error", 11)==0) || otherError) { KexiDBDbg << parser->statement() << endl; TQString ptrline = ""; for(int i=0; i < current; i++) ptrline += " "; ptrline += "^"; KexiDBDbg << ptrline << endl; //lexer may add error messages TQString lexerErr = parser->error().error(); TQString errtypestr(str); if (lexerErr.isEmpty()) { #if 0 if (errtypestr.startsWith("parse error, unexpected ")) { //something like "parse error, unexpected IDENTIFIER, expecting ',' or ')'" TQString e = errtypestr.mid(24); KexiDBDbg << e <=2) { // KexiDBDbg << "**" << captured.at(1) << endl; // KexiDBDbg << "**" << captured.at(2) << endl; } } // IDENTIFIER, expecting '")) { e = errtypestr.mid(47); KexiDBDbg << e <isReservedKeyword(ctoken.latin1())) parser->setError( ParserError(i18n("Syntax Error"), i18n("\"%1\" is a reserved keyword").arg(ctoken)+lexerErr, ctoken, current) ); else parser->setError( ParserError(i18n("Syntax Error"), i18n("Syntax Error near \"%1\"").arg(ctoken)+lexerErr, ctoken, current) ); } } } void setError(const TQString& errName, const TQString& errDesc) { parser->setError( ParserError(errName, errDesc, ctoken, current) ); yyerror(errName.latin1()); } void setError(const TQString& errDesc) { setError("other error", errDesc); } /* this is better than assert() */ #define IMPL_ERROR(errmsg) setError("Implementation error", errmsg) bool parseData(Parser *p, const char *data) { /* todo: make this REENTRANT */ parser = p; parser->clear(); field = 0; fieldList.clear(); // requiresTable = false; if (!data) { ParserError err(i18n("Error"), i18n("No query specified"), ctoken, current); parser->setError(err); yyerror(""); parser = 0; return false; } tokenize(data); if (!parser->error().type().isEmpty()) { parser = 0; return false; } yyparse(); bool ok = true; if(parser->operation() == Parser::OP_Select) { KexiDBDbg << "parseData(): ok" << endl; // KexiDBDbg << "parseData(): " << tableDict.count() << " loaded tables" << endl; /* TableSchema *ts; for(TQDictIterator it(tableDict); TableSchema *s = tableList.first(); s; s = tableList.next()) { KexiDBDbg << " " << s->name() << endl; }*/ /*removed Field::ListIterator it = parser->select()->fieldsIterator(); for(Field *item; (item = it.current()); ++it) { if(tableList.findRef(item->table()) == -1) { ParserError err(i18n("Field List Error"), i18n("Unknown table '%1' in field list").arg(item->table()->name()), ctoken, current); parser->setError(err); yyerror("fieldlisterror"); ok = false; } }*/ //take the dummy table out of the query // parser->select()->removeTable(dummy); } else { ok = false; } // tableDict.clear(); parser = 0; return ok; } /* Adds \a column to \a querySchema. \a column can be in a form of table.field, tableAlias.field or field */ bool addColumn( ParseInfo& parseInfo, BaseExpr* columnExpr ) { if (!columnExpr->validate(parseInfo)) { setError(parseInfo.errMsg, parseInfo.errDescr); return false; } VariableExpr *v_e = columnExpr->toVariable(); if (columnExpr->exprClass() == KexiDBExpr_Variable && v_e) { //it's a variable: if (v_e->name=="*") {//all tables asterisk if (parseInfo.querySchema->tables()->isEmpty()) { setError(i18n("\"*\" could not be used if no tables are specified")); return false; } parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) ); } else if (v_e->tableForQueryAsterisk) {//one-table asterisk parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, v_e->tableForQueryAsterisk) ); } else if (v_e->field) {//"table.field" or "field" (bound to a table or not) parseInfo.querySchema->addField(v_e->field, v_e->tablePositionForField); } else { IMPL_ERROR("addColumn(): unknown case!"); return false; } return true; } //it's complex expression parseInfo.querySchema->addExpression(columnExpr); #if 0 KexiDBDbg << "found variable name: " << varName << endl; int dotPos = varName.find('.'); TQString tableName, fieldName; //TODO: shall we also support db name? if (dotPos>0) { tableName = varName.left(dotPos); fieldName = varName.mid(dotPos+1); } if (tableName.isEmpty()) {//fieldname only fieldName = varName; if (fieldName=="*") { parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) ); } else { //find first table that has this field Field *firstField = 0; for (TableSchema::ListIterator it(*parseInfo.querySchema->tables()); it.current(); ++it) { Field *f = it.current()->field(fieldName); if (f) { if (!firstField) { firstField = f; } else if (f->table()!=firstField->table()) { //ambiguous field name setError(i18n("Ambiguous field name"), i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. " "Use \".%4\" notation to specify table name.") .arg(firstField->table()->name()).arg(f->table()->name()) .arg(fieldName).arg(fieldName)); return false; } } } if (!firstField) { setError(i18n("Field not found"), i18n("Table containing \"%1\" field not found").arg(fieldName)); return false; } //ok parseInfo.querySchema->addField(firstField); } } else {//table.fieldname or tableAlias.fieldname tableName = tableName.lower(); TableSchema *ts = parseInfo.querySchema->table( tableName ); if (ts) {//table.fieldname //check if "table" is covered by an alias const TQValueList tPositions = parseInfo.querySchema->tablePositions(tableName); TQValueList::ConstIterator it = tPositions.constBegin(); TQCString tableAlias; bool covered = true; for (; it!=tPositions.constEnd() && covered; ++it) { tableAlias = parseInfo.querySchema->tableAlias(*it); if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1()) covered = false; //uncovered KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl; } if (covered) { setError(i18n("Could not access the table directly using its name"), i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", " "you can write \"%3\"").arg(tableName) .arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1())); return false; } } int tablePosition = -1; if (!ts) {//try to find tableAlias tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() ); if (tablePosition>=0) { ts = parseInfo.querySchema->tables()->at(tablePosition); if (ts) { // KexiDBDbg << " --it's a tableAlias.name" << endl; } } } if (ts) { TQValueList *positionsList = repeatedTablesAndAliases[ tableName ]; if (!positionsList) { IMPL_ERROR(tableName + "." + fieldName + ", !positionsList "); return false; } if (fieldName=="*") { if (positionsList->count()>1) { setError(i18n("Ambiguous \"%1.*\" expression").arg(tableName), i18n("More than one \"%1\" table or alias defined").arg(tableName)); return false; } parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, ts) ); } else { // KexiDBDbg << " --it's a table.name" << endl; Field *realField = ts->field(fieldName); if (realField) { // check if table or alias is used twice and both have the same column // (so the column is ambiguous) int numberOfTheSameFields = 0; for (TQValueList::iterator it = positionsList->begin(); it!=positionsList->end();++it) { TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it); if (otherTS->field(fieldName)) numberOfTheSameFields++; if (numberOfTheSameFields>1) { setError(i18n("Ambiguous \"%1.%2\" expression").arg(tableName).arg(fieldName), i18n("More than one \"%1\" table or alias defined containing \"%2\" field") .arg(tableName).arg(fieldName)); return false; } } parseInfo.querySchema->addField(realField, tablePosition); } else { setError(i18n("Field not found"), i18n("Table \"%1\" has no \"%2\" field") .arg(tableName).arg(fieldName)); return false; } } } else { tableNotFoundError(tableName); return false; } } #endif return true; } //! clean up no longer needed temporary objects #define CLEANUP \ delete colViews; \ delete tablesList; \ delete options QuerySchema* buildSelectQuery( QuerySchema* querySchema, NArgExpr* colViews, NArgExpr* tablesList, SelectOptionsInternal* options ) { ParseInfo parseInfo(querySchema); //-------tables list // assert( tablesList ); //&& tablesList->exprClass() == KexiDBExpr_TableList ); uint columnNum = 0; /*TODO: use this later if there are columns that use database fields, e.g. "SELECT 1 from table1 t, table2 t") is ok however. */ //used to collect information about first repeated table name or alias: // TQDict tableNamesAndTableAliases(997, false); // TQString repeatedTableNameOrTableAlias; if (tablesList) { for (int i=0; iargs(); i++, columnNum++) { BaseExpr *e = tablesList->arg(i); VariableExpr* t_e = 0; TQCString aliasString; if (e->exprClass() == KexiDBExpr_SpecialBinary) { BinaryExpr* t_with_alias = e->toBinary(); assert(t_with_alias); assert(t_with_alias->left()->exprClass() == KexiDBExpr_Variable); assert(t_with_alias->right()->exprClass() == KexiDBExpr_Variable && (t_with_alias->token()==AS || t_with_alias->token()==0)); t_e = t_with_alias->left()->toVariable(); aliasString = t_with_alias->right()->toVariable()->name.latin1(); } else { t_e = e->toVariable(); } assert(t_e); TQCString tname = t_e->name.latin1(); TableSchema *s = parser->db()->tableSchema(TQString(tname)); if(!s) { setError(//i18n("Field List Error"), i18n("Table \"%1\" does not exist").arg(TQString(tname))); // yyerror("fieldlisterror"); CLEANUP; return 0; } TQCString tableOrAliasName; if (!aliasString.isEmpty()) { tableOrAliasName = aliasString; // KexiDBDbg << "- add alias for table: " << aliasString << endl; } else { tableOrAliasName = tname; } // 1. collect information about first repeated table name or alias // (potential ambiguity) TQValueList *list = parseInfo.repeatedTablesAndAliases[tableOrAliasName]; if (list) { //another table/alias with the same name list->append( i ); // KexiDBDbg << "- another table/alias with name: " << tableOrAliasName << endl; } else { list = new TQValueList(); list->append( i ); parseInfo.repeatedTablesAndAliases.insert( tableOrAliasName, list ); // KexiDBDbg << "- first table/alias with name: " << tableOrAliasName << endl; } /* if (repeatedTableNameOrTableAlias.isEmpty()) { if (tableNamesAndTableAliases[tname]) repeatedTableNameOrTableAlias=tname; else tableNamesAndTableAliases.insert(tname, (const char*)1); } if (!aliasString.isEmpty()) { KexiDBDbg << "- add alias for table: " << aliasString << endl; // querySchema->setTableAlias(columnNum, aliasString); //2. collect information about first repeated table name or alias // (potential ambiguity) if (repeatedTableNameOrTableAlias.isEmpty()) { if (tableNamesAndTableAliases[aliasString]) repeatedTableNameOrTableAlias=aliasString; else tableNamesAndTableAliases.insert(aliasString, (const char*)1); } }*/ // KexiDBDbg << "addTable: " << tname << endl; querySchema->addTable( s, aliasString ); } } /* set parent table if there's only one */ // if (parser->select()->tables()->count()==1) if (querySchema->tables()->count()==1) querySchema->setMasterTable(querySchema->tables()->first()); //-------add fields if (colViews) { BaseExpr *e; columnNum = 0; const uint colCount = colViews->list.count(); // for (BaseExpr::ListIterator it(colViews->list);(e = it.current()); columnNum++) colViews->list.first(); for (; columnNumlist.current(); bool moveNext = true; //used to avoid ++it when an item is taken from the list BaseExpr *columnExpr = e; VariableExpr* aliasVariable = 0; if (e->exprClass() == KexiDBExpr_SpecialBinary && e->toBinary() && (e->token()==AS || e->token()==0)) { //KexiDBExpr_SpecialBinary: with alias columnExpr = e->toBinary()->left(); // isFieldWithAlias = true; aliasVariable = e->toBinary()->right()->toVariable(); if (!aliasVariable) { setError(i18n("Invalid alias definition for column \"%1\"") .arg(columnExpr->toString())); //ok? CLEANUP; return 0; } } const int c = columnExpr->exprClass(); const bool isExpressionField = c == KexiDBExpr_Const || c == KexiDBExpr_Unary || c == KexiDBExpr_Arithm || c == KexiDBExpr_Logical || c == KexiDBExpr_Relational || c == KexiDBExpr_Const || c == KexiDBExpr_Function || c == KexiDBExpr_Aggregation; if (c == KexiDBExpr_Variable) { //just a variable, do nothing, addColumn() will handle this } else if (isExpressionField) { //expression object will be reused, take, will be owned, do not destroy // KexiDBDbg << colViews->list.count() << " " << it.current()->debugString() << endl; colViews->list.take(); //take() doesn't work moveNext = false; } else if (aliasVariable) { //take first (left) argument of the special binary expr, will be owned, do not destroy e->toBinary()->m_larg = 0; } else { setError(i18n("Invalid \"%1\" column definition").arg(e->toString())); //ok? CLEANUP; return 0; } if (!addColumn( parseInfo, columnExpr )) { CLEANUP; return 0; } if (aliasVariable) { // KexiDBDbg << "ALIAS \"" << aliasVariable->name << "\" set for column " // << columnNum << endl; querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1()); } /* if (e->exprClass() == KexiDBExpr_SpecialBinary && dynamic_cast(e) && (e->type()==AS || e->type()==0)) { //also add alias VariableExpr* aliasVariable = dynamic_cast(dynamic_cast(e)->right()); if (!aliasVariable) { setError(i18n("Invalid column alias definition")); //ok? return 0; } kdDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " << columnNum << endl; querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1()); }*/ if (moveNext) { colViews->list.next(); // ++it; } } } //----- SELECT options if (options) { //----- WHERE expr. if (options->whereExpr) { if (!options->whereExpr->validate(parseInfo)) { setError(parseInfo.errMsg, parseInfo.errDescr); CLEANUP; return false; } querySchema->setWhereExpression(options->whereExpr); } //----- ORDER BY if (options->orderByColumns) { OrderByColumnList &orderByColumnList = querySchema->orderByColumnList(); OrderByColumnInternal::ListConstIterator it = options->orderByColumns->constEnd(); uint count = options->orderByColumns->count(); --it; for (;count>0; --it, --count) /*opposite direction due to parser specifics*/ { //first, try to find a column name or alias (outside of asterisks) QueryColumnInfo *columnInfo = querySchema->columnInfo( (*it).aliasOrName, false/*outside of asterisks*/ ); if (columnInfo) { orderByColumnList.appendColumn( *columnInfo, (*it).ascending ); } else { //failed, try to find a field name within all the tables if ((*it).columnNumber != -1) { if (!orderByColumnList.appendColumn( *querySchema, (*it).ascending, (*it).columnNumber-1 )) { setError(i18n("Could not define sorting - no column at position %1") .arg((*it).columnNumber)); CLEANUP; return 0; } } else { Field * f = querySchema->findTableField((*it).aliasOrName); if (!f) { setError(i18n("Could not define sorting - " "column name or alias \"%1\" does not exist").arg((*it).aliasOrName)); CLEANUP; return 0; } orderByColumnList.appendField( *f, (*it).ascending ); } } } } } // KexiDBDbg << "Select ColViews=" << (colViews ? colViews->debugString() : TQString()) // << " Tables=" << (tablesList ? tablesList->debugString() : TQString()) << endl; CLEANUP; return querySchema; } #undef CLEANUP