mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1230 lines
34 KiB
1230 lines
34 KiB
/*++
|
|
|
|
|
|
|
|
// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
|
|
|
|
Module Name:
|
|
|
|
WQLSCAN.CPP
|
|
|
|
Abstract:
|
|
|
|
WQL Prefix Scanner
|
|
|
|
This module implements a specially cased shift-reduce parser to
|
|
parse out selected columns, JOINed tables and aliases, while ignoring
|
|
the rest of the query.
|
|
|
|
History:
|
|
|
|
raymcc 17-Oct-97 SMS extensions.
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
#include <stdio.h>
|
|
|
|
#include <flexarry.h>
|
|
#include <wqllex.h>
|
|
#include <wqlnode.h>
|
|
#include <wqlscan.h>
|
|
|
|
#include <helpers.h>
|
|
|
|
#define trace(x) printf x
|
|
|
|
|
|
class CTokenArray : public CFlexArray
|
|
{
|
|
public:
|
|
~CTokenArray() { Empty(); }
|
|
void Empty()
|
|
{
|
|
for (int i = 0; i < Size(); i++) delete PWSLexToken(GetAt(i));
|
|
CFlexArray::Empty();
|
|
}
|
|
};
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::CWQLScanner
|
|
//
|
|
// Constructor
|
|
//
|
|
// Parameters:
|
|
// <pSrc> A source from which to lex from.
|
|
//
|
|
//***************************************************************************
|
|
|
|
CWQLScanner::CWQLScanner(CGenLexSource *pSrc)
|
|
{
|
|
m_pLexer = new CGenLexer(WQL_LexTable, pSrc);
|
|
m_nLine = 0;
|
|
m_pTokenText = 0;
|
|
m_nCurrentToken = 0;
|
|
m_bCount = FALSE;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::~CWQLScanner
|
|
//
|
|
//***************************************************************************
|
|
|
|
|
|
CWQLScanner::~CWQLScanner()
|
|
{
|
|
delete m_pLexer;
|
|
|
|
ClearTokens();
|
|
ClearTableRefs();
|
|
ClearPropRefs();
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
|
|
BOOL CWQLScanner::GetReferencedAliases(CWStringArray &aAliases)
|
|
{
|
|
for (int i = 0; i < m_aTableRefs.Size(); i++)
|
|
{
|
|
WSTableRef *pTRef = (WSTableRef *) m_aTableRefs[i];
|
|
aAliases.Add(pTRef->m_pszAlias);
|
|
}
|
|
return TRUE;
|
|
}
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
|
|
BOOL CWQLScanner::GetReferencedTables(CWStringArray &aClasses)
|
|
{
|
|
for (int i = 0; i < m_aTableRefs.Size(); i++)
|
|
{
|
|
WSTableRef *pTRef = (WSTableRef *) m_aTableRefs[i];
|
|
aClasses.Add(pTRef->m_pszTable);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
void CWQLScanner::ClearTokens()
|
|
{
|
|
for (int i = 0; i < m_aTokens.Size(); i++)
|
|
delete (WSLexToken *) m_aTokens[i];
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
void CWQLScanner::ClearPropRefs()
|
|
{
|
|
for (int i = 0; i < m_aPropRefs.Size(); i++)
|
|
delete (SWQLColRef *) m_aPropRefs[i];
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
|
|
void CWQLScanner::ClearTableRefs()
|
|
{
|
|
for (int i = 0; i < m_aTableRefs.Size(); i++)
|
|
delete (WSTableRef *) m_aTableRefs[i];
|
|
m_aTableRefs.Empty();
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// Next()
|
|
//
|
|
// Advances to the next token and recognizes keywords, etc.
|
|
//
|
|
//***************************************************************************
|
|
|
|
struct WqlKeyword
|
|
{
|
|
LPWSTR m_pKeyword;
|
|
int m_nTokenCode;
|
|
};
|
|
|
|
static WqlKeyword KeyWords[] = // Keep this alphabetized for binary search
|
|
{
|
|
L"ALL", WQL_TOK_ALL,
|
|
L"AND", WQL_TOK_AND,
|
|
L"AS", WQL_TOK_AS,
|
|
L"BETWEEN", WQL_TOK_BETWEEN,
|
|
L"BY", WQL_TOK_BY,
|
|
L"COUNT", WQL_TOK_COUNT,
|
|
L"DATEPART", WQL_TOK_DATEPART,
|
|
L"DISTINCT", WQL_TOK_DISTINCT,
|
|
L"FIRSTROW", WQL_TOK_FIRSTROW,
|
|
L"FROM", WQL_TOK_FROM,
|
|
L"FULL", WQL_TOK_FULL,
|
|
L"GROUP", WQL_TOK_GROUP,
|
|
L"HAVING", WQL_TOK_HAVING,
|
|
L"IN", WQL_TOK_IN,
|
|
L"INNER", WQL_TOK_INNER,
|
|
L"IS", WQL_TOK_IS,
|
|
L"ISA", WQL_TOK_ISA,
|
|
L"ISNULL", WQL_TOK_ISNULL,
|
|
L"JOIN", WQL_TOK_JOIN,
|
|
L"LEFT", WQL_TOK_LEFT,
|
|
L"LIKE", WQL_TOK_LIKE,
|
|
L"LOWER", WQL_TOK_LOWER,
|
|
L"NOT", WQL_TOK_NOT,
|
|
L"NULL", WQL_TOK_NULL,
|
|
L"ON", WQL_TOK_ON,
|
|
L"OR", WQL_TOK_OR,
|
|
L"ORDER", WQL_TOK_ORDER,
|
|
L"OUTER", WQL_TOK_OUTER,
|
|
L"QUALIFIER", WQL_TOK_QUALIFIER,
|
|
L"RIGHT", WQL_TOK_RIGHT,
|
|
L"SELECT", WQL_TOK_SELECT,
|
|
L"UNION", WQL_TOK_UNION,
|
|
L"UPPER", WQL_TOK_UPPER,
|
|
L"WHERE", WQL_TOK_WHERE
|
|
|
|
};
|
|
|
|
const int NumKeywords = sizeof(KeyWords)/sizeof(WqlKeyword);
|
|
|
|
BOOL CWQLScanner::Next()
|
|
{
|
|
if (!m_pLexer)
|
|
return FALSE;
|
|
|
|
m_nCurrentToken = m_pLexer->NextToken();
|
|
if (m_nCurrentToken == WQL_TOK_ERROR)
|
|
return FALSE;
|
|
|
|
m_nLine = m_pLexer->GetLineNum();
|
|
m_pTokenText = m_pLexer->GetTokenText();
|
|
if (m_nCurrentToken == WQL_TOK_EOF)
|
|
m_pTokenText = L"<end of file>";
|
|
|
|
// Keyword check. Do a binary search
|
|
// on the keyword table.
|
|
// =================================
|
|
|
|
if (m_nCurrentToken == WQL_TOK_IDENT)
|
|
{
|
|
int l = 0, u = NumKeywords - 1;
|
|
|
|
while (l <= u)
|
|
{
|
|
int m = (l + u) / 2;
|
|
if (_wcsicmp(m_pTokenText, KeyWords[m].m_pKeyword) < 0)
|
|
u = m - 1;
|
|
else if (_wcsicmp(m_pTokenText, KeyWords[m].m_pKeyword) > 0)
|
|
l = m + 1;
|
|
else // Match
|
|
{
|
|
m_nCurrentToken = KeyWords[m].m_nTokenCode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::ExtractNext
|
|
//
|
|
//***************************************************************************
|
|
|
|
PWSLexToken CWQLScanner::ExtractNext()
|
|
{
|
|
if (m_aTokens.Size() == 0)
|
|
return NULL;
|
|
|
|
PWSLexToken pTok = PWSLexToken(m_aTokens[0]);
|
|
m_aTokens.RemoveAt(0);
|
|
return pTok;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::Pushback
|
|
//
|
|
//***************************************************************************
|
|
|
|
void CWQLScanner::Pushback(PWSLexToken pPushbackTok)
|
|
{
|
|
m_aTokens.InsertAt(0, pPushbackTok);
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// Shift-reduce parser entry.
|
|
//
|
|
//***************************************************************************
|
|
|
|
int CWQLScanner::Parse()
|
|
{
|
|
int nRes = SYNTAX_ERROR;
|
|
if (m_pLexer == NULL)
|
|
return FAILED;
|
|
|
|
m_pLexer->Reset();
|
|
|
|
if (!Next())
|
|
return LEXICAL_ERROR;
|
|
|
|
|
|
// Completely tokenize the entire query and build a parse-stack.
|
|
// =============================================================
|
|
|
|
if (m_nCurrentToken == WQL_TOK_SELECT)
|
|
{
|
|
while (1)
|
|
{
|
|
WSLexToken *pTok = new WSLexToken;
|
|
if (!pTok)
|
|
return FAILED;
|
|
|
|
pTok->m_nToken = m_nCurrentToken;
|
|
pTok->m_pszTokenText = Macro_CloneLPWSTR(m_pTokenText);
|
|
|
|
if (!pTok->m_pszTokenText)
|
|
return FAILED;
|
|
|
|
m_aTokens.Add(pTok);
|
|
|
|
if (m_nCurrentToken == WQL_TOK_EOF)
|
|
break;
|
|
|
|
if (!Next())
|
|
return LEXICAL_ERROR;
|
|
}
|
|
}
|
|
else
|
|
return SYNTAX_ERROR;
|
|
|
|
// Reduce by extracting the select type keywords if possible.
|
|
// ==========================================================
|
|
|
|
nRes = ExtractSelectType();
|
|
if (nRes)
|
|
return nRes;
|
|
|
|
// Eliminate all tokens from WHERE onwards.
|
|
// ========================================
|
|
|
|
StripWhereClause();
|
|
|
|
// Reduce by extracting the select list.
|
|
// =====================================
|
|
|
|
if (!m_bCount)
|
|
{
|
|
nRes = SelectList();
|
|
if (nRes != 0)
|
|
return nRes;
|
|
}
|
|
else
|
|
{
|
|
// Strip everything until the FROM keyword is encountered.
|
|
// =======================================================
|
|
|
|
WSLexToken *pTok = ExtractNext();
|
|
|
|
|
|
while (pTok)
|
|
{
|
|
if (pTok->m_nToken == WQL_TOK_FROM)
|
|
{
|
|
Pushback(pTok);
|
|
break;
|
|
}
|
|
// Bug #46728: the count(*) clause
|
|
// can be the only element of the select clause.
|
|
|
|
else if (!wcscmp(pTok->m_pszTokenText, L","))
|
|
{
|
|
delete pTok;
|
|
return SYNTAX_ERROR;
|
|
}
|
|
|
|
delete pTok;
|
|
pTok = ExtractNext();
|
|
}
|
|
if (pTok == 0)
|
|
return SYNTAX_ERROR;
|
|
}
|
|
|
|
// Extract tables/aliases from JOIN clauses.
|
|
// =========================================
|
|
|
|
if (ReduceSql89Joins() != TRUE)
|
|
{
|
|
ClearTableRefs();
|
|
if (ReduceSql92Joins() != TRUE)
|
|
return SYNTAX_ERROR;
|
|
}
|
|
|
|
|
|
// Post process select clause to determine if
|
|
// columns are tables or aliases.
|
|
// ==========================================
|
|
for (int i = 0; i < m_aPropRefs.Size(); i++)
|
|
{
|
|
SWQLColRef *pCRef = (SWQLColRef *) m_aPropRefs[i];
|
|
if (pCRef->m_pTableRef != 0)
|
|
{
|
|
LPWSTR pTbl = AliasToTable(pCRef->m_pTableRef);
|
|
if (pTbl == 0)
|
|
continue;
|
|
|
|
if (_wcsicmp(pTbl, pCRef->m_pTableRef) == 0)
|
|
pCRef->m_dwFlags |= WQL_FLAG_TABLE;
|
|
else
|
|
pCRef->m_dwFlags |= WQL_FLAG_ALIAS;
|
|
}
|
|
}
|
|
|
|
|
|
if (m_aTableRefs.Size() == 0)
|
|
return SYNTAX_ERROR;
|
|
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::StripWhereClause
|
|
//
|
|
// If present, removes the WHERE or ORDER BY clause. Because
|
|
// of SQL Syntax, stripping the first of {ORDER BY, WHERE} will automatically
|
|
// get rid of the other.
|
|
//
|
|
//***************************************************************************
|
|
BOOL CWQLScanner::StripWhereClause()
|
|
{
|
|
for (int i = 0; i < m_aTokens.Size(); i++)
|
|
{
|
|
WSLexToken *pCurrent = (WSLexToken *) m_aTokens[i];
|
|
|
|
// If a WHERE token is found, we have something to strip.
|
|
// ======================================================
|
|
|
|
if (pCurrent->m_nToken == WQL_TOK_WHERE ||
|
|
pCurrent->m_nToken == WQL_TOK_ORDER)
|
|
{
|
|
int nNumTokensToRemove = m_aTokens.Size() - i - 1;
|
|
for (int i2 = 0; i2 < nNumTokensToRemove; i2++)
|
|
{
|
|
delete PWSLexToken(m_aTokens[i]);
|
|
m_aTokens.RemoveAt(i);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::ExtractSelectType
|
|
//
|
|
// Examines the prefix to reduce the query by eliminating the SELECT
|
|
// and select-type keywords, such as ALL, DISTINCT, FIRSTROW, COUNT
|
|
//
|
|
// If COUNT is used, move past the open-close parentheses.
|
|
//
|
|
//***************************************************************************
|
|
|
|
int CWQLScanner::ExtractSelectType()
|
|
{
|
|
// Verify that SELECT is the first token.
|
|
// ======================================
|
|
|
|
WSLexToken *pFront = ExtractNext();
|
|
|
|
if (pFront == 0 || pFront->m_nToken == WQL_TOK_EOF)
|
|
{
|
|
delete pFront;
|
|
return SYNTAX_ERROR;
|
|
}
|
|
|
|
if (pFront->m_nToken != WQL_TOK_SELECT)
|
|
{
|
|
delete pFront;
|
|
return SYNTAX_ERROR;
|
|
}
|
|
|
|
delete pFront;
|
|
|
|
// Check for possible select-type and extract it.
|
|
// ==============================================
|
|
|
|
pFront = ExtractNext();
|
|
if (pFront == 0)
|
|
return SYNTAX_ERROR;
|
|
|
|
if (pFront->m_nToken == WQL_TOK_COUNT)
|
|
{
|
|
delete pFront;
|
|
m_bCount = TRUE;
|
|
}
|
|
else if (pFront->m_nToken == WQL_TOK_ALL ||
|
|
pFront->m_nToken == WQL_TOK_DISTINCT ||
|
|
pFront->m_nToken == WQL_TOK_FIRSTROW
|
|
)
|
|
delete pFront;
|
|
else
|
|
Pushback(pFront);
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::SelectList
|
|
//
|
|
// Extracts all tokens up to the FROM keyword and builds a list
|
|
// of selected properties/columns. FROM is left on the parse-stack on exit.
|
|
//
|
|
//***************************************************************************
|
|
|
|
int CWQLScanner::SelectList()
|
|
{
|
|
// If the first token is FROM, then we have a SELECT FROM <rest>
|
|
// which is the same as SELECT * FROM <rest>. We simply
|
|
// alter the parse-stack and let the following loop handle it.
|
|
// =============================================================
|
|
|
|
WSLexToken *pTok = ExtractNext();
|
|
|
|
if (pTok->m_nToken == WQL_TOK_FROM)
|
|
{
|
|
WSLexToken *pAsterisk = new WSLexToken;
|
|
if (pAsterisk == NULL)
|
|
return FAILED;
|
|
|
|
pAsterisk->m_nToken = WQL_TOK_ASTERISK;
|
|
pAsterisk->m_pszTokenText = Macro_CloneLPWSTR(L"*");
|
|
if (!pAsterisk->m_pszTokenText)
|
|
return FAILED;
|
|
Pushback(pTok);
|
|
Pushback(pAsterisk);
|
|
}
|
|
else
|
|
Pushback(pTok);
|
|
|
|
// Otherwise, some kind of column selection is present.
|
|
// ====================================================
|
|
|
|
BOOL bTerminate = FALSE;
|
|
|
|
while (!bTerminate)
|
|
{
|
|
pTok = ExtractNext();
|
|
if (pTok == 0)
|
|
return SYNTAX_ERROR;
|
|
|
|
// We must begin at a legal token.
|
|
// ===============================
|
|
|
|
if (pTok->m_nToken != WQL_TOK_EOF)
|
|
{
|
|
CTokenArray Tokens;
|
|
Tokens.Add(pTok);
|
|
|
|
while (1)
|
|
{
|
|
pTok = ExtractNext();
|
|
if (pTok == 0 || pTok->m_nToken == WQL_TOK_EOF)
|
|
{
|
|
delete pTok;
|
|
return SYNTAX_ERROR;
|
|
}
|
|
if (pTok->m_nToken == WQL_TOK_FROM)
|
|
{
|
|
Pushback(pTok);
|
|
bTerminate = TRUE;
|
|
break;
|
|
}
|
|
else if (pTok->m_nToken == WQL_TOK_COMMA)
|
|
{
|
|
delete pTok;
|
|
break;
|
|
}
|
|
else
|
|
Tokens.Add(pTok);
|
|
}
|
|
|
|
SWQLColRef *pColRef = new SWQLColRef;
|
|
if (pColRef == 0)
|
|
return FAILED;
|
|
|
|
BOOL bRes = BuildSWQLColRef(Tokens, *pColRef);
|
|
if (bRes)
|
|
m_aPropRefs.Add(pColRef);
|
|
else
|
|
{
|
|
delete pColRef;
|
|
return SYNTAX_ERROR;
|
|
}
|
|
}
|
|
|
|
// Else an illegal token, such as WQL_TOK_EOF.
|
|
// ===========================================
|
|
else
|
|
{
|
|
delete pTok;
|
|
return SYNTAX_ERROR;
|
|
|
|
}
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::ReduceSql89Joins
|
|
//
|
|
// Attempts to reduce the FROM clause, assuming it is based on SQL-89
|
|
// join syntax or else a simple unary select.
|
|
//
|
|
// The supported forms are:
|
|
//
|
|
// FROM x
|
|
// FROM x, y
|
|
// FROM x as x1, y as y1
|
|
// FROM x x1, y y1
|
|
//
|
|
// If incompatible tokens are encountered, the entire function
|
|
// returns FALSE and the results are ignored, and the parse-stack
|
|
// is unaffected, in essence, allowing backtracking to try the SQL-92
|
|
// syntax branch instead.
|
|
//
|
|
//***************************************************************************
|
|
BOOL CWQLScanner::ReduceSql89Joins()
|
|
{
|
|
int i = 0;
|
|
|
|
// Parse the FROM keyword.
|
|
// =======================
|
|
|
|
WSLexToken *pCurr = (WSLexToken *) m_aTokens[i++];
|
|
if (pCurr->m_nToken != WQL_TOK_FROM)
|
|
return FALSE;
|
|
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
|
|
while (1)
|
|
{
|
|
if (pCurr->m_nToken != WQL_TOK_IDENT)
|
|
return FALSE;
|
|
|
|
// If here, we are looking at the beginnings of a table ref.
|
|
// =========================================================
|
|
|
|
WSTableRef *pTRef = new WSTableRef;
|
|
if (pTRef == 0)
|
|
return FALSE;
|
|
|
|
pTRef->m_pszTable = Macro_CloneLPWSTR(pCurr->m_pszTokenText);
|
|
if (!pTRef->m_pszTable)
|
|
return FALSE;
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pCurr->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
m_aTableRefs.Add(pTRef);
|
|
|
|
// Attempt to recognize an alias.
|
|
// ==============================
|
|
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
if (pCurr == WQL_TOK_EOF || pCurr->m_nToken == WQL_TOK_UNION)
|
|
break;
|
|
|
|
if (pCurr->m_nToken == WQL_TOK_AS)
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
|
|
if (pCurr->m_nToken == WQL_TOK_COMMA)
|
|
{
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
continue;
|
|
}
|
|
|
|
if (pCurr->m_nToken == WQL_TOK_EOF || pCurr->m_nToken == WQL_TOK_UNION)
|
|
break;
|
|
|
|
if (pCurr->m_nToken != WQL_TOK_IDENT)
|
|
return FALSE;
|
|
|
|
delete [] pTRef->m_pszAlias;
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pCurr->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
|
|
// We have completely parsed a table reference.
|
|
// Now we move on to the next one.
|
|
// ============================================
|
|
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
|
|
if (pCurr->m_nToken == WQL_TOK_EOF || pCurr->m_nToken == WQL_TOK_UNION)
|
|
break;
|
|
|
|
if (pCurr->m_nToken != WQL_TOK_COMMA)
|
|
return FALSE;
|
|
|
|
pCurr = (WSLexToken *) m_aTokens[i++];
|
|
}
|
|
|
|
if (m_aTableRefs.Size())
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::ReduceSql92Joins
|
|
//
|
|
// This scans SQL-92 JOIN syntax looking for table aliases. See the
|
|
// algorithm at the end of this file.
|
|
//
|
|
//***************************************************************************
|
|
|
|
BOOL CWQLScanner::ReduceSql92Joins()
|
|
{
|
|
WSLexToken *pCurrent = 0, *pRover = 0, *pRight = 0, *pLeft;
|
|
int nNumTokens = m_aTokens.Size();
|
|
DWORD dwNumJoins = 0;
|
|
int iCurrBase = 0;
|
|
|
|
for (int i = 0; i < nNumTokens; i++)
|
|
{
|
|
pCurrent = (WSLexToken *) m_aTokens[i];
|
|
|
|
// If a JOIN token is found, we have a candidate.
|
|
// ==============================================
|
|
|
|
if (pCurrent->m_nToken == WQL_TOK_JOIN)
|
|
{
|
|
dwNumJoins++;
|
|
|
|
// Analyze right-context.
|
|
// ======================
|
|
|
|
if (i + 1 < nNumTokens)
|
|
pRover = PWSLexToken(m_aTokens[i + 1]);
|
|
else
|
|
pRover = NULL;
|
|
|
|
if (pRover && pRover->m_nToken == WQL_TOK_IDENT)
|
|
{
|
|
// Check for aliased table by checking for
|
|
// AS or two juxtaposed idents.
|
|
// =======================================
|
|
|
|
if (i + 2 < nNumTokens)
|
|
pRight = PWSLexToken(m_aTokens[i + 2]);
|
|
else
|
|
pRight = NULL;
|
|
|
|
|
|
if (pRight && pRight->m_nToken == WQL_TOK_AS)
|
|
{
|
|
if (i + 3 < nNumTokens)
|
|
pRight = PWSLexToken(m_aTokens[i + 3]);
|
|
else
|
|
pRight = NULL;
|
|
}
|
|
|
|
if (pRight && pRight->m_nToken == WQL_TOK_IDENT)
|
|
{
|
|
WSTableRef *pTRef = new WSTableRef;
|
|
if (pTRef == 0)
|
|
return FALSE;
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pRight->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
pTRef->m_pszTable = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszTable)
|
|
return FALSE;
|
|
m_aTableRefs.Add(pTRef);
|
|
}
|
|
else // An alias wasn't used, just a simple table ref.
|
|
{
|
|
WSTableRef *pTRef = new WSTableRef;
|
|
if (pTRef == 0)
|
|
return FALSE;
|
|
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
pTRef->m_pszTable = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszTable)
|
|
return FALSE;
|
|
m_aTableRefs.Add(pTRef);
|
|
}
|
|
// discontinue analysis of right-context.
|
|
}
|
|
|
|
|
|
// Analyze left-context.
|
|
// =====================
|
|
|
|
int nLeft = i - 1;
|
|
|
|
if (nLeft >= 0)
|
|
pRover = PWSLexToken(m_aTokens[nLeft--]);
|
|
else
|
|
continue; // No point in continuing
|
|
|
|
// Verify the ANSI join syntax.
|
|
|
|
if (nLeft)
|
|
{
|
|
int iTemp = nLeft;
|
|
WSLexToken *pTemp = pRover;
|
|
bool bInner = false;
|
|
bool bDir = false;
|
|
bool bOuter = false;
|
|
bool bFail = false;
|
|
bool bIdent = false;
|
|
while (iTemp >= iCurrBase)
|
|
{
|
|
if (pTemp->m_nToken == WQL_TOK_INNER)
|
|
{
|
|
if (bOuter || bIdent || bInner)
|
|
bFail = TRUE;
|
|
bInner = true;
|
|
}
|
|
else if (pTemp->m_nToken == WQL_TOK_OUTER)
|
|
{
|
|
if (bInner || bIdent || bOuter)
|
|
bFail = TRUE;
|
|
bOuter = true;
|
|
}
|
|
else if (pTemp->m_nToken == WQL_TOK_FULL ||
|
|
pTemp->m_nToken == WQL_TOK_LEFT ||
|
|
pTemp->m_nToken == WQL_TOK_RIGHT
|
|
)
|
|
{
|
|
if (bDir || bIdent)
|
|
bFail = TRUE;
|
|
bDir = true;
|
|
}
|
|
else
|
|
bIdent = TRUE;
|
|
|
|
// We are trying to enforce correct ANSI-92 joins
|
|
// even though we don't support them ourselves:
|
|
// OK: LEFT OUTER JOIN
|
|
// OUTER LEFT JOIN
|
|
// LEFT JOIN
|
|
// INNER JOIN
|
|
// NOT: LEFT LEFT JOIN
|
|
// LEFT INNER JOIN
|
|
// LEFT RIGHT JOIN
|
|
// OUTER INNER JOIN
|
|
// OUTER LEFT OUTER JOIN
|
|
// OUTER GARBAGE LEFT JOIN
|
|
// (no right side)
|
|
|
|
if ((bDir && bInner) || bFail)
|
|
return FALSE;
|
|
|
|
pTemp = PWSLexToken(m_aTokens[iTemp--]);
|
|
}
|
|
|
|
}
|
|
|
|
// Skip past potential JOIN modifiers : INNER, OUTER,
|
|
// FULL, LEFT, RIGHT
|
|
// ==================================================
|
|
|
|
if (pRover->m_nToken == WQL_TOK_INNER ||
|
|
pRover->m_nToken == WQL_TOK_OUTER ||
|
|
pRover->m_nToken == WQL_TOK_FULL ||
|
|
pRover->m_nToken == WQL_TOK_LEFT ||
|
|
pRover->m_nToken == WQL_TOK_RIGHT
|
|
)
|
|
{
|
|
if (nLeft >= 0)
|
|
pRover = PWSLexToken(m_aTokens[nLeft--]);
|
|
else
|
|
pRover = 0;
|
|
}
|
|
|
|
if (pRover->m_nToken == WQL_TOK_INNER ||
|
|
pRover->m_nToken == WQL_TOK_OUTER ||
|
|
pRover->m_nToken == WQL_TOK_FULL ||
|
|
pRover->m_nToken == WQL_TOK_LEFT ||
|
|
pRover->m_nToken == WQL_TOK_RIGHT
|
|
)
|
|
{
|
|
if (nLeft >= 0)
|
|
pRover = PWSLexToken(m_aTokens[nLeft--]);
|
|
else
|
|
pRover = 0;
|
|
}
|
|
|
|
// Now we look to see if the roving pointer is pointing
|
|
// to an ident.
|
|
// ====================================================
|
|
|
|
if (pRover && pRover->m_nToken != WQL_TOK_IDENT)
|
|
{
|
|
// No chance that we are looking at an aliased
|
|
// table in a JOIN clause.
|
|
// ===========================================
|
|
continue;
|
|
}
|
|
|
|
iCurrBase = i;
|
|
|
|
// If here, we are now possibliy looking at the second half
|
|
// of an alias, the 'alias' name proper. We mark this
|
|
// by leaving pRover alone and continue to move into the
|
|
// left context with a different pointer.
|
|
// ========================================================
|
|
|
|
if (nLeft >= 0)
|
|
pLeft = PWSLexToken(m_aTokens[nLeft--]);
|
|
else
|
|
pLeft = 0;
|
|
|
|
if (pLeft && pLeft->m_nToken == WQL_TOK_AS)
|
|
{
|
|
if (nLeft >= 0)
|
|
pLeft = PWSLexToken(m_aTokens[nLeft--]);
|
|
else
|
|
pLeft = 0;
|
|
}
|
|
|
|
// The critical test. Are we at an ident?
|
|
// =======================================
|
|
|
|
if (pLeft && pLeft->m_nToken == WQL_TOK_IDENT)
|
|
{
|
|
WSTableRef *pTRef = new WSTableRef;
|
|
if (pTRef == 0)
|
|
return FALSE;
|
|
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
pTRef->m_pszTable = Macro_CloneLPWSTR(pLeft->m_pszTokenText);
|
|
if (!pTRef->m_pszTable)
|
|
return FALSE;
|
|
m_aTableRefs.Add(pTRef);
|
|
}
|
|
else if (pLeft && pLeft->m_nToken == WQL_TOK_FROM)
|
|
{
|
|
WSTableRef *pTRef = new WSTableRef;
|
|
if (pTRef == 0)
|
|
return FALSE;
|
|
|
|
pTRef->m_pszAlias = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszAlias)
|
|
return FALSE;
|
|
pTRef->m_pszTable = Macro_CloneLPWSTR(pRover->m_pszTokenText);
|
|
if (!pTRef->m_pszTable)
|
|
return FALSE;
|
|
m_aTableRefs.Add(pTRef);
|
|
if (nLeft >= 0)
|
|
{
|
|
pLeft = PWSLexToken(m_aTokens[nLeft--]);
|
|
if (pLeft && pLeft->m_nToken == WQL_TOK_FROM)
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find next JOIN occurrence
|
|
}
|
|
|
|
// Make sure there are two sides to every join reference.
|
|
|
|
if (dwNumJoins+1 != (DWORD)m_aTableRefs.Size())
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//***************************************************************************
|
|
//
|
|
//***************************************************************************
|
|
void CWQLScanner::Dump()
|
|
{
|
|
WSLexToken *pCurrent = 0;
|
|
|
|
printf("---Token Stream----\n");
|
|
|
|
for (int i = 0; i < m_aTokens.Size(); i++)
|
|
{
|
|
pCurrent = (WSLexToken *) m_aTokens[i];
|
|
|
|
printf("Token %d <%S>\n", pCurrent->m_nToken, pCurrent->m_pszTokenText);
|
|
}
|
|
|
|
printf("---Table Refs---\n");
|
|
|
|
for (i = 0; i < m_aTableRefs.Size(); i++)
|
|
{
|
|
WSTableRef *pTRef = (WSTableRef *) m_aTableRefs[i];
|
|
printf("Table = %S Alias = %S\n", pTRef->m_pszTable, pTRef->m_pszAlias);
|
|
}
|
|
|
|
|
|
if (!m_bCount)
|
|
{
|
|
printf("---Select List---\n");
|
|
|
|
for (i = 0; i < m_aPropRefs.Size(); i++)
|
|
{
|
|
SWQLColRef *pCRef = (SWQLColRef *) m_aPropRefs[i];
|
|
pCRef->DebugDump();
|
|
}
|
|
}
|
|
else
|
|
printf(" -> COUNT query\n");
|
|
|
|
printf("\n\n---<end of dump>---\n\n");
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------------------
|
|
|
|
Algorithm for detecting aliased tables in SQL-92 join syntax.
|
|
|
|
The JOIN keyword must appear.
|
|
|
|
It may appear in several contexts which are not
|
|
relevant to the aliasing problem, such as the following:
|
|
|
|
select distinct t1a.name, t2a.id, t3.value from
|
|
(t1 as t1a join t2 as t2a on t1a.name = t2a.name)
|
|
join
|
|
(t1 as t1b join t3 on t1b.id = t3.id and (t3.id = t1b.id or t1b.id = t3.id))
|
|
on
|
|
t1a.id = t3.id
|
|
where a = b and c = d
|
|
|
|
where the middle join is against anonymous result sets.
|
|
|
|
When analyzing the JOIN, we can easily parse the right-context. Either
|
|
an identifier follows (possibly further followed by AS),and an optional
|
|
identifier if the JOIN is aliased. Otherwise, we hit ON immediately, or
|
|
a parenthesis.
|
|
|
|
The problem is the left-context of the JOIN token.
|
|
|
|
For an alias to occur, an identifier must appear immediately to
|
|
the left of the JOIN.
|
|
|
|
id JOIN id2 as id3 ON ...
|
|
^
|
|
|
|
If here, there is a chance we are looking at the left hand side of a
|
|
SQL92 join, a table reference. However, we might be looking at the end of
|
|
an ON clause which ends in an identifier:
|
|
|
|
idx = id JOIN id2 as id3 ON...
|
|
^
|
|
To disambiguate, we have to do further analysis of left context.
|
|
|
|
Consider the follow left-context possibilities:
|
|
|
|
(1) t1 AS id JOIN id2 as id3 ON
|
|
^
|
|
(2) t1 id JOIN id2 as id3 ON
|
|
^
|
|
(3) <keyword (except AS)> id JOIN id2 as id3 ON
|
|
^
|
|
(4) on x <rel op> id JOIN id2 as id3 ON
|
|
^
|
|
|
|
Once we have identified <id>, we have to consider the above cases.
|
|
|
|
(1) Case 1 is easy. An AS clearly tells us we have an alias
|
|
and we know how to get at the table and alias names.
|
|
|
|
(2) Case 2 is easy. Two juxtaposed identifiers to the left always
|
|
indicates an alias.
|
|
|
|
In all other cases, like (3) and (4), etc., the table is not
|
|
aliased anyway. Therefore, we only have to determine whether we
|
|
are looking at an unaliased table name or the trailing end of
|
|
another construct like an ON clause. This is easy. Only the
|
|
FROM keyword can precede <id> if <id> is a simple table name.
|
|
|
|
---------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
//***************************************************************************
|
|
//
|
|
// CWQLScanner::BuildSWQLColRef
|
|
//
|
|
//***************************************************************************
|
|
|
|
BOOL CWQLScanner::BuildSWQLColRef(
|
|
IN CFlexArray &aTokens,
|
|
IN OUT SWQLColRef &ColRef // Empty on entry
|
|
)
|
|
{
|
|
if (aTokens.Size() == 0)
|
|
return FALSE;
|
|
int nCurrent = 0;
|
|
WSLexToken *pTok = PWSLexToken(aTokens[nCurrent++]);
|
|
|
|
// Initial state: single asterisk or else prop name.
|
|
// =================================================
|
|
|
|
if (pTok->m_nToken == WQL_TOK_ASTERISK && aTokens.Size() == 1)
|
|
{
|
|
ColRef.m_pColName = Macro_CloneLPWSTR(L"*");
|
|
if (!ColRef.m_pColName)
|
|
return FALSE;
|
|
ColRef.m_dwFlags = WQL_FLAG_ASTERISK;
|
|
ColRef.m_pQName = new SWQLQualifiedName;
|
|
if (ColRef.m_pQName == 0)
|
|
return FALSE;
|
|
SWQLQualifiedNameField *pField = new SWQLQualifiedNameField;
|
|
if (pField == 0)
|
|
return FALSE;
|
|
|
|
pField->m_pName = Macro_CloneLPWSTR(L"*");
|
|
if (!pField->m_pName)
|
|
return FALSE;
|
|
ColRef.m_pQName->Add(pField);
|
|
return TRUE;
|
|
}
|
|
|
|
// If not an identifier, we have an error.
|
|
// =======================================
|
|
|
|
else if (pTok->m_nToken == WQL_TOK_EOF)
|
|
return FALSE;
|
|
|
|
// If here, we have an identifier.
|
|
// ===============================
|
|
|
|
ColRef.m_pQName = new SWQLQualifiedName;
|
|
if (ColRef.m_pQName == NULL)
|
|
return FALSE;
|
|
SWQLQualifiedNameField *pField = new SWQLQualifiedNameField;
|
|
if (pField == 0)
|
|
return FALSE;
|
|
|
|
pField->m_pName = Macro_CloneLPWSTR(pTok->m_pszTokenText);
|
|
if (!pField->m_pName)
|
|
return FALSE;
|
|
ColRef.m_pQName->Add(pField);
|
|
|
|
// Subsequent states.
|
|
// ==================
|
|
|
|
while (1)
|
|
{
|
|
if (nCurrent == aTokens.Size())
|
|
break;
|
|
|
|
pTok = PWSLexToken(aTokens[nCurrent++]);
|
|
|
|
if (pTok->m_nToken == WQL_TOK_DOT)
|
|
{
|
|
pField = new SWQLQualifiedNameField;
|
|
if (pField == 0)
|
|
return FALSE;
|
|
|
|
ColRef.m_pQName->Add(pField);
|
|
|
|
if (nCurrent == aTokens.Size())
|
|
return FALSE;
|
|
pTok = PWSLexToken(aTokens[nCurrent++]);
|
|
if (pTok->m_nToken != WQL_TOK_IDENT &&
|
|
pTok->m_nToken != WQL_TOK_ASTERISK
|
|
)
|
|
return FALSE;
|
|
|
|
pField->m_pName = Macro_CloneLPWSTR(pTok->m_pszTokenText);
|
|
if (!pField->m_pName)
|
|
return FALSE;
|
|
}
|
|
else if (pTok->m_nToken == WQL_TOK_OPEN_BRACKET)
|
|
{
|
|
return FALSE; // Not supported at present!
|
|
}
|
|
else // illegal token
|
|
return FALSE;
|
|
}
|
|
|
|
// Post-process. If the name is not complex, then we
|
|
// can fill out fields of ColRef.
|
|
// ==================================================
|
|
if (ColRef.m_pQName->GetNumNames() == 2)
|
|
{
|
|
ColRef.m_pTableRef = Macro_CloneLPWSTR(ColRef.m_pQName->GetName(0));
|
|
if (!ColRef.m_pTableRef)
|
|
return FALSE;
|
|
ColRef.m_pColName = Macro_CloneLPWSTR(ColRef.m_pQName->GetName(1));
|
|
if (!ColRef.m_pColName)
|
|
return FALSE;
|
|
if (_wcsicmp(ColRef.m_pColName, L"NULL") == 0)
|
|
ColRef.m_dwFlags |= WQL_FLAG_NULL;
|
|
}
|
|
else if (ColRef.m_pQName->GetNumNames() == 1)
|
|
{
|
|
LPWSTR pName = ColRef.m_pQName->GetName(0);
|
|
ColRef.m_pColName = Macro_CloneLPWSTR(pName);
|
|
if (!ColRef.m_pColName)
|
|
return FALSE;
|
|
if (_wcsicmp(ColRef.m_pColName, L"NULL") == 0)
|
|
ColRef.m_dwFlags |= WQL_FLAG_NULL;
|
|
}
|
|
else
|
|
{
|
|
ColRef.m_pTableRef = Macro_CloneLPWSTR(ColRef.m_pQName->GetName(0));
|
|
if (!ColRef.m_pTableRef)
|
|
return FALSE;
|
|
ColRef.m_dwFlags = WQL_FLAG_COMPLEX_NAME;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
|
|
const LPWSTR CWQLScanner::AliasToTable(LPWSTR pszAlias)
|
|
{
|
|
if (pszAlias == 0)
|
|
return 0;
|
|
|
|
for (int i = 0; i < m_aTableRefs.Size(); i++)
|
|
{
|
|
WSTableRef *pTRef = (WSTableRef *) m_aTableRefs[i];
|
|
if (_wcsicmp(pszAlias, pTRef->m_pszAlias) == 0)
|
|
return pTRef->m_pszTable;
|
|
|
|
if (_wcsicmp(pszAlias, pTRef->m_pszTable) == 0)
|
|
return pTRef->m_pszTable;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|