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

1965 lines
46 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2006, 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
----
In this file, the pltty mode is defined.
Is the name of the mode suggest, this mode is a mix between the
optimizer interface and the usual secondo interface.
In this mode, four different kinds of commands are distinguished.
The first catefory of commands are internal commands. These commands are
handled directly by the user interface. Examples are '@<filename>', 'help' or
'quit'.
The second category include sql commands. These commands will be translated
into a plan using the sqlToPlan predicate defined in the optimizer. Before
optimization, some rewritings are done within the command. For example, symbols
starting with a upper case letter are replaced by lower case letters.
Furthermore, brackets are checked before optimization. If the optimization
succeds, the generated plan is send to the kernel. The result is displayed
using the kernel display functions.
The third category of commands are commands that are directly send to the
kernel without aid of the optimizer.
If a command cannot assigned to one of the categories mentioned before,
it is tried to convert it into a callable prolog term and call it using the
prolog engine. All generated solutions are printed out.
*/
/*
1 Includes
*/
#include <algorithm>
#include <iostream>
#include <stack>
#define PL_ARITY_AS_SIZE 1
#include "SWI-Prolog.h"
#include <stdlib.h>
#include "SecondoConfig.h"
#include "Application.h"
#include "TTYParameter.h"
#include <string>
#include "StringUtils.h"
#include "SecondoInterface.h"
#include "DisplayTTY.h"
#include <stdio.h>
#ifndef SECONDO_WIN32
#include <termios.h>
#endif
#include <unistd.h>
#include "getCommand.h"
#ifdef HAVE_LIBREADLINE
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
#define HISTORY_FILE ".secondopltty_history"
#define HISTORY_FILE_ENTRIES 200
#endif
using namespace std;
string blanks = " \t\n\r";
string blanks2 = blanks + "[](){}?.,";
/*
2 predicates
The ~predicates~ array contains all prolog extensions required by
Secondo. These things are defined in SecondoPL.
*/
extern PL_extension predicates[];
extern void handle_exit(void);
extern bool StartSecondoC(TTYParameter& tp);
extern SecondoInterface* si;
NestedList* mnl;
bool globalAbort = false; // used if quit occurs within a script
namespace pltty{
bool plttydebug = false;
// forward declaration of processCommands
bool processCommands(istream&, bool,bool);
/*
~isStdInput~
This boolean value describes whether the current input is cin or
a file.
*/
bool isStdInput = true;
string prompt = "";
void ShowPrompt( const bool first ) {
prompt = first ? "SecondoPLTTY => ": "SecondoPLTTY -> ";
#ifdef HAVE_LIBREADLINE
rl_set_prompt( prompt.c_str() );
#else
cout << prompt;
#endif
}
#ifndef SECONDO_WIN32
char getChar(){
struct termios old,n;
char res;
tcgetattr(fileno(stdin),&old);
n=old;
n.c_lflag &= ~ICANON;
n.c_lflag &= ~ECHO;
tcsetattr(fileno(stdin),TCSANOW,&n);
res = getc(stdin);
tcsetattr(fileno(stdin),TCSANOW,&old);
return res;
}
#else
char getChar(){
DWORD mode, cc;
HANDLE h = GetStdHandle( STD_INPUT_HANDLE );
if (h == NULL) {
return 0; // console not found
}
GetConsoleMode( h, &mode );
SetConsoleMode( h, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT) );
TCHAR c = 0;
ReadConsole( h, &c, 1, &cc, NULL );
SetConsoleMode( h, mode );
return c;
}
#endif
/*
~isUpperCase~
Auxiliary function chinging whether an character is an upper case.
*/
bool isUpperCase(char c){
return (c>='A') && (c <='Z');
}
/*
~tolower~
This function converts an upper case character into a lower case.
*/
char tolower(char c){
if(!isUpperCase(c)) return c;
return (c + 'a') - 'A';
}
/*
~isIdentChar~
This functions checks whether a character is allowed to be part of
an identifier.
*/
bool isIdentChar(char c){
return stringutils::isLetter(c) || stringutils::isDigit(c) || (c=='_');
}
string term2string(term_t t){
string res ="<error>";
fid_t fid = PL_open_foreign_frame();
predicate_t pl_convert = PL_predicate("term_to_atom",2,"");
term_t args = PL_new_term_refs(2);
term_t r = args+1;
PL_put_term(args, t);
qid_t qid = PL_open_query(NULL, PL_Q_CATCH_EXCEPTION, pl_convert, args);
if(!PL_next_solution(qid)){
PL_close_query(qid);
PL_discard_foreign_frame(fid);
return res;
}
char* s;
if( PL_get_atom_chars(r,&s)){
res = string(s);
}
PL_close_query(qid);
PL_discard_foreign_frame(fid);
return res;
}
bool isWhiteSpace(char c){
string ws = " \r\n\t";
for(size_t i=0;i<ws.length();i++){
if(ws[i]==c) return true;
}
return false;
}
/*
~removeOuterBrackets~
This function removes outer rounf brackets from a string.
If the source is not included into round brackets, the original
string is returned.
*/
bool hasOuterBrackets(string& src, bool remove){
// states
// 0 : at the beginning (white spaces are ignored)
// 1 : open bracket at start found
// 2 : within a double quoted string
// 3 : within a single quoted string
// 4 :: opening bracket closed
// 5 : within double quoted string after backslash
// 6 : within single quoted string after backslash
int state = 0;
int bcount = 0;
for(size_t i=0;i<src.length(); i++){
char c = src[i];
switch(state){
case 0 : if(isWhiteSpace(c)){
} else if(c=='('){
state = 1;
bcount++;
} else {
return false;
}
break;
case 1 : if( c=='(') {
bcount++;
} else if(c==')'){
bcount--;
if(bcount==0) {
state = 4;
}
} else if(c=='"'){
state = 2;
} else if(c=='\''){
state = 3;
}
break;
case 4 : if(!isWhiteSpace(c)){ // chars after closing bracket found
return false;
}
break;
case 2 : if(c=='"'){
state = 1;
} else if(c=='\\'){
state = 5;
}
break;
case 3 : if(c=='\''){
state = 1;
} else if(c=='\\'){
state = 6;
}
break;
case 5 : state = 2;
break;
case 6 : state = 3;
break;
default : cout << "error in finding surrounding brackets" << endl;
return false;
}
}
if(bcount!=0) {
return false; // closing bracked not found
}
if(remove){
src= src.substr(src.find_first_of('(')+1);
src = src.substr(0,src.find_last_of(')'));
}
return true;
}
/*
~checkSQLCommand~
This function checks the brackets within an command.
The command itselfs is rewritten by converting symbols starting
with an upper case into lower case symbols. If brackets are
not correct, the problem description is stored into errMsg and
correct is set to false;
*/
string checkSQLCommand(const string& cmd, bool& correct, string& errMsg){
stack<char> brackets;
int state = 0; // start of a word
// states
// 0 begin of a symbol
// 1 within a double quoted string
// 2 within a single quoted string
// 3 within a symbol starting with a capital
// 4 within a symbol starting lower case
stringstream res;
size_t pos = 0;
while(pos < cmd.size()){
char c = cmd[pos];
switch(state){ // somewhere
case 0: {
if(c=='"'){
state = 1;
res << c;
} else if(c=='\''){
state = 2;
res << c;
} else if(stringutils::isLetter(c)){
if(isUpperCase(c)){
state = 3;
res << tolower(c);
} else {
state = 4;
res << c;
}
} else {
if((c=='(') || c=='[' || c=='{'){
brackets.push(c);
} else if(c==')' || c==']' || c=='}'){
if(brackets.empty()){
errMsg = string("found closing ") + c + " that was not opened";
correct = false;
return "";
}
char open = brackets.top();
brackets.pop();
switch(c){
case ')' : if (open!='(') {
errMsg = string("closing ") + c
+ " was opened by " + open;
correct= false;
return "";
}
break;
case ']' : if (open!='[') {
errMsg = string("closing ") + c
+ " was opened by " + open;
correct = false;
return "";
}
break;
case '}' : if (open!='{') {
errMsg = string("closing ") + c
+ " was opened by " + open;
correct = false;
return "";
}
break;
}
} else if(c=='.') { // replace followed by a letter by a ':'
if(pos<cmd.size()-1){
if(stringutils::isLetter(cmd[pos+1])){
c = ':';
}
}
}
res << c;
}
pos++;
break;
}
case 1 : { // within an double quoted string
res << c;
if(c=='"'){
state = 0;
}
pos++;
break;
}
case 2: { // within a single quoted string
res << c;
if(c=='\''){
state = 0;
}
pos++;
break;
}
case 3: // within an identifier to rewrite
if(isIdentChar(c)){
//res << tolower(c);
res << c;
pos++;
} else {
state = 0;
}
break;
case 4:
if(isIdentChar(c)){ // whithin an normal identifier
res << c;
pos++;
} else {
state = 0;
}
break;
}
}
// check whether all opened brackts are closed
if(!brackets.empty()){
correct = false;
errMsg = "found unclosed brackets";
return "";
}
correct = true;
errMsg = "";
return res.str();
}
/*
~IsInternalCommand~
This function checks whether a command should be handeld internally.
*/
bool IsInternalCommand(const string& cmd){
if(cmd=="quit") return true;
if(cmd=="q") return true;
if(cmd=="?") return true;
if(cmd=="debugOn") return true;
if(cmd=="debugOff") return true;
if(cmd.size()>1){
if(cmd[0]=='@'){
return true;
}
}
return false;
}
/*
~processQuit~
This function is called if a wuit command is executed.
*/
bool processQuit(){
cout << "Thank you for using Secondo." << endl;
globalAbort = true;
return true;
}
/*
~processHelp~
This function is the implementation of the help command.
*/
bool processHelp(){
cout << endl
<< "secondo or optimizer command are executed as usual " << endl
<< "if an sql select statement ends with a dot, the user is " << endl
<< "ask whether the optimized query should be executed " << endl
<< "? - show this help" << endl
<< "@FILE - read commands from file " << endl
<< "@@FILE - read commands from file until an error occurred" << endl
<< "@\%FILE - read commands from file ignoring pd comments" << endl
<< "@&FILE - read commands from file ignoring pd comments "
<< "until an error occurred" << endl
<< "debugOn - shows sql commands before sending to prolog" << endl
<< "debugOff - switches of debugging messages " << endl
<< "quit - exits the program" << endl;
return true;
}
bool processDebug(bool enable){
cout << endl
<< "switched debugging " << (enable?"on":"off") << endl;
plttydebug = enable;
return true;
}
/*
~processScript~
This command is called if the command starts with an @ mentioning the
execution of some script.
*/
bool processScript(const string& cmd){
bool lastStdInput = isStdInput;
bool isPD = false;
bool haltOnErrors = false;
int pos=1;
char second = cmd[1];
if(second=='@'){
haltOnErrors = true;
pos++;
}else if(second=='%'){
isPD = true;
pos++;
} else if(second=='&'){
isPD=true;
haltOnErrors = true;
pos++;
}
string filename = cmd.substr(pos);
stringutils::trim(filename);
ifstream in(filename.c_str());
if(!in){
cout << "Error: could not open file: " << filename << endl;
return false;
}
isStdInput = false;
bool res = processCommands(in,haltOnErrors, isPD);
isStdInput = lastStdInput;
if(!res){
cout << "there was errors during processing " << filename << endl;
} else {
cout << "file " << filename << " successfull processed" << endl;
}
in.close();
return res;
}
/*
~processInternalCommand~
This function is called if an internal command has been recognized.
It checks the kind of the internal command and calles the
appropriate function.
*/
bool processInternalCommand(const string& cmd){
if(cmd=="quit" || cmd=="q"){
return processQuit();
}
if(cmd=="?"){
return processHelp();
}
if(cmd=="debugOn"){
return processDebug(true);
}
if(cmd=="debugOff"){
return processDebug(false);
}
if(cmd.size()>1){
if(cmd[0]=='@'){
return processScript(cmd);
}
}
cout << "Error: command " << cmd << " recognized as an internal "
<< "command but handled not correctly";
return false;
}
/*
~getCommand~
This function extracts the next command from the input stream.
If the isPD flag is set to true, comments in PD-style are
ignored.
*/
string getCommand(istream& in, bool isPD){
function<void(const bool)> showPrompt
= [](const bool first) {ShowPrompt(first); };
function<bool(const string&)> isInternalCommand
= [](const std::string& c) {return IsInternalCommand(c); };
std::string cmd;
::getCommand(in, isPD, cmd,
showPrompt, isInternalCommand,
isStdInput, prompt);
return cmd;
}
/*
3.1 Function ~isDirectSecondoCommand~
Returns true if the command starts with a secondo keyword,
a bracket or a curly bracket
*/
bool isDirectSecondoCommand(const string& cmd){
stringutils::StringTokenizer st(cmd, blanks2, true);
if(!st.hasNextToken()){
return false;
}
string first = st.nextToken();
// simple, recognize using the first keyword
if( first=="query"
|| first=="if"
|| first=="while"
|| stringutils::startsWith(cmd,"(") // nl command
|| stringutils::startsWith(cmd,"{") // command sequence
){
return true;
}
// inquiries
if(first=="list"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if( second=="operators"
|| second=="algebras"
|| second=="databases"
|| second=="types"
|| second=="objects"){
return !st.hasNextToken();
}
if(second=="type"){
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(third!="constructors"){
return false;
}
return !st.hasNextToken();
}
if(second=="algebra"){
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(!stringutils::isIdent(third)){
return false;
}
return !st.hasNextToken();
}
return false;
}
// transactions
if( first=="begin"
|| first=="commit"
|| first=="abort"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(second!="transaction"){
return false;
}
return !st.hasNextToken();
}
// close database
if(first=="close"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(second!="database"){
return false;
}
return !st.hasNextToken();
}
// kill
if(first=="kill"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
return !st.hasNextToken();
}
// save
if(first=="save"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
// not that also 'database' is a valid identifier
return false;
}
if(!st.hasNextToken()){
return false;
}
if(st.nextToken()!="to"){
return false;
}
// filename must be given
return st.hasNextToken();
}
// restore
if(first=="restore"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
// bring object restore and database restore to the same rest
if(second=="database"){
if(!st.hasNextToken()){
return false;
}
second=st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
}
// check from <file>
if(!st.hasNextToken()){
return false;
}
if(st.nextToken()!="to"){
return false;
}
return st.hasNextToken(); // the filename
}
// delete variants
if(first=="delete"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
if(!st.hasNextToken()){ // delete object
return true;
}
if(second!="database" && second != "type"){
return false;
}
if(!st.hasNextToken()){ // type or database name
return false;
}
string third = st.nextToken();
if(!stringutils::isIdent(third)){
return false;
}
return !st.hasNextToken();
}
// create commands
if(first=="create"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(second=="database"){
if(!stringutils::isIdent(third)){
return false;
}
return !st.hasNextToken();
}
// object creattion
if(third!=":"){
return false;
}
return st.hasNextToken();
}
// let, derive & type
if(first=="let" || first=="derive" || first=="type"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(third!="="){
return false;
}
return st.hasNextToken();
}
// update
if(first=="update"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(!stringutils::isIdent(second)){
return false;
}
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(third!=":="){
return false;
}
return st.hasNextToken();
}
// currently, no other secondo commands are known
// extend here if this changed
return false;
}
/*
~catalogChanges~
This funciton checks whether a direct secondo command manipulates
the content of a database. In such cases. the optimizer has to be
informed about this.
*/
bool catalogChanges(const string& cmd){
stringutils::StringTokenizer st(cmd, blanks2, true);
if(!st.hasNextToken()){
return false;
}
string first = st.nextToken();
if( first=="if" || stringutils::startsWith(cmd, "{")
|| first=="kill" || first=="derive"
|| first=="let" || stringutils::startsWith(cmd,"(")
|| first=="restore"){
return true;
}
if( first =="create" || first=="delete"){
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
return second!="database";
}
return false;
}
/*
~closeOptDB~
This function calles the closeDB predicate of the optimizer.
*/
bool closeOptDB(){
fid_t fid = PL_open_foreign_frame();
static predicate_t p;
p=PL_predicate("closeDB",0,"");
qid_t id;
bool ok= false;
try{
term_t dummy = PL_new_term_ref();
id = PL_open_query(NULL, PL_Q_NORMAL, p, dummy);
if(PL_next_solution(id)){
ok = true;
}
PL_close_query(id);
} catch(...){
ok = false;
}
PL_discard_foreign_frame(fid);
return ok;
}
/*
~updateOptCatalog~
This function updates the optimizer's catalog.
*/
bool updateOptCatalog(){
if(plttydebug){
cout << "Updating the optimizer's catalog" << endl;
}
fid_t fid = PL_open_foreign_frame();
static predicate_t p;
p=PL_predicate("updateCatalog",0,"");
qid_t id;
bool ok= false;
try{
term_t dummy=PL_new_term_ref();
id = PL_open_query(NULL, PL_Q_NORMAL, p, dummy);
if(PL_next_solution(id)){
ok = true;
}
PL_close_query(id);
} catch(...){
ok = false;
}
if(!ok){
cout << "update optimizer catalog failed" << endl;
}
PL_discard_foreign_frame(fid);
return ok;
}
/*
~showResult~
Shows a nested list in a formatted manner.
*/
void showResult(ListExpr res){
if(!mnl->HasLength(res,2)){
mnl->WriteStringTo(res,cout);
} else {
if(!DisplayTTY::GetInstance().DisplayResult(mnl->First(res),
mnl->Second(res))){
mnl->WriteStringTo(res,cout);
}
}
cout << endl << endl;
}
bool isBlank(char c){
for(size_t i=0;i<blanks.length();i++){
if(c==blanks[i]) return true;
}
return false;
}
vector<pair<int,int> > detectSelects(const string& cmd, bool& ok){
vector<pair<int,int> > res;
// search for ( <blank>* select ... ) within cmd and
// returns the positions of the brackets within cmd
// if several such constrvcts are present in cmd, all
// occurrences will be returned
size_t pos=0;
size_t state = 0;
// 0 = outside select
// 1 = inside double quoted string outside select
// 2 = inside single quoted string outside select
// 3 = after bracket outside select
// 4 = after s
// 5 = after e
// 6 = after l
// 7 = after e
// 8 = after c
// 9 = after t
// 10 = select reached
// 11 = inside double quoted string inside select
// 12 = inside single quoted string inside select
int open_brackets = 0; // number of open round brackets
// within select
int begin=-1;
while(pos < cmd.length()){
char c = cmd[pos];
switch(state){
case 0 :
switch(c){
case '"' : state = 1; break;
case '\'' : state = 2; break;
case '(' : state = 3;
begin = pos;
break;
// for all other cases, the state is not changed
}
pos++;
break;
case 1 :
if(c=='"'){
state = 0;
}
pos++;
break;
case 2 :
if(c=='\''){
state = 0;
}
pos++;
break;
case 3:
if(c=='s'){
state = 4;
pos++;
} else if(isBlank(c)){
pos++;
} else {
state = 0;
}
break;
case 4:
if(c=='e'){
state = 5;
pos++;
} else {
state = 0;
}
break;
case 5:
if(c=='l'){
state = 6;
pos++;
} else {
state = 0;
}
break;
case 6:
if(c=='e'){
state = 7;
pos++;
} else {
state = 0;
}
break;
case 7:
if(c=='c'){
state = 8;
pos++;
} else {
state = 0;
}
break;
case 8:
if(c=='t'){
state = 9;
pos++;
} else {
state = 0;
}
break;
case 9:
if(isBlank(c)){
state = 10;
pos++;
} else {
state = 0;
}
break;
case 10:
switch(c){
case '"' : state=11; break;
case '\'' : state=12; break;
case '(' : open_brackets++; break;
case ')' :
if(open_brackets==0){
// end of select reached
res.push_back(pair<int,int>(begin,pos));
state = 0;
} else {
open_brackets--;
}
}
pos++;
break;
case 11:
if(c=='"'){
state=10;
}
pos++;
break;
case 12:
if(c=='\''){
state=10;
}
pos++;
break;
default : assert(false);
}
}
ok = (state==0) && (open_brackets==0);
return res;
}
string select2Secondo(const string& select, bool& correct){
string errMsg;
string cmd = checkSQLCommand("sql "+select,correct, errMsg);
if(!correct){
cout << "syntax error in command :" << errMsg << endl;
return "";
}
fid_t fid = PL_open_foreign_frame();
term_t a0 = PL_new_term_refs(2);
PL_put_atom_chars(a0, (cmd).c_str());
predicate_t p = PL_predicate("sqlToPlan",2,"");
qid_t id;
string plan="";
int count =0;
try{
id = PL_open_query(NULL, PL_Q_CATCH_EXCEPTION, p, a0);
if(PL_next_solution(id)){
count++;
char* res;
if(PL_get_atom_chars(a0+1,&res)){
string answer(res);
if(stringutils::startsWith(answer,"::ERROR::")){
errMsg = answer.substr(9);
correct = false;
} else {
cmd = answer;
}
} else {
correct = false;
}
}
PL_close_query(id);
} catch(...){
errMsg = "Exception occurred";
correct = false;
}
PL_discard_foreign_frame(fid);
return cmd;
}
string rewriteSelects(const string& cmd, bool& ok){
vector<pair<int,int> > selects = detectSelects(cmd,ok);
if((selects.size()==0) || !ok){
return cmd;
}
string res="";
size_t posCmd = 0;
size_t posSel = 0;
while(posCmd < cmd.length() && ok){
int l = selects[posSel].first - posCmd;
res += cmd.substr(posCmd, l);
res += "( "+ select2Secondo(cmd.substr(selects[posSel].first + 1,
selects[posSel].second - (1 + selects[posSel].first)),ok)
+ " )";
posCmd = selects[posSel].second + 1;
posSel++;
if(posSel==selects.size()){
res += cmd.substr(posCmd);
posCmd = cmd.length();
}
}
if(ok){
cout << endl;
cout << "command after replacing embedded select clauses:" << endl;
cout << res << endl << endl;
}
return res;
}
/*
~processDirectSecondoCommand~
This functions sends a command to the kernel and shows the
result.
*/
bool processDirectSecondoCommand(string cmd){
SecErrInfo err;
ListExpr resList;
if(stringutils::startsWith(cmd,"close")){
// redirect to close predicate
bool ok = closeOptDB();
if(ok){
cout << "database successfully closed" << endl;
} else {
cout << "closing database failed" << endl;
}
return ok;
} else if(stringutils::startsWith(cmd,"(")){
// assume command in nested list format
ListExpr cmdList;
if(!mnl->ReadFromString(cmd,cmdList)){
cout << "Error in parsing command in nested list format";
return false;
}
si->Secondo(cmdList, resList,err);
} else {
// usual secondo command without in user level syntax
bool ok=true;
cmd = rewriteSelects(cmd, ok);
if(!ok){
cout << "error during detecting embedded select clauses" << endl;
return false;
}
si->Secondo(cmd, resList, err);
}
if(err.code==0){
// output of result
showResult(resList);
// special treatment for optimizer
if(catalogChanges(cmd)){
cout << "Catalog has been changed" << endl;
updateOptCatalog();
}
} else {
cout << "error in command " << cmd << endl;
cout << "error code = " << err.code << endl;
cout << err.msg << endl << endl;
}
return err.code==0;
}
/*
~show Error~
prints ou some error message.
*/
void showError(const string& s){
cerr << s << endl;
}
/*
~getBindings~
Converts a bindings description into a vector of bindings/term pairs.
*/
bool getBindings( term_t bindings,
vector<pair<string, term_t> > & res ){
// bindings should be a list of ' = ( name, term )'
if(!PL_is_list(bindings)){
cerr << "bindings is not a list" << endl;
return false;
}
while(!PL_get_nil(bindings)){
term_t head = PL_new_term_ref();
term_t tail = PL_new_term_ref();
if(!PL_get_list(bindings, head, tail)){
cerr << "problem in dividing list" << endl;
return false;
}
bindings = tail; // get the rest of the list
size_t arity;
term_t name = PL_new_term_ref();
if(!PL_get_name_arity(head, &name, &arity)){
cerr << "name_arity failed" << endl;
return false;
}
if(arity!=2){
cerr << "found invalid arity in binding" << endl;
return false;
}
term_t a1 = PL_new_term_ref();
if(!PL_get_arg(1, head,a1)){
cerr << "get a1 failed" << endl;
return false;
}
term_t a2 = PL_new_term_ref();
if(!PL_get_arg (2,head,a2)){
cerr << "get a2 failed" << endl;
return false;
}
string s1;
char* s;
if(PL_get_chars(a1,&s, CVT_ALL)){
s1 = string(s);
} else {
cerr << "getting a1's name failed" << endl;
return false;
}
pair<string,term_t> p(s1, a2);
res.push_back(p);
}
return true;
}
/*
3.5 ~display~
*/
bool display(term_t t) {
string st = term2string(t);
cout << st;
return true;
}
/*
~processPrologCommand~
This function processes an arbitrary prolog question.
It converts the string into a term and extract the variables
within the term.
After that, the predicate is called and for each existing
solution, the values are printed out.
*/
bool processPrologCommand(const string& cmd){
if(plttydebug){
cout << "process prolog command: " << endl
<< cmd
<< endl;
}
fid_t fid = PL_open_foreign_frame();
// convert command into a string and get list of bindings
predicate_t conv = PL_predicate("atom_to_term", 3,"");
term_t a0 = PL_new_term_refs(3);
term_t term = a0+1;
term_t bindings = a0+2;
PL_put_atom_chars(a0, cmd.c_str());
if(!PL_call_predicate(NULL, PL_Q_NODEBUG, conv, a0)){
cerr << "converting string to query failed" << endl;
PL_discard_foreign_frame(fid);
return false;
}
if(!PL_is_callable(term)){
cerr << "Created term is not a query" << endl;
return false;
}
// extract variables and there names
vector<pair<string, term_t> > b;
if(!getBindings( bindings, b)){
cerr << "problem in analyisng term" << endl;
PL_discard_foreign_frame(fid);
return false;
}
// try to avaluate the term using the call predicate
predicate_t pl_call = PL_predicate("call",1,"");
qid_t qid = PL_open_query(NULL, PL_Q_CATCH_EXCEPTION, pl_call, term);
// print all solutions
int count = 0;
bool next = true;
while(next && PL_next_solution(qid)){
if(count > 0){
char k = getChar();
next = k!='.';
if(next){
cout << ";" << endl;
}
}
if(next){
count++;
for(size_t i=0;i<b.size();i++){
cout << b[i].first << " = ";
display(b[i].second);
if(i<b.size()-1){
cout << endl;
}
}
}
}
if(b.size()>0){
cout << ".";
}
cout << endl;
if(count==0){
cerr << "no" << endl;;
} else {
cout << "computed " << count << " solution" << (count!=1?"s":"")<< endl;
}
PL_close_query(qid);
PL_discard_foreign_frame(fid);
return true;
PL_discard_foreign_frame(fid);
return true;
}
/*
~processSqlCommand~
This function rewrites the command by replacing upper cases to lower cases
and sends the result to the optimizers ~sqlToPlan~ predicate. If this
was successful, the result is sent to the kernel and the result of this
call is displayed.
*/
bool processSqlCommand(string& cmd, bool isDot){
bool correct;
string errMsg;
cmd = checkSQLCommand(cmd,correct, errMsg);
if(!correct){
cout << "syntax error in command :" << errMsg << endl;
return false;
}
stringutils::StringTokenizer st(cmd,blanks,true);
string s = st.nextToken(); // always sql
s = st.nextToken();
if(s!="select"){
// handle only select statements using sqlToPlan
// all other kinds of sql statements are directly
// handled by the optimizer
return processPrologCommand(cmd);
}
fid_t fid = PL_open_foreign_frame();
term_t a0 = PL_new_term_refs(3);
PL_put_atom_chars(a0, (cmd).c_str());
predicate_t p = PL_predicate("sqlToPlan",3,"");
qid_t id;
bool ok= true;
string plan="";
string costs="";
int count =0;
try{
id = PL_open_query(NULL, PL_Q_CATCH_EXCEPTION, p, a0);
if(PL_next_solution(id)){
count++;
char* res;
if(PL_get_atom_chars(a0+1,&res)){
string answer(res);
if(stringutils::startsWith(answer,"::ERROR::")){
errMsg = answer.substr(9);
ok = false;
} else {
plan = "query "+ answer;
costs = term2string(a0+2);
}
} else {
ok = false;
}
}
PL_close_query(id);
} catch(...){
errMsg = "Exception occurred";
ok = false;
}
PL_discard_foreign_frame(fid);
if(!ok){
cout << "error in optimization" << endl;
if(errMsg.size()>0){
showError(errMsg);
}
return false;
}
if((count !=1)){
cout << "found no solution in optimization" << endl;
return false;
}
cout << endl;
cout << "Optimized plan is:" << endl << plan << endl;
cout << endl << "Estimated Costs are:" << endl << costs << endl << endl;
if(isStdInput && isDot){
char k=' ';
do{
cout << "execute query ? (y/n) ";
k = getChar();
k = tolower(k);
} while((k!='n') && (k!='y'));
cout << endl;
if(k=='n'){
return true;
}
}
return processDirectSecondoCommand(plan);
};
/*
~isSqlCommand~
This function checks whether a command is a sql command.
*/
bool isSqlCommand(string& cmd){
string cmdcopy = cmd;
stringutils::trim(cmdcopy);
stringutils::toLower(cmdcopy);
if(cmdcopy.length()==0){ // empty command
return false;
}
if(cmdcopy[0]=='{'){ // command sequence
return false;
}
stringutils::StringTokenizer st(cmdcopy, blanks2, true);
if(!st.hasNextToken()){
return false;
}
string first = st.nextToken();
// complete sql command
if(first=="sql"){
return true; // no change required
}
// without sql, first token is sufficient
if(first == "drop"){
cmd = "sql " + cmd;
return true;
}
if(first == "select"){
cmd = "sql " + cmd;
return true;
}
if(first == "union"){
cmd = "sql " + cmd;
return true;
}
if(first == "intersection"){
cmd = "sql " + cmd;
return true;
}
// next token required
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
if(first=="delete" && second=="from"){
cmd = "sql " + cmd;
return true;
}
if(first=="insert" && second=="into"){
cmd = "sql " + cmd;
return true;
}
if(first=="create" && second=="table"){
cmd = "sql " + cmd;
return true;
}
if(first=="create" && second=="index"){
cmd = "sql " + cmd;
return true;
}
// third token required
if(!st.hasNextToken()){
return false;
}
string third = st.nextToken();
if(first=="update" && third=="set"){
cmd = "sql " + cmd;
return true;
}
return false;
}
bool rewriteRestore(string& cmd){
stringutils::StringTokenizer st(cmd," \t\n",true);
if(!st.hasNextToken()) return false;
string first = st.nextToken();
if(first!="restore"){
return false;
}
// the restore commands have the following formats
// restore a from b
// restore database a from b
// note that b may contain spaces
if(!st.hasNextToken()){
return false;
}
string second = st.nextToken();
string database = "";
string name ="";
if(second == "database"){
database = "database ";
if(!st.hasNextToken()){
return false;
}
name = st.nextToken();
} else {
name = second;
}
if(!stringutils::isSymbol(name)){ // invalid database or object name
return false;
}
if(!st.hasNextToken()){
return false;
}
if(st.nextToken()!="from"){
return false;
}
if(!st.hasNextToken()){
return false;
}
string filename = st.getRest();
stringutils::trim(filename);
if(!stringutils::startsWith(filename,"'")){
filename = "'" + filename + "'";
}
if(!stringutils::startsWith(name,"'")){
name = "'" + name + "'";
}
cmd = "restore " + database + name + " from " + filename;
return true;
}
/*
~rewriteLet~
This function checkes whether cmd is in form
let IDENT = expr
and expr starts with one of the keywords select, union, or intersection
In this case, the command is returned as let(IDENT,expr)
Otherwise the command is returns completely unchanged
*/
std::string rewriteLet(std::string& cmd){
stringutils::StringTokenizer st(cmd," \t\n",true);
if(!st.hasNextToken()){
return cmd;
}
string first = st.nextToken();
if(first!="let"){
return cmd;
}
if(!st.hasNextToken()){
return cmd;
}
string ident = st.nextToken();
if(!stringutils::isIdent(ident)){
return cmd;
}
if(!st.hasNextToken()){
return cmd;
}
string assign = st.nextToken();
if(assign!="="){
return cmd;
}
string rest = st.getRest();
if(!st.hasNextToken()){
return cmd;
}
hasOuterBrackets(rest, true);
stringutils::StringTokenizer st2(rest," \t\n",true);
if(!st2.hasNextToken()){
return cmd;
}
string sqlindicator = st2.nextToken();
if(sqlindicator == "select"
|| sqlindicator == "union"
|| sqlindicator == "intersection"){
bool correct;
string errMsg;
string conv = checkSQLCommand(rest, correct, errMsg);
if(!correct){
cout << "sql part of an let command not correct :" << rest << endl;
cout << errMsg << endl;
return cmd;
}
string res = "let(" + ident+"," + conv+")";
if(plttydebug){
cout << "rewite let command to " << res << endl;
}
return res;
}
return cmd;
}
/*
~processCommand~
This function determines the kind of the command and calles the
appropriate command handler.
*/
bool processCommand(string& cmd){
stringutils::trim(cmd);
cmd = rewriteLet(cmd);
bool isDot = !cmd.empty() && (cmd[cmd.length()-1]=='.');
if(IsInternalCommand(cmd)){
if(plttydebug){
cout << "internal command recognized" << endl;
}
return processInternalCommand(cmd);
} else if (isSqlCommand(cmd)) {
if(plttydebug){
cout << "sql command recognized" << endl;
}
return processSqlCommand(cmd,isDot);
} else if(isDirectSecondoCommand(cmd)){
if(plttydebug){
cout << "direct secondo command recognized" << endl;
}
return processDirectSecondoCommand(cmd);
} else {
if(plttydebug){
cout << "general prolog command recognized" << endl;
}
if(rewriteRestore(cmd)){
if(plttydebug){
cout << "restore command recognized and rewritten to "
<< cmd << endl;
}
}
return processPrologCommand(cmd);
}
}
/*
~processCommands~
This function extracts commands from a stream and executes them.
If haltOnErrors is true, the processing is stoped immediately
if an error is occured. If the flag ~pdstyle~ is set, comments
in pd style in the stream are ignored during processing.
*/
bool processCommands(istream& in, bool haltOnErrors, bool pdstyle ){
string cmd = "";
bool error = false;
bool stop = false;
while((cmd!="quit") && (cmd!="q") && !globalAbort && !stop && !in.eof()){
cmd = getCommand(in, pdstyle);
stringutils::trim(cmd);
if(cmd.length()>0){
if(!processCommand(cmd)){
cout << "Error occurred during command '" << cmd << "'" << endl;
error = true;
if(haltOnErrors){
stop = true;
}
}
}
}
return !error;
}
} // end of namespace pltty
/*
The function ~secondo\_completion~ enables
tab extension if the readline library is used.
*/
#ifdef HAVE_LIBREADLINE
extern char** secondo_completion(const char* text, int start, int end);
#endif
/*
2 SecondoPLTTYMode
This function is the ~main~ function of SecondoPLTTY.
*/
int SecondoPLTTYMode(TTYParameter& tp)
{
atexit(handle_exit);
if( !StartSecondoC(tp) )
{
cout << "Usage : SecondoPL [Secondo-options] [Prolog-options]"
<< endl;
exit(1);
}
#ifdef HAVE_LIBREADLINE
rl_initialize();
rl_readline_name = "secondopl";
rl_attempted_completion_function = secondo_completion;
//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
/* Start PROLOG interpreter with our extensions. */
PL_register_extensions(predicates);
/* initialize the PROLOG engine */
int argc = 0;
char** argv = tp.Get_plargs(argc);
cerr << endl <<__FILE__ << ":" << __LINE__
<< " Calling PL_initialize with ";
for (int i = 0; i < argc; i++) {
cerr << argv[i] << " ";
}
cerr << endl << endl;
if( !PL_initialise(argc,argv) )
{
PL_halt(1);
}
else
{
{
// VTA - 15.11.2005
// I added this piece of code in order to run with newer versions
// of prolog. Without this code, the libraries (e.g. list.pl) are
// not automatically loaded. It seems that something in our code
// (auxiliary.pl and calloptimizer.pl) prevents them to be
// automatically loaded. In order to solve this problem I added
// a call to 'member(x, []).' so that the libraries are loaded
// before running our scripts.
term_t t0 = PL_new_term_refs(2),
t1 = t0+1;
PL_put_atom_chars(t0, "x");
if(!PL_put_list_chars(t1, "")){
assert(false);
}
predicate_t p = PL_predicate("member",2,"");
PL_call_predicate(NULL,PL_Q_NORMAL,p,t0);
// end VTA
}
/* load the auxiliary and calloptimizer */
term_t a0 = PL_new_term_refs(1);
static predicate_t p = PL_predicate("consult",1,"");
PL_put_atom_chars(a0,"auxiliary");
PL_call_predicate(NULL,PL_Q_NORMAL,p,a0);
PL_put_atom_chars(a0,"calloptimizer");
PL_call_predicate(NULL,PL_Q_NORMAL,p,a0);
/* switch to prolog-user-interface */
}
mnl = si->GetNestedList();
//DisplayTTY::Set_SI(si);
DisplayTTY::Set_NL(mnl);
NList::setNLRef(mnl);
DisplayTTY::Initialize();
bool ok;
if(tp.iFileName.length()==0){
ok = pltty::processCommands(cin, false, false);
} else {
ok = pltty::processScript("@"+tp.iFileName);
}
#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
DisplayTTY::Finish();
PL_halt(0);
return ok?0:1;
}