blob: 2738e3752b93ced18919b9d7e37e5b570d3c5b1f [file] [log] [blame]
/**********************************************************************
// @@@ START COPYRIGHT @@@
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
// @@@ END COPYRIGHT @@@
//
**********************************************************************/
/* -*-C++-*-
*****************************************************************************
*
* File: InputStmt.C
* RCS: $Id: InputStmt.cpp,v 1.2 2007/10/19 16:07:26 Exp $
* Description:
*
* Created: 4/15/95
* Modified: $ $Date: 2007/10/19 16:07:26 $ (GMT)
* Language: C++
* Status: $State: Exp $
*
*
*
*
*****************************************************************************
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "Platform.h"
#include "ComASSERT.h"
#include "InputStmt.h"
#include "ParserMsg.h"
#include "SqlciEnv.h"
#include "SqlciError.h"
#include "SqlciParser.h"
#include "str.h"
#include "ComDiags.h"
extern ComDiagsArea sqlci_DA;
InputStmt::InputStmt(SqlciEnv * the_sqlci_env)
{
sqlci_env = the_sqlci_env;
first_fragment = NULL;
packed_string = command = text = NULL;
text_pos = command_pos = text_maxlen = 0;
setInHistoryList(FALSE);
isIgnoreStmt_ = 0;
ignoreJustThis_ = FALSE;
veryFirstLine_ = FALSE;
blockStmt_ = 0;
shellCmd_ = 0;
allowCSinsqlci_ = !!getenv("ALLOW_CS_IN_SQLCI");
} // ctor
// Copy some context from source, but mostly copy a previously packed string
// into this, and retrofit a single fragment from it.
// This may make long, hard-to-FC stmts, but that's just too bad.
InputStmt::InputStmt(const InputStmt * source, const char * packed)
{
sqlci_env = source->sqlci_env; // save environment
setInHistoryList(FALSE);
isIgnoreStmt_ = 0;
ignoreJustThis_ = FALSE;
veryFirstLine_ = FALSE;
blockStmt_ = 0;
shellCmd_ = 0;
allowCSinsqlci_ = !!getenv("ALLOW_CS_IN_SQLCI");
size_t len = strlen(packed) + 1;
first_fragment = new StringFragment();
first_fragment->fragment = new char[len];
first_fragment->next = NULL;
strcpy(first_fragment->fragment, packed);
packed_string = command = text = NULL;
text_pos = command_pos = text_maxlen = 0;
// The packed string likely comes from the FC command, which has chopped up
// multiple stmts like "abc; def;" into "abc;" and " def;".
// We'll explicitly repack now to trim initial whitespace.
pack();
} // ctor
InputStmt::~InputStmt()
{
delete [] packed_string;
if (command)
delete [] command;
// delete text; // text points into memory owned by someone else
StringFragment * curr_fragment = first_fragment;
StringFragment * prev_fragment;
while (curr_fragment)
{
prev_fragment = curr_fragment;
delete [] curr_fragment->fragment;
curr_fragment = curr_fragment->next;
delete prev_fragment;
}
} // dtor
void InputStmt::operator=(const InputStmt * source)
{
sqlci_env = source->sqlci_env;
first_fragment = new StringFragment();
first_fragment->fragment = new char[strlen(source->first_fragment->fragment) + 1];
strcpy(first_fragment->fragment, source->first_fragment->fragment);
first_fragment->next = 0;
StringFragment * source_fragment = source->first_fragment->next;
StringFragment * target_fragment;
StringFragment * cur_fragment = first_fragment;
while (source_fragment)
{
// Create a new fragment from the source
target_fragment=new StringFragment();
target_fragment->fragment = new char[strlen(source_fragment->fragment)+ 1];
strcpy(target_fragment->fragment, source_fragment->fragment);
target_fragment->next = 0;
// Link the new fragment in the chain
cur_fragment->next = target_fragment;
// ... and set up to add the next
source_fragment = source_fragment->next;
cur_fragment = cur_fragment->next;
}
} // operator=()
////////////////////////////////////////////////////////
// Macros for input
////////////////////////////////////////////////////////
// Make sure that eof on stdin not treated as session <EOF>.
// The following is required, probably because of a bug in
// Centerline's iostream implementation. It should automatically
// clear error on stdin if sync_with_stdio has been called for
// this process.
// The cout gets us to a new line past the ^D (or ^Y);
// it does not go into the sqlci log file.
#define CLEAR_STDIN_EOF \
{ \
cin.clear(); \
clearerr(stdin); \
cout << endl; \
}
volatile char Sqlci_PutbackChar = '\0'; // global for Break handling
////////////////////////////////////////////////////////
// Returns:
// 1, if string is fine, but no semicolon seen yet
// 0, if string is fine, and semicolon-terminated
// -1, invalid string
// -2, eof (^Z or F6 on NT, ^D on Unix, ^Y on NSK)
// -20, break (^C)
// -3, error
// -4, if an eof immediately follows a valid string
////////////////////////////////////////////////////////
Int32 InputStmt::getLine(char * input_str, FILE * nonstdin, Int32 first_line)
{
char a = ' ';
size_t i = 0;
Int32 retcode = 1;
Int32 white = -1;
Int32 skipGetc = 0;
Int32 quick_eof = 0; // flag for a tight eof
size_t normalstart = 0;
enum InputState
{
INITIAL, INITIAL_WHITESPACE, DOUBLE_QUOTE, SINGLE_QUOTE,
NORMAL, PROBABLY_COMMENT, PROBABLY_INITIAL_COMMENT, CONSUME,
NORMAL_OR_SHELL, EOL, EOSTMT // , ESCAPE_CHAR
};
InputState state = INITIAL;
if (sqlci_env->prevErrFlushInput() && !nonstdin)
{
while (a != '\n')
{
cin.get(a);
}
state = EOL;
sqlci_env->resetPrevErrFlushInput();
}
while (state != EOL)
{
if (!nonstdin)
{
if (Sqlci_PutbackChar)
{
a = Sqlci_PutbackChar;
Sqlci_PutbackChar = '\0';
}
else
{
cin.get(a);
if (cin.eof())
{
Sleep(50); // give control to Break thre
Sqlci_PutbackChar = '\0';
return -2;
}
Sqlci_PutbackChar = '\0';
if (!cin)
return -3;
}
}
else
{
if (sqlci_env->prevErrFlushInput()&& !sqlci_env->eolSeenOnInput())
{
while ((a != '\n'&& a != '\t' && a != '-') && !feof(nonstdin))
{
a = getc(nonstdin);
}
sqlci_env->resetPrevErrFlushInput();
if (a == '\t' || a == '-')
skipGetc = -1;
}
if (!skipGetc)
a = getc(nonstdin);
skipGetc = 0;
// if after a statement, there is no '\n'
// between the statement and the eof.
if (feof(nonstdin) && state == EOSTMT)
{
quick_eof = 1; // set a flag
a = '\n'; // replace the eof with a '\n'
state = EOL; // process the statement
}
else
if (feof(nonstdin))
return -2;
else if (ferror(nonstdin))
return -3;
}
// Error: we're about to overrun the buffer!
if (i > MAX_FRAGMENT_LEN)
{
retcode = SqlciEnv::MAX_FRAGMENT_LEN_OVERFLOW;
state = CONSUME;
}
switch (state)
{
case INITIAL_WHITESPACE:
// NOTE this must precede the INITIAL case! Here's why:
//
// If the cur character is whitespace do nothing. Simply break out of
// the case body, continuing as state INITIAL_WHITESPACE.
// However, if it is not a whitespace character, then this state is
// actually the INITIAL state. Continue processing as if we were
// in the INITIAL state. NOTE the absence of break in the ELSE part
// of the IF statement.
shellCmd_ = 0;
if (a == ' ' || a == '\t')
break;
else
state = INITIAL; // fall thru to INITIAL, next
case INITIAL:
{
switch (a)
{
case ' ':
case '\t':
state = INITIAL_WHITESPACE;
break;
case '?':
case '#':
if (first_line) // ?SECTION, #IGNORE
{
retcode = 0;
state = CONSUME;
}
break;
case '-':
state = PROBABLY_INITIAL_COMMENT;
break;
case '\'':
state = SINGLE_QUOTE;
break;
case '"':
state = DOUBLE_QUOTE;
break;
case '\n':
state = EOL;
break;
case ';':
if (!inBlockStmt()) {
retcode = 0;
state = EOSTMT;
}
else {
state = NORMAL; normalstart = i;
}
break;
case 's':
case 'S':
if (first_line) {
state = NORMAL_OR_SHELL;
normalstart = i;
break;
}
// otherwise fall through to default case
default:
state = NORMAL; normalstart = i;
break;
} // switch (a)
} // case INITIAL
break;
case SINGLE_QUOTE:
{
switch (a)
{
case '\n':
retcode = -1;
state = EOL;
break;
case '\'':
state = NORMAL; normalstart = i;
break;
}
}
break;
case DOUBLE_QUOTE:
{
switch (a)
{
case '\n':
retcode = -1;
state = EOL;
break;
case '"':
state = NORMAL; normalstart = i;
break;
}
}
break;
case CONSUME:
if (retcode == SqlciEnv::MAX_FRAGMENT_LEN_OVERFLOW)
{
while (a != ';')
{
cin.get(a);
}
a = ' ';
cin.get(a);
if (a != '\n')
sqlci_env->setEol(0);
state = EOL;
}
if (a == '\n')
{
state = EOL;
}
else if (shellCmd_)
{
// check whether the shell command is terminated
// by a semicolon and adjust retcode
if (a == ';')
retcode = 0;
else
retcode = 1;
}
break;
case EOSTMT:
{
if (a != '\n')
{
sqlci_env->setEol(0);
if (!nonstdin)
{
/////////////// This doesn't work on all platforms (cin.putback fails)
// cin.putback(a);
// if (!cin)
// return -3;
/////////////// So we use this little kludge instead:
Sqlci_PutbackChar = a; // kludge
}
else
{
if (ungetc(a, nonstdin) == EOF)
return -3;
}
}
state = EOL;
}
break;
case PROBABLY_COMMENT:
case PROBABLY_INITIAL_COMMENT:
if (a == '-')
{
if (state == PROBABLY_INITIAL_COMMENT && first_line)
retcode = 0;
state = CONSUME;
break;
}
else
{
normalstart = i;
state = NORMAL; // fall thru to NORMAL, next
}
case NORMAL:
case NORMAL_OR_SHELL:
{
Int32 searchForShCmd = (state == NORMAL_OR_SHELL);
switch (a)
{
case '-':
state = PROBABLY_COMMENT;
findBlockStmt(input_str, normalstart, i-1, searchForShCmd);
break;
case '\'':
state = SINGLE_QUOTE;
findBlockStmt(input_str, normalstart, i-1, searchForShCmd);
break;
case '"':
state = DOUBLE_QUOTE;
findBlockStmt(input_str, normalstart, i-1, searchForShCmd);
break;
case '\n':
state = EOL;
findBlockStmt(input_str, normalstart, i-1, searchForShCmd);
break;
case ';':
findBlockStmt(input_str, normalstart, i-1, searchForShCmd);
if (!inBlockStmt())
{
state = EOSTMT;
retcode = 0;
}
else
normalstart = i;
break;
}
if (shellCmd_)
state = CONSUME; // just consume to end of line for sh command
}
break;
case EOL:
// if there is an eof immediately after a statement
// change the return code to reflect it
if (quick_eof) retcode = -4;
break;
} // switch
// Change '\n' to terminating null.
// Check if non-whitespace non-semicolon character.
// If interactive, then "FC" command is operative, so
// change tabs to spaces so FC aligns correctly;
// if not interactive, leave tabs as is for pretty formatting
// of echoed/logged lines.
if (state == EOL)
{
if (a == '\n')
sqlci_env->setEol(-1);
a = '\0';
}
else if (a != ' ' && a != '\t' && a != ';') // as in InputStmt::isEmpty
white = 0;
else if (a == '\t' && sqlci_env->isInteractiveNow())
if (state == INITIAL ||
state == INITIAL_WHITESPACE ||
state == NORMAL ||
state == DOUBLE_QUOTE) // see NOTE in pack() below!
a = ' '; // so "FC" command works
input_str[i++] = a;
// reset 'a' to prevent a loop caused that could be caused when a previous
// statment in a multi-stmt input line cause an error. In the previous error
// process, the tabs are maintained.
a = ' ';
} // while state != EOL
// A first line of any number of whitespaces followed by ";" or '\n',
// or a continuation line of whitespaces with no ";" (only '\n'),
// is trimmed to the equivalent empty string.
if (retcode >= 0 && white)
if (retcode > 0 || first_line)
input_str[0] = '\0';
return retcode;
} // getLine()
Int32 InputStmt::consumeLine(FILE * nonstdin)
{
//char input_str[MAX_FRAGMENT_LEN+1];
char * input_str = new char[MAX_FRAGMENT_LEN+1];
Int32 rc = getLine(input_str, nonstdin, -1/*first line*/);
delete [] input_str;
return rc;
}
////////////////////////////////////////////////////////
// Returns:
// 0, if string is fine, and semicolon-terminated
// -1, invalid string
// -2, eof (^Z or F6 on NT, ^D on Unix, ^Y on NSK)
// -20, non-first-line interactive eof (used by FixCommand::process)
// OR break (^C)
// -99, error
////////////////////////////////////////////////////////
Int32 InputStmt::readStmt(FILE * nonstdin, Int32 suppress_blank_line_output)
{
// char input_str[MAX_FRAGMENT_LEN+1];
char * input_str = new char[MAX_FRAGMENT_LEN+1];
Int32 prompt = sqlci_env->isInteractiveNow();
Int32 skip_first_fragment = 0;
Int32 error = 0;
StringFragment * prev_fragment = NULL;
Int32 done = 0;
//char buffer[256];
//SYSTEMTIME sysTime;
// If called by FixCommand, with a partially completed statement,
// blip past the get-first-line do-loop.
if (first_fragment)
{
skip_first_fragment = 1;
delete packed_string;
packed_string = NULL;
}
else
{
// Get the first line, looping until we get a nonblank line
do
{
Int32 eolSeenOrig = sqlci_env->eolSeenOnInput();
if ((prompt && eolSeenOrig) || (sqlci_env->prevErrFlushInput() && !nonstdin))
{
cout << ">>";
//cout << ">>" << endl;
}
error = getLine(input_str, nonstdin, -1/*first line*/);
sqlci_env->resetPrevErrFlushInput();
// Let NSK ESC be equivalent of NT ^Z or F6, i.e. EOF
// (at least over a Telnet connection,
// although unfortunately via OutsideView emulator).
const char ESC = 27; // ASCII ESCAPE character
switch (error)
{
case 1 : // no ";" seen yet, keep looking
{
// if this is an 'fc' or 'e' or 'exit' command,
// don't require a terminating semicolon (';').
if ((! sqlci_env->isInteractiveNow()) ||
(input_str[0] == '\0'))
break;
// skip leading blanks
UInt32 len = strlen(input_str);
UInt32 i = 0;
while ((i < len) && (input_str[i] == ' '))
{
i++;
}
// 'i' cannot be equal to 'len', as that will make it a blank
// line and we will not be in this 'case'.
// if input string doesn't start with an 'e'(xit) or an 'f'(c),
// break. It cannot be an exit or fc command.
if ((input_str[i] != 'f') &&
(input_str[i] != 'F') &&
(input_str[i] != 'e') &&
(input_str[i] != 'E'))
break;
// find the next blank or end of line, whichever comes first.
UInt32 k = i + 1;
while ((k < len) && (input_str[k] != ' '))
k++;
UInt32 cmdLen = k - i;
char cmd[10];
// 4 is len of 'exit', non-semicolon terminating commands cant
// be more than 4 chars in length.
if (cmdLen > 4)
break;
// copy upshifted 'cmdLen' chars into 'cmd' buffer.
UInt32 j = 0;
for (j = 0; j < cmdLen; j++, i++)
cmd[j] = (char)toupper(input_str[i]);
cmd[j] = 0;
if ((strcmp(cmd, "FC") == 0) ||
(strcmp(cmd, "E") == 0) ||
(strcmp(cmd, "EXIT") == 0))
{
// found a non-semicolon terminating command.
// If an 'E' is seen, change it to 'EXIT'. Sqlci
// parser only recognizes an 'EXIT'.
if (strcmp(cmd, "E") == 0)
strcpy(input_str, "EXIT");
// Append a semicolon to this input.
strcat(input_str, ";");
error = 0;
}
}
break;
case 0 : // normal input string
case -4 : // statement followed immediately with eof
break;
case -1 : // bad string -- unmatched quote
// call syntaxErrorOnMissingQuote() at end, after all frags saved
break;
case -2 : // eof (^Z) on first line of stmt (i.e. before the line)
if (prompt) cout << endl;
sqlci_env->setEol(-1); // Reset to initial eol state
goto return_error; // return error;
case -20 : // break (^C) on first line of stmt
if (prompt) CLEAR_STDIN_EOF;
sqlci_env->setEol(-1); // Reset to initial eol state
// return error;
goto return_error; // return error;
case -3 : // error while reading input / obey file.
{
ErrorParam *p1 = new ErrorParam (errno);
SqlciError (SQLCI_ERROR_READING_FILE,
p1,
(ErrorParam *) 0);
delete p1;
//return -99;
error = -99;
goto return_error; // return error;
}
case SqlciEnv::MAX_FRAGMENT_LEN_OVERFLOW :
SqlciError (SQLCI_FRAGMENT_LEN_REACHED,
(ErrorParam *) 0);
// return error;
goto return_error; // return error;
default : // this case should never be reached
{
SqlciError (SQLCI_INTERNAL_ERROR, (ErrorParam *) 0);
// return error;
goto return_error; // return error;
}
}
// Log an all-blank line here simply as a prompt;
// also, if the input is not from terminal or
// input is from an obey file, display the blank line on stdout
// (logging and echoing of non-blank lines is done separately, in
// SqlciEnv::executeCommands, Obey::process,
// Repeat::process, FixCommand::process).
// Except don't log trailing blanks in a multi-stmt line, e.g.
// log "abc;def; " as "abc;" and "def;" but not the final "".
if (input_str[0] == '\0' && !suppress_blank_line_output)
if (eolSeenOrig || eolSeenOrig == sqlci_env->eolSeenOnInput())
{
if (!prompt)
cout << ">>" << endl;
if (sqlci_env->get_logfile()->IsOpen())
sqlci_env->get_logfile()->Write (">>", 2);
}
}
while (input_str[0] == '\0'); // ignore any number of whitespaces
// followed by ";" or '\n'
}
do
{
if (skip_first_fragment)
{
// We already have (from FC) the first fragment(s).
// No need to append an empty one and cause a blank line in history.
skip_first_fragment = 0;
prev_fragment = first_fragment;
while (prev_fragment->next)
prev_fragment = prev_fragment->next;
error = 1; // getLine code for no semicolon seen
}
else
{
StringFragment * new_fragment = new StringFragment();
new_fragment->fragment = new char[strlen(input_str) + 1];
strcpy(new_fragment->fragment, input_str);
new_fragment->next = 0;
if (!first_fragment)
first_fragment = new_fragment;
else
prev_fragment->next = new_fragment;
prev_fragment = new_fragment;
}
if (error == 1) // no ";" seen yet, keep looking
{
if (prompt)
cout << "+>";
error = getLine(input_str, nonstdin, 0/*not first line*/);
// cerr << "##getLine: " << error << " " << cin.eof() << " " << prompt << endl;
switch (error)
{
case 0 : // normal input string
case 1 : // no ";" seen yet, keep looking
case -4 : // end of file
break;
case -1 : // bad string -- unmatched quote
// call syntaxErrorOnMissingQuote() at end, after all frags saved
break;
case -2 : // end of file
// Reset to initial eol state
sqlci_env->setEol(-1);
// On a non-first line, EOF only means get back to the main
// prompt without processing this command. It does not mean exit
// from sqlci. So now make sure that this condition not treated
// as session EOF, by clearing stdin and by returning a different
// return code, signifying a "special" EOF (one to be logged and
// historied, but not executed).
if (prompt)
{
CLEAR_STDIN_EOF;
error *= 10; // -20
}
// return error;
goto return_error; // return error;
case -3 : // error while reading input / obey file.
{
ErrorParam *p1 = new ErrorParam (errno);
SqlciError (SQLCI_ERROR_READING_FILE,
p1,
(ErrorParam *) 0);
delete p1;
error = -99; //return -99;
goto return_error; // return error;
}
case SqlciEnv::MAX_FRAGMENT_LEN_OVERFLOW :
SqlciError (SQLCI_FRAGMENT_LEN_REACHED,
(ErrorParam *) 0);
// return error;
goto return_error; // return error;
default : // this case should never be reached
SqlciError (SQLCI_INTERNAL_ERROR, (ErrorParam *) 0);
// return error;
goto return_error; // return error;
}
}
else
done = -1;
}
while (!done);
if (error == -1) // unmatched quote
syntaxErrorOnMissingQuote();
return_error:
if (input_str)
delete [] input_str;
return error;
} // readStmt()
////////////////////////////////////////////////////////
// Returns 0 if no error, nonzero if mismatched quotes.
// Always sets packed_string to a non-NULL string
// (it may be the empty string, however).
////////////////////////////////////////////////////////
Int32 InputStmt::pack()
{
StringFragment * curr_fragment = first_fragment;
size_t packed_string_len = 0;
size_t i = 0;
char quote_seen = 0;
size_t j;
while (curr_fragment)
{
packed_string_len += strlen(curr_fragment->fragment);
packed_string_len += 1; // for a blank space between continuing lines
curr_fragment = curr_fragment->next;
}
delete packed_string;
packed_string = new char[packed_string_len + 1];
if (packed_string_len)
{
// If an obey file is encoded in UTF8 encoding, the first 3 characters must be ignored.
if(veryFirstLine_)
{
veryFirstLine_ = FALSE;
char c0 = first_fragment->fragment[0];
char c1 = first_fragment->fragment[1];
char c2 = first_fragment->fragment[2];
if(c0 == (char)0xef )
if(c1 == (char)0xbb )
if (c2 == (char)0xbf)
{
first_fragment->fragment[0] = ' ';
first_fragment->fragment[1] = ' ';
first_fragment->fragment[2] = ' ';
}
}
/* now pack it*/
curr_fragment = first_fragment;
Int32 ignore = 0;
Int32 first = -1;
// Save info in private data members isIgnoreStmt_ + ignoreJustThis_
isIgnoreStmt_ = isIgnoreStmt(curr_fragment->fragment, &ignoreJustThis_);
if (isIgnoreStmt_ || ignoreJustThis_)
{
// first statement is an ?ignore
size_t fraglen = strlen(curr_fragment->fragment);
for (j = 0; j < fraglen; j++)
{
packed_string[i++] = curr_fragment->fragment[j];
}
}
else
{
while (curr_fragment)
{
Int32 skip_it = 0;
// For subsequent frags, use local vars for isIgnoreStmt stuff!
NABoolean ignoreJustThis;
Int32 isIgnore =
isIgnoreStmt(curr_fragment->fragment, &ignoreJustThis);
if (isIgnore)
ignore = NOT ignore;
if (!ignore && !isIgnore && !ignoreJustThis)
{
j = 0;
size_t fraglen = strlen(curr_fragment->fragment);
if (first)
{
// Get past initial whitespace so that strcmp in
// history buffer lookup will find a match
// ("!cmd" command calling SqlciStmts::get).
// Also get past initial semicolons which user can
// input via FC cmd.
for ( ; j < fraglen; j++)
{
char c = curr_fragment->fragment[j];
if (c != ' ' && c != '\t' && c != ';')
break;
else if (sqlci_env->isInteractiveNow())
curr_fragment->fragment[j] = ' ';
}
if (j < fraglen)
first = 0;
}
for ( ; j < fraglen; j++)
{
switch (curr_fragment->fragment[j])
{
case '\'':
case '"':
if (!skip_it)
{
// Remember which type of quote (' or ") was
// seen first, and only reset when its matching
// quote is seen.
if (!quote_seen)
quote_seen = curr_fragment->fragment[j];
else if (quote_seen == curr_fragment->fragment[j])
quote_seen = 0;
}
break;
case '&':
if (!skip_it && !quote_seen)
{
/* do something. later. */
}
break;
case '-':
if (!quote_seen)
{
// comment, skip till eol
if (curr_fragment->fragment[j+1] == '-')
skip_it = -1;
}
break;
default:
break;
}
if (!skip_it)
{
// Always convert unquoted tabs to spaces
// in the packed string, so parser syntax error
// messages will be aligned properly
// (cf. getLine, we converted only if interactive,
// for the FC command).
//
char a = curr_fragment->fragment[j];
/*****
if (a == '\t' && !quote_seen)
a = ' ';
packed_string[i++] = a;
******
The above test is commented out because
some Common code disallows tabs in
delimited identifiers (dquotes (")),
which is correct per Ansi
(although parts of SqlParser.y try to allow it,
but unfortunately tries inconsistently).
So let's just convert them all (within dquotes)
to spaces, so FC and syntax align is better yet.
NOTE: we do this conversion for DOUBLE_QUOTE
in getLine above too.
*****/
if (a == '\t' && quote_seen != '\'')
{
a = ' ';
if (sqlci_env->isInteractiveNow())
curr_fragment->fragment[j] = ' ';
}
packed_string[i++] = a;
}
}
if (quote_seen) // unmatched quote on this line (this frag)
{
// Commented out: do not break!
// instead pack all the frags
// whether good or not, so FC can repair them.
// break;
// If unterminated quoting, convert any tabs on right of
// the quote to spaces (for FC command alignment -- which
// works on frags, not the packed string, so don't bother
// converting the latter).
char * frag = curr_fragment->fragment;
for (j = strlen(frag); j>0 && frag[j] != quote_seen; j--)
if (frag[j] == '\t')
frag[j] = ' ';
}
}
curr_fragment = curr_fragment->next;
if (curr_fragment && i)
packed_string[i++] = ' '; // insert a blank separator.
}
}
}
packed_string[i] = '\0';
if (isEmpty())
packed_string[0] = '\0';
// Or, delete the string and allocate a new char[1] for it, containing '\0'
// (don't just delete it and set it to NULL; that would force a re-pack
// when getPackedString was next called).
return quote_seen;
} // pack()
//////////////////////////////////////////
// Returns -1 if stmt (or passed string) contains only zero or more whitespaces
// and optionally a semicolon (" ", " ", ";", " ;", " ; ", etc).
//////////////////////////////////////////
Int32 InputStmt::isEmpty(const char *s)
{
Int32 empty = -1;
if (!s)
s = getPackedString();
for ( ; *s; s++)
if (*s != ' ' && *s != '\t' && *s != ';')
{
empty = 0;
break;
}
return empty;
} // isEmpty()
//////////////////////////////////////////
// Return:
// - pointer to one past the semicolon terminating the passed string
// and quote_seen_pos always zero
// - or NULL if no terminating semicolon exists, in which case
// quote_seen_pos is nonzero position in the string if additionally
// no terminating quote was found (thus quote_seen_pos is ONE-based,
// not zero-based -- see syntaxErrorOnMissingQuote!).
//////////////////////////////////////////
char * InputStmt::findEnd(char * s, size_t &quote_seen_pos)
{
char *orig = s;
char quote_seen;
for (quote_seen=quote_seen_pos=0; ; s++)
switch (*s)
{
case '\'':
case '"':
if (!quote_seen)
{
quote_seen = *s;
quote_seen_pos = s - orig + 1; // +1: one-based position!
}
else if (quote_seen == *s)
quote_seen = quote_seen_pos = 0;
break;
case ';':
if (!quote_seen)
return ++s;
break;
case '\0':
return NULL;
}
} // findEnd
//////////////////////////////////////
void InputStmt::syntaxErrorOnMissingQuote(char * str)
{
if (!str)
str = getPackedString();
SqlciError (SQLCI_INPUT_MISSING_QUOTE, (ErrorParam *) 0);
size_t quote_pos = 0;
findEnd(str, quote_pos);
// subtract one from quote_pos to convert 1-based offset to 0-based
StoreSyntaxError(str, --quote_pos, sqlci_DA);
sqlci_parser_syntax_error_cleanup(NULL,sqlci_env);
} // syntaxErrorOnMissingQuote
//////////////////////////////////////
// Append a bogus string ("!" is an illegal SQL_TEXT character)
// to a copy of the unterminated string
// so the parser is guaranteed to exit with a syntax error message.
// Note that since this is to be called only on EOF,
// we assume this InputStmt will not be saved on the history list
// so there's no need to append <bangeof> to its fragment list and repack it.
//////////////////////////////////////
void InputStmt::syntaxErrorOnEof(const char * str)
{
// We must embed a space in obeybangeof, else an unterminated OBEY cmd
// will hang forever (because of the sqlci lexer's special <FNAME> state,
// called by sqlci_parser_syntax_error_cleanup).
#define bangeof " !EOF!"
#define obeybangeof " !EOF EOF!"
if (!str)
str = getPackedString();
char * err;
err = new char[strlen(str) + strlen(bangeof) + 2]; // +2: \n, \0
strcpy(err,"\n");
strcat(err,str);
strcat(err,bangeof);
ErrorParam *p1 = new ErrorParam (err);
SqlciError (SQLCI_INPUT_PREMATURE_EOF,
p1,
(ErrorParam *) 0
);
delete p1;
delete [] err;
} // syntaxErrorOnEof
void InputStmt::display(UInt16 Distinguish_arg, NABoolean noPrompt) const // 64-bit
{
display((Lng32) 0, noPrompt); // 64-bit
}
void InputStmt::display(Lng32 stmt_num_, NABoolean noPrompt) const
{
if (!first_fragment) return;
char pfxbuf[20] = "";
Int32 pfxlen;
if (!noPrompt)
{
if (stmt_num_ <= 0)
cout << ">>";
else
{
sprintf(pfxbuf, "%d> %n", stmt_num_, &pfxlen);
cout << pfxbuf;
memset(pfxbuf, ' ', pfxlen);
pfxbuf[pfxlen] = '\0';
}
}
cout << first_fragment->fragment;
StringFragment * fragment = first_fragment->next;
while (fragment)
{
cout << endl;
if (stmt_num_ <= 0)
cout << "+>";
else
cout << pfxbuf;
cout << fragment->fragment;
fragment = fragment->next;
}
cout << endl;
} // display()
void InputStmt::logStmt(NABoolean noPrompt) const
{
if (!sqlci_env->get_logfile()->IsOpen()) return;
StringFragment * fragment = first_fragment;
if (!first_fragment) return;
while (fragment)
{
char * log_str = new char[2 /* to display sqlci prompt */ +
strlen(fragment->fragment)];
size_t start = 0;
if (!noPrompt)
{
start = 2;
if (fragment == first_fragment)
strncpy(log_str, ">>", 2);
else
strncpy(log_str, "+>", 2);
}
size_t fraglen = strlen(fragment->fragment);
strncpy(&log_str[start], fragment->fragment, fraglen);
sqlci_env->get_logfile()->Write(log_str, start + fraglen);
delete [] log_str;
fragment = fragment->next;
}
} // logStmt()
InputStmt::Option InputStmt::nextOption()
{
Option option;
switch (command[command_pos])
{
case 'i':
case 'I':
option = INSERT_O;
command_pos += 1;
break;
case 'd':
case 'D':
option = DELETE_O;
command_pos += 1;
break;
case 'r':
case 'R':
option = EXPLICIT_REPLACE_O;
command_pos += 1;
break;
case ' ':
option = ADVANCE_O;
command_pos += 1;
break;
case '/':
{
if (command[command_pos+1] == '/')
{
if ((command_pos == 0) && (command[command_pos+2] == 0))
option = ABORT_O;
else
{
option = END_O;
command_pos += 2;
}
}
else
{
option = REPLACE_O;
}
}
break;
case 0:
{
if (command_pos == 0)
option = DONE_O;
else
option = AGAIN_O;
}
break;
default:
option = REPLACE_O;
break;
}
return option;
} // nextOption()
size_t InputStmt::getCommandLen() const
{
Int32 done = 0;
size_t i = 0;
while (!done)
{
if (command[command_pos + i] == 0)
done = -1;
else
if ((command[command_pos+i] == '/') &&
(command[command_pos+i+1] == '/'))
done = -1;
else
i++;
}
return i;
} // getCommandLen()
void InputStmt::processInsert()
{
size_t command_len = getCommandLen();
if ((command_len > 0) && (text_maxlen > 0))
{
size_t j = text_maxlen - 1;
while (j >= text_pos)
{
text[j+command_len] = text[j];
if (j-- == 0) break;
}
str_cpy_all(&text[text_pos], &command[command_pos], command_len);
command_pos += command_len;
text_pos += 2 * command_len + 1;
text_maxlen += command_len;
if (text_pos > text_maxlen)
text_maxlen = text_pos;
}
} // processInsert()
void InputStmt::processReplace()
{
size_t command_len = getCommandLen();
if (command_len > 0)
{
str_cpy_all(&text[text_pos], &command[command_pos], command_len);
command_pos += command_len;
text_pos += command_len;
if (text_pos > text_maxlen)
text_maxlen = text_pos;
}
} // processReplace()
void InputStmt::processDelete()
{
size_t j = text_pos;
if (j < text_maxlen)
{
while (j < text_maxlen)
{
text[j] = text[j+1];
j++;
}
text_maxlen -= 1;
}
} // processDelete()
//////////////////////////////////////////////////////
// Member "text" is used to communicate with the above processXxx methods;
// the resulting string is returned to caller in parameter "fixed_data",
// not in "text" ("text" not used elsewhere).
// (Member "command" and the "pos/len" members -- same for them.)
//////////////////////////////////////////////////////
InputStmt::Option InputStmt::fix_string(const char * in_data,
char * fixed_data,
size_t data_maxlen)
{
command = new char[data_maxlen + 1];
command_pos = 0;
text_pos = 0;
text = fixed_data;
text_maxlen = strlen(in_data);
str_pad(text, data_maxlen, ' ');
str_cpy_all(text, in_data, strlen(in_data));
char c;
Option option;
cout << ">>" << in_data << endl;
cout << "..";
if (cin.peek() != '\n')
cin.get(command, data_maxlen, '\n');
else
command[0] = '\0';
if (cin.eof())
{
// Abort the "FC", not the whole SQLCI session
CLEAR_STDIN_EOF;
option = ABORT_O;
}
else
{
// consume the eol ('\n')
cin.get(c);
option = EMPTY_O;
}
while ((option != DONE_O) && (option != ABORT_O))
{
option = nextOption();
switch (option)
{
case INSERT_O:
processInsert();
break;
case REPLACE_O:
processReplace();
break;
case EXPLICIT_REPLACE_O:
processReplace();
text_pos++;
break;
case DELETE_O:
processDelete();
break;
case ADVANCE_O:
text_pos += 1;
break;
case END_O:
text_pos += 2;
break;
case ABORT_O:
strncpy(text, in_data, strlen(in_data));
break;
case DONE_O:
text[text_maxlen] = 0;
break;
case AGAIN_O:
text[text_maxlen] = 0;
cout << ">>" << text << endl;
text[text_maxlen] = ' ';
cout << "..";
if (cin.peek() != '\n')
cin.get(command, data_maxlen, '\n');
else
command[0] = '\0';
if (cin.eof())
{
CLEAR_STDIN_EOF;
option = ABORT_O;
}
else
{
// consume the eol ('\n')
cin.get(c);
command_pos = 0;
text_pos = 0;
}
break;
default:
break;
}
}
delete [] command;
command = 0;
// delete text; // text points to fixed_data, memory owned by caller!
text = 0;
return option;
} // fix_string()
//////////////////////////////////////////
// return value: 0, execute new statement.
// -20, abort it.
// other error codes from readStmt()
//////////////////////////////////////////
Int32 InputStmt::fix(Int32 append_only)
{
Int32 retval = 0;
if (!append_only)
{
Option option;
StringFragment * fragment = first_fragment;
while (fragment)
{
char * new_fragment = new char[MAX_FRAGMENT_LEN + 1];
option = fix_string(fragment->fragment,new_fragment,MAX_FRAGMENT_LEN);
if (option == ABORT_O)
return -20; // caller is responsible for deleting this InputStmt
delete [] fragment->fragment;
fragment->fragment = new_fragment;
fragment = fragment->next;
}
}
else
{
retval = readStmt(NULL/*interactive only, so input is stdin*/);
}
// Pack (or repack) the new stmt.
pack();
return retval;
} // fix()
//////////////////////////////////////////
// -1, if input contains a "?section secname" statement
// and secname matches section_name. Or if no section
// name is specified in which case this is a wildcard
// section match.
// 0, otherwise.
//////////////////////////////////////////
// This will allow "?SECTION ABC" or "?SECTION ABC;" or "?SECTION ABC+FOO"
// (the ";" and the "+FOO" will be ignored -- section ABC will match).
// Likewise, either of "?SECTION" or "SECTION;" or "SECTION ;" is allowed.
// This will disallow "?SECTIONA" or "?SECTION1" or "?IGNOREB" or "?IGNORE_2".
//
// The rationale is that "?xxx" directives are not lexed/parsed by Sqlci,
// but "obey file(sect)" is, and "sect" allows only identifier characters.
#define IS_SECTION_NAME_CHAR(a) (isalnum(a) || a == '_')
Int32 InputStmt::sectionMatches(const char * section_name)
{
const char * str = getPackedString(); // any tabs have become spaces!
// (so no need to test below)
// Although "?SECTION" does not cause any ambiguities with a sqlci PARAM
// named "?SECTION", here we allow "#SECTION" for a symmetry of sorts with
// "#IGNORE" and "#IFDEF".
if (*str != '?' && *str != '#') // ?SECTION *or* #SECTION
return 0;
if (strlen(str) < 8)
return 0;
char tmp[10];
size_t i = 0;
for (i=0; i < 8; i++)
tmp[i] = toupper(str[i]);
tmp[i] = '\0';
tmp[0] = '?'; // #SECTION -> ?SECTION
if (strncmp(tmp, "?SECTION", 8) != 0)
return 0;
if (IS_SECTION_NAME_CHAR(str[8]))
return 0;
// We have so far matched "?SECTION"
if (!section_name) // wildcard match
return -1;
for (i = 8; str[i] == ' '; i++) ; // now skip whitespace to section name
size_t s = 0;
for (s = 0; IS_SECTION_NAME_CHAR(str[i]) && section_name[s]; i++, s++)
if (toupper(str[i]) != toupper(section_name[s]))
return 0;
if (IS_SECTION_NAME_CHAR(str[i]) || section_name[s])
return 0;
return -1;
} // sectionMatches
static NABoolean isIfdefStmtTransition(NAString &nsUpTrim, // toUpper+trimmed
NABoolean *ignoreJustThis)
{
enum { IFDEF_, IFNDEF_, ELSE_, ENDIF_ };
Int32 which;
char *str = (char *)nsUpTrim.data();
char *s, c;
for (s = str; *s && !isspace((unsigned char)*s); s++) ; // find end of 1st tok // For VS2003
c = *s;
*s = '\0';
if (strcmp(str, "IFDEF") == 0) which = IFDEF_;
else if (strcmp(str, "IFNDEF") == 0) which = IFNDEF_;
else if (strcmp(str, "ELSE") == 0) which = ELSE_;
else if (strcmp(str, "ENDIF") == 0) which = ENDIF_;
else return FALSE;
if (ignoreJustThis)
*ignoreJustThis = TRUE;
static const size_t MAX_IFDEF_NESTING_LEVEL = 20;
static size_t level = 0;
static NABoolean execute[MAX_IFDEF_NESTING_LEVEL+1];
execute[0] = TRUE;
if (which == IFDEF_ || which == IFNDEF_) {
if (level >= MAX_IFDEF_NESTING_LEVEL) {
++level;
return FALSE;
}
else if (!execute[level])
execute[++level] = FALSE;
else {
*s = c;
for ( ; isspace((unsigned char)*s) || *s == '$' || *s == '='; s++) ; // beg of 2nd tok // For VS2003
const char *env = *s ? getenv(s) : NULL;
NABoolean isdef = (env && *env && *env != '0');
NABoolean ifdef = (which == IFDEF_);
execute[++level] = (isdef == ifdef);
}
}
else if (which == ELSE_) {
if (level <= MAX_IFDEF_NESTING_LEVEL && level > 0)
if (execute[level-1]) {
execute[level] = NOT execute[level];
return TRUE;
}
}
else {
if (level <= MAX_IFDEF_NESTING_LEVEL && level > 0) {
NABoolean transition = (execute[level] != execute[level-1]);
--level;
return (transition);
}
}
if (level <= MAX_IFDEF_NESTING_LEVEL && level > 0)
return (execute[level] != execute[level-1]);
if (level == 0 && ignoreJustThis)
*ignoreJustThis = FALSE;
return FALSE;
} // isIfdefStmtTransition
//////////////////////////////////////////
// Checks for ?ignore, ?ifMX, ?ifNSKRel1
// and #ignore, #ifMX, #ifNSKRel1,
// which are equivalent, but the ? syntax allows a grammar ambiguity:
// SET PARAM ?ifmx 'abc';
// SELECT * FROM T WHERE
// ?ifmx
// = columnA;
// The ? syntax we continue to support until 100 to 200 regress
// tests+expected files (!) can be edited to change to the unambiguous # syntax.
//
// Notice that the new #ifdef, #ifndef, #else, #endif syntax
// supports ONLY the unambiguous form --
// ?ifdef will ALWAYS be interpreted as a sqlci PARAM.
//
// Function returns:
// -1, if input is ?IGNORE, or
// input is ?ifMX && we're on NSK Rel 1, or
// input is ?ifNSKRel1 && we're on either {NT or NSK Rel 2 or later}
// 0, otherwise -- in which case
// "ignoreJustThis" returns TRUE if and only if
// input is any of ?IGNORE or ?ifMX or ?ifNSKRel1.
//
// The caller is responsible for ignoring all input between stmts on which
// this function returns -1.
//////////////////////////////////////////
Int32 InputStmt::isIgnoreStmt(const char *str, NABoolean *ignoreJustThis)
{
if (!str) {
ComASSERT(packed_string);
if (ignoreJustThis)
*ignoreJustThis = ignoreJustThis_;
return isIgnoreStmt_;
}
if (ignoreJustThis)
*ignoreJustThis = FALSE;
if (*str != '?' && *str != '#')
return 0;
// These directives can only be used when running regressions. Regressions
// can be run by setting the SQLMX_REGRESS environment variable.
// If we are not running regressions, then execute all statements
// char *ev = getenv("SQLMX_REGRESS");
// if (!ev)
// return 0;
NAString ns(&str[1]);
TrimNAStringSpace(ns);
ns.toUpper();
NABoolean ignoreAll = FALSE;
NABoolean ignoreOnMX = FALSE;
NABoolean ignoreOnMP = FALSE;
NABoolean ignoreOnNT = FALSE;
NABoolean ignoreOnLINUX = FALSE;
NABoolean ignoreOnNSK = FALSE;
// #ifNT -> include the input stmt on NT or LINUX platform
// #ifLINUX -> include the input stme on LINUX platform
// #ifNSK -> include the input stme on NSK platform
if (ns == "IFMX") ignoreOnMP = TRUE;
else if (ns == "IFMP") ignoreOnMX = TRUE;
else if (ns == "IFNT") ignoreOnNSK = TRUE;
else if (ns == "IFNTNOTLINUX") { ignoreOnNSK = TRUE; ignoreOnLINUX = TRUE; }
else if (ns == "IFNSK") { ignoreOnNT = TRUE; ignoreOnLINUX = TRUE; }
else if (ns == "IFLINUX") { ignoreOnNSK = TRUE; ignoreOnNT = TRUE; }
else if (ns == "IGNORE") ignoreAll = TRUE;
else if (*str != '#') // Only the #xxxx form is acceptable
return 0; // for all future keywords we add!
else if (isIfdefStmtTransition(ns, ignoreJustThis))
return -1;
else
return 0;
if (ignoreJustThis)
*ignoreJustThis = TRUE;
// We are on an NSK system
if (ignoreOnLINUX || ignoreOnMX)
ignoreAll = TRUE;
return (ignoreAll ? -1 : 0);
} // isIgnoreStmt
//////////////////////////////////////////
// Block statement handler.
//////////////////////////////////////////
void InputStmt::findBlockStmt
(char *s, // (IN) : a "NORMAL" string
size_t xbeg, // (IN) : beginning index of NORMAL string in s
size_t xend, // (IN) : ending index of NORMAL string in s
NABoolean searchForShellCmd) // (IN) : recognize shell commands
// requires: s[xbeg..xend] is a "NORMAL" string, ie, a string of characters
// read by getLine from the NORMAL state
// modifies: blockStmt_, shellCmd_
// effects : scans s for compound statements and updates blockStmt_ to
// record the number of unterminated compound statements.
// NB: scan must avoid the pitfalls of genesis case 10-990419-8752, viz:
// create table t(
// if int, while int
// );
// must not cause sqlci to hang. Furthermore, single-line compound
// statements like
// begin insert into t values(1,2); end;
// must not cause false syntax errors in arkcmp (due to a premature
// truncation of the statement at the first ";").
// We recognize only the following compound statements:
// begin ... end;
// case ... end [case];
// if ... then ... end if;
// "begin ... end" is a compound statement but "begin work" is not.
// "begin work" cannot straddle a line.
// "end if" cannot straddle a line.
// We must also handle the following compound statement correctly
// begin;
// delete from t;
// insert into t
// select * from t sample first
// balance when a < 10 then 5 rows
// when a >=10 then 5 rows
// END sort by a;
// end;
// Specifically, the END of a data-mining sample first balance expr
// must not cause a false syntax error due to a premature end of the
// compound statment. To do this, we match "balance when" with its
// "end". But, in doing so, we must not be tripped by "balance when" in
// select case balance when 1 then 10 else 7 from t;
// (Isn't parsing fun?)
// Other compound statements that may be supported in the future include
// loop ... end loop;
// while ... end while;
// repeat ... until ... end repeat;
// for ... end for;
{
// do nothing if compound statements are not allowed in sqlci
if (!allowCSinsqlci_) return;
static const char* tokenDelimiter=" \t\n\r\f\v(),+-*/;<>=|[]";
if (xbeg < xend) {
size_t len = xend-xbeg+1;
char *p, *tempStr = new char[len+1];
if (tempStr) {
strncpy(tempStr, &s[xbeg], len);
tempStr[len] = 0;
p = strtok(tempStr, tokenDelimiter);
while (p) {
if (shellCmd_) {
// don't search for block commands inside a shell command
}
else if (searchForShellCmd &&
(_stricmp(p, "SH")==0 || _stricmp(p, "SHELL")==0)) {
shellCmd_ = 1;
}
else if (_stricmp(p, "BEGIN")==0) {
if ((p=strtok(NULL, tokenDelimiter)) != NULL &&
_stricmp(p, "WORK")==0) {
// fall thru & consume "BEGIN WORK" as one statement
}
else {
// consume "BEGIN" as a compound stmt token
blockStmt_++;
continue; // examine next token
}
}
else if (_stricmp(p, "IF")==0)
blockStmt_++;
else if (_stricmp(p, "CASE")==0) {
blockStmt_++;
// consume any "BALANCE WHEN" here as ordinary tokens
if ((p=strtok(NULL, tokenDelimiter)) != NULL &&
_stricmp(p, "BALANCE")==0)
p = strtok(NULL, tokenDelimiter);
continue;
}
else if (_stricmp(p, "END")==0) {
blockStmt_--;
if ((p=strtok(NULL, tokenDelimiter)) != NULL &&
_stricmp(p, "IF")==0) {
// fall thru & consume "END IF" as one token
}
else if (p != NULL && _stricmp(p, "CASE")==0) {
// fall thru & consume "END CASE" as one token
}
else {
// consume "END" as a token & examine next token
continue;
}
}
else if (_stricmp(p, "BALANCE")==0) {
if ((p=strtok(NULL, tokenDelimiter)) != NULL &&
_stricmp(p, "WHEN")==0) {
// "BALANCE WHEN" starts a new nesting level
blockStmt_++;
}
else {
// "BALANCE" is an ordinary identifier
continue; // examine next token
}
}
// the shell command must be the first token
searchForShellCmd = 0;
// get the next token in the string
p = strtok(NULL, tokenDelimiter);
}
delete [] tempStr;
}
}
}