git-svn-id: https://svn.code.sf.net/p/flaim/code/trunk@709 0109f412-320b-0410-ab79-c3e0c5ffbbe6
633 lines
14 KiB
C++
633 lines
14 KiB
C++
//------------------------------------------------------------------------------
|
|
// 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;
|
|
F_INDEX * pIndex;
|
|
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 <search_criteria>]
|
|
// [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].bScan = FALSE;
|
|
tableList [uiNumTables].uiIndexNum = 0;
|
|
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 (f_stricmp( szToken, "using") == 0)
|
|
{
|
|
Get_Index:
|
|
if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE,
|
|
&uiTokenLineOffset, &uiTokenLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
if (f_stricmp( szToken, "noindex") == 0)
|
|
{
|
|
tableList [uiNumTables - 1].bScan = TRUE;
|
|
tableList [uiNumTables - 1].uiIndexNum = 0;
|
|
}
|
|
else if (f_stricmp( szToken, "index") == 0)
|
|
{
|
|
|
|
// Get the index name.
|
|
|
|
if (RC_BAD( rc = getIndexName( TRUE, pTable, szToken, sizeof( szToken),
|
|
&uiTokenLen, &pIndex)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
tableList [uiNumTables - 1].bScan = FALSE;
|
|
tableList [uiNumTables - 1].uiIndexNum = pIndex->uiIndexNum;
|
|
}
|
|
else
|
|
{
|
|
setErrInfo( m_uiCurrLineNum,
|
|
uiTokenLineOffset,
|
|
SQL_ERR_EXPECTING_INDEX_OR_NOINDEX,
|
|
m_uiCurrLineFilePos,
|
|
m_uiCurrLineBytes);
|
|
rc = RC_SET( NE_SFLM_INVALID_SQL);
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if ((szToken [0] >= 'a' && szToken [0] <= 'z') ||
|
|
(szToken [0] >= 'A' && szToken [0] <= 'Z'))
|
|
{
|
|
|
|
// If token is the keyword "as", it must be followed by the
|
|
// alias name.
|
|
|
|
if (f_stricmp( szToken, "as") == 0)
|
|
{
|
|
if (RC_BAD( rc = getName( szToken, sizeof( szToken),
|
|
&uiTokenLen, &uiTokenLineOffset)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
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 (f_stricmp( szToken, "using") == 0)
|
|
{
|
|
goto Get_Index;
|
|
}
|
|
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 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 (tableList [uiLoop].bScan)
|
|
{
|
|
if (RC_BAD( rc = sqlQuery.setIndex( tableList [uiLoop].uiTableNum, 0)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else if (tableList [uiLoop].uiIndexNum)
|
|
{
|
|
if (RC_BAD( rc = sqlQuery.setIndex( tableList [uiLoop].uiTableNum,
|
|
tableList [uiLoop].uiIndexNum)))
|
|
{
|
|
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;
|
|
}
|
|
|
|
// Token better be a comma
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|