/*
* opencog/persist/api/StorageNode.cc
*
* Copyright (c) 2008-2010 OpenCog Foundation
* Copyright (c) 2009,2013,2020,2022,2025 Linas Vepstas
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License v3 as
* published by the Free Software Foundation and including the exceptions
* at http://opencog.org/wiki/Licenses
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to:
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string>
#include <opencog/persist/storage/storage_types.h>
#include <opencog/atoms/signature/TypeNode.h>
#include <opencog/atoms/value/BoolValue.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/StringValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include "StorageNode.h"
#include "DispatchHash.h"
using namespace opencog;
template class ObjectCRTP<StorageNode>;
// ====================================================================
StorageNode::StorageNode(Type t, std::string uri) :
ObjectCRTP<StorageNode>(t, std::move(uri)),
_target_as(nullptr)
{
if (not nameserver().isA(t, STORAGE_NODE))
throw RuntimeException(TRACE_INFO, "Bad inheritance!");
}
StorageNode::~StorageNode()
{
}
/// Evaluate the ValuePtr, trying to get an AtomSpace out of it,
/// either directly, or by execution. If the result is null, then
/// use the StorageNode atomspace as the default.
AtomSpace* StorageNode::get_target_as(const ValuePtr& value) const
{
if (nullptr == value)
return _target_as;
if (value->is_type(ATOM_SPACE))
return AtomSpaceCast(value).get();
if (0 == value->size())
return _target_as;
ValuePtr vp(value);
if (vp->is_type(ATOM))
{
Handle h(HandleCast(value));
if (h->is_executable())
vp = h->execute();
}
if (nullptr == vp)
return _target_as;
if (vp->is_type(ATOM_SPACE))
return AtomSpaceCast(vp).get();
if (0 == vp->size())
return _target_as;
throw RuntimeException(TRACE_INFO,
"Expected AtomSpace; got %s", value->to_string().c_str());
}
void StorageNode::setValue(const Handle& key, const ValuePtr& value)
{
// The value must be stored only if it is not one of the values
// that causes an action to be taken. Action messages must not be
// recorded, as otherwise, restore from disk/net will cause the
// action to be triggered!
if (PREDICATE_NODE != key->get_type())
{
Atom::setValue(key, value);
return;
}
// Create a fast dispatch table by using case-statement
// branching, instead of string compare.
static constexpr uint32_t p_open =
dispatch_hash("*-open-*");
static constexpr uint32_t p_open_ro =
dispatch_hash("*-open-ro-*");
static constexpr uint32_t p_close =
dispatch_hash("*-close-*");
static constexpr uint32_t p_load_atomspace =
dispatch_hash("*-load-atomspace-*");
static constexpr uint32_t p_store_atomspace =
dispatch_hash("*-store-atomspace-*");
static constexpr uint32_t p_load_atoms_of_type =
dispatch_hash("*-load-atoms-of-type-*");
static constexpr uint32_t p_store_atom =
dispatch_hash("*-store-atom-*");
static constexpr uint32_t p_store_value =
dispatch_hash("*-store-value-*");
static constexpr uint32_t p_update_value =
dispatch_hash("*-update-value-*");
static constexpr uint32_t p_fetch_atom =
dispatch_hash("*-fetch-atom-*");
static constexpr uint32_t p_fetch_value =
dispatch_hash("*-fetch-value-*");
static constexpr uint32_t p_fetch_incoming_set =
dispatch_hash("*-fetch-incoming-set-*");
static constexpr uint32_t p_fetch_incoming_by_type =
dispatch_hash("*-fetch-incoming-by-type-*");
static constexpr uint32_t p_fetch_query =
dispatch_hash("*-fetch-query-*");
static constexpr uint32_t p_delete = dispatch_hash("*-delete-*");
static constexpr uint32_t p_delete_recursive =
dispatch_hash("*-delete-recursive-*");
static constexpr uint32_t p_barrier = dispatch_hash("*-barrier-*");
// *-load-frames-* is in getValue
static constexpr uint32_t p_store_frames = dispatch_hash("*-store-frames-*");
static constexpr uint32_t p_delete_frame = dispatch_hash("*-delete-frame-*");
static constexpr uint32_t p_erase = dispatch_hash("*-erase-*");
static constexpr uint32_t p_proxy_open = dispatch_hash("*-proxy-open-*");
static constexpr uint32_t p_proxy_close = dispatch_hash("*-proxy-close-*");
static constexpr uint32_t p_set_proxy = dispatch_hash("*-set-proxy-*");
// There's almost no chance at all that any user will use some key
// that is a PredicateNode that has a string name that collides with
// one of the above. That's because there's really no reason to set
// static values on a StorageNode. That I can think of. Still there's
// some chance of a hash collision. In this case, define
// COLLISION_PROOF, and recompile. Sorry in advance for the awful
// debug session you had that caused you to discover this comment!
//
// #define COLLISION_PROOF
#ifdef COLLISION_PROOF
#define COLL(STR) if (0 != pred.compare(STR)) break;
#else
#define COLL(STR)
#endif
const std::string& pred = key->get_name();
switch (dispatch_hash(pred.c_str()))
{
case p_open: {
COLL("*-open-*");
AtomSpace* as = get_target_as(value);
if (nullptr == as) as = getAtomSpace();
_target_as = as;
open();
return;
}
case p_open_ro: {
COLL("*-open-ro-*");
AtomSpace* as = get_target_as(value);
if (nullptr == as) as = getAtomSpace();
_target_as = as;
open_read_only();
return;
}
case p_close:
COLL("*-close-*");
close();
_target_as = nullptr;
return;
case p_load_atomspace:
COLL("*-load-atomspace-*");
load_atomspace(get_target_as(value));
return;
case p_store_atomspace:
COLL("*-store-atomspace-*");
store_atomspace(get_target_as(value));
return;
case p_load_atoms_of_type: {
COLL("*-load-atoms-of-type-*");
load_atoms_of_type_msg(value);
return;
}
case p_store_atom: {
COLL("*-store-atom-*");
if (value->is_type(ATOM))
{
store_atom(HandleCast(value));
return;
}
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
for (const ValuePtr& vp : vsq)
store_atom(HandleCast(vp));
return;
}
case p_store_value: {
COLL("*-store-value-*");
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
size_t nv = vsq.size();
if (2 > nv) return;
Handle atm(HandleCast(vsq[0]));
for (size_t i=1; i<nv; i++)
store_value(atm, HandleCast(vsq[i]));
return;
}
case p_update_value: {
COLL("*-update-value-*");
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
if (3 > vsq.size()) return;
update_value(HandleCast(vsq[0]), HandleCast(vsq[1]), vsq[2]);
return;
}
case p_fetch_atom: {
COLL("*-fetch-atom-*");
if (value->is_type(ATOM))
{
fetch_atom(HandleCast(value));
return;
}
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
AtomSpace* as = _target_as;
for (const ValuePtr& vp : vsq)
{
if (vp->is_type (ATOM_SPACE))
{
as = AtomSpaceCast(HandleCast(vp)).get();
continue;
}
fetch_atom(HandleCast(vp), as);
}
return;
}
case p_fetch_value: {
COLL("*-fetch-value-*");
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
size_t nv = vsq.size();
// Format: [AtomSpace, Atom, Key1, Key2, ...] or [Atom, Key1, Key2, ...]
if (3 <= nv && vsq[0]->is_type(ATOM_SPACE)) {
AtomSpace* as = AtomSpaceCast(vsq[0]).get();
Handle atm = HandleCast(vsq[1]);
for (size_t i=2; i<nv; i++)
fetch_value(atm, HandleCast(vsq[i]), as);
} else if (2 <= nv) {
Handle atm = HandleCast(vsq[0]);
for (size_t i=1; i<nv; i++)
fetch_value(atm, HandleCast(vsq[i]));
}
return;
}
case p_fetch_incoming_set: {
COLL("*-fetch-incoming-set-*");
if (value->is_type(ATOM))
{
fetch_incoming_set(HandleCast(value), false);
return;
}
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
AtomSpace* as = _target_as;
for (const ValuePtr& vp : vsq)
{
if (vp->is_type (ATOM_SPACE))
{
as = AtomSpaceCast(HandleCast(vp)).get();
continue;
}
fetch_incoming_set(HandleCast(vp), false, as);
}
return;
}
case p_fetch_incoming_by_type: {
COLL("*-fetch-incoming-by-type-*");
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
size_t nv = vsq.size();
// Format: [AtomSpace, Atom1, Type1, Atom2, Type2, ...] or [Atom1, Type1, Atom2, Type2, ...]
if (3 <= nv && vsq[0]->is_type(ATOM_SPACE)) {
AtomSpace* as = AtomSpaceCast(vsq[0]).get();
// Process pairs of (Atom, Type) starting from index 1
for (size_t i=1; i+1<nv; i+=2) {
Handle atom = HandleCast(vsq[i]);
Type t = TypeNodeCast(HandleCast(vsq[i+1]))->get_kind();
fetch_incoming_by_type(atom, t, as);
}
} else if (2 <= nv) {
// Process pairs of (Atom, Type) starting from index 0
for (size_t i=0; i+1<nv; i+=2) {
Handle atom = HandleCast(vsq[i]);
Type t = TypeNodeCast(HandleCast(vsq[i+1]))->get_kind();
fetch_incoming_by_type(atom, t);
}
}
return;
}
case p_fetch_query:
COLL("*-fetch-query-*");
fetch_query_msg(value);
return;
case p_delete:
COLL("*-delete-*");
remove_msg(value, false);
return;
case p_delete_recursive:
COLL("*-delete-recursive-*");
remove_msg(value, true);
return;
case p_barrier:
COLL("*-barrier-*");
barrier(get_target_as(value));
return;
case p_store_frames:
COLL("*-store-frames-*");
store_frames(HandleCast(value));
return;
case p_delete_frame:
COLL("*-delete-frame-*");
delete_frame(HandleCast(value));
return;
case p_erase:
COLL("*-erase-*");
erase();
return;
case p_proxy_open:
COLL("*-proxy-open-*");
proxy_open();
return;
case p_proxy_close:
COLL("*-proxy-close-*");
proxy_close();
return;
case p_set_proxy:
COLL("*-set-proxy-*");
set_proxy(HandleCast(value));
return;
default:
break;
}
// Some other predicate. Store it.
Atom::setValue(key, value);
}
ValuePtr StorageNode::getValue(const Handle& key) const
{
if (PREDICATE_NODE != key->get_type())
return Atom::getValue(key);
const std::string& pred(key->get_name());
if (0 == pred.compare("*-connected?-*"))
{
bool is_con = const_cast<StorageNode*>(this)->connected();
return createBoolValue(is_con);
}
if (0 == pred.compare("*-load-frames-*"))
return createLinkValue(const_cast<StorageNode*>(this)->load_frames());
if (0 == pred.compare("*-monitor-*"))
return createStringValue(const_cast<StorageNode*>(this)->monitor());
return Atom::getValue(key);
}
// ====================================================================
void StorageNode::barrier(AtomSpace* as)
{
if (nullptr == as) as = _target_as;
as->barrier();
}
void StorageNode::store_atom(const Handle& h)
{
if (_target_as->get_read_only())
throw RuntimeException(TRACE_INFO,
"Attempt to store to read-only target AtomSpace %s",
_target_as->to_string().c_str());
storeAtom(h);
}
void StorageNode::store_value(const Handle& h, const Handle& key)
{
if (_target_as->get_read_only())
throw RuntimeException(TRACE_INFO, "Read-only AtomSpace!");
storeValue(h, key);
}
void StorageNode::update_value(const Handle& h, const Handle& key,
const ValuePtr& delta)
{
if (_target_as->get_read_only())
throw RuntimeException(TRACE_INFO, "Read-only AtomSpace!");
updateValue(h, key, delta);
}
void StorageNode::remove_atom(AtomSpace* as, Handle h, bool recursive)
{
removeAtom(as, h, recursive);
}
Handle StorageNode::fetch_atom(const Handle& h, AtomSpace* as)
{
if (nullptr == h) return Handle::UNDEFINED;
if (nullptr == as) as = _target_as;
// Now, get the latest values from the backing store.
// The operation here is to CLOBBER the values, NOT to merge them!
// The goal of an explicit fetch is to explicitly fetch the values,
// and not to play monkey-shines with them. If you want something
// else, then save the old TV, fetch the new TV, and combine them
// with your favorite algo.
Handle ah = as->add_atom(h);
if (nullptr == ah) return ah; // if read-only, then cannot update.
getAtom(ah);
return ah;
}
Handle StorageNode::fetch_value(const Handle& h, const Handle& key,
AtomSpace* as)
{
if (nullptr == as) as = _target_as;
Handle lkey = as->add_atom(key);
Handle lh = as->add_atom(h);
loadValue(lh, lkey);
return lh;
}
Handle StorageNode::fetch_incoming_set(const Handle& h, bool recursive,
AtomSpace* as)
{
if (nullptr == as) as = _target_as;
Handle lh = as->get_atom(h);
if (nullptr == lh) return lh;
// Get everything from the backing store.
fetchIncomingSet(as, lh);
if (not recursive) return lh;
IncomingSet vh(h->getIncomingSet());
for (const Handle& lp : vh)
fetch_incoming_set(lp, true, as);
return lh;
}
Handle StorageNode::fetch_incoming_by_type(const Handle& h, Type t,
AtomSpace* as)
{
if (nullptr == as) as = _target_as;
Handle lh = as->get_atom(h);
if (nullptr == lh) return lh;
// Get everything from the backing store.
fetchIncomingByType(as, lh, t);
return lh;
}
Handle StorageNode::fetch_query(const Handle& query, const Handle& key,
const Handle& metadata, bool fresh,
AtomSpace* as)
{
// Queries can be anything executable or evaluatable.
if (not query->is_executable() and not query->is_evaluatable())
throw RuntimeException(TRACE_INFO, "Not executable!");
if (nullptr == as) as = _target_as;
Handle lkey = as->add_atom(key);
Handle lq = as->add_atom(query);
Handle lmeta = metadata;
if (Handle::UNDEFINED != lmeta) lmeta = as->add_atom(lmeta);
runQuery(lq, lkey, lmeta, fresh);
return lq;
}
void StorageNode::load_atomspace(AtomSpace* as)
{
if (nullptr == as) as = _target_as;
loadAtomSpace(as);
}
/**
* Use the backing store to store entire AtomSpace.
*/
void StorageNode::store_atomspace(AtomSpace* as)
{
if (nullptr == as) as = _target_as;
storeAtomSpace(as);
}
void StorageNode::fetch_all_atoms_of_type(Type t, AtomSpace* as)
{
if (nullptr == as) as = _target_as;
loadType(as, t);
}
HandleSeq StorageNode::load_frames(void)
{
return loadFrameDAG();
}
void StorageNode::store_frames(const Handle& has)
{
return storeFrameDAG((AtomSpace*)has.get());
}
void StorageNode::delete_frame(const Handle& has)
{
return deleteFrame((AtomSpace*)has.get());
}
// ====================================================================
void StorageNode::proxy_open(void)
{
throw RuntimeException(TRACE_INFO,
"This StorageNode does not implement proxying!");
}
void StorageNode::proxy_close(void)
{
throw RuntimeException(TRACE_INFO,
"This StorageNode does not implement proxying!");
}
void StorageNode::open_read_only(void)
{
throw RuntimeException(TRACE_INFO,
"This StorageNode does not support read-only opening!");
}
void StorageNode::set_proxy(const Handle&)
{
throw RuntimeException(TRACE_INFO,
"This StorageNode does not implement proxying!");
}
std::string StorageNode::monitor(void)
{
return "This StorageNode does not implement a monitor.\n";
}
// ====================================================================
// Message handlers.
// Message handler for remove_atom. Message may be the single Atom
// to be deleted, or a LinkValue giving a mixture of Atoms to be deleted,
// preceded by the AtomSpaces they are to be removed from.
void StorageNode::remove_msg(const ValuePtr& value, bool recursive)
{
if (value->is_type(ATOM))
{
Handle atm(HandleCast(value));
remove_atom(atm->getAtomSpace(), atm, recursive);
return;
}
// Assume a LinkValue of some kind.
// ?? Do nothing? Or throw an error? XXX FIXME.
if (not value->is_type(LINK_VALUE)) return;
const LinkValuePtr& lvp(LinkValueCast(value));
AtomSpacePtr asp;
for (const ValuePtr& vp: lvp->value())
{
if (vp->is_type(ATOM_SPACE))
{
asp = AtomSpaceCast(vp);
continue;
}
if (asp)
remove_atom(asp.get(), HandleCast(vp), recursive);
else
{
Handle atm(HandleCast(vp));
remove_atom(atm->getAtomSpace(), atm, recursive);
}
}
}
// Message handler for load_atom_of_type. Message may be the single
// Type to be loaded, or a LinkValue giving a mixture of types,
// preceded by the AtomSpaces they are to loaded into.
void StorageNode::load_atoms_of_type_msg(const ValuePtr& value)
{
// Expect either a single Type, or a LinkValue of multiple
// types giving the AtomSpace and the Atom to delete.
if (value->is_type(TYPE_NODE))
{
Handle h(HandleCast(value));
Type t = TypeNodeCast(h)->get_kind();
loadType(h->getAtomSpace(), t);
return;
}
// Assume a LinkValue of some kind.
// ?? Do nothing? Or throw an error? XXX FIXME.
if (not value->is_type(LINK_VALUE)) return;
const LinkValuePtr& lvp(LinkValueCast(value));
AtomSpacePtr asp;
for (const ValuePtr& vp: lvp->value())
{
if (vp->is_type(ATOM_SPACE))
{
asp = AtomSpaceCast(vp);
continue;
}
// Ignore anything we don't understand.
// Or we could throw an exception here. I dunno.
if (not value->is_type(TYPE_NODE)) continue;
if (asp)
{
Type t = TypeNodeCast(HandleCast(vp))->get_kind();
loadType(asp.get(), t);
}
else
{
Handle h(HandleCast(vp));
Type t = TypeNodeCast(h)->get_kind();
loadType(h->getAtomSpace(), t);
}
}
}
void StorageNode::fetch_query_msg(const ValuePtr& value)
{
if (not value->is_type(LINK_VALUE)) return;
const ValueSeq& vsq(LinkValueCast(value)->value());
size_t nv = vsq.size();
// Format:
// [AtomSpace, Query, Key, Metadata, Fresh] or
// [AtomSpace, Query, Key] or
// [Query, Key, Metadata, Fresh] or
// [Query, Key]
// This is a blargle-mess but I don't feel like cleaning up today.
// XXX FIXME this should be cleaned up when serious users arrive.
if (5 <= nv && vsq[0]->is_type(ATOM_SPACE)) {
AtomSpace* as = AtomSpaceCast(vsq[0]).get();
Handle query = HandleCast(vsq[1]);
Handle key = HandleCast(vsq[2]);
Handle metadata = HandleCast(vsq[3]);
bool fresh = vsq[4]->is_type(BOOL_VALUE) && BoolValueCast(vsq[4])->get_bit(0);
fetch_query(query, key, metadata, fresh, as);
} else if (3 == nv && vsq[0]->is_type(ATOM_SPACE)) {
AtomSpace* as = AtomSpaceCast(vsq[0]).get();
Handle query = HandleCast(vsq[1]);
Handle key = HandleCast(vsq[2]);
fetch_query(query, key, Handle::UNDEFINED, false, as);
} else if (4 == nv) {
Handle query = HandleCast(vsq[0]);
Handle key = HandleCast(vsq[1]);
Handle metadata = HandleCast(vsq[2]);
bool fresh = vsq[3]->is_type(BOOL_VALUE) && BoolValueCast(vsq[3])->get_bit(0);
fetch_query(query, key, metadata, fresh);
} else if (2 == nv) {
Handle query = HandleCast(vsq[0]);
Handle key = HandleCast(vsq[1]);
fetch_query(query, key, Handle::UNDEFINED, false);
}
}
// ====================================================================