Files
secondo/Algebras/Distributed4/ConnectionSession.cpp
2026-01-23 17:03:45 +08:00

412 lines
12 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2017, 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
----
//paragraph [10] title: [{\Large \bf] [}]
//characters [1] tt: [\texttt{] [}]
//[secondo] [{\sc Secondo}]
[10] Implementation of Class ConnectionSession
2017-10-28: Sebastian J. Bronner $<$sebastian@bronner.name$>$
\tableofcontents
1 Preliminary Setup
*/
#include "ConnectionSession.h"
namespace distributed2 {
extern bool showCommands;
extern distributed2::CommandLog commandLog;
}
namespace distributed4 {
using distributed2::ConnectionInfo;
using distributed2::commandLog;
using distributed2::showCommands;
using std::cerr;
using std::runtime_error;
using std::string;
using std::to_string;
/*
2 Constructors
2.1 "ConnectionSession(const ConnectionInfo[*], bool)"[1]
"ci"[1] is a pointer to a "ConnectionInfo" object. The database on the secondo
instance connected by "ci"[1] is changed to the current local database. As a
side-effect that ensures that "ci"[1] is functional. "d"[1] specifies whether
this object is responsible for deleting "ci"[1], or not (the default when it is
passed in, in that case the caller is implicitly held responsible for its
pointer).
*/
ConnectionSession::ConnectionSession(ConnectionInfo* ci, bool d): ci{ci},
deleteci{d} {
if(!ci)
throw runtime_error{"The ConnectionInfo pointer passed to "
"ConnectionSession is invalid."};
try {
switchDatabase();
} catch(const runtime_error&) {
if(d)
delete ci;
throw;
}
}
/*
2.2 "ConnectionSession(const string, int, const string)"[1]
"host"[1], "port"[1], and "config"[1] describe the secondo instance to which a
connection should be established. That connection is established and the
database changed to the current local database.
*/
ConnectionSession::ConnectionSession(const string& host, int port, const
string& config) {
string config_temp{config}; // because non-const in createConnection
ci = ConnectionInfo::createConnection(host, port, config_temp);
if(!ci)
throw runtime_error{"A connection to " + host + ":" + to_string(port) +
" could not be established."};
try {
switchDatabase();
} catch(const runtime_error&) {
delete ci;
throw;
}
}
/*
3 Destructor
"ci"[1] is handed in by the caller or passed back by
"ConnectionInfo::createConnection"[1]. Depending on the situation,
responsibility for deleting it may lie with the caller or it may lie here.
Which is the case is tracked in "deleteci"[1].
*/
ConnectionSession::~ConnectionSession() {
if(!rollback.empty()) {
cerr << "ConnectionSession for " << ci->getHost() << ":" << ci->getPort()
<< " was destroyed with " << rollback.size() << " entries in the "
"rollback list. Performing rollback now." << endl;
int err;
string msg;
ListExpr res;
double rt;
for(auto it{rollback.rbegin()}; it != rollback.rend(); ++it)
ci->simpleCommand(*it, err, msg, res, false, rt, true,
commandLog);
}
if(deleteci)
delete ci;
}
/*
4 Informational Member Functions
4.1 "getHost"[1]
*/
string ConnectionSession::getHost() const {
return ci->getHost();
}
/*
4.2 "getPort"[1]
*/
int ConnectionSession::getPort() const {
return ci->getPort();
}
/*
4.3 "getConfig"[1]
*/
string ConnectionSession::getConfig() const {
return ci->getConfig();
}
/*
5 Active Member Functions
These member functions perform some action on the connected [secondo] instance.
5.1 "run"[1]
The command passed in "cmd"[1] will be executed via "ci"[1]. If "uncmd"[1] is
specified (is not the empty string), it is added to "rollback"[1] after
"cmd"[1] was executed successfully on each connection. In case of an error, a
"runtime\_error"[1] is thrown with an error message. If the caller doesn't
handle the exception, the commands in "rollback"[1] will be executed as a
consequence of leaving scope.
*/
NList ConnectionSession::run(const string& cmd, const string& uncmd) {
int err;
string msg;
ListExpr res;
double rt;
ci->simpleCommand(cmd, err, msg, res, false, rt, showCommands,
commandLog);
if(err)
throw runtime_error{"Peer " + ci->getHost() + ":" +
to_string(ci->getPort()) + ": " + cmd + ": " + msg};
if(!uncmd.empty())
rollback.push_back(uncmd);
return NList{res};
}
/*
5.2 "clearRollback"[1]
After running a sequence of commands, the caller may reach a state where it is
no longer desirable (or necessary) to perform a rollback. That is where
"clearRollback"[1] comes in. "rollback"[1] is emptied of its stored commands so
that any future rollback will only be performed on new commands.
*/
void ConnectionSession::clearRollback() {
rollback.clear();
}
/*
5.3 "switchDatabase"[1]
*/
void ConnectionSession::switchDatabase(const string& dbname) {
if(!ci->switchDatabase(dbname, false, showCommands))
throw runtime_error{"The database \"" + dbname + "\" could not be "
"opened on " + ci->getHost() + ":" + to_string(ci->getPort()) + "."};
}
/*
5.4 "beginTransaction"[1]
*/
void ConnectionSession::beginTransaction() {
run("begin transaction", "abort transaction");
}
/*
5.5 "commitTransaction"[1]
*/
void ConnectionSession::commitTransaction() {
clearRollback();
run("commit transaction");
}
/*
6 Object Manipulation Member Functions
6.1 "updateObject"[1]
*/
void ConnectionSession::updateObject(const string& name, const Address
address) {
SecondoCatalog* ctlg{SecondoSystem::GetCatalog()};
NList type{ctlg->GetObjectTypeExpr(name)};
NList value{ctlg->OutObject(type.listExpr(), Word{address})};
run("update " + name + " := [const " + type.convertToString() + " value " +
value.convertToString() + "]");
}
/*
6.2 "letObject"[1]
Create a new object named "name"[1] of type "type"[1] with the value at
"address"[1]. Specifying the type is optional; if it is not specified, it is
derived from a local object named "name"[1]. It is also possible to specify
that an object is to be created with an empty value (but defined). This is
indicated by passing "nullptr"[1] in "address"[1] (or not specifying it at all
if "type"[1] is also not specified). In this case an attempt is made to create
the object by instantiating it with the nested list "()". This is not possible
for all objects, though, and will abort with an error for those objects. It is
useful, though, for example, to create relations with no tuples in them.
*/
void ConnectionSession::letObject(const string& name, const Address address,
NList type)
{
SecondoCatalog* ctlg{SecondoSystem::GetCatalog()};
if(type.isEmpty())
type = NList{ctlg->GetObjectTypeExpr(name)};
NList value{address ? ctlg->OutObject(type.listExpr(), Word{address}) :
NList{}};
run("let " + name + " = [const " + type.convertToString() + " value " +
value.convertToString() + "]", "delete " + name);
}
/*
6.3 "deleteObject"[1]
*/
void ConnectionSession::deleteObject(const string& name) {
run("delete " + name);
}
/*
6.4 "lockObject"[1]
*/
void ConnectionSession::lockObject(const string& name, bool exclusive) {
try {
run("query trylock(\"" + name + "\", " + (exclusive ? "TRUE" : "FALSE") +
")");
} catch(const runtime_error& e) {
cmsg.info() << e.what() << " Waiting for " << (exclusive ? "exclusive" :
"sharable") << " ownership." << endl;
cmsg.send();
run("query lock(\"" + name + "\", " + (exclusive ? "TRUE" : "FALSE") +
")", "query unlock(\"" + name + "\")");
}
}
/*
6.5 "unlockObject"[1]
*/
void ConnectionSession::unlockObject(const string& name) {
run("query unlock(\"" + name + "\")");
}
/*
7 Atom Query Member Functions
These member functions handle the common case where a single value atom is
expected as a response to a query.
7.1 "queryAtom"[1]
Determine the response from any query that responds with a simple value. If the
response is more complex than that, "runtime\_error"[1] is thrown. "cmd"[1] may
be given without the keyword "query"[1].
*/
NList ConnectionSession::queryAtom(string cmd) {
if(cmd.substr(0, 6) != "query ")
cmd = "query " + cmd;
NList resp{run(cmd)};
if(!(resp.length() == 2 && resp.first().isSymbol()))
throw runtime_error{"Received a response from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not an atom: " + cmd + " -> " +
resp.convertToString()};
return resp;
}
/*
7.2 "querySymbol"[1]
*/
string ConnectionSession::querySymbol(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == "symbol" && resp.second().isSymbol()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not a symbol: " + cmd + " -> " +
resp.convertToString()};
return resp.second().str();
}
/*
7.3 "queryString"[1]
*/
string ConnectionSession::queryString(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == CcString::BasicType() &&
resp.second().isString()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not a string: " + cmd + " -> " +
resp.convertToString()};
return resp.second().str();
}
/*
7.4 "queryText"[1]
*/
string ConnectionSession::queryText(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == FText::BasicType() && resp.second().isText()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not a text: " + cmd + " -> " +
resp.convertToString()};
return resp.second().str();
}
/*
7.5 "queryInt"[1]
*/
int ConnectionSession::queryInt(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == CcInt::BasicType() && resp.second().isInt()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not an integer: " + cmd + " -> " +
resp.convertToString()};
return resp.second().intval();
}
/*
7.6 "queryReal"[1]
*/
double ConnectionSession::queryReal(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == CcReal::BasicType() && resp.second().isReal()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not a real: " + cmd + " -> " +
resp.convertToString()};
return resp.second().realval();
}
/*
7.7 "queryBool"[1]
*/
bool ConnectionSession::queryBool(const string& cmd) {
NList resp{queryAtom(cmd)};
if(!(resp.first().str() == CcBool::BasicType() && resp.second().isBool()))
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is not a bool: " + cmd + " -> " +
resp.convertToString()};
return resp.second().boolval();
}
/*
7.8 "queryNumeric"[1]
*/
double ConnectionSession::queryNumeric(const string& cmd) {
try {
return queryReal(cmd);
} catch(const runtime_error&) {
try {
return static_cast<double>(queryInt(cmd));
} catch(const runtime_error&) {
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is neither a real nor an integer: "
+ cmd};
}
}
}
/*
7.9 "queryTextual"[1]
*/
string ConnectionSession::queryTextual(const string& cmd) {
try {
return queryString(cmd);
} catch(const runtime_error&) {
try {
return queryText(cmd);
} catch(const runtime_error&) {
throw runtime_error{"Received a value from " + ci->getHost() + ":" +
to_string(ci->getPort()) + " that is neither a string nor a text: "
+ cmd};
}
}
}
}