diff --git a/sql/src/sqlstatement.cpp b/sql/src/sqlstatement.cpp new file mode 100644 index 0000000..8c0dd6e --- /dev/null +++ b/sql/src/sqlstatement.cpp @@ -0,0 +1,1580 @@ +// Desc: This module contains routines for doing database updates +// +// 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; + m_pColumnValues = &m_columnValues [0]; + m_uiColumnValueArraySize = SQL_DEFAULT_COLUMNS; + resetStatement(); +} + +/**************************************************************************** +Desc: Destructor +****************************************************************************/ +SQLStatement::~SQLStatement() +{ + resetStatement(); + + if( m_pucCurrLineBuf) + { + f_free( &m_pucCurrLineBuf); + } + + if (m_pColumnValues != &m_columnValues [0]) + { + f_free( &m_pColumnValues); + } + 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; + m_pTable = NULL; + m_uiNumColumnValues = 0; + 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 current line has the specified token in it starting + from the current offset. +****************************************************************************/ +FLMBOOL SQLStatement::lineHasToken( + const char * pszToken) +{ + FLMUINT uiOffset; + + uiOffset = m_uiCurrLineOffset; + while (uiOffset < m_uiCurrLineBytes) + { + if (m_pucCurrLineBuf [uiOffset] != (char)(*pszToken)) + { + + // Do NOT change m_uiCurrLineOffset if we return FALSE. + + return( FALSE); + } + pszToken++; + uiOffset++; + if (*pszToken == 0) + { + m_uiCurrLineOffset = uiOffset; + return( TRUE); + } + } + return( FALSE); +} + +/**************************************************************************** +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 table, column, or index name. +//------------------------------------------------------------------------------ +RCODE SQLStatement::getName( + char * pszName, + FLMUINT uiNameBufSize, + FLMUINT * puiNameLen) +{ + RCODE rc = NE_SFLM_OK; + FLMUINT uiCharCount = 0; + FLMBYTE ucChar; + + // 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_TABLE_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_TABLE_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 table name for the current statement. Make sure it is valid. +//------------------------------------------------------------------------------ +RCODE SQLStatement::getTableName( + FLMBOOL bMustExist) +{ + RCODE rc = NE_SFLM_OK; + char szTableName [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiTableNameLen; + + if (RC_BAD( rc = getName( szTableName, sizeof( szTableName), &uiTableNameLen))) + { + goto Exit; + } + + // See if the table name is defined + + if (RC_BAD( rc = m_pDb->m_pDict->getTable( szTableName, &m_pTable, 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: Reallocate the column value array if needed. +//------------------------------------------------------------------------------ +RCODE SQLStatement::allocColumnValueArray( + FLMUINT uiNumColumnsNeeded) +{ + RCODE rc = NE_SFLM_OK; + + if (uiNumColumnsNeeded > m_uiColumnValueArraySize) + { + F_COLUMN_VALUE * pNewArray; + + // Increase the array size by at least 20. + + uiNumColumnsNeeded += 20; + if (m_pColumnValues == &m_columnValues [0]) + { + if (RC_BAD( rc = f_alloc( sizeof( F_COLUMN_VALUE) * uiNumColumnsNeeded, + &pNewArray))) + { + goto Exit; + } + if (m_uiNumColumnValues) + { + f_memcpy( pNewArray, m_pColumnValues, + m_uiNumColumnValues * sizeof( F_COLUMN_VALUE)); + } + m_pColumnValues = pNewArray; + } + else + { + if (RC_BAD( rc = f_realloc( sizeof( F_COLUMN_VALUE) * uiNumColumnsNeeded, + &m_pColumnValues))) + { + goto Exit; + } + } + m_uiColumnValueArraySize = uiNumColumnsNeeded; + } + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------------ +// Desc: Parse a string value from the input stream. +//------------------------------------------------------------------------------ +RCODE SQLStatement::getStringValue( + 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; + } + + // 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; + FLMBYTE ucChar; + FLMUINT64 ui64Value = 0; + FLMBOOL bNeg = FALSE; + FLMBOOL bHex = FALSE; + FLMUINT uiDigitCount = 0; + FLMUINT uiDigitValue = 0; + FLMUINT uiSavedLineNum = m_uiCurrLineNum; + FLMUINT uiSavedOffset = m_uiCurrLineOffset; + FLMUINT uiSavedFilePos = m_uiCurrLineFilePos; + FLMUINT uiSavedLineBytes = m_uiCurrLineBytes; + FLMBYTE * pucValue; + + // Leading white space has already been skipped. + + // 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 + { + bNeg = 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 (bNeg && 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; + } + + // 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_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; + } + } + + // 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_VALUE * pColumnValue) +{ + RCODE rc = NE_SFLM_OK; + + switch (pColumnValue->eColumnDataType) + { + case SFLM_STRING_TYPE: + if (RC_BAD( rc = getStringValue( 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( pColumnValue))) + { + goto Exit; + } + break; + default: + flmAssert( 0); + break; + } + +Exit: + + return( rc); +} + +//------------------------------------------------------------------------------ +// Desc: Insert a row into the database. +//------------------------------------------------------------------------------ +RCODE F_Db::insertRow( + FLMUINT uiTableNum, + F_COLUMN_VALUE * pColumnValues, + FLMUINT uiNumColumnValues) +{ + RCODE rc = NE_SFLM_OK; + F_Row * pRow = NULL; + const FLMBYTE * pucValue; + const FLMBYTE * pucEnd; + FLMUINT64 ui64Num; + FLMUINT uiNumChars; + FLMBOOL bNeg; + + // Create a row object. + + if (RC_BAD( rc = gv_SFlmSysData.pRowCacheMgr->createRow( this, + uiTableNum, &pRow))) + { + goto Exit; + } + + // Set the column values into the row. + + for (; uiNumColumnValues; uiNumColumnValues--, pColumnValues++) + { + if (!pColumnValues->uiValueLen) + { + continue; + } + switch (pColumnValues->eColumnDataType) + { + case SFLM_STRING_TYPE: + pucValue = (const FLMBYTE *)pColumnValues->pucColumnValue; + pucEnd = pucValue + pColumnValues->uiValueLen; + if (RC_BAD( rc = f_decodeSEN( &pucValue, pucEnd, &uiNumChars))) + { + goto Exit; + } + if (RC_BAD( rc = pRow->setUTF8( this, + pColumnValues->uiColumnNum, + (const char *)pucValue, + (FLMUINT)(pucEnd - pucValue), + uiNumChars))) + { + goto Exit; + } + break; + case SFLM_NUMBER_TYPE: + pucValue = (const FLMBYTE *)pColumnValues->pucColumnValue; + pucEnd = pucValue + pColumnValues->uiValueLen; + + bNeg = (FLMBOOL)(*pucValue ? (FLMBOOL)TRUE : (FLMBOOL)FALSE); + pucValue++; + + if (RC_BAD( rc = f_decodeSEN64( &pucValue, pucEnd, &ui64Num))) + { + goto Exit; + } + if (RC_BAD( rc = pRow->setNumber64( this, + pColumnValues->uiColumnNum, ui64Num, bNeg))) + { + goto Exit; + } + break; + case SFLM_BINARY_TYPE: + if (RC_BAD( rc = pRow->setBinary( this, + pColumnValues->uiColumnNum, + (const void *)(pColumnValues->pucColumnValue), + pColumnValues->uiValueLen))) + { + goto Exit; + } + break; + default: + flmAssert( 0); + break; + } + } + + if (RC_BAD( rc = m_pDatabase->m_pRfl->logInsertRow( this, uiTableNum, pColumnValues, + uiNumColumnValues))) + { + goto Exit; + } + +Exit: + + if (pRow) + { + pRow->ReleaseRow(); + } + + return( rc); +} + +//------------------------------------------------------------------------------ +// Desc: Process the insert statement. The "INSERT" keyword has already been +// parsed. +//------------------------------------------------------------------------------ +RCODE SQLStatement::processInsertRow( void) +{ + RCODE rc = NE_SFLM_OK; + FLMBOOL bStartedTrans = FALSE; + F_COLUMN_VALUE * pColumnValue; + F_COLUMN * pColumn; + FLMUINT uiLoop; + char szColumnName [MAX_SQL_NAME_LEN + 1]; + FLMUINT uiColumnNameLen; + + // If we are in a read transaction, we cannot do this operation + + if (RC_BAD( rc = m_pDb->checkTransaction( SFLM_UPDATE_TRANS, &bStartedTrans))) + { + goto Exit; + } + + // SYNTAX: INSERT INTO table_name (column1,column2,...) VALUES (value1,value2,...) + // OR: INSERT INTO table_name VALUES (value1,value2,...) + + // Whitespace must follow the "INSERT" + + if (RC_BAD( rc = skipWhitespace( TRUE))) + { + goto Exit; + } + + // INTO must follow the INSERT. + + if (!lineHasToken( "into")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_INTO, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + // Whitespace must follow the "INTO" + + if (RC_BAD( rc = skipWhitespace( TRUE))) + { + goto Exit; + } + + // Get the table name. + + if (RC_BAD( rc = getTableName( TRUE))) + { + goto Exit; + } + + if (m_pTable->bSystemTable) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_CANNOT_UPDATE_SYSTEM_TABLE, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + // Whitespace does not have to follow the table name + + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + + // If left paren follows table name, then columns are being listed. + + m_uiNumColumnValues = 0; + if (lineHasToken( "(")) + { + + // Get the list of columns for which there will be values. + + for (;;) + { + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + + // Get the column name + + if (RC_BAD( rc = getName( szColumnName, sizeof( szColumnName), + &uiColumnNameLen))) + { + goto Exit; + } + + // See if the column is defined in the table. + + if (uiColumnNameLen) + { + if ((pColumn = m_pDb->m_pDict->findColumn( m_pTable, szColumnName)) == NULL) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_UNDEFINED_COLUMN, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + // Make room in the column value table for this column + + if (m_uiNumColumnValues == m_uiColumnValueArraySize) + { + if (RC_BAD( rc = allocColumnValueArray( m_uiNumColumnValues + 1))) + { + goto Exit; + } + } + pColumnValue = &m_pColumnValues [m_uiNumColumnValues]; + pColumnValue->uiColumnNum = pColumn->uiColumnNum; + pColumnValue->eColumnDataType = pColumn->eDataTyp; + pColumnValue->uiValueLen = 0; + m_uiNumColumnValues++; + } + + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + + // See if we are at the end of the list of columns + + if (lineHasToken( ")")) + { + break; + } + else if (!lineHasToken( ",")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_COMMA, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + } + else + { + + // Allocate the column value array + + if (RC_BAD( rc = allocColumnValueArray( m_pTable->uiNumColumns))) + { + goto Exit; + } + for (uiLoop = 0, pColumn = m_pTable->pColumns, pColumnValue = m_pColumnValues; + uiLoop < m_pTable->uiNumColumns; + uiLoop++, pColumn++, pColumnValue++) + { + if (pColumn->uiColumnNum) + { + pColumnValue->uiColumnNum = pColumn->uiColumnNum; + pColumnValue->eColumnDataType = pColumn->eDataTyp; + pColumnValue->uiValueLen = 0; + m_uiNumColumnValues++; + } + } + } + + // Allow for no values to be specified if no columns were. + + if (m_uiNumColumnValues) + { + if (!lineHasToken( "values")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_INTO, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + + // Should be a left paren + + if (!lineHasToken( "(")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_LPAREN, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + + for (uiLoop = 0, pColumnValue = m_pColumnValues; + uiLoop < m_uiNumColumnValues; + uiLoop++, pColumnValue++) + { + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + + // Get the column value + + if (RC_BAD( rc = getValue( pColumnValue))) + { + goto Exit; + } + + if (RC_BAD( rc = skipWhitespace( FALSE))) + { + goto Exit; + } + if (uiLoop == m_uiNumColumnValues - 1) + { + if (!lineHasToken( ")")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_RPAREN, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + else + { + break; + } + } + else if (!lineHasToken( ",")) + { + setErrInfo( m_uiCurrLineNum, + m_uiCurrLineOffset, + SQL_ERR_EXPECTING_COMMA, + m_uiCurrLineFilePos, + m_uiCurrLineBytes); + rc = RC_SET( NE_SFLM_INVALID_SQL); + goto Exit; + } + } + } + + // Insert the row. + + if (RC_BAD( rc = m_pDb->insertRow( m_pTable->uiTableNum, + m_pColumnValues, m_uiNumColumnValues))) + { + goto Exit; + } + + // Commit the transaction if we started it + + if (bStartedTrans) + { + bStartedTrans = FALSE; + if (RC_BAD( rc = m_pDb->transCommit())) + { + goto Exit; + } + } + +Exit: + + if (bStartedTrans) + { + m_pDb->transAbort(); + } + + 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; + FLMBOOL bWhitespaceRequired = FALSE; + + // 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 = skipWhitespace( bWhitespaceRequired))) + { + if (rc == NE_SFLM_EOF_HIT) + { + rc = NE_SFLM_OK; + break; + } + goto Exit; + } + + if (lineHasToken( "insert")) + { + if( RC_BAD( rc = processInsertRow())) + { + goto Exit; + } + } + bWhitespaceRequired = TRUE; + } + + // 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_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); +} diff --git a/sql/src/sqlstatement.h b/sql/src/sqlstatement.h new file mode 100644 index 0000000..8542a11 --- /dev/null +++ b/sql/src/sqlstatement.h @@ -0,0 +1,194 @@ +//------------------------------------------------------------------------------ +// Desc: This file contains SQL statement class. +// +// 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$ +//------------------------------------------------------------------------------ + +#ifndef SQLSTATEMENT_H +#define SQLSTATEMENT_H + +// Maximum name length for table names, column names, and index names + +#define MAX_SQL_NAME_LEN 128 + +typedef enum +{ + SQL_PARSE_STATS +} eSQLStatus; + +typedef RCODE (* SQL_STATUS_HOOK)( + eSQLStatus eStatusType, + void * pvArg1, + void * pvArg2, + void * pvArg3, + void * pvUserData); + +/*============================================================================ +Desc: SQL statement class. Parses and executes SQL statements. +============================================================================*/ +class SQLStatement : public F_Object +{ +public: + + SQLStatement(); + + virtual ~SQLStatement(); + + RCODE setupStatement( void); + + void resetStatement( void); + + RCODE executeSQL( + IF_IStream * pStream, + F_Db * pDb, + SQL_STATS * pSQLStats); + +private: + + // Methods + + RCODE getByte( + FLMBYTE * pucByte); + + FINLINE void ungetByte( + FLMBYTE ucByte) + { + // Can only unget a single byte. + + flmAssert( !m_ucUngetByte); + m_ucUngetByte = ucByte; + m_sqlStats.uiChars--; + } + + RCODE getLine( void); + + FINLINE FLMBYTE getChar( void) + { + if (m_uiCurrLineOffset == m_uiCurrLineBytes) + { + return( (FLMBYTE)0); + } + else + { + FLMBYTE ucChar = m_pucCurrLineBuf [m_uiCurrLineOffset++]; + return( ucChar); + } + } + + FINLINE FLMBYTE peekChar( void) + { + if (m_uiCurrLineOffset == m_uiCurrLineBytes) + { + return( (FLMBYTE)0); + } + else + { + return( m_pucCurrLineBuf [m_uiCurrLineOffset]); + } + } + + FINLINE void ungetChar( void) + { + + // There should never be a reason to unget past the beginning of the current + // line. + + flmAssert( m_uiCurrLineOffset); + m_uiCurrLineOffset--; + } + + RCODE skipWhitespace( + FLMBOOL bRequired); + + FLMBOOL lineHasToken( + const char * pszToken); + + FINLINE void setErrInfo( + FLMUINT uiErrLineNum, + FLMUINT uiErrLineOffset, + SQLParseError eErrorType, + FLMUINT uiErrLineFilePos, + FLMUINT uiErrLineBytes) + { + m_sqlStats.uiErrLineNum = uiErrLineNum; + m_sqlStats.uiErrLineOffset = uiErrLineOffset; + m_sqlStats.eErrorType = eErrorType; + m_sqlStats.uiErrLineFilePos = uiErrLineFilePos; + m_sqlStats.uiErrLineBytes = uiErrLineBytes; + } + + RCODE getName( + char * pszName, + FLMUINT uiNameBufSize, + FLMUINT * puiNameLen); + + RCODE getTableName( + FLMBOOL bMustExist); + + RCODE allocColumnValueArray( + FLMUINT uiNumColumns); + + RCODE getStringValue( + F_COLUMN_VALUE * pColumnValue); + + RCODE getNumberValue( + F_COLUMN_VALUE * pColumnValue); + + RCODE getBinaryValue( + F_COLUMN_VALUE * pColumnValue); + + RCODE getValue( + F_COLUMN_VALUE * pColumnValue); + + RCODE insertRow( void); + + RCODE processInsertRow( void); + + // Data + + F_Db * m_pDb; + F_TABLE * m_pTable; +#define SQL_DEFAULT_COLUMNS 20 + F_COLUMN_VALUE m_columnValues [SQL_DEFAULT_COLUMNS]; + F_COLUMN_VALUE * m_pColumnValues; + FLMUINT m_uiColumnValueArraySize; + FLMUINT m_uiNumColumnValues; + IF_XML * m_pXml; + FLMBYTE m_ucUngetByte; + FLMBYTE * m_pucCurrLineBuf; + FLMUINT m_uiCurrLineBufMaxBytes; + FLMUINT m_uiCurrLineOffset; + FLMUINT m_uiCurrLineNum; + FLMUINT m_uiCurrLineFilePos; + FLMUINT m_uiCurrLineBytes; + IF_IStream * m_pStream; + FLMUINT m_uiFlags; + SQL_STATUS_HOOK m_fnStatus; + void * m_pvCallbackData; + SQL_STATS m_sqlStats; + F_Pool m_tmpPool; + +friend class F_Db; +friend class F_Database; +}; + +#endif // SQLSTATEMENT_H