Files
secondo/UserInterfaces/SecondoTTY.cpp
2026-01-23 17:03:45 +08:00

911 lines
22 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2004-2009, University in Hagen, Faculty of Mathematics and
Computer Science, Database Systems for New Applications.
SECONDO is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SECONDO 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 SECONDO; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
//[_] [\_]
\vspace{1cm}
\centerline{\LARGE \bf SecondoTTY}
Changes:
December 1997: Friedhelm Becker
July 1999: Jose Antonio Cotelo Lema: changes in the code and interface of the
Gettext() and getline() functions, to allow input commands of arbitrary size.
Dec 2004, M. Spiekermann. The read in command lines will be separated by a
"\\n" symbol, otherwise the parser can't calculate a position in terms of lines
and cols.
July 2005, M. Spiekermann. Help message improved.
December 2005, Victor Almeida deleted the deprecated algebra levels
(~executable~, ~descriptive~, and ~hibrid~). Only the executable
level remains.
February 2006, M. Spiekermann reorganized the makefiles in order to save linking
instructions. Now only two applications are built, namely ~SecondoBDB~ and ~SecondoCS~.
The former known applications are replaced by shell scripts invoking one of the applications
above with suitable paramaters.
\begin{center}
\footnotesize
\tableofcontents
\end{center}
1 Overview
This is the TTY-Interface of Secondo. It makes it possible to enter queries
to Secondo and returns the output from Secondo. Input can be from keyboard
or from file. In the latter you must enter ~uf~ or ~use file~ and
then you will be prompted for the filename.
2 Includes and defines
*/
#include <algorithm>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <ctype.h>
#include "getCommand.h"
#ifdef HAVE_LIBREADLINE
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#define HISTORY_FILE ".secondo_history"
#define HISTORY_FILE_ENTRIES 200
#endif
#include "Application.h"
#include "Profiles.h"
#include "FileSystem.h"
#include "SecondoSystem.h"
#include "SecondoInterface.h"
#if !defined(SECONDO_CLIENT_SERVER) && !defined(REPLAY)
#include "SecondoInterfaceTTY.h"
#elif defined(SECONDO_CLIENT_SERVER)
#include "SecondoInterfaceCS.h"
#elif defined(REPLAY)
#include "SecondoInterfaceREPLAY.h"
#else
#include "SecondoInterfaceCS.h"
#endif
#include "SecondoSMI.h"
#include "NestedList.h"
#include "DisplayTTY.h"
#include "CharTransform.h"
#include "LogMsg.h"
#include "TTYParameter.h"
using namespace std;
class SecondoTTY : public Application
{
public:
SecondoTTY( const TTYParameter& t );
virtual ~SecondoTTY() {};
bool AbortOnSignal( int sig ) const;
int Execute();
private:
void Usage();
bool ProcessFile( const string& fileName, const bool stopOnError,
const bool isPD );
bool ProcessCommand();
bool ProcessCommands( const bool stopOnError, const bool isPD);
void ShowPrompt( const bool first );
void TypeOutputList ( ListExpr list );
bool IsInternalCommand( const string& line );
bool GetCommand( const bool isPD);
void ShowQueryResult( ListExpr list );
ListExpr CallSecondo();
void CallSecondo2();
bool MatchQuery(string& w, istringstream& is) const;
string ReadCommand(istringstream& is) const;
string parmFile;
string home;
string user;
string pswd;
string host;
string port;
string replayFile;
string iFileName;
string oFileName;
string cmd;
string prompt;
const string prompt_first;
const string prompt_next;
int errorCode;
bool isStdInput;
bool quit;
NestedList* nl;
bool isQuery;
//SecondoInterface* si;
#if !defined(SECONDO_CLIENT_SERVER) && !defined(REPLAY)
SecondoInterfaceTTY* si;
#elif defined(SECONDO_CLIENT_SERVER)
SecondoInterfaceCS* si;
#elif defined(REPLAY)
SecondoInterfaceREPLAY* si;
#else
SecondoInterfaceCS* si;
#endif
};
SecondoTTY::SecondoTTY( const TTYParameter& t )
: Application( t.numArgs, (const char**)t.argValues ),
cmd(""),
prompt(""),
prompt_first("Secondo => "),
prompt_next("Secondo -> ")
{
parmFile = t.parmFile;
home = t.home;
user = t.user;
pswd = t.pswd;
host = t.host;
port = t.port;
replayFile = t.replayFile;
iFileName = t.iFileName;
oFileName = t.oFileName;
isStdInput = iFileName.empty();
quit = false;
nl = 0;
si = 0;
errorCode = 0;
}
bool
SecondoTTY::AbortOnSignal( int sig ) const
{
return (true);
}
void
SecondoTTY::Usage()
{
stringstream cmdList;
cmdList <<
"The following internal commands are available:\n" <<
"\n" <<
" ?, HELP - display this message\n" <<
"\n" <<
" @FILE - read commands from file 'FILE' (may be nested)\n" <<
" @@FILE - read commands from file 'FILE' until 'FILE' is \n" <<
" completely processed or an error occurred\n" <<
" @\%FILE - read commands from file ignoring comments in pd style\n" <<
" @&FILE - read commands from file ignoring comments in pd style\n" <<
" until file is completely processed or an error occurred\n"<<
"\n" <<
" DEBUG n - set debug level to n where n is an integer where each \n" <<
" bit corresponds to one setting:\n" <<
" bit 0: debug mode (show annotated query and operator tree)\n" <<
" bit 1: trace (show recursive calls)\n" <<
" bit 2: trace nodes (construction of nodes of the op. tree,\n" <<
" and execution of the query processor's Eval() method)\n"<<
" bit 3: localInfo (prints a warning if an operator did not\n" <<
" destroy its localinfo before the operator tree\n"<<
" is deconstructed)\n" <<
" bit 4: debug progress (after sending a REQUESTPROGRESS\n" <<
" message to an operator, the ranges in the \n" <<
" ProgressInfo are checked for whether tey are\n" <<
" reasonable. If not so, the according operator and \n" <<
" ProgressInfo are reported) \n" <<
" bit 5: trace progress (prints the result of \n" <<
" each REQUESTPROGRESS message) \n" <<
" bit 6: show type mappings \n" <<
"\n" <<
" Q, QUIT - exit the program\n" <<
" # ... - comment line (first character on line has to be '#')\n" <<
" REPEAT n <query> - execute <query> n times.\n" <<
"\n" <<
"Moreover, you may enter any valid SECONDO command introduced by the \n" <<
"keywords 'query', 'let', 'restore', etc. "
"Refer to the \"User Manual\" for \n" <<
"details. Internal commands are restricted to ONE line, while SECONDO \n" <<
"commands may span several lines; a semicolon as the last character on \n" <<
"a line terminates a command, but is not part of the command. \n" <<
"Alternatively, you may enter an empty line.\n";
cout << cmdList.str() << endl << endl;
}
bool
SecondoTTY::ProcessFile( const string& fileName , const bool stopOnError,
const bool isPD)
{
bool saveIsStdInput = isStdInput;
streambuf* oldBuffer;
ifstream fileInput( fileName.c_str() );
if ( fileInput ) {
oldBuffer = cin.rdbuf( fileInput.rdbuf() );
cout << "*** Begin processing file '" << fileName << "'." << endl;
isStdInput = false;
StopWatch scriptTime;
scriptTime.start();
bool res = ProcessCommands(stopOnError, isPD);
cout << "Runtime for " << fileName << ": "
<< scriptTime.diffTimes() << endl;
isStdInput = saveIsStdInput;
if(!res){
cout << "Errors during processing the file " << fileName << "."<< endl;
} else {
cout << "File " << fileName << " successfully processed." << endl;
}
cin.rdbuf( oldBuffer );
return res;
} else {
cerr << "*** Error: Could not access file '" << fileName << "'." << endl;
return false;
}
}
string
SecondoTTY::ReadCommand(istringstream& is) const
{
string cmdWord = parse<string>(is);
transform( cmdWord.begin(), cmdWord.end(), cmdWord.begin(),
ToUpperProperFunction );
return cmdWord;
}
bool
SecondoTTY::MatchQuery(string& cmdWord, istringstream& is) const
{
bool isQuery = false;
size_t pos = cmd.find("query");
size_t pos2 = cmd.find("querynt");
size_t pos3 = cmd.find("pquery");
if ((pos == string::npos) && (pos2==string::npos) && (pos3==string::npos)){
return isQuery;
}
// remove leading brackets
while(!cmdWord.empty() && cmdWord[0]=='('){
cmdWord = cmdWord.substr(1);
}
if ( (cmdWord == "QUERY") || (cmdWord== "QUERYNT") || (cmdWord=="PQUERY") )
{
isQuery = true;
}
else
{
if ( cmdWord == "" )
{
cmdWord = ReadCommand(is);
if((cmdWord == "QUERY") || (cmdWord == "QUERYNT") || (cmdWord=="PQUERY"))
isQuery = true;
else
isQuery = false;
}
}
return isQuery;
}
bool SecondoTTY::ProcessCommand()
{
if(cmd.find_first_not_of(" \t\t\n")==string::npos){
// found empty command
cmd="";
return true;
}
istringstream is(cmd);
string cmdWord = ReadCommand(is);
bool success = true;
// analyse command
if ( cmdWord == "?" || cmdWord == "HELP" )
{
Usage();
}
else if ( cmdWord == "DEBUG" )
{
int debugLevel = parse<int>(is);
si->SetDebugLevel( debugLevel );
cout << "*** Debug level set to " << debugLevel << "." << endl;
}
else if ( cmdWord == "Q" || cmdWord == "QUIT" )
{
cout << "*** Thank you for using SECONDO!" << endl << endl;
quit = true;
}
else if ( cmdWord[0] == '@' )
{
bool stopOnError = false;
int start=1;
bool pdfile=false;
if(cmdWord.length()>1 && cmdWord[1] == '@'){
start = 2;
stopOnError = true;
}
if(cmdWord.length()>1 && cmdWord[1] == '%'){
start = 2;
pdfile = true;
}
if(cmdWord.length()>1 && cmdWord[1] == '&'){
start = 2;
pdfile = true;
stopOnError = true;
}
success = ProcessFile( cmd.substr( start, ( cmd.length() - start ) ),
stopOnError, pdfile );
}
else if ( cmdWord == "REPEAT" )
{
int repeatCtr = parse<int>(is);
cmdWord = ReadCommand(is);
isQuery = MatchQuery(cmdWord, is);
const string err =
"Syntax Error: Expecting REPEAT n { query ... | ( query ...]}!";
if (!isQuery)
{
cerr << err << endl;
success = false;
}
else
{
// remove repeat <n> from the cmd
cmd = cmd.substr(6);
size_t pos = cmd.find_first_of("(q");
if ( pos != string::npos )
{
cmd = cmd.substr( pos );
cout << "Repeating next query " << repeatCtr << " times ..." << endl;
while (repeatCtr > 0) {
CallSecondo2();
if (errorCode > 0){ // exit loop on failure
success = false;
break;
}
repeatCtr--;
}
}
else
{
// should never happen !
success = false;
cerr << err << endl;
}
}
}
else
{
isQuery = MatchQuery(cmdWord,is);
CallSecondo2();
success = errorCode == 0;
}
cmd="";
return success;
}
void
SecondoTTY::ShowPrompt( const bool first )
{
if ( !isStdInput ) // don't show command prompt
return;
prompt = first ? prompt_first : prompt_next;
#ifdef HAVE_LIBREADLINE
rl_set_prompt( prompt.c_str() );
#else
cout << prompt;
#endif
}
bool
SecondoTTY::IsInternalCommand( const string& line )
{
string cmdWord;
istringstream is( line );
is >> cmdWord;
transform( cmdWord.begin(), cmdWord.end(), cmdWord.begin(),
ToUpperProperFunction );
return ( cmdWord == "?" || cmdWord == "HELP" ||
cmdWord == "Q" || cmdWord == "QUIT" ||
cmdWord == "DEBUG" || cmdWord == "SHOW" || cmdWord[0] == '@' );
}
bool
SecondoTTY::GetCommand( const bool isPD)
{
function<void(const bool)> showPrompt
= [this](const bool first) {ShowPrompt(first); };
function<bool(const string&)> isInternalCommand
= [this](const std::string& c) {return IsInternalCommand(c); };
bool res = getCommand(cin, isPD, cmd,
showPrompt, isInternalCommand,
isStdInput, prompt);
return res;
}
bool
SecondoTTY::ProcessCommands(const bool stopOnError, const bool isPD) {
bool errorFound = false;;
while (!cin.eof() && !quit && ( !stopOnError || !errorFound ))
{
if ( GetCommand(isPD) )
{
try {
if( ! ProcessCommand()){
errorFound = true;
}
}
catch (SecondoException &e) {
cerr << "Exception caught: " << e.msg() << endl;
}
}
}
return !errorFound;
}
/*
8 TypeOutputList
TypeOutputList prints the result of a nonquery input (e. g. list).
*/
void
SecondoTTY::TypeOutputList ( ListExpr list )
{
if ( nl->IsEmpty( list ) )
{
cout << "=> []" << endl;
}
else
{
cout << "=> Result:" << endl;
nl->WriteListExpr( list, cout );
cout << endl;
}
}
/*
9 ShowQueryResult
This function prints the result of a query by calling DisplayTTY,
which writes the result in a file. This file is then printed by
this function. The filename is given to DisplayTTY by this function.
*/
void
SecondoTTY::ShowQueryResult( ListExpr list )
{
if ( nl->IsEmpty( list ) ||
(nl->ListLength( list ) != 2) ||
( (nl->ListLength( list ) == 2) && (nl->IsEmpty(nl->Second( list ))) )
)
{
cout << "=> []" << endl;
return;
}
else
{
DisplayTTY::GetInstance().DisplayResult( nl->First( list ),
nl->Second( list ) );
}
}
/*
11 CallSecondo
This function gives a query to secondo and receives the result from secondo.
*/
ListExpr
SecondoTTY::CallSecondo()
{
errorCode = 0;
int errorPos = 0;
ListExpr cmdList = nl->TheEmptyList();
ListExpr outList = nl->TheEmptyList();
string errorMessage = "";
string errorText = "";
if ( cmd[cmd.find_first_not_of(" \n\r\t\v\b\a\f")] == '(' )
{
if ( nl->ReadFromString( cmd, cmdList ) )
{
si->Secondo( cmd, cmdList, 0, false, false,
outList, errorCode, errorPos, errorMessage );
NList::setNLRef(nl);
}
else
{
cmsg.error() << endl << "*** Error: list expression expected!" << endl;
cmsg.send();
}
}
else
{
si->Secondo( cmd, cmdList, 1, false, false,
outList, errorCode, errorPos, errorMessage );
NList::setNLRef(nl);
}
if ( errorCode != 0 )
{
si->WriteErrorList( outList );
nl->Destroy( outList );
outList = nl->TheEmptyList();
}
if ( cmdList != nl->TheEmptyList() )
{
nl->Destroy( cmdList );
}
// print out error messages
cerr << endl;
if (errorCode != 0)
{
if (errorMessage != "")
{
cerr << color(red) << errorMessage << color(normal) << endl;
}
else
{
// retrieve error message for errorCode
cmsg.error() << si->GetErrorMessage( errorCode ) << endl;
cmsg.send();
}
if(errorCode == ERR_SYSTEM_DIED) {
cerr << "A fatal has occurred!" << endl;
cerr << "Please check the logs for more information." << endl;
exit(EXIT_FAILURE);
}
}
return (outList);
}
/*
12 Secondo2
This would normally be the main function of SecondoTTY.
*/
void
SecondoTTY::CallSecondo2()
{
ListExpr result;
result = CallSecondo();
#ifdef NL_DEBUG
cerr << endl << "### ResultStr: " << nl->ToString(result) << endl;
#endif
if ( isQuery ) {
ShowQueryResult( result );
}
else {
if(nl->HasLength(result,2) &&
nl->IsEqual(nl->First(result),"inquiry")){
ShowQueryResult( result );
} else {
TypeOutputList( result );
}
}
nl->initializeListMemory();
}
/*
1 Execute
This function checks the configuration of the Secondo system. If the
configuration seems to be ok the system is intialized. If the initialization
succeeds the user commands are processed. If the initialization fails or the
user finishes work the system is terminated.
*/
int
SecondoTTY::Execute()
{
int rc = 0;
cout << endl
<< "*** Secondo TTY ***"
<< endl << endl;
streambuf* oldOutputBuffer = 0;
ofstream fileOutput;
bool useOutputFile = oFileName.length() > 0;
#if !defined(SECONDO_CLIENT_SERVER) && !defined(REPLAY)
si = new SecondoInterfaceTTY(false);
#elif defined(SECONDO_CLIENT_SERVER)
cout << "create a new SecondoInterfaceCS " << endl;
//SecondoInterfaceCS* si1 = new SecondoInterfaceCS(true,0,true);
//si = si1;
si = new SecondoInterfaceCS(true,0,true);
cout << "finished creation of SecondoInterfaceCS" << endl;
#elif defined(REPLAY)
si = new SecondoInterfaceREPLAY(true);
((SecondoInterfaceREPLAY*)si)->setReplayFile(replayFile);
#else
si = new SecondoInterfaceCS(true,0,true);
#endif
string errorMsg("");
if ( si->Initialize( user, pswd, host, port, parmFile,home, errorMsg ) )
{
// initialize the pointer to the nested list instance
nl = si->GetNestedList();
NList::setNLRef(nl);
// DisplayTTY::Set_SI( si );
DisplayTTY::Set_NL( nl );
DisplayTTY::Initialize();
if ( useOutputFile )
{
fileOutput.open( oFileName.c_str() );
if ( fileOutput.is_open() )
{
oldOutputBuffer = cout.rdbuf( fileOutput.rdbuf() );
cout << endl << "Redirecting output to " << oFileName << endl;
}
else
{
cerr << "Error: Could not redirect ouput to " << oFileName << endl;
useOutputFile = false;
}
}
if ( isStdInput )
{
cout << endl << "Secondo TTY ready for operation." << endl
<< "Type 'HELP' to get a list of available commands." << endl;
ProcessCommands( false, false);
}
else
{
ProcessFile(iFileName, false, false);
}
if ( useOutputFile ){
cout.rdbuf( oldOutputBuffer );
}
DisplayTTY::Finish();
} else {
cerr << "Error in initializing the secondo system: "
<< errorMsg << endl;
}
try {
si->Terminate();
delete si;
si = 0;
}
catch (SecondoException &e)
{
cerr << e.msg() << endl;
rc = 17;
}
return (rc);
}
#ifdef HAVE_LIBREADLINE
/*
15 Keyword extraction
~commands~
This array contains all keywords used in SECONDO for expansion with
the tab key.
Duplicates are not allowed in this array and the last entry has to
be NULL.
*/
const char* keywords[] = { "abort", "algebra", "algebras", "begin", "commit",
"close", "constructors", "consume","count", "create",
"database", "databases", "DEBUG", "delete", "derive",
"else","endif","endwhile","extend", "feed", "filter",
"from", "if", "kill", "let", "list","objects", "open",
"operators", "query","restore", "save", "SHOW", "then",
"transaction", "type","types", "update","while",
"SEC2TYPEINFO","SEC2OPERATORUSAGE","SEC2OPERATORINFO",
"SEC2FILEINFO","SEC2COUNTERS","SEC2COMMANDS",
"SEC2CACHEINFO",
(char *)0 };
/*
~dupstr~
This funCtion returns a clone of the argument string;
*/
char *
dupstr (const char* s)
{
char *r;
r =(char*) malloc (strlen (s) + 1);
strcpy (r, s);
return (r);
}
/*
~strcmp~
The function ~strcmp~ compares two string up to a given length.
*/
int strcmp(const char* s1,const char* s2, int len){
int index = 0;
while((s1[index]==s2[index]) && (index<len)) index++;
if (index==len) index--;
if(s1[index]<s2[index]) return -1;
if(s1[index]>s2[index]) return 1;
return 0;
}
/*
~command[_]generator~
This function computes the next match of the current partial input
to the avaiable keywords.
*/
char* command_generator(const char* text, int state)
{
static int index,len;
const char* name;
if(!state){
index=0;
len = strlen(text);
}
while( (name = keywords[index])){
index++;
int cmp = strcmp(name,text,len);
if(cmp==0){
return (dupstr(name));
}
}
return ((char*)NULL);
}
/*
~secondo[_]completion~
This function is used by the readline library to determine all string
with the same beginning like the current input.
*/
char** secondo_completion(const char* text, int start, int end){
return rl_completion_matches(text,command_generator);
}
#endif
int SecondoTTYMode(const TTYParameter& tp)
{
SecondoTTY* appPointer = new SecondoTTY( tp );
#ifdef HAVE_LIBREADLINE
rl_initialize();
rl_readline_name = "secondo";
rl_attempted_completion_function = secondo_completion;
/* read the history from file */
ifstream hist_file(HISTORY_FILE);
string histline;
if(hist_file){
string query("");
while(!hist_file.eof()){
getline(hist_file,histline);
if(histline.find_last_not_of(" \t\n")!=string::npos){
if(query!=""){
query = query + "\n" + histline;
} else {
query = histline;
}
} else if(query.length()>0){
add_history(query.c_str());
query = "";
}
}
if(query!=""){
add_history(query.c_str());
query = "";
}
hist_file.close();
}
#endif
int rc = appPointer->Execute();
delete appPointer;
#ifdef HAVE_LIBREADLINE
/*
* save the last HISTORY_FILE_ENTRIES elements of the
* history to a file
*/
fstream out_history;
out_history.open(HISTORY_FILE, fstream::out | fstream::trunc);
if(! out_history.bad()) {
HIST_ENTRY* he;
int start_history = max(history_length - HISTORY_FILE_ENTRIES, 0);
for(int i = start_history; i < history_length; i++) {
he = history_get(i);
if(he) {
out_history << he->line << endl << endl;
}
}
out_history.close();
} else {
cerr << "Error: could not write the SECONDO history file" << endl;
}
#endif
return (rc);
}