diff --git a/sql/src/createdatabase.cpp b/sql/src/createdatabase.cpp index 857ee18..f79de49 100644 --- a/sql/src/createdatabase.cpp +++ b/sql/src/createdatabase.cpp @@ -557,7 +557,7 @@ RCODE SQLStatement::processCreateDatabase( void) } if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -677,7 +677,7 @@ RCODE SQLStatement::processCreateDatabase( void) // Option must be followed by a comma or right paren if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -700,7 +700,7 @@ RCODE SQLStatement::processCreateDatabase( void) // Comma must be followed by the next option if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } diff --git a/sql/src/createindex.cpp b/sql/src/createindex.cpp index 38ff424..d1fce3a 100644 --- a/sql/src/createindex.cpp +++ b/sql/src/createindex.cpp @@ -610,7 +610,7 @@ RCODE SQLStatement::processCreateIndex( for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -828,7 +828,7 @@ Invalid_Ix_Option: for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { if (rc == NE_SFLM_EOF_HIT) { diff --git a/sql/src/createtable.cpp b/sql/src/createtable.cpp index 7347360..f8a18cb 100644 --- a/sql/src/createtable.cpp +++ b/sql/src/createtable.cpp @@ -309,7 +309,7 @@ RCODE SQLStatement::getDataType( // long varbinary if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -346,7 +346,7 @@ RCODE SQLStatement::getDataType( else if (f_stricmp( szToken, "long") == 0) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -559,7 +559,7 @@ RCODE SQLStatement::processCreateTable( void) } if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } diff --git a/sql/src/deleterow.cpp b/sql/src/deleterow.cpp index d759d19..c9263a5 100644 --- a/sql/src/deleterow.cpp +++ b/sql/src/deleterow.cpp @@ -294,7 +294,7 @@ RCODE SQLStatement::processDeleteRows( void) // Null terminate the list. tableList [1].uiTableNum = 0; - if (RC_BAD( rc = parseCriteria( &tableList [0], FALSE, FALSE, &sqlQuery))) + if (RC_BAD( rc = parseCriteria( &tableList [0], NULL, TRUE, NULL, &sqlQuery))) { goto Exit; } diff --git a/sql/src/dropdatabase.cpp b/sql/src/dropdatabase.cpp index c7c3660..3a021b8 100644 --- a/sql/src/dropdatabase.cpp +++ b/sql/src/dropdatabase.cpp @@ -360,7 +360,7 @@ RCODE SQLStatement::processDropDatabase( void) for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { if (rc == NE_SFLM_EOF_HIT) { diff --git a/sql/src/flaimsql.h b/sql/src/flaimsql.h index fe7d876..f9b3a81 100644 --- a/sql/src/flaimsql.h +++ b/sql/src/flaimsql.h @@ -127,9 +127,9 @@ typedef enum SQL_ERR_CANNOT_DROP_SYSTEM_TABLE, ///< 47 = Cannot drop internal system tables. SQL_ERR_UNDEFINED_ENCDEF, ///< 48 = Encryption definition specified does not exist. SQL_ERR_ENCDEF_ALREADY_DEFINED, ///< 49 = Encryption definition name is already defined in the database. - SQL_ERR_INVALID_WHERE_TABLE, ///< 50 = Invalid table name or alias specified for column in where clause. + SQL_ERR_UNDEFINED_TABLE_NAME, ///< 50 = Invalid table name or alias specified for column name specifier. SQL_ERR_AMBIGUOUS_COLUMN_NAME, ///< 51 = Ambiguous column name specified in where clause - belongs to more than one table. - SQL_ERR_INVALID_WHERE_COLUMN, ///< 52 = Invalid column specified in where clause. + SQL_ERR_INVALID_COLUMN_NAME, ///< 52 = Invalid column name specified. SQL_ERR_UNEXPECTED_NOT_OPERATOR, ///< 53 = Unexpected NOT operator in where clause. SQL_ERR_EXPECTING_OPERATOR, ///< 54 = Expecting an operator in where clause. SQL_ERR_INVALID_OPERAND, ///< 55 = Invalid operand specified in where clause. @@ -141,6 +141,10 @@ typedef enum SQL_ERR_EXPECTING_WHERE, ///< 61 = Expecting "WHERE" keyword. SQL_ERR_EXPECTING_SET, ///< 62 = Expecting "SET" keyword. SQL_ERR_EXPECTING_PERIOD, ///< 63 = Expecting a period after the table name. + SQL_ERR_DUPLICATE_TABLE_NAME, ///< 64 = Table name specified multiple times in SELECT statement. + SQL_ERR_DUPLICATE_ALIAS_NAME, ///< 65 = Table alias name specified multiple times in SELECT statement. + SQL_ERR_TOO_MANY_TABLES, ///< 66 = Too many tables specified in SELECT statement. + SQL_ERR_EXPECTING_BY, ///< 67 = Expecting "BY" keyword. // IMPORTANT NOTE: If new codes are added, please update gv_SQLParseErrors in fshell.cpp SQL_NUM_ERRORS @@ -1367,6 +1371,11 @@ typedef struct #define NE_SFLM_Q_UNEXPECTED_COLUMN 0xE213 ///< 0xE213 = Not expecting a column name in query expression. #define NE_SFLM_Q_UNEXPECTED_CONSTANT 0xE214 ///< 0xE214 = Not expecting a constant in query expression. #define NE_SFLM_Q_UNEXPECTED_BOOLEAN 0xE215 ///< 0xE215 = Not expecting a boolean constant in query expression. +#define NE_SFLM_Q_AMBIGUOUS_COLUMN_NAME 0xE216 ///< 0xE216 = Column name specified in SELECT statement found in multiple tables - must specify table. +#define NE_SFLM_Q_INVALID_COLUMN_NAME 0xE217 ///< 0xE217 = Column name specified in SELECT statement not defined in the tables that were specified. +#define NE_SFLM_Q_UNDEFINED_TABLE_FOR_COLUMN 0xE218 ///< 0xE218 = Table name specified for a column in SELECT statement not defined in the tables that were specified. +#define NE_SFLM_Q_BAD_ORDER_BY_TABLE 0xE219 ///< 0xE219 = Table name specified in ORDER BY is not part of the query. +#define NE_SFLM_Q_DUP_COLUMN_IN_ORDER_BY 0xE21A ///< 0xE21A = Duplicate table.column name specified in ORDER BY part of query. // Desc: NICI / Encryption Errors diff --git a/sql/src/insertrow.cpp b/sql/src/insertrow.cpp index 8a13074..d3a6653 100644 --- a/sql/src/insertrow.cpp +++ b/sql/src/insertrow.cpp @@ -314,7 +314,7 @@ RCODE SQLStatement::processInsertRow( void) } if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } diff --git a/sql/src/opendatabase.cpp b/sql/src/opendatabase.cpp index 9665074..b296107 100644 --- a/sql/src/opendatabase.cpp +++ b/sql/src/opendatabase.cpp @@ -1989,7 +1989,7 @@ RCODE SQLStatement::processOpenDatabase( void) for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { if (rc == NE_SFLM_EOF_HIT) { diff --git a/sql/src/select.cpp b/sql/src/select.cpp new file mode 100644 index 0000000..f11ab00 --- /dev/null +++ b/sql/src/select.cpp @@ -0,0 +1,518 @@ +//------------------------------------------------------------------------------ +// Desc: This module contains the routines for inserting a row into a table. +// +// Tabs: 3 +// +// Copyright (c) 2006 Novell, Inc. All Rights Reserved. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of version 2 of the GNU General Public +// License as published by the Free Software Foundation. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, contact Novell, Inc. +// +// To contact Novell about this file by physical or electronic mail, +// you may find current contact information at www.novell.com +// +// $Id$ +//------------------------------------------------------------------------------ + +#include "flaimsys.h" + +static const char * gv_selectExprTerminators [3] = +{ + "from", + ",", + NULL +}; + +static const char * gv_selectWhereTerminators [3] = +{ + "order", + NULL +}; + +//------------------------------------------------------------------------------ +// Desc: Process the expressions that are going to be retrieved in a SELECT +// statement. +//------------------------------------------------------------------------------ +RCODE SQLStatement::parseSelectExpressions( + SELECT_EXPR ** ppFirstSelectExpr, + SELECT_EXPR ** ppLastSelectExpr) +{ + RCODE rc = NE_SFLM_OK; + SELECT_EXPR * pSelectExpr; + SQLQuery * pSqlQuery = NULL; + const char * pszTerminator; + + // See if they specified "*" + + if (RC_OK( rc = haveToken( "*", FALSE))) + { + + // Better be followed by "from" - need to consume the "from" token + // because the caller expects that it will have been consumed. + + rc = haveToken( "from", FALSE, SQL_ERR_EXPECTING_FROM); + goto Exit; + } + else if (rc == NE_SFLM_NOT_FOUND) + { + rc = NE_SFLM_OK; + } + else + { + goto Exit; + } + + for (;;) + { + + // Allocate a SELECT_EXPR structure + + if (RC_BAD( rc = m_tmpPool.poolAlloc( sizeof( SELECT_EXPR), + (void **)&pSelectExpr))) + { + goto Exit; + } + + + // Allocate an SQLQuery object, have the pColumnSet structure + // point to it, and link the pColumnSet structure into the linked + // list. + + if ((pSqlQuery = f_new SQLQuery) == NULL) + { + rc = RC_SET( NE_SFLM_MEM); + goto Exit; + } + pSelectExpr->pSqlQuery = pSqlQuery; + pSelectExpr->pNext = NULL; + if (*ppLastSelectExpr) + { + (*ppLastSelectExpr)->pNext = pSelectExpr; + } + else + { + *ppFirstSelectExpr = pSelectExpr; + } + *ppLastSelectExpr = pSelectExpr; + + // Now parse the criteria + + if (RC_BAD( rc = parseCriteria( NULL, + &gv_selectExprTerminators [0], FALSE, + &pszTerminator, pSqlQuery))) + { + goto Exit; + } + + // Strip out NOT operators, resolve constant arithmetic expressions, + // and weed out boolean constants, but do not flatten the AND + // and OR operators in the query tree. + + if (RC_BAD( rc = pSqlQuery->reduceTree( FALSE))) + { + goto Exit; + } + + // Terminator should never be NULL, because we passed a FALSE into + // parseCriteria for the bEofOK flag. + + flmAssert( pszTerminator); + + // Terminator will have been either be a comma or the FROM keyword. + + if (f_stricmp( pszTerminator, "from") == 0) + { + break; + } + } + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------------ +// Desc: Process the SELECT statement. The "SELECT" keyword has already been +// parsed. +//------------------------------------------------------------------------------ +RCODE SQLStatement::processSelect( void) +{ +#define MAX_SELECT_TABLES 15 + RCODE rc = NE_SFLM_OK; + FLMBOOL bStartedTrans = FALSE; + char szToken [MAX_SQL_TOKEN_SIZE + 1]; + FLMUINT uiTokenLineOffset; + FLMUINT uiTokenLen; + FLMBOOL bDistinct; + F_TABLE * pTable; + SELECT_EXPR * pFirstSelectExpr = NULL; + SELECT_EXPR * pLastSelectExpr = NULL; + SELECT_EXPR * pSelectExpr; + SQLQuery sqlQuery; + TABLE_ITEM tableList [MAX_SELECT_TABLES + 1]; + FLMUINT uiNumTables = 0; + FLMUINT uiLoop; + const char * pszTerminator; + char szName [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiNameLen; + char szColumnName [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiColumnNameLen; + FLMUINT uiTableNum; + FLMUINT uiColumnNum; + FLMBOOL bDescending; + SQLParseError eParseError; + FLMBOOL bHaveWhere = FALSE; + FLMBOOL bHaveOrderBy = TRUE; + FLMBOOL bDoneParsingOrderBy; + + // Make sure we have at least a read transaction going. + + if (RC_BAD( rc = m_pDb->checkTransaction( SFLM_READ_TRANS, &bStartedTrans))) + { + goto Exit; + } + + // SYNTAX: SELECT [ALL | DISTINCT] {* | expression [,expression]...} + // FROM table_name [table_alias] [, table_name [table_alias]]... + // [WHERE ] + // [ORDER BY column [ASC | DESC] [, column [ASC | DESC]]...] + + // See if "ALL" or "DISTINCT" were specified + + bDistinct = FALSE; + + if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, + &uiTokenLineOffset, NULL))) + { + goto Exit; + } + if (f_stricmp( szToken, "all") == 0) + { + } + else if (f_stricmp( szToken, "distinct") == 0) + { + bDistinct = TRUE; + } + else + { + + // Push the token back into the stream so it will be read again. + + m_uiCurrLineOffset = uiTokenLineOffset; + } + + // Parse the expressions that are to be selected. + + if (RC_BAD( rc = parseSelectExpressions( &pFirstSelectExpr, &pLastSelectExpr))) + { + goto Exit; + } + + // Get the table names and their aliases. + + for (;;) + { + if (RC_BAD( rc = getTableName( TRUE, szName, sizeof( szName), + &uiNameLen, &pTable))) + { + goto Exit; + } + + // See if we have already defined this table - cannot define it twice. + + for (uiLoop = 0; uiLoop < uiNumTables; uiLoop++) + { + if (tableList [uiLoop].uiTableNum == pTable->uiTableNum) + { + setErrInfo( m_uiCurrLineNum, + uiTokenLineOffset, + SQL_ERR_DUPLICATE_TABLE_NAME, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + + // Must not overflow the name table. + + if (uiNumTables == MAX_SELECT_TABLES) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset - 1, + SQL_ERR_TOO_MANY_TABLES, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + // Add the table name to the list + + tableList [uiNumTables].uiTableNum = pTable->uiTableNum; + tableList [uiNumTables].pszTableAlias = NULL; + uiNumTables++; + + // See if a table alias was specified, or if we are at a comma or + // an "ORDER BY" or EOF. + + if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, + &uiTokenLineOffset, &uiTokenLen))) + { + if (rc == NE_SFLM_EOF_HIT) + { + rc = NE_SFLM_OK; + break; + } + goto Exit; + } + if (f_stricmp( szToken, "where") == 0) + { + bHaveWhere = TRUE; + break; + } + else if (f_stricmp( szToken, "order") == 0) + { + bHaveOrderBy = TRUE; + break; + } + else if ((szToken [0] >= 'a' && szToken [0] <= 'z') || + (szToken [0] >= 'A' && szToken [0] <= 'Z')) + { + // See if this alias name has been used in the table list already. + + for (uiLoop = 0; uiLoop < uiNumTables; uiLoop++) + { + if (f_stricmp( tableList [uiLoop].pszTableAlias, szToken) == 0) + { + setErrInfo( m_uiCurrLineNum, + uiTokenLineOffset, + SQL_ERR_DUPLICATE_ALIAS_NAME, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + + // Set the alias name for our current table. NOTE: Our current + // table is at element uiNumTables-1, because we have already + // incremented uiNumTables. + + if (RC_BAD( rc = m_tmpPool.poolAlloc( uiTokenLen + 1, + (void **)&tableList [uiNumTables - 1].pszTableAlias))) + { + goto Exit; + } + f_memcpy( &tableList [uiNumTables - 1].pszTableAlias, szToken, + uiTokenLen + 1); + + } + else if (f_stricmp( szToken, ",") != 0) + { + setErrInfo( m_uiCurrLineNum, + uiTokenLineOffset, + SQL_ERR_EXPECTING_COMMA, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + + // parseSelectExpressions will have already parsed the "FROM" keyword. + // We must now parse table names until we hit a "WHERE" or "ORDER BY" or + // EOF. + + // Resolve the column names that were parsed in the select expressions. + + tableList [uiNumTables].uiTableNum = 0; + tableList [uiNumTables].pszTableAlias = NULL; + for (pSelectExpr = pFirstSelectExpr; pSelectExpr; pSelectExpr = pSelectExpr->pNext) + { + if (RC_BAD( rc = pSelectExpr->pSqlQuery->resolveColumnNames( &tableList [0]))) + { + goto Exit; + } + } + + // Add each of the tables in the table list. + + for (uiLoop = 0; uiLoop < uiNumTables; uiLoop++) + { + if (RC_BAD( rc = sqlQuery.addTable( tableList [uiLoop].uiTableNum, NULL))) + { + goto Exit; + } + } + + // If there is a WHERE clause, parse it. + + if (bHaveWhere) + { + if (RC_BAD( rc = parseCriteria( &tableList [0], + &gv_selectWhereTerminators [0], TRUE, + &pszTerminator, &sqlQuery))) + { + goto Exit; + } + if (pszTerminator && f_stricmp( pszTerminator, "order") == 0) + { + bHaveOrderBy = TRUE; + } + } + + // If there is an ORDER BY clause, parse it. + + if (bHaveOrderBy) + { + + // Make sure we have the "BY" keyword + + + if (RC_BAD( rc = haveToken( "by", FALSE, SQL_ERR_EXPECTING_BY))) + { + goto Exit; + } + + for (;;) + { + char * pszTableAlias; + char * pszColumnName; + + // Get either a table name or column name. + + if (RC_BAD( rc = getName( szName, sizeof( szName), + &uiNameLen, &uiTokenLineOffset))) + { + goto Exit; + } + + // See if we have a period after the name. + + if (RC_BAD( rc = haveToken( ".", TRUE))) + { + if (rc != NE_SFLM_NOT_FOUND && rc != NE_SFLM_EOF_HIT) + { + goto Exit; + } + pszTableAlias = NULL; + pszColumnName = &szName [0]; + } + else + { + if (RC_BAD( rc = getName( szColumnName, sizeof( szColumnName), + &uiColumnNameLen, &uiTokenLineOffset))) + { + goto Exit; + } + pszTableAlias = &szName [0]; + pszColumnName = &szColumnName [0]; + } + if (RC_BAD( rc = resolveColumnName( m_pDb, &tableList [0], pszTableAlias, + pszColumnName, &uiTableNum, &uiColumnNum, + &eParseError))) + { + if (eParseError != SQL_NO_ERROR) + { + setErrInfo( m_uiCurrLineNum, + uiTokenLineOffset, + eParseError, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + } + goto Exit; + } + + // See if the next token is the keyword "ASC" or "DESC" or comma. + + bDoneParsingOrderBy = FALSE; + bDescending = FALSE; + if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, + &uiTokenLineOffset, &uiTokenLen))) + { + if (rc == NE_SFLM_EOF_HIT) + { + rc = NE_SFLM_OK; + bDoneParsingOrderBy = TRUE; + } + else + { + goto Exit; + } + } + else if (f_stricmp( szToken, "asc") == 0 || + f_stricmp( szToken, "desc") == 0) + { + if (szToken [0] == 'D' || szToken [0] == 'd') + { + bDescending = TRUE; + } + if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, + &uiTokenLineOffset, &uiTokenLen))) + { + if (rc == NE_SFLM_EOF_HIT) + { + rc = NE_SFLM_OK; + bDoneParsingOrderBy = TRUE; + } + else + { + goto Exit; + } + } + } + + // Add an ORDER BY to the query + + if (RC_BAD( rc = sqlQuery.orderBy( uiTableNum, uiColumnNum, bDescending))) + { + goto Exit; + } + if (bDoneParsingOrderBy) + { + break; + } + } + } + + // Commit the transaction if we started it + + if (bStartedTrans) + { + bStartedTrans = FALSE; + if (RC_BAD( rc = m_pDb->transCommit())) + { + goto Exit; + } + } + +Exit: + + while (pFirstSelectExpr) + { + if (pFirstSelectExpr->pSqlQuery) + { + pFirstSelectExpr->pSqlQuery->Release(); + pFirstSelectExpr->pSqlQuery = NULL; + } + pFirstSelectExpr = pFirstSelectExpr->pNext; + } + + if (bStartedTrans) + { + m_pDb->transAbort(); + } + + return( rc); +} + diff --git a/sql/src/sqlquery.h b/sql/src/sqlquery.h index 69012e5..d9af7c6 100644 --- a/sql/src/sqlquery.h +++ b/sql/src/sqlquery.h @@ -246,8 +246,10 @@ typedef struct SQL_TABLE typedef struct SQL_COLUMN { - SQL_TABLE * pTable; - FLMUINT uiColumnNum; + const char * pszTableAlias; + const char * pszColumnName; + SQL_TABLE * pTable; + FLMUINT uiColumnNum; } SQL_COLUMN; typedef struct SQL_NODE @@ -273,6 +275,13 @@ typedef struct SQL_NODE } nd; } SQL_NODE; +typedef struct SQL_ORDER_BY +{ + SQL_TABLE * pTable; + FLMUINT uiColumnNum; + SQL_ORDER_BY * pNext; +} SQL_ORDER_BY; + FINLINE FLMBOOL isSQLNodeBool( SQL_NODE * pNode) { @@ -332,6 +341,15 @@ public: FLMUINT uiTableNum, SQL_TABLE ** ppTable); + RCODE resolveColumnNames( + TABLE_ITEM * pTableList); + + RCODE addColumn( + const char * pszTableAlias, + FLMUINT uiTableAliasLen, + const char * pszColumnName, + FLMUINT uiColumnNameLen); + RCODE addColumn( FLMUINT uiTableNum, FLMUINT uiColumnNum); @@ -399,6 +417,11 @@ public: : TRUE); } + RCODE orderBy( + FLMUINT uiTableNum, + FLMUINT uiColumnNum, + FLMBOOL bDescending); + RCODE getNext( F_Row ** ppRow); @@ -488,6 +511,9 @@ private: SQL_SUBQUERY * m_pLastSubQuery; SQL_TABLE * m_pFirstTable; SQL_TABLE * m_pLastTable; + SQL_ORDER_BY * m_pFirstOrderBy; + SQL_ORDER_BY * m_pLastOrderBy; + FLMBOOL m_bResolveNames; FLMBOOL m_bOptimized; F_Database * m_pDatabase; F_Db * m_pDb; diff --git a/sql/src/sqlstatement.cpp b/sql/src/sqlstatement.cpp index 9275f3e..31beb16 100644 --- a/sql/src/sqlstatement.cpp +++ b/sql/src/sqlstatement.cpp @@ -103,11 +103,13 @@ RCODE SQLStatement::getToken( char * pszToken, FLMUINT uiTokenBufSize, FLMBOOL bEofOK, - FLMUINT * puiTokenLineOffset) + FLMUINT * puiTokenLineOffset, + FLMUINT * puiTokenLen) { RCODE rc = NE_SFLM_OK; FLMUINT uiOffset; char cChar; + char * pszTokenStart = pszToken; // Always leave room for a null terminating character @@ -283,6 +285,11 @@ RCODE SQLStatement::getToken( } } + if (puiTokenLen) + { + *puiTokenLen = (FLMUINT)(pszToken - pszTokenStart); + } + Exit: return( rc); @@ -301,7 +308,7 @@ RCODE SQLStatement::haveToken( FLMUINT uiTokenLineOffset; if (RC_BAD( rc = getToken( szToken, sizeof( szToken), bEofOK, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { if (rc == NE_SFLM_EOF_HIT) { @@ -2040,11 +2047,18 @@ RCODE SQLStatement::executeSQL( for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } - if (f_stricmp( szToken, "insert") == 0) + if (f_stricmp( szToken, "select") == 0) + { + if (RC_BAD( rc = processSelect())) + { + goto Exit; + } + } + else if (f_stricmp( szToken, "insert") == 0) { if (RC_BAD( rc = processInsertRow())) { @@ -2079,7 +2093,7 @@ RCODE SQLStatement::executeSQL( else if (f_stricmp( szToken, "create") == 0) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } @@ -2130,7 +2144,7 @@ RCODE SQLStatement::executeSQL( else if (f_stricmp( szToken, "drop") == 0) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, - &uiTokenLineOffset))) + &uiTokenLineOffset, NULL))) { goto Exit; } diff --git a/sql/src/sqlstatement.h b/sql/src/sqlstatement.h index 58fe4a6..6a10da8 100644 --- a/sql/src/sqlstatement.h +++ b/sql/src/sqlstatement.h @@ -32,6 +32,12 @@ class SQLQuery; +typedef struct SELECT_EXPR +{ + SQLQuery * pSqlQuery; + SELECT_EXPR * pNext; +} SELECT_EXPR; + typedef struct COLUMN_SET { FLMUINT uiColumnNum; @@ -145,7 +151,8 @@ private: char * pszToken, FLMUINT uiTokenBufSize, FLMBOOL bEofOK, - FLMUINT * puiTokenLineOffset); + FLMUINT * puiTokenLineOffset, + FLMUINT * puiTokenLen); FINLINE void setErrInfo( FLMUINT uiErrLineNum, @@ -257,7 +264,8 @@ private: TABLE_ITEM * pTableList, COLUMN_SET ** ppFirstColumnSet, COLUMN_SET ** ppLastColumnSet, - FLMUINT * puiNumColumnsToSet); + FLMUINT * puiNumColumnsToSet, + FLMBOOL * pbHadWhere); RCODE processUpdateRows( void); @@ -265,17 +273,24 @@ private: RCODE processAlphaToken( TABLE_ITEM * pTableList, - FLMBOOL bSelectStatement, - FLMBOOL bUpdateExpression, + const char ** ppszTerminatingTokens, + const char ** ppszTerminator, SQLQuery * pSqlQuery, FLMBOOL * pbDone); RCODE parseCriteria( TABLE_ITEM * pTableList, - FLMBOOL bSelectStatement, - FLMBOOL bUpdateExpression, + const char ** ppszTerminatingTokens, + FLMBOOL bEofOK, + const char ** ppszTerminator, SQLQuery * pSqlQuery); + RCODE parseSelectExpressions( + SELECT_EXPR ** ppFirstSelectExpr, + SELECT_EXPR ** ppLastSelectExpr); + + RCODE processSelect( void); + // Data F_Db * m_pDb; @@ -298,4 +313,13 @@ friend class F_Db; friend class F_Database; }; +RCODE resolveColumnName( + F_Db * pDb, + TABLE_ITEM * pTableList, + const char * pszTableAlias, + const char * pszColumnName, + FLMUINT * puiTableNum, + FLMUINT * puiColumnNum, + SQLParseError * peParseError); + #endif // SQLSTATEMENT_H diff --git a/sql/src/updaterow.cpp b/sql/src/updaterow.cpp index dc7138d..4b28014 100644 --- a/sql/src/updaterow.cpp +++ b/sql/src/updaterow.cpp @@ -25,6 +25,13 @@ #include "flaimsys.h" +static const char * gv_setExprTerminators [3] = +{ + "where", + ",", + NULL +}; + FSTATIC RCODE convertValueToStringFormat( SQL_VALUE * pSqlValue, F_COLUMN * pColumn, @@ -1005,14 +1012,14 @@ Exit: } //------------------------------------------------------------------------------ -// Desc: Process the UPDATE statement. The "UPDATE" keyword has already been -// parsed. +// Desc: Parse the columns that are to be set in the UPDATE statement. //------------------------------------------------------------------------------ RCODE SQLStatement::parseSetColumns( TABLE_ITEM * pTableList, COLUMN_SET ** ppFirstColumnSet, COLUMN_SET ** ppLastColumnSet, - FLMUINT * puiNumColumnsToSet) + FLMUINT * puiNumColumnsToSet, + FLMBOOL * pbHadWhere) { RCODE rc = NE_SFLM_OK; char szToken [MAX_SQL_TOKEN_SIZE + 1]; @@ -1024,6 +1031,8 @@ RCODE SQLStatement::parseSetColumns( COLUMN_SET * pColumnSet; SQLQuery * pSqlQuery = NULL; + *pbHadWhere = FALSE; + // Must have the keyword "SET" if (RC_BAD( rc = haveToken( "set", FALSE, SQL_ERR_EXPECTING_SET))) @@ -1131,9 +1140,41 @@ RCODE SQLStatement::parseSetColumns( // Now parse the criteria for the SET command, unless NULL has already // been detected. - if (pSqlQuery) + if (!pSqlQuery) { - if (RC_BAD( rc = parseCriteria( pTableList, FALSE, TRUE, pSqlQuery))) + if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, + &uiTokenLineOffset, NULL))) + { + if (rc == NE_SFLM_EOF_HIT) + { + rc = NE_SFLM_OK; + break; + } + goto Exit; + } + else if (f_stricmp( szToken, "where") == 0) + { + *pbHadWhere = TRUE; + break; + } + else if (f_stricmp( szToken, ",") != 0) + { + setErrInfo( m_uiCurrLineNum, + uiTokenLineOffset, + SQL_ERR_EXPECTING_COMMA, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + else + { + const char * pszTerminator; + + if (RC_BAD( rc = parseCriteria( pTableList, + &gv_setExprTerminators [0], TRUE, + &pszTerminator, pSqlQuery))) { goto Exit; } @@ -1146,44 +1187,22 @@ RCODE SQLStatement::parseSetColumns( { goto Exit; } - } - // Next token should either be a comma or the WHERE keyword, or we - // should have hit EOF. - - if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, - &uiTokenLineOffset))) - { - if (rc == NE_SFLM_EOF_HIT) + // Next token should either be a comma or the WHERE keyword, or we + // should have hit EOF. pszTerminator will return NULL if EOF was + // hit. + + if (!pszTerminator) { - rc = NE_SFLM_OK; break; } - else + + if (f_stricmp( pszTerminator, "where") == 0) { - goto Exit; + *pbHadWhere = TRUE; + break; } } - - if (f_stricmp( szToken, "where") == 0) - { - - // Push the WHERE token back into the input stream - so that - // the caller can handle it. - - m_uiCurrLineOffset = uiTokenLineOffset; - break; - } - else if (f_stricmp( szToken, ",") != 0) - { - setErrInfo( m_uiCurrLineNum, - uiTokenLineOffset, - SQL_ERR_EXPECTING_COMMA, - m_uiCurrLineFilePos, - m_uiCurrLineBytes); - rc = RC_SET( NE_SFLM_INVALID_SQL); - goto Exit; - } } Exit: @@ -1207,6 +1226,7 @@ RCODE SQLStatement::processUpdateRows( void) COLUMN_SET * pLastColumnSet = NULL; FLMUINT uiNumColumnsToSet = 0; SQLQuery sqlQuery; + FLMBOOL bHadWhere; // If we are in a read transaction, we cannot do this operation @@ -1250,31 +1270,24 @@ RCODE SQLStatement::processUpdateRows( void) tableList [1].uiTableNum = 0; if (RC_BAD( rc = parseSetColumns( &tableList [0], &pFirstColumnSet, - &pLastColumnSet, &uiNumColumnsToSet))) + &pLastColumnSet, &uiNumColumnsToSet, + &bHadWhere))) { goto Exit; } // See if we have a WHERE clause - if (RC_BAD( rc = haveToken( "where", TRUE))) + if (!bHadWhere) { - if (rc == NE_SFLM_NOT_FOUND || rc == NE_SFLM_EOF_HIT) - { - if (RC_BAD( rc = sqlQuery.addTable( pTable->uiTableNum, NULL))) - { - goto Exit; - } - } - else + if (RC_BAD( rc = sqlQuery.addTable( pTable->uiTableNum, NULL))) { goto Exit; } } else { - - if (RC_BAD( rc = parseCriteria( &tableList [0], FALSE, FALSE, &sqlQuery))) + if (RC_BAD( rc = parseCriteria( &tableList [0], NULL, TRUE, NULL, &sqlQuery))) { goto Exit; } diff --git a/sql/src/whereclause.cpp b/sql/src/whereclause.cpp index 19e16b9..1d7c680 100644 --- a/sql/src/whereclause.cpp +++ b/sql/src/whereclause.cpp @@ -33,6 +33,11 @@ FSTATIC void sqlLinkLastChild( SQL_NODE * pParent, SQL_NODE * pChild); +FSTATIC FLMBOOL isTerminatingToken( + const char * pszToken, + const char ** ppszTerminatingTokens, + const char ** ppszTerminator); + static FLMUINT uiSQLOpPrecedenceTable[ SQL_NEG_OP - SQL_AND_OP + 1] = { 2, // SQL_AND_OP @@ -73,6 +78,9 @@ SQLQuery::SQLQuery() m_pLastSubQuery = NULL; m_pFirstTable = NULL; m_pLastTable = NULL; + m_pFirstOrderBy = NULL; + m_pLastOrderBy = NULL; + m_bResolveNames = FALSE; m_bOptimized = FALSE; m_bScan = FALSE; m_bScanIndex = FALSE; @@ -552,6 +560,240 @@ Exit: return( rc); } +//------------------------------------------------------------------------- +// Desc: Determine which table/column a table alias name and column name +// refer to. +//------------------------------------------------------------------------- +RCODE resolveColumnName( + F_Db * pDb, + TABLE_ITEM * pTableList, + const char * pszTableAlias, + const char * pszColumnName, + FLMUINT * puiTableNum, + FLMUINT * puiColumnNum, + SQLParseError * peParseError) +{ + RCODE rc = NE_SFLM_OK; + F_COLUMN * pColumn; + F_TABLE * pTable; + F_COLUMN * pFoundColumn; + F_TABLE * pFoundTable; + TABLE_ITEM * pTableItem; + + if (pszTableAlias) + { + for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) + { + if (f_stricmp( pszTableAlias, pTableItem->pszTableAlias) == 0) + { + pTable = pDb->getDict()->getTable( pTableItem->uiTableNum); + if ((pColumn = pDb->getDict()->findColumn( pTable, + pszColumnName)) == NULL) + { + if (peParseError) + { + *peParseError = SQL_ERR_INVALID_COLUMN_NAME; + } + rc = RC_SET( NE_SFLM_Q_INVALID_COLUMN_NAME); + goto Exit; + } + *puiTableNum = pTable->uiTableNum; + *puiColumnNum = pColumn->uiColumnNum; + goto Exit; + } + } + + // If we get this far, we didn't find the table. + + if (peParseError) + { + *peParseError = SQL_ERR_UNDEFINED_TABLE_NAME; + } + rc = RC_SET( NE_SFLM_Q_UNDEFINED_TABLE_FOR_COLUMN); + goto Exit; + } + else + { + pFoundColumn = NULL; + pFoundTable = NULL; + for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) + { + pTable = pDb->getDict()->getTable( pTableItem->uiTableNum); + if ((pColumn = pDb->getDict()->findColumn( pTable, + pszColumnName)) != NULL) + { + // Column name is ambiguous - belongs to more than one of + // the tables specified. + + if (pFoundColumn) + { + if (peParseError) + { + *peParseError = SQL_ERR_AMBIGUOUS_COLUMN_NAME; + } + rc = RC_SET( NE_SFLM_Q_AMBIGUOUS_COLUMN_NAME); + goto Exit; + } + pFoundColumn = pColumn; + pFoundTable = pTable; + } + } + + if (!pFoundColumn) + { + if (peParseError) + { + *peParseError = SQL_ERR_INVALID_COLUMN_NAME; + } + rc = RC_SET( NE_SFLM_Q_INVALID_COLUMN_NAME); + goto Exit; + } + *puiTableNum = pFoundTable->uiTableNum; + *puiColumnNum = pFoundColumn->uiColumnNum; + } + if (peParseError) + { + *peParseError = SQL_NO_ERROR; + } + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------- +// Desc: Resolve all of the column names that have been specified to one of +// the tables specified in the table list. Each column must belong +// to one of the tables in the list. +//------------------------------------------------------------------------- +RCODE SQLQuery::resolveColumnNames( + TABLE_ITEM * pTableList) +{ + RCODE rc = NE_SFLM_OK; + SQL_NODE * pSQLNode; + SQL_TABLE * pSQLTable; + FLMUINT uiTableNum; + FLMUINT uiColumnNum; + + // May not be any names to resolve if the select statement specifed "*" + // for the list of columns. + + if (!m_bResolveNames) + { + goto Exit; + } + + pSQLNode = m_pQuery; + + for (;;) + { + if (pSQLNode->pFirstChild) + { + pSQLNode = pSQLNode->pFirstChild; + continue; + } + + if (pSQLNode->eNodeType == SQL_COLUMN_NODE) + { + if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, + pSQLNode->nd.column.pszTableAlias, + pSQLNode->nd.column.pszColumnName, + &uiTableNum, &uiColumnNum, NULL))) + { + goto Exit; + } + + if (RC_BAD( rc = addTable( uiTableNum, &pSQLTable))) + { + goto Exit; + } + pSQLNode->nd.column.pTable = pSQLTable; + pSQLNode->nd.column.uiColumnNum = uiColumnNum; + } + + // Continue to the next sibling - or the next sibling of the first + // node in parent chain that has a sibling. + + for (;;) + { + if (pSQLNode->pNextSib) + { + pSQLNode = pSQLNode->pNextSib; + break; + } + if ((pSQLNode = pSQLNode->pParent) == NULL) + { + break; + } + } + if (!pSQLNode) + { + break; + } + } + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------- +// Desc: Add a column name. +//------------------------------------------------------------------------- +RCODE SQLQuery::addColumn( + const char * pszTableAlias, + FLMUINT uiTableAliasLen, + const char * pszColumnName, + FLMUINT uiColumnNameLen) +{ + RCODE rc = NE_SFLM_OK; + SQL_NODE * pSQLNode; + char * pszTmp; + + // Must be expecting an operand + + if (!expectingOperand()) + { + rc = RC_SET( NE_SFLM_Q_UNEXPECTED_COLUMN); + goto Exit; + } + + // Allocate a column node + + if (RC_BAD( rc = allocOperandNode( SQL_COLUMN_NODE, &pSQLNode))) + { + goto Exit; + } + if (RC_BAD( rc = m_pool.poolAlloc( uiTableAliasLen + uiColumnNameLen + 2, + (void **)&pszTmp))) + { + goto Exit; + } + + // Save the names - will resolve to numbers later. + + if (uiTableAliasLen) + { + pSQLNode->nd.column.pszTableAlias = pszTmp; + f_memcpy( pszTmp, pszTableAlias, uiTableAliasLen + 1); + pszTmp += (uiTableAliasLen + 1); + } + else + { + pSQLNode->nd.column.pszTableAlias = NULL; + } + pSQLNode->nd.column.pszColumnName = pszTmp; + f_memcpy( pszTmp, pszColumnName, uiColumnNameLen + 1); + pSQLNode->nd.column.pTable = NULL; + pSQLNode->nd.column.uiColumnNum = 0; + + m_bResolveNames = TRUE; + +Exit: + + return( rc); +} + //------------------------------------------------------------------------- // Desc: Add a FLMUINT64 number constant. //------------------------------------------------------------------------- @@ -836,14 +1078,107 @@ Exit: return( rc); } +//------------------------------------------------------------------------- +// Desc: Add an ORDER BY component +//------------------------------------------------------------------------- +RCODE SQLQuery::orderBy( + FLMUINT uiTableNum, + FLMUINT uiColumnNum, + FLMBOOL bDescending) +{ + RCODE rc = NE_SFLM_OK; + SQL_TABLE * pTable; + SQL_ORDER_BY * pOrderBy; + + // Find the table structure for the table - should already exist. + + pTable = m_pFirstTable; + while (pTable && pTable->uiTableNum != uiTableNum) + { + pTable = pTable->pNext; + } + if (!pTable) + { + rc = RC_BAD( NE_SFLM_Q_BAD_ORDER_BY_TABLE); + goto Exit; + } + + // Make sure that the order by component has not already been + // specified. + + pOrderBy = m_pFirstOrderBy; + while (pOrderBy) + { + if (pOrderBy->pTable == pTable && + pOrderBy->uiColumnNum == uiColumnNum) + { + rc = RC_SET( NE_SFLM_Q_DUP_COLUMN_IN_ORDER_BY); + goto Exit; + } + pOrderBy = pOrderBy->pNext; + } + + // Allocate an SQL_ORDER_BY structure and link it into the list of + // SQL_ORDER_BY structures. It is assumed that the order in which this + // method is called specifies 1st, 2nd, 3rd, etc. order of the sort + // components. + + if (RC_BAD( rc = m_pool.poolAlloc( sizeof( SQL_ORDER_BY), + (void **)&pOrderBy))) + { + goto Exit; + } + pOrderBy->pTable = pTable; + pOrderBy->uiColumnNum = uiColumnNum; + pOrderBy->pNext = NULL; + if (m_pLastOrderBy) + { + m_pLastOrderBy->pNext = pOrderBy; + } + else + { + m_pFirstOrderBy = pOrderBy; + } + m_pLastOrderBy = pOrderBy; + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------------ +// Desc: Determine if the current token is one that would terminate the +// expression. +//------------------------------------------------------------------------------ +FSTATIC FLMBOOL isTerminatingToken( + const char * pszToken, + const char ** ppszTerminatingTokens, + const char ** ppszTerminator) +{ + FLMUINT uiLoop; + + if (ppszTerminatingTokens) + { + for (uiLoop = 0; ppszTerminatingTokens [uiLoop]; uiLoop++) + { + if (f_stricmp( ppszTerminatingTokens [uiLoop], pszToken) == 0) + { + *ppszTerminator = ppszTerminatingTokens [uiLoop]; + return( TRUE); + } + } + } + return( FALSE); +} + //------------------------------------------------------------------------------ // Desc: Process a token in the expression that begins with an alphabetic // character. //------------------------------------------------------------------------------ RCODE SQLStatement::processAlphaToken( TABLE_ITEM * pTableList, - FLMBOOL bSelectStatement, - FLMBOOL bUpdateExpression, + const char ** ppszTerminatingTokens, + const char ** ppszTerminator, SQLQuery * pSqlQuery, FLMBOOL * pbDone) { @@ -852,20 +1187,21 @@ RCODE SQLStatement::processAlphaToken( FLMUINT uiSaveLineOffset; FLMUINT uiSaveLineFilePos; FLMUINT uiSaveLineBytes; - char szToken [MAX_SQL_TOKEN_SIZE + 1]; + char szToken [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiTokenLen; + char szColumnName [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiColumnNameLen; FLMUINT uiTokenLineOffset; - TABLE_ITEM * pTableItem; - F_TABLE * pTable; - F_COLUMN * pColumn; + FLMUINT uiTableNum; + FLMUINT uiColumnNum; + SQLParseError eParseError; FLMBYTE ucBuffer [200]; F_DynaBuf dynaBuf( ucBuffer, sizeof( ucBuffer)); *pbDone = FALSE; - - if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, &uiTokenLineOffset))) + if (RC_BAD( rc = getName( szToken, sizeof( szToken), + &uiTokenLen, &uiTokenLineOffset))) { - // Cannot be EOF hit at this point. - flmAssert( rc != NE_SFLM_EOF_HIT); goto Exit; } uiSaveLineNum = m_uiCurrLineNum; @@ -873,22 +1209,11 @@ RCODE SQLStatement::processAlphaToken( uiSaveLineFilePos = m_uiCurrLineFilePos; uiSaveLineBytes = m_uiCurrLineBytes; - // If this is a select statement, see we have hit the "ORDER" keyword - // because that signals the beginning of the ORDER BY clause. - // If this is an expression in the SET part of an UPDATE statement, - // see if we have hit the "WHERE" keyword - which would signal the - // end of the criteria. - - if ((bSelectStatement && f_stricmp( szToken, "order") == 0) || - (bUpdateExpression && f_stricmp( szToken, "where") == 0)) + if (isTerminatingToken( szToken, ppszTerminatingTokens, + ppszTerminator)) { if (pSqlQuery->criteriaIsComplete()) { - - // This essentially pushes the toke back into the stream so that - // it will be handled later. - - m_uiCurrLineOffset = uiTokenLineOffset; *pbDone = TRUE; goto Exit; } @@ -995,107 +1320,86 @@ RCODE SQLStatement::processAlphaToken( // name or a table alias. See if there is a period for the // next token. - pTableItem = NULL; if (RC_BAD( rc = haveToken( ".", TRUE))) { - F_COLUMN * pFoundColumn; - F_TABLE * pFoundTable; - if (rc != NE_SFLM_NOT_FOUND && rc != NE_SFLM_EOF_HIT) { goto Exit; } rc = NE_SFLM_OK; - // See if the column name is valid for more than one - // of the tables. If so, it is ambiguous. - - pFoundColumn = NULL; - pFoundTable = NULL; - for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) + if (!pTableList) { - pTable = m_pDb->m_pDict->getTable( pTableItem->uiTableNum); - if ((pColumn = m_pDb->m_pDict->findColumn( pTable, szToken)) != NULL) + + // If there is no table list, the list of tables has not yet been + // specified, so we will have to save this column in a slightly + // different way. + + if (RC_BAD( rc = pSqlQuery->addColumn( NULL, 0, szToken, uiTokenLen))) { - // Column name is ambiguous - belongs to more than one of - // the tables specified. - - if (pFoundColumn) + goto Exit; + } + } + else + { + if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, NULL, + szToken, &uiTableNum, &uiColumnNum, + &eParseError))) + { + if (eParseError != SQL_NO_ERROR) { setErrInfo( uiSaveLineNum, uiSaveLineOffset, - SQL_ERR_AMBIGUOUS_COLUMN_NAME, + eParseError, uiSaveLineFilePos, uiSaveLineBytes); - rc = RC_SET( NE_SFLM_INVALID_SQL); - goto Exit; } - pFoundColumn = pColumn; - pFoundTable = pTable; + goto Exit; + } + if (RC_BAD( rc = pSqlQuery->addColumn( uiTableNum, uiColumnNum))) + { + goto Exit; } - } - - if (!pFoundColumn) - { - setErrInfo( uiSaveLineNum, - uiSaveLineOffset, - SQL_ERR_INVALID_WHERE_COLUMN, - uiSaveLineFilePos, - uiSaveLineBytes); - rc = RC_SET( NE_SFLM_INVALID_SQL); - goto Exit; - } - if (RC_BAD( rc = pSqlQuery->addColumn( pFoundTable->uiTableNum, - pFoundColumn->uiColumnNum))) - { - goto Exit; } } else { - // Token better be a table alias. - - for (pTableItem = pTableList; pTableItem->uiTableNum; pTableItem++) + if (RC_BAD( rc = getName( szColumnName, sizeof( szColumnName), + &uiColumnNameLen, &uiTokenLineOffset))) { - if (f_stricmp( pTableItem->pszTableAlias, szToken) == 0) + goto Exit; + } + + if (!pTableList) + { + + // No table list yet - save using the table alias and column name. + + if (RC_BAD( rc = pSqlQuery->addColumn( szToken, uiTokenLen, + szColumnName, uiColumnNameLen))) { - break; + goto Exit; } } - if (!pTableItem->uiTableNum) + else { - setErrInfo( uiSaveLineNum, - uiSaveLineOffset, - SQL_ERR_INVALID_WHERE_TABLE, - uiSaveLineFilePos, - uiSaveLineBytes); - rc = RC_SET( NE_SFLM_INVALID_SQL); - goto Exit; + if (RC_BAD( rc = resolveColumnName( m_pDb, pTableList, szToken, + szColumnName, &uiTableNum, &uiColumnNum, + &eParseError))) + { + if (eParseError != SQL_NO_ERROR) + { + setErrInfo( uiSaveLineNum, + uiSaveLineOffset, + eParseError, + uiSaveLineFilePos, + uiSaveLineBytes); + } + goto Exit; + } } - pTable = m_pDb->m_pDict->getTable( pTableItem->uiTableNum); - - // Get next token - better be a column name for the table. - - if (RC_BAD( rc = getToken( szToken, sizeof( szToken), - TRUE, &uiTokenLineOffset))) - { - // Cannot be EOF hit at this point. - flmAssert( rc != NE_SFLM_EOF_HIT); - goto Exit; - } - if ((pColumn = m_pDb->m_pDict->findColumn( pTable, szToken)) == NULL) - { - setErrInfo( uiSaveLineNum, - uiSaveLineOffset, - SQL_ERR_INVALID_WHERE_COLUMN, - uiSaveLineFilePos, - uiSaveLineBytes); - rc = RC_SET( NE_SFLM_INVALID_SQL); - goto Exit; - } - if (RC_BAD( rc = pSqlQuery->addColumn( pTable->uiTableNum, - pColumn->uiColumnNum))) + if (RC_BAD( rc = pSqlQuery->addColumn( uiTableNum, uiColumnNum))) { goto Exit; } @@ -1107,14 +1411,13 @@ Exit: } //------------------------------------------------------------------------------ -// Desc: Process the WHERE clause of a SELECT, DELETE, or UPDATE statement. -// The "WHERE" keyword has already been parsed. If it is the -// SELECT statement, the "ORDER BY" keyword can terminate the clause. +// Desc: Process criteria. //------------------------------------------------------------------------------ RCODE SQLStatement::parseCriteria( TABLE_ITEM * pTableList, - FLMBOOL bSelectStatement, - FLMBOOL bUpdateExpression, + const char ** ppszTerminatingTokens, + FLMBOOL bEofOK, + const char ** ppszTerminator, SQLQuery * pSqlQuery) { RCODE rc = NE_SFLM_OK; @@ -1124,7 +1427,7 @@ RCODE SQLStatement::parseCriteria( FLMBYTE ucBuffer [200]; F_DynaBuf dynaBuf( ucBuffer, sizeof( ucBuffer)); FLMUINT uiNumChars; - eSQLQueryOperators eOperator; + eSQLQueryOperators eOperator = SQL_UNKNOWN_OP; FLMUINT uiNestedParens = 0; FLMUINT uiTokenLineOffset; @@ -1136,13 +1439,26 @@ RCODE SQLStatement::parseCriteria( { if (rc == NE_SFLM_EOF_HIT) { - if (!pSqlQuery->criteriaIsComplete()) + *ppszTerminator = NULL; + if (bEofOK) { - goto Incomplete_Query; + if (!pSqlQuery->criteriaIsComplete()) + { + goto Incomplete_Query; + } + else + { + rc = NE_SFLM_OK; + } } else { - rc = NE_SFLM_OK; + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_UNEXPECTED_EOF, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); } } goto Exit; @@ -1338,7 +1654,8 @@ Add_Operator: FLMBOOL bDone; if (RC_BAD( rc = processAlphaToken( pTableList, - bSelectStatement, bUpdateExpression, + ppszTerminatingTokens, + ppszTerminator, pSqlQuery, &bDone))) { goto Exit; @@ -1376,17 +1693,13 @@ Add_Operator: goto Exit; } } - else if (cChar == ',' && bUpdateExpression && - pSqlQuery->criteriaIsComplete()) - { - - // Comma is left in the stream, so it can be handled by - // the caller. - - goto Exit; - } + + // At this point, it has to be an invalid token for selection + // criteria. + else { + Incomplete_Query: if (pSqlQuery->expectingOperator()) {