//------------------------------------------------------------------------------ // Desc: This module contains routines for parsing and executing SQL statements. // // 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" /**************************************************************************** Desc: Constructor ****************************************************************************/ SQLStatement::SQLStatement() { m_fnStatus = NULL; m_pvCallbackData = NULL; m_tmpPool.poolInit( 4096); m_pucCurrLineBuf = NULL; m_uiCurrLineBufMaxBytes = 0; m_pXml = NULL; resetStatement(); } /**************************************************************************** Desc: Destructor ****************************************************************************/ SQLStatement::~SQLStatement() { resetStatement(); if( m_pucCurrLineBuf) { f_free( &m_pucCurrLineBuf); } m_tmpPool.poolFree(); } /**************************************************************************** Desc: Resets member variables so the object can be reused ****************************************************************************/ void SQLStatement::resetStatement( void) { m_uiCurrLineNum = 0; m_uiCurrLineOffset = 0; m_ucUngetByte = 0; m_uiCurrLineFilePos = 0; m_uiCurrLineBytes = 0; m_pStream = NULL; m_uiFlags = 0; m_pDb = NULL; if (m_pXml) { m_pXml->Release(); m_pXml = NULL; } f_memset( &m_sqlStats, 0, sizeof( SQL_STATS)); m_tmpPool.poolReset( NULL); } /**************************************************************************** Desc: Initializes the SQL statement object (allocates buffers, etc.) ****************************************************************************/ RCODE SQLStatement::setupStatement( void) { RCODE rc = NE_SFLM_OK; resetStatement(); if (RC_BAD( rc = FlmGetXMLObject( &m_pXml))) { goto Exit; } Exit: return( rc); } /**************************************************************************** Desc: See if the next token in the stream is the specified keyword. ****************************************************************************/ RCODE SQLStatement::getToken( char * pszToken, FLMUINT uiTokenBufSize, FLMBOOL bEofOK, FLMUINT * puiTokenLineOffset) { RCODE rc = NE_SFLM_OK; FLMUINT uiOffset; char cChar; // Always leave room for a null terminating character uiTokenBufSize--; // Token buffer must hold at least one character and a null terminating // character! if (!uiTokenBufSize) { rc = RC_SET( NE_SFLM_BUFFER_OVERFLOW); goto Exit; } // Skip any whitespace preceding the token if (RC_BAD( rc = skipWhitespace( FALSE))) { if (rc == NE_SFLM_EOF_HIT) { if (!bEofOK) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UNEXPECTED_EOF, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); } } goto Exit; } // If the first character is alphanumeric, we need to parse until // we hit a non-alphanumeric character. *puiTokenLineOffset = uiOffset = m_uiCurrLineOffset; cChar = (char)m_pucCurrLineBuf [uiOffset]; if ((cChar >= 'a' && cChar <= 'z') || (cChar >= 'A' && cChar <= 'Z') || (cChar >= '0' && cChar <= '9') || cChar == '_') { *pszToken++ = cChar; uiTokenBufSize--; uiOffset++; while (uiOffset < m_uiCurrLineBytes) { cChar = (char)m_pucCurrLineBuf [uiOffset]; if ((cChar >= 'a' && cChar <= 'z') || (cChar >= 'A' && cChar <= 'Z') || (cChar >= '0' && cChar <= '9') || cChar == '_') { if (!uiTokenBufSize) { rc = RC_SET( NE_SFLM_BUFFER_OVERFLOW); goto Exit; } *pszToken++ = cChar; uiTokenBufSize--; uiOffset++; } else { break; } } *pszToken = 0; m_uiCurrLineOffset = uiOffset; } else { switch (cChar) { case ',': case '(': case ')': case '[': case ']': case '{': case '}': case '$': case '@': case '.': case ':': case ';': case '"': case '\'': case '?': case '#': *pszToken++ = cChar; *pszToken = 0; m_uiCurrLineOffset = uiOffset + 1; break; case '>': case '<': case '+': case '-': case '*': case '/': case '=': case '!': case '%': case '^': *pszToken++ = cChar; if (uiOffset + 1 < m_uiCurrLineBytes && m_pucCurrLineBuf [uiOffset + 1] == '=') { if (uiTokenBufSize < 2) { rc = RC_SET( NE_SFLM_BUFFER_OVERFLOW); goto Exit; } *pszToken++ = '='; m_uiCurrLineOffset = uiOffset + 2; } else { m_uiCurrLineOffset = uiOffset + 1; } *pszToken = 0; break; case '|': *pszToken++ = cChar; if (uiOffset + 1 < m_uiCurrLineBytes && (m_pucCurrLineBuf [uiOffset + 1] == '=' || m_pucCurrLineBuf [uiOffset + 1] == '|')) { if (uiTokenBufSize < 2) { rc = RC_SET( NE_SFLM_BUFFER_OVERFLOW); goto Exit; } *pszToken++ = (char)m_pucCurrLineBuf [uiOffset + 1]; m_uiCurrLineOffset = uiOffset + 2; } else { m_uiCurrLineOffset = uiOffset + 1; } *pszToken = 0; break; case '&': *pszToken++ = cChar; if (uiOffset + 1 < m_uiCurrLineBytes && (m_pucCurrLineBuf [uiOffset + 1] == '=' || m_pucCurrLineBuf [uiOffset + 1] == '&')) { if (uiTokenBufSize < 2) { rc = RC_SET( NE_SFLM_BUFFER_OVERFLOW); goto Exit; } *pszToken++ = (char)m_pucCurrLineBuf [uiOffset + 1]; m_uiCurrLineOffset = uiOffset + 2; } else { m_uiCurrLineOffset = uiOffset + 1; } *pszToken = 0; break; default: setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_INVALID_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } Exit: return( rc); } /**************************************************************************** Desc: See if the next token in the stream is the specified keyword. ****************************************************************************/ RCODE SQLStatement::haveToken( const char * pszToken, FLMBOOL bEofOK, SQLParseError eNotHaveErr) { RCODE rc = NE_SFLM_OK; char szToken [MAX_SQL_TOKEN_SIZE + 1]; FLMUINT uiTokenLineOffset; if (RC_BAD( rc = getToken( szToken, sizeof( szToken), bEofOK, &uiTokenLineOffset))) { if (rc == NE_SFLM_EOF_HIT) { flmAssert( bEofOK && eNotHaveErr == SQL_NO_ERROR); rc = RC_SET( NE_SFLM_NOT_FOUND); } goto Exit; } if (f_stricmp( pszToken, szToken) != 0) { // Restore the position where the token started - so it can // still be parsed. m_uiCurrLineOffset = uiTokenLineOffset; // At this point, we know we don't have the token if (eNotHaveErr != SQL_NO_ERROR) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, eNotHaveErr, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); } else { rc = RC_SET( NE_SFLM_NOT_FOUND); } goto Exit; } Exit: return( rc); } /**************************************************************************** Desc: Get next byte from input stream. ****************************************************************************/ RCODE SQLStatement::getByte( FLMBYTE * pucByte) { RCODE rc = NE_SFLM_OK; if (m_ucUngetByte) { *pucByte = m_ucUngetByte; m_ucUngetByte = 0; } else { if( RC_BAD( rc = m_pStream->read( (char *)pucByte, 1, NULL))) { goto Exit; } } m_sqlStats.uiChars++; Exit: return( rc); } /**************************************************************************** Desc: Reads next line from the input stream. ****************************************************************************/ RCODE SQLStatement::getLine( void) { RCODE rc = NE_SFLM_OK; FLMBYTE ucBytes [4]; FLMUINT uiNumBytes; FLMUINT uiLoop; m_uiCurrLineBytes = 0; m_uiCurrLineOffset = 0; m_uiCurrLineFilePos = m_sqlStats.uiChars; for (;;) { if( RC_BAD( rc = getByte( &ucBytes [0]))) { if (rc == NE_SFLM_EOF_HIT) { if (m_uiCurrLineBytes) { rc = NE_SFLM_OK; } } goto Exit; } // Keep count of the characters. if( m_fnStatus && (m_sqlStats.uiChars % 1024) == 0) { m_fnStatus( SQL_PARSE_STATS, (void *)&m_sqlStats, NULL, NULL, m_pvCallbackData); } // Convert CRLF->CR if( ucBytes [0] == ASCII_CR) { if( RC_BAD( rc = getByte( &ucBytes [0]))) { if (rc == NE_SFLM_EOF_HIT) { rc = NE_SFLM_OK; break; } else { goto Exit; } } if( ucBytes [0] != ASCII_NEWLINE) { ungetByte( ucBytes [0]); } // End of the line break; } else if (ucBytes [0] == ASCII_NEWLINE) { // End of the line break; } if( ucBytes [0] <= 0x7F) { uiNumBytes = 1; } else { if( RC_BAD( rc = getByte( &ucBytes [1]))) { if (rc == NE_SFLM_EOF_HIT) { rc = RC_SET( NE_SFLM_BAD_UTF8); } goto Exit; } if( (ucBytes [1] >> 6) != 0x02) { rc = RC_SET( NE_SFLM_BAD_UTF8); goto Exit; } if( (ucBytes [0] >> 5) == 0x06) { uiNumBytes = 2; } else { if( RC_BAD( rc = getByte( &ucBytes [2]))) { if (rc == NE_SFLM_EOF_HIT) { rc = RC_SET( NE_SFLM_BAD_UTF8); } goto Exit; } if( (ucBytes [2] >> 6) != 0x02 || (ucBytes [0] >> 4) != 0x0E) { rc = RC_SET( NE_SFLM_BAD_UTF8); goto Exit; } uiNumBytes = 3; } } // We have a character, add it to the current line. if (m_uiCurrLineBytes + uiNumBytes > m_uiCurrLineBufMaxBytes) { // Allocate more space for the line buffer if (RC_BAD( rc = f_realloc( m_uiCurrLineBufMaxBytes + 512, &m_pucCurrLineBuf))) { goto Exit; } m_uiCurrLineBufMaxBytes += 512; } for (uiLoop = 0; uiLoop < uiNumBytes; uiLoop++) { m_pucCurrLineBuf [m_uiCurrLineBytes++] = ucBytes [uiLoop]; } } // Increment the line count m_uiCurrLineNum++; m_sqlStats.uiLines++; if( m_fnStatus && (m_sqlStats.uiLines % 100) == 0) { m_fnStatus( SQL_PARSE_STATS, (void *)&m_sqlStats, NULL, NULL, m_pvCallbackData); } Exit: return( rc); } /**************************************************************************** Desc: Skips any whitespace characters in the input stream ****************************************************************************/ RCODE SQLStatement::skipWhitespace( FLMBOOL bRequired) { FLMBYTE ucChar; FLMUINT uiCount = 0; RCODE rc = NE_SFLM_OK; for( ;;) { if ((ucChar = getChar()) == 0) { uiCount++; if (RC_BAD( rc = getLine())) { goto Exit; } continue; } if (ucChar != ASCII_SPACE && ucChar != ASCII_TAB) { ungetChar(); break; } uiCount++; } if( !uiCount && bRequired) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_EXPECTING_WHITESPACE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a UTF8 string from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getUTF8String( FLMBOOL bMustHaveEqual, FLMBYTE * pszStr, FLMUINT uiStrBufSize, FLMUINT * puiStrLen) { RCODE rc = NE_SFLM_OK; FLMBYTE ucChar; FLMBYTE ucQuoteChar = 0; FLMBOOL bEscaped = FALSE; FLMUINT uiNumChars = 0; if (bMustHaveEqual) { if (RC_BAD( rc = haveToken( "=", FALSE, SQL_ERR_EXPECTING_EQUAL))) { goto Exit; } } if (RC_BAD( rc = skipWhitespace( FALSE))) { goto Exit; } // Always leave room for a null terminating character uiStrBufSize--; // See if we have a quote character. ucChar = getChar(); if (ucChar == '"' || ucChar == '\'') { ucQuoteChar = ucChar; } for (;;) { // If we hit end of line, it is invalid without hitting quote, // it is an error. if ((ucChar = getChar()) == 0) { if (ucQuoteChar) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_MISSING_QUOTE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { break; } } if (bEscaped) { // Can only escape backslash (the escape character), quotes, and // a few other characters. if (ucChar == 'n') { ucChar = ASCII_NEWLINE; } else if (ucChar == 't') { ucChar = ASCII_TAB; } else if (ucChar == 'r') { ucChar = ASCII_CR; } else if (ucChar == '\'' || ucChar == '"' || ucChar == ASCII_SPACE || ucChar == ASCII_TAB || ucChar == ',' || ucChar == ')') { } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_INVALID_ESCAPED_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } if (uiNumChars == uiStrBufSize) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UTF8_STRING_TOO_LARGE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } *pszStr++ = ucChar; uiNumChars++; } else if (ucChar == '\\') { bEscaped = TRUE; } else if (ucChar == ucQuoteChar) { break; } else if (ucChar == ASCII_SPACE && ucChar == ASCII_TAB || ucChar == ',' || ucChar == ')') { ungetChar(); break; } else { if (uiNumChars == uiStrBufSize) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UTF8_STRING_TOO_LARGE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Save the character to our buffer. *pszStr++ = ucChar; uiNumChars++; // Handle multi-byte UTF8 characters. The getLine() method has // already checked for valid UTF8, so that is all we should be // seeing here - thus the asserts. if (ucChar > 0x7F) { // It is at least two bytes. if (uiNumChars == uiStrBufSize) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UTF8_STRING_TOO_LARGE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } ucChar = getChar(); flmAssert( (ucChar >> 6) == 0x02); *pszStr++ = ucChar; uiNumChars++; // See if it is three bytes. if ((ucChar >> 5) != 0x06) { if (uiNumChars == uiStrBufSize) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_UTF8_STRING_TOO_LARGE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } ucChar = getChar(); flmAssert( (ucChar >> 6) == 0x02); *pszStr++ = ucChar; uiNumChars++; } } } } // There will always be room for a null terminating byte if we // get to this point. *pszStr = 0; Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a numeric value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getNumber( FLMBOOL bMustHaveEqual, FLMUINT64 * pui64Num, FLMBOOL * pbNeg, FLMBOOL bNegAllowed) { RCODE rc = NE_SFLM_OK; FLMBYTE ucChar; FLMUINT64 ui64Value = 0; FLMBOOL bHex = FALSE; FLMUINT uiDigitCount = 0; FLMUINT uiDigitValue = 0; FLMUINT uiSavedLineNum; FLMUINT uiSavedOffset; FLMUINT uiSavedFilePos; FLMUINT uiSavedLineBytes; *pbNeg = FALSE; if (bMustHaveEqual) { if (RC_BAD( rc = haveToken( "=", FALSE, SQL_ERR_EXPECTING_EQUAL))) { goto Exit; } } if (RC_BAD( rc = skipWhitespace( FALSE))) { goto Exit; } uiSavedLineNum = m_uiCurrLineNum; uiSavedOffset = m_uiCurrLineOffset; uiSavedFilePos = m_uiCurrLineFilePos; uiSavedLineBytes = m_uiCurrLineBytes; // Go until we hit a character that is not a number. for (;;) { // If we hit the end of the line, we are done. if ((ucChar = getChar()) == 0) { break; } // Ignore white space { continue; } if (ucChar >= '0' && ucChar <= '9') { uiDigitValue = (FLMUINT)(ucChar - '0'); uiDigitCount++; } else if (ucChar >= 'a' && ucChar <= 'f') { if (!bHex) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_ILLEGAL_HEX_DIGIT, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } uiDigitValue = (FLMUINT)(ucChar - 'a' + 10); uiDigitCount++; } else if (ucChar >= 'A' && ucChar <= 'F') { if (!bHex) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_ILLEGAL_HEX_DIGIT, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } uiDigitValue = (FLMUINT)(ucChar - 'A' + 10); uiDigitCount++; } else if (ucChar == ',' || ucChar == ')' || ucChar == ASCII_SPACE || ucChar == ASCII_TAB) { // terminate when we hit a comma or right paren or white // space. Need to unget the character so the caller can handle it. ungetChar(); break; } else if (ucChar == 'X' || ucChar == 'x') { if (bHex || uiDigitCount != 1 || ui64Value) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NON_NUMERIC_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { bHex = TRUE; uiDigitCount = 0; continue; } } else if (ucChar == '-') { if (bHex || uiDigitCount) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NON_NUMERIC_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else if (!bNegAllowed) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_INVALID_NEGATIVE_NUM, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { *pbNeg = TRUE; continue; } } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NON_NUMERIC_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } if (bHex) { if (ui64Value > (FLM_MAX_UINT64 >> 4)) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NUMBER_OVERFLOW, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } ui64Value <<= 4; ui64Value += (FLMUINT64)uiDigitValue; } else { if (ui64Value > (FLM_MAX_UINT64 / 10)) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NUMBER_OVERFLOW, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } ui64Value *= 10; ui64Value += (FLMUINT64)uiDigitValue; } // If it is a negative number, make sure we have not // exceeded the maximum negative value. if (*pbNeg && ui64Value > ((FLMUINT64)1 << 63)) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NUMBER_OVERFLOW, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } // If we didn't hit any digits, we have an invalid number. if (!uiDigitCount) { setErrInfo( uiSavedLineNum, uiSavedOffset, SQL_ERR_NUMBER_VALUE_EMPTY, uiSavedFilePos, uiSavedLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } *pui64Num = ui64Value; Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a boolean value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getBool( FLMBOOL bMustHaveEqual, FLMBOOL * pbBool) { RCODE rc = NE_SFLM_OK; char szBool [20]; FLMUINT uiBoolLen; if (RC_BAD( rc = getUTF8String( bMustHaveEqual, (FLMBYTE *)szBool, sizeof( szBool), &uiBoolLen))) { goto Exit; } if (f_stricmp( szBool, "true") == 0) { *pbBool = TRUE; } else if (f_stricmp( szBool, "false") == 0) { *pbBool = FALSE; } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_EXPECTING_BOOLEAN, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a FLMUINT numeric value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getUINT( FLMBOOL bMustHaveEqual, FLMUINT * puiNum) { RCODE rc = NE_SFLM_OK; FLMBOOL bNeg; FLMUINT64 ui64Value; if (RC_BAD( rc = getNumber( bMustHaveEqual, &ui64Value, &bNeg, FALSE))) { goto Exit; } // Number must be less than FLM_MAX_UINT if (ui64Value > (FLMUINT64)FLM_MAX_UINT) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NUMBER_OVERFLOW, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } *puiNum = (FLMUINT)ui64Value; Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a table, column, or index name. //------------------------------------------------------------------------------ RCODE SQLStatement::getName( char * pszName, FLMUINT uiNameBufSize, FLMUINT * puiNameLen) { RCODE rc = NE_SFLM_OK; FLMUINT uiCharCount = 0; FLMBYTE ucChar; if (RC_BAD( rc = skipWhitespace( FALSE))) { goto Exit; } // Always leave room for a null terminating character. uiNameBufSize--; // Get the first character - must be between A and Z ucChar = getChar(); if ((ucChar >= 'a' && ucChar <= 'z') || (ucChar >= 'A' && ucChar <= 'Z')) { *pszName = (char)ucChar; uiCharCount++; } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_ILLEGAL_NAME_CHAR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Cannot go off of the current line for (;;) { if ((ucChar = getChar()) == 0) { break; } if ((ucChar >= 'a' && ucChar <= 'z') || (ucChar >= 'A' && ucChar <= 'Z') || (ucChar >= '0' && ucChar <= '9') || (ucChar == '_')) { if (uiCharCount >= uiNameBufSize) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_NAME_TOO_LONG, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } pszName [uiCharCount++] = (char)ucChar; } else { ungetChar(); break; } } pszName [uiCharCount] = 0; Exit: if (puiNameLen) { *puiNameLen = uiCharCount; } return( rc); } //------------------------------------------------------------------------------ // Desc: Parse the encryption definition name for the current statement. // Make sure it is valid. //------------------------------------------------------------------------------ RCODE SQLStatement::getEncDefName( FLMBOOL bMustExist, char * pszEncDefName, FLMUINT uiEncDefNameBufSize, FLMUINT * puiEncDefNameLen, F_ENCDEF ** ppEncDef) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = getName( pszEncDefName, uiEncDefNameBufSize, puiEncDefNameLen))) { goto Exit; } // See if the encryption definition name is defined if ((*ppEncDef = m_pDb->m_pDict->findEncDef( pszEncDefName)) == NULL) { if (bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_UNDEFINED_ENCDEF, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { rc = NE_SFLM_OK; } } else { if (!bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_ENCDEF_ALREADY_DEFINED, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse the table name for the current statement. Make sure it is valid. //------------------------------------------------------------------------------ RCODE SQLStatement::getTableName( FLMBOOL bMustExist, char * pszTableName, FLMUINT uiTableNameBufSize, FLMUINT * puiTableNameLen, F_TABLE ** ppTable) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = getName( pszTableName, uiTableNameBufSize, puiTableNameLen))) { goto Exit; } // See if the table name is defined if (RC_BAD( rc = m_pDb->m_pDict->getTable( pszTableName, ppTable, TRUE))) { if (rc != NE_SFLM_BAD_TABLE) { goto Exit; } if (bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_UNDEFINED_TABLE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { rc = NE_SFLM_OK; } } else { if (!bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_TABLE_ALREADY_DEFINED, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse the index name for the current statement. Make sure it is valid. //------------------------------------------------------------------------------ RCODE SQLStatement::getIndexName( FLMBOOL bMustExist, char * pszIndexName, FLMUINT uiIndexNameBufSize, FLMUINT * puiIndexNameLen, F_INDEX ** ppIndex) { RCODE rc = NE_SFLM_OK; if (RC_BAD( rc = getName( pszIndexName, uiIndexNameBufSize, puiIndexNameLen))) { goto Exit; } // See if the index name is defined if (RC_BAD( rc = m_pDb->m_pDict->getIndex( pszIndexName, ppIndex, TRUE))) { if (rc != NE_SFLM_BAD_IX) { goto Exit; } if (bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_UNDEFINED_INDEX, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } else { rc = NE_SFLM_OK; } } else { if (!bMustExist) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset - 1, SQL_ERR_INDEX_ALREADY_DEFINED, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a string value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getStringValue( F_COLUMN * pColumn, F_COLUMN_VALUE * pColumnValue) { RCODE rc = NE_SFLM_OK; FLMBYTE ucChar; FLMBYTE ucQuoteChar = 0; FLMBOOL bEscaped = FALSE; FLMBYTE szTmpBuf [300]; F_DynaBuf dynaBuf( szTmpBuf, sizeof( szTmpBuf)); FLMUINT uiNumChars = 0; FLMBYTE * pucValue; FLMUINT uiSenLen; // Leading white space has already been skipped. // See if we have a quote character. ucChar = getChar(); if (ucChar == '"' || ucChar == '\'') { ucQuoteChar = ucChar; } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_EXPECTING_QUOTE_CHAR, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } for (;;) { // Should not hit the end of the line if quoted. if ((ucChar = getChar()) == 0) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_MISSING_QUOTE, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } if (bEscaped) { // Can only escape backslash (the escape character), quotes, and // a few other characters. if (ucChar == 'n') { ucChar = ASCII_NEWLINE; } else if (ucChar == 't') { ucChar = ASCII_TAB; } else if (ucChar == 'r') { ucChar = ASCII_CR; } else if (ucChar == '\'' || ucChar == '"') { } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_INVALID_ESCAPED_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Save the escaped character to the buffer. if (RC_BAD( rc = dynaBuf.appendByte( ucChar))) { goto Exit; } uiNumChars++; } else if (ucChar == '\\') { bEscaped = TRUE; } else if (ucChar == ucQuoteChar) { break; } else { // Save the character to our buffer. if (RC_BAD( rc = dynaBuf.appendByte( ucChar))) { goto Exit; } // Handle multi-byte UTF8 characters. The getLine() method has // already checked for valid UTF8, so that is all we should be // seeing here - thus the asserts. if (ucChar > 0x7F) { // It is at least two bytes. ucChar = getChar(); flmAssert( (ucChar >> 6) == 0x02); if (RC_BAD( rc = dynaBuf.appendByte( ucChar))) { goto Exit; } // See if it is three bytes. if ((ucChar >> 5) != 0x06) { ucChar = getChar(); flmAssert( (ucChar >> 6) == 0x02); if (RC_BAD( rc = dynaBuf.appendByte( ucChar))) { goto Exit; } } } uiNumChars++; } } // Add a null terminating byte if (RC_BAD( rc = dynaBuf.appendByte( 0))) { goto Exit; } // See if the string is too long. if (pColumn->uiMaxLen && uiNumChars > pColumn->uiMaxLen) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_STRING_TOO_LONG, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_STRING_TOO_LONG); goto Exit; } // Allocate space for the UTF8 string. uiSenLen = f_getSENByteCount( uiNumChars); pColumnValue->uiValueLen = dynaBuf.getDataLength() + uiSenLen; if (RC_BAD( rc = m_tmpPool.poolAlloc( pColumnValue->uiValueLen, (void **)&pucValue))) { goto Exit; } pColumnValue->pucColumnValue = pucValue; f_encodeSEN( uiNumChars, &pucValue); // Copy the string from the dynaBuf to the column. f_memcpy( pucValue, dynaBuf.getBufferPtr(), dynaBuf.getDataLength()); Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a numeric value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getNumberValue( F_COLUMN_VALUE * pColumnValue) { RCODE rc = NE_SFLM_OK; FLMUINT64 ui64Value = 0; FLMBOOL bNeg = FALSE; FLMBYTE * pucValue; if (RC_BAD( rc = getNumber( FALSE, &ui64Value, &bNeg, TRUE))) { goto Exit; } // Allocate space for ui64Value SEN plus one byte for the sign. pColumnValue->uiValueLen = f_getSENByteCount( ui64Value) + 1; if (RC_BAD( rc = m_tmpPool.poolAlloc( pColumnValue->uiValueLen, (void **)&pucValue))) { goto Exit; } pColumnValue->pucColumnValue = pucValue; *pucValue++ = (FLMBYTE)(bNeg ? (FLMBYTE)1 : (FLMBYTE)0); // Set the number into the data. uiNumChars will hold bNeg. f_encodeSEN( ui64Value, &pucValue); Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a binary value from the input stream. //------------------------------------------------------------------------------ RCODE SQLStatement::getBinaryValue( F_COLUMN * pColumn, F_COLUMN_VALUE * pColumnValue) { RCODE rc = NE_SFLM_OK; FLMBYTE ucChar; FLMBYTE szTmpBuf [300]; F_DynaBuf dynaBuf( szTmpBuf, sizeof( szTmpBuf)); FLMBYTE ucCurrByte; FLMBOOL bGetHighNibble; FLMUINT uiSavedLineNum = m_uiCurrLineNum; FLMUINT uiSavedOffset = m_uiCurrLineOffset; FLMUINT uiSavedFilePos = m_uiCurrLineFilePos; FLMUINT uiSavedLineBytes = m_uiCurrLineBytes; // Leading white space has already been skipped. // Go until we hit a character that is not a hex digit. ucCurrByte = 0; bGetHighNibble = TRUE; for (;;) { // It is OK for white space to be in the middle of a binary // piece of data. It is also allowed to span multiple lines. if ((ucChar = getChar()) == 0) { if (RC_BAD( rc = getLine())) { goto Exit; } continue; } // Ignore white space if (ucChar == ASCII_SPACE || ucChar == ASCII_TAB) { continue; } if (ucChar >= '0' && ucChar <= '9') { if (bGetHighNibble) { ucCurrByte = (ucChar - '0') << 4; } else { ucCurrByte |= (ucChar - '0'); } } else if (ucChar >= 'a' && ucChar <= 'f') { if (bGetHighNibble) { ucCurrByte = (ucChar - 'a' + 10) << 4; } else { ucCurrByte |= (ucChar - 'a' + 10); } } else if (ucChar >= 'A' && ucChar <= 'F') { if (bGetHighNibble) { ucCurrByte = (ucChar - 'A' + 10) << 4; } else { ucCurrByte |= (ucChar - 'A' + 10); } } else if (ucChar == ',' || ucChar == ')') { // terminate when we hit a comma or right paren. Need to // unget the character because the caller will be expecting // to see it. ungetChar(); break; } else { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_NON_HEX_CHARACTER, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } if (bGetHighNibble) { bGetHighNibble = FALSE; } else { if (RC_BAD( rc = dynaBuf.appendByte( ucCurrByte))) { goto Exit; } bGetHighNibble = TRUE; ucCurrByte = 0; } } // Add last byte if bGetHighNibble is FALSE - means we got the high nibble // into the high four bits of ucCurrByte if (!bGetHighNibble) { if (RC_BAD( rc = dynaBuf.appendByte( ucCurrByte))) { goto Exit; } } // See if the binary data is too long. if (pColumn->uiMaxLen && dynaBuf.getDataLength() > pColumn->uiMaxLen) { setErrInfo( m_uiCurrLineNum, m_uiCurrLineOffset, SQL_ERR_BINARY_TOO_LONG, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_BINARY_TOO_LONG); goto Exit; } // An empty binary value is invalid. if ((pColumnValue->uiValueLen = dynaBuf.getDataLength()) == 0) { setErrInfo( uiSavedLineNum, uiSavedOffset, SQL_ERR_BINARY_VALUE_EMPTY, uiSavedFilePos, uiSavedLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } // Allocate space for the binary data. if (RC_BAD( rc = m_tmpPool.poolAlloc( pColumnValue->uiValueLen, (void **)&pColumnValue->pucColumnValue))) { goto Exit; } // Copy the binary data from the dynaBuf to the column. f_memcpy( pColumnValue->pucColumnValue, dynaBuf.getBufferPtr(), pColumnValue->uiValueLen); Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse a value from the input stream. Value must be of the specified // type. //------------------------------------------------------------------------------ RCODE SQLStatement::getValue( F_COLUMN * pColumn, F_COLUMN_VALUE * pColumnValue) { RCODE rc = NE_SFLM_OK; switch (pColumn->eDataTyp) { case SFLM_STRING_TYPE: if (RC_BAD( rc = getStringValue( pColumn, pColumnValue))) { goto Exit; } break; case SFLM_NUMBER_TYPE: if (RC_BAD( rc = getNumberValue( pColumnValue))) { goto Exit; } break; case SFLM_BINARY_TYPE: if (RC_BAD( rc = getBinaryValue( pColumn, pColumnValue))) { goto Exit; } break; default: flmAssert( 0); break; } Exit: return( rc); } //------------------------------------------------------------------------------ // Desc: Parse and execute an SQL statement. //------------------------------------------------------------------------------ RCODE SQLStatement::executeSQL( IF_IStream * pStream, F_Db * pDb, SQL_STATS * pSQLStats) { RCODE rc = NE_SFLM_OK; char szToken [MAX_SQL_TOKEN_SIZE + 1]; FLMUINT uiTokenLineOffset; // Reset the state of the parser if (RC_BAD( rc = setupStatement())) { goto Exit; } m_pStream = pStream; m_pDb = pDb; // Process all of the statements. for (;;) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), TRUE, &uiTokenLineOffset))) { goto Exit; } if (f_stricmp( szToken, "insert") == 0) { if (RC_BAD( rc = processInsertRow())) { goto Exit; } } else if (f_stricmp( szToken, "open") == 0) { if (RC_BAD( rc = haveToken( "database", FALSE, SQL_ERR_INVALID_OPEN_OPTION))) { goto Exit; } if (RC_BAD( rc = processOpenDatabase())) { goto Exit; } } else if (f_stricmp( szToken, "create") == 0) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, &uiTokenLineOffset))) { goto Exit; } if (f_stricmp( szToken, "database") == 0) { if (RC_BAD( rc = processCreateDatabase())) { goto Exit; } } else if (f_stricmp( szToken, "table") == 0) { if (RC_BAD( rc = processCreateTable())) { goto Exit; } } else if (f_stricmp( szToken, "index") == 0) { if (RC_BAD( rc = processCreateIndex( FALSE))) { goto Exit; } } else if (f_stricmp( szToken, "unique") == 0) { if (RC_BAD( rc = haveToken( "index", FALSE, SQL_ERR_EXPECTING_INDEX))) { goto Exit; } if (RC_BAD( rc = processCreateIndex( TRUE))) { goto Exit; } } else { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_INVALID_CREATE_OPTION, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } else if (f_stricmp( szToken, "drop") == 0) { if (RC_BAD( rc = getToken( szToken, sizeof( szToken), FALSE, &uiTokenLineOffset))) { goto Exit; } if (f_stricmp( szToken, "database") == 0) { if (RC_BAD( rc = processDropDatabase())) { goto Exit; } } else if (f_stricmp( szToken, "table") == 0) { if (RC_BAD( rc = processDropTable())) { goto Exit; } } else if (f_stricmp( szToken, "index") == 0) { if (RC_BAD( rc = processDropIndex())) { goto Exit; } } else { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_INVALID_DROP_OPTION, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } else { setErrInfo( m_uiCurrLineNum, uiTokenLineOffset, SQL_ERR_INVALID_SQL_STATEMENT, m_uiCurrLineFilePos, m_uiCurrLineBytes); rc = RC_SET( NE_SFLM_INVALID_SQL); goto Exit; } } // Call the status hook one last time if (m_fnStatus) { m_fnStatus( SQL_PARSE_STATS, (void *)&m_sqlStats, NULL, NULL, m_pvCallbackData); } // Tally and return the SQL statistics if( pSQLStats) { pSQLStats->uiLines += m_sqlStats.uiLines; pSQLStats->uiChars += m_sqlStats.uiChars; } Exit: if (rc == NE_SFLM_EOF_HIT) { rc = NE_SFLM_OK; } if( RC_BAD( rc) && pSQLStats) { pSQLStats->uiErrLineNum = m_sqlStats.uiErrLineNum ? m_sqlStats.uiErrLineNum : m_uiCurrLineNum; pSQLStats->uiErrLineOffset = m_sqlStats.uiErrLineOffset ? m_sqlStats.uiErrLineOffset : m_uiCurrLineOffset; pSQLStats->eErrorType = m_sqlStats.eErrorType; pSQLStats->uiErrLineFilePos = m_sqlStats.uiErrLineFilePos; pSQLStats->uiErrLineBytes = m_sqlStats.uiErrLineBytes; } m_pDb = NULL; return( rc); }