OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace-storage / opencog / persist / api / StorageNode.h

392 lines · c ref:
/*
 * opencog/persist/api/StorageNode.h
 *
 * Copyright (C) 2008-2011 OpenCog Foundation
 * Copyright (C) 2002-2007 Novamente LLC
 * Copyright (C) 2015,2020,2022 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.
 */

#ifndef _OPENCOG_STORAGE_NODE_H
#define _OPENCOG_STORAGE_NODE_H

#include <opencog/atoms/core/ObjectNode.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/persist/api/BackingStore.h>
#include <opencog/atoms/atom_types/atom_types.h>

namespace opencog
{
/** \addtogroup grp_atomspace
 *  @{
 */

class StorageNode;
extern template class ObjectCRTP<StorageNode>;

class StorageNode : public ObjectCRTP<StorageNode>, protected BackingStore
{
	// The write-thru proxies need to call into the BackingStore
	// directly. We declare them as friends.
	friend class WriteThruProxy;
	friend class ReadWriteProxy;
	friend class ObjectCRTP<StorageNode>;
protected:
	AtomSpace* _target_as;
	AtomSpace* get_target_as(const ValuePtr&) const;

	// Friendship cannot be inherited in c++. So we pass through selected
	// functions to derived classes that will need them.
	static inline Handle add_nocheck(AtomSpace* as, const Handle& h)
		{ return as->add(h); }
	static inline void get_absent_atoms(const AtomSpace* as, HandleSeq& missing)
		{ as->get_absent_atoms(missing); }
	static inline void get_atoms_in_frame(const AtomSpace* as, HandleSeq& fseq)
		{ as->get_atoms_in_frame(fseq); }

	static inline void markAtomIsKey(const Handle& h) { h->markIsKey(); }
	static inline void markAtomIsMessage(const Handle& h) { h->markIsMessage(); }

	void remove_msg(const ValuePtr&, bool recursive=false);
	void load_atoms_of_type_msg(const ValuePtr&);
	void fetch_query_msg(const ValuePtr&);

public:
	StorageNode(Type, std::string);
	virtual ~StorageNode();

	virtual void setValue(const Handle&, const ValuePtr&);
	virtual ValuePtr getValue(const Handle&) const;

	// ----------------------------------------------------------------
	// Operations regarding the connection to the remote URI.
	/**
	 * Open a connection to the indicated URI.
	 */
	virtual void open(void) = 0;

	/**
	 * Open a read-only connection to the indicated URI.
	 */
	virtual void open_read_only(void);

	/**
	 * Close an active connection.
	 */
	virtual void close(void) = 0;

	/**
	 * Return true if the connection to the remote end appears to
	 * be established and functioning.
	 */
	virtual bool connected(void) = 0;

	/**
	 * Initialize storage at the remote end. There must already be
	 * an open connection to the remote end; and the remote end must
	 * be vacant or empty.  For example: for an SQL server, this can
	 * be used to create the database, the tables in the database for
	 * the first time.
	 */
	virtual void create(void) = 0;

	/**
	 * Destroy the storage at the remote end. Empties the remote end of
	 * data, and then undoes whaterver `create()` did. Remote ends might
	 * not honor this request, e.g. if other clients have open
	 * connections.
	 */
	virtual void destroy(void) = 0;

	/**
	 * Erase the entire contents of the remote end. Performs a bulk
	 * deletion of all data.
	 */
	virtual void erase(void) = 0;

	/**
	 * For StorageNodes that support proxying, open the proxy for operation.
	 */
	virtual void proxy_open(void);

	/**
	 * For StorageNodes that support proxying, close the proxy for operation.
	 */
	virtual void proxy_close(void);

	/**
	 * For StorageNodes that support proxying, define what the proxy will be.
	 * The argument must be of the form `(FooProxyNode "blah")`.
	 */
	virtual void set_proxy(const Handle&);

	/**
	 * Return debug diagnostics and/or performance monitoring stats.
	 */
	virtual std::string monitor(void);

	// ----------------------------------------------------------------
	// Operations regarding specific atomspace contents.

	/**
	 * Make sure all atom writes have completed, before returning.
	 * This only has an effect when the atomspace is backed by some
	 * sort of storage, or is sending atoms to some remote location
	 * asynchronously. This simply guarantees that the asynch
	 * operations have completed.
	 * NB: at this time, we don't distinguish barrier and flush.
	 */
	void barrier(AtomSpace* = nullptr);

	/**
	 * Unconditionally fetch an atom from the backingstore.
	 *
	 * If there is no backingstore, then Handle::UNDEFINED is returned.
	 * If the atom is found in the backingstore, then it is placed in
	 * the atomtable before returning.  If the atom is already in the
	 * atomtable, and is also found in the backingstore, then all of
	 * the values on the atom are updated from the database. (Values
	 * not in the database are not touched).
	 *
	 * To avoid a fetch if the atom already is in the atomtable, use
	 * the get_atom() method instead.
	 */
	Handle fetch_atom(const Handle&, AtomSpace* = nullptr);

	/**
	 * Fetch the Value located at `key` on `atom` from the remote
	 * server, and attach it to `key` on this `atom`.  If the remote
	 * server does not have a value at `key`, then the local value
	 * at `key` (if any) will be deleted.
	 *
	 * This method is more granular than `fetch_atom()`, as it
	 * operates only on one particular key.
	 *
	 * This fetch is synchronous: the value at `key` will be updated
	 * before this method returns. Existing code depends on this
	 * synchronous behavior.
	 */
	Handle fetch_value(const Handle& atom, const Handle& key,
	                   AtomSpace* = nullptr);

	/**
	 * Use the backing store to load all atoms of the given atom type.
	 */
	void fetch_all_atoms_of_type(Type t, AtomSpace* = nullptr);

	/**
	 * Use the backing store to load entire AtomSpace.
	 */
	void load_atomspace(AtomSpace* = nullptr);

	/**
	 * Use the backing store to store entire AtomSpace.
	 */
	void store_atomspace(AtomSpace* = nullptr);

	/**
	 * Return the DAG of all AtomSpaces in the backing store.
	 * The AtomSpaces themselves will not be populated; use the
	 * `load_atomspace` method above to accomplish that.
	 *
	 * The returned HandleSeq consists of AtomSpacePtr's to the
	 * AtomSpaces at the top of the DAG.
	 */
	HandleSeq load_frames(void);

	/**
	 * Store the DAG of all AtomSpaces to the backing store.
	 * The contents of the AtomSpaces themselves will not be stored;
	 * use the `store_atomspace` method above to accomplish that.
	 *
	 * The argument must be an AtomSpacePtr to the top of the DAG.
	 */
	void store_frames(const Handle&);

	/**
	 * Delete the contents of the indicated AtomSpace. This will remove
	 * only those atoms in this particular frame, and not those of any
	 * frame above or below. Atoms marked Absent will also be removed,
	 * which may cause lower atoms to become visible.
	 *
	 * The argument must be an AtomSpacePtr somewhere in the DAG.
	 */
	void delete_frame(const Handle&);

	/**
	 * Use the backing store to load the entire incoming set of the
	 * atom.
	 *
	 * If the flag is true, then the load is done recursively.
	 * This method queries the backing store to obtain all atoms that
	 * contain this one in their outgoing sets. All of these atoms are
	 * then loaded into this atomtable/atomspace.
	 *
	 * See also `fetch_query` which can be used with a JoinLink to
	 * obtain a custom-tailored incoming set.
	 */
	Handle fetch_incoming_set(const Handle&, bool = false, AtomSpace* = nullptr);

	/**
	 * Use the backing store to load the incoming set of the
	 * atom, but only those atoms of the given type.
	 * The fetch is not recursive; that is, only the immediate,
	 * single-level incoming set is fetched.
	 *
	 * See also `fetch_query` which can be used with a JoinLink to
	 * obtain a custom-tailored incoming set.
	 */
	Handle fetch_incoming_by_type(const Handle&, Type, AtomSpace* = nullptr);

	/**
	 * Run the `query` on the remote server, and place the query
	 * results onto the `key`, both locally, and remotely.
	 * The `query` must be either a JoinLink, MeetLink or QueryLink.
	 *
	 * Because MeetLinks and QueryLinks can be cpu-intensive, not
	 * all backends will honor this request. (JoinLinks will be
	 * honored, in general; they can be thought of as a generalized
	 * incoming set, and are much faster to process.) Backends are
	 * free to return previously-cached results for the search,
	 * rather than running a fresh search. If the flag `fresh` is
	 * set to `true`, then the server may interpret this as a
	 * request to perform a fresh search.  It is not required to
	 * honor this request.
	 *
	 * If the `metadata_key` is provided, then metadata about the
	 * search is returned. This may include a time-stamp indicating
	 * when the search was last performed. If the search was refused,
	 * a value indicating that will be returned.  The metadata is
	 * intended to allow the receiver (i.e the user of this local
	 * AtomSpace) what to do next.
	 *
	 * Note that the remote server may periodically purge search
	 * results to save on storage usage. This is why the search
	 * results are returned and placed in the local space.
	 *
	 * Only the Atoms that were the result of the search are returned.
	 * Any Values hanging off those Atoms are not transferred from the
	 * remote server to the local AtomSpace.
	 */
	Handle fetch_query(const Handle& query, const Handle& key,
	                   const Handle& metadata_key = Handle::UNDEFINED,
	                   bool fresh = false,
	                   AtomSpace* = nullptr);

	/**
	 * Recursively store the Atom to the backing store.
	 * I.e. if the Atom is a Link, then store all of the Atoms
	 * in its outgoing set as well, recursively. Store all Keys
	 * and Values attached to the Atom (but not for any Atoms in
	 * it outgoing set; the store of Values is NOT recursive!)
	 */
	void store_atom(const Handle& h);

	/**
	 * Store the Value located at `key` on `atom` to the remote
	 * server. If the `atom` does not yet exist on the remote
	 * server, it is created there.  This method is more granular
	 * than `store_atom` above, as it works with only one value,
	 * instead of all of them.
	 *
	 * Note that Values can be deleted merely by having a null
	 * value hanging on that key.
	 */
	void store_value(const Handle& atom, const Handle& key);

	/**
	 * Update the Value located at `key` on `atom` at the remote
	 * server, incorporating a delta-change `delta`. This is an
	 * atomic read-modify-write update at the server end. The goal
	 * of this method is to allow multiple clients perform delta
	 * changes to values, without race conditions.
	 *
	 * At this time, the only supported updates are increments of
	 * counts: i.e. incremenets of FloatValues. Perhaps other kinds
	 * of updates might be implemented in the future?
	 *
	 * If the `atom` does not yet exist on the remote server, it is
	 * created there.
	 */
	void update_value(const Handle& atom, const Handle& key,
	                  const ValuePtr& delta);

	/**
	 * Removes an atom from attached storage. It is NOT removed from
	 * the local AtomSpace!
	 *
	 * @param h The Handle of the atom to be removed.
	 * @param recursive Recursive-removal flag. If the flag is set,
	 *       then this atom, and *everything* that points to it will
	 *       also be removed. This can cause a large cascade of
	 *       removals!  If the flag is not set, then the atom will be
	 *       removed only if its incoming set is empty.
	 *       By default, recursion is disabled.
	 */
	void remove_atom(AtomSpace*, Handle, bool recursive=false);
	void remove_atom(const AtomSpacePtr& as, Handle h, bool recursive=false)
		{ remove_atom(as.get(), h, recursive); }

protected:
	static constexpr const char* _messages[] = {
		"*-open-*",
		"*-open-ro-*",
		"*-close-*",
		"*-load-atomspace-*",
		"*-store-atomspace-*",
		"*-load-atoms-of-type-*",
		"*-store-atom-*",
		"*-store-value-*",
		"*-update-value-*",

		"*-fetch-atom-*",
		"*-fetch-value-*",
		"*-fetch-incoming-set-*",
		"*-fetch-incoming-by-type-*",
		"*-fetch-query-*",

		"*-delete-*",
		"*-delete-recursive-*",
		"*-barrier-*",

		"*-store-frames-*",
		"*-delete-frame-*",
		"*-erase-*",

		"*-proxy-open-*",
		"*-proxy-close-*",
		"*-set-proxy-*",

		// Messages used in getValue only don't need to be published here,
		// because only the setValue ones need the COW, R/O work-around.
		// ???
		"*-load-frames-*",
		"*-connected?-*",
		"*-monitor-*"
	};
};

NODE_PTR_DECL(StorageNode)

typedef std::vector<StorageNodePtr> StorageNodeSeq;

/** @}*/
} // namespace opencog

#endif // _OPENCOG_STORAGE_NODE_H