lumiera_/src/common/query.hpp
Ichthyostega 555af315b3 Upgrade: improve Doxygen parameters and treat some warnings
- remove obsolete configuration settings
- walk through all settings according to the documentation
  https://www.doxygen.nl/manual/config.html
- now try to use the new feature to rely on Clang for C++ parsing
- walk through the doxygen-warnings.txt and fix some obvious misspellings
  and structural problems in the documentation comments.

With Debian-Trixie, we are now using Doxygen 1.9.8 —
which produces massively better results in various fine points.

However, there are still problems with automatic cross links,
especially from implementation to the corresponding test classes.
2025-04-27 05:00:14 +02:00

617 lines
18 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
QUERY.hpp - interface for generic queries
Copyright (C)
2008, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** 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. See the file COPYING for further details.
*/
/** @file query.hpp
** Basic and generic representation of an internal query.
** This header provides the foundation for issuing queries instead of using hard wired
** logic and defaults. This is a fundamental architecture pattern within Lumiera, and serves
** to decouple the parts of the application and allows for a rules based configuration and
** orchestration of the internal workings.
**
** A Query is a request for _just someone_ to come up with a solution, a preconfigured
** setup, some existing data object or contextual information. In order to be usable,
** actually a QueryResolver needs to be available to compute the solution and retrieve
** the results. As a common denominator, queries can be _generic queries_ given
** in predicate logic syntax; in this case a generic query resolver (Planned feature
** as of 1/2013) will be able at least to determine a suitable facility for delegating
** the resolution. Besides, specific subsystems are using more specific kinds of
** queries and provide a specialised resolution mechanism, kind of a shortcut,
** which in these cases can be addressed directly.
**
** \par General usage pattern
** Some parts of the application allow to issue queries -- typically these parts do
** also expose a service point for clients to issue similar queries. In any case, a
** query remains in the ownership of the issuer, which is also responsible to keep
** the storage alive during results retrieval. Queries can't be copied and are passed
** by reference, since #Query is an interface baseclass. Each query instance bears
** at least a type tag to indicate the type of the returned result, plus a classification
** tag to indicate the kind of query. In addition, queries are usually required to provide
** a syntactical representation, allowing to transform each query into a generic query.
**
** To resolve the query, a lumiera::QueryResolver instance is necessary, and this
** query resolver needs the ability to deal with this specific kind of query. Typically
** this is achieved by installing a resolution function into the resolver on application start.
** The QueryResolver returns a result set, actually a Query::Cursor, which can be used to
** enumerate multiple solutions, if any.
**
** Queries are _immutable_, but it is possible to re-build and remould a query using
** a Query<TY>::Builder, accessible via Query#build() and Query#rebuild().
**
** @note as of 1/2013 this is rather a concept draft, but some parts of the code base
** are already actively using some more specific queries
**
** @see lumiera::QueryResolver
** @see mobject::session::DefsManager
** @see asset::StructFactory
** @see config-resolver.hpp specialised setup for the Steam-Layer
** @see fake-configrules.hpp currently used dummy-implementation
** @see SessionServiceExploreScope
** @see PlacementIndexQueryResolver
**
*/
#ifndef LUMIERA_QUERY_H
#define LUMIERA_QUERY_H
#include "lib/hash-combine.hpp"
#include "lib/typed-counter.hpp"
#include "lib/iter-adapter.hpp"
#include "lib/query-text.hpp"
#include "lib/query-util.hpp"
#include "lib/nocopy.hpp"
#include "lib/symbol.hpp"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/operators.hpp>
#include <memory>
#include <typeinfo>
#include <cctype>
#include <string>
namespace lumiera {
using lib::IxID;
using lib::Symbol;
using lib::Literal;
using util::isnil;
using util::unConst;
using boost::lexical_cast;
using std::string;
/* ==== common definitions for rule based queries ==== */
class Goal;
class Resolution;
class QueryResolver;
class QueryKey;
/** Allow to take ownership of a result set */
typedef std::shared_ptr<Resolution> PReso;
/**
* Query ABC: unspecific goal for resolution or retrieval.
* Goal elements are used within the backbone of a generic query system
* to access individual resolution mechanisms based on an internal classification
* of the type of query.
*/
class Goal
: util::Cloneable
{
public:
virtual ~Goal(); ///< this is a marker baseclass
enum Kind
{ EMPTY = 0
, GENERIC = 1
, DISCOVERY
, PLACEMENT
};
struct QueryID
{
Kind kind;
IxID type;
explicit
QueryID(Kind k =EMPTY, IxID t=1)
: kind(k)
, type(t)
{ }
};
QueryID const&
getQID() const
{
return id_;
}
/**
* Single Solution, possibly part of a result set.
* A pointer-like object, usually to be down-casted
* to a specifically typed Query::Cursor
* @see Resolution
*/
class Result
{
void* cur_;
protected:
void point_at(void* p) { cur_ = p; }
template<typename RES>
RES&
access()
{
REQUIRE (cur_);
return *reinterpret_cast<RES*> (cur_);
}
public:
explicit operator bool() const { return isValid(); }
bool isValid() const { return bool(cur_); }
Result() : cur_(0) { } ///< create an NIL result
};
protected:
QueryID id_;
Goal (QueryID qid)
: id_(qid)
{ }
};
inline bool
operator< (Goal::QueryID const& id1, Goal::QueryID const& id2)
{
return id1.kind < id2.kind
||(id1.kind == id2.kind && id1.type < id2.type);
}
inline bool
operator== (Goal::QueryID const& id1, Goal::QueryID const& id2)
{
return id1.kind == id2.kind
&& id1.type == id2.type;
}
inline bool
operator!= (Goal::QueryID const& id1, Goal::QueryID const& id2)
{
return not (id1 == id2);
}
namespace {
/** Context used for generating type-IDs to denote
* the specific result types of issued queries */
typedef lib::TypedContext<Goal::Result> ResultType;
template<typename RES>
inline IxID
getResultTypeID() ///< @return unique ID denoting result type RES
{
return ResultType::ID<RES>::get();
}
/** includes the QueryID type distinction into the given hash value */
inline size_t
taggedHash (size_t hash, Goal::QueryID typeID)
{
lib::hash::combine (hash, typeID.kind);
lib::hash::combine (hash, typeID.type);
return hash;
}
}
/**
* Generic interface to express a query
* for specifically typed result elements
* exposing some capabilities or fulfilling
* some properties. This is a generic umbrella
* for several kinds of queries and provides a
* mechanism for uniform usage of various
* resolution mechanisms.
*
* Any query bears internal type classification and can be
* represented in a common syntactical form based on predicate logic.
* Query instances are created by some facilities allowing to query for objects.
* These query providers \em do know the specific kind (type) of query to expose.
* While client code uses these queries only by reference, there is the possibility
* to involve a generic QueryResolver, which -- behind the scenes -- manages a registry
* of specific resolution mechanisms. This way, clients may retrieve a set of results,
* where each result represents a possible solution to the original query.
*
* @remarks lumiera::Query is an interface, but can be used as-is to represent a generic query.
* Specialised subclasses are required to provide a syntactic representation, but are
* free to do so only on demand. In this case, generate an \em empty lib::QueryText
* definition and implement the Query#buildSyntacticRepresentation function.
* Every fundamentally different kind of query needs to be listed in Goal::Kind.
*
* @note until we really integrate a rules based system
* this can be considered dummy placeholder implementation.
* Some more specific query resolvers are available already,
* so, depending on the circumstances the actual resolution might be
* substantial or just a fake.
* @warning especially the classical resolution-type queries are just
* faked and use the given query-string as-is, without any normalisation.
* Moreover, as especially the fake-configrules match by string comparison,
* this may led to unexpected mis-matches. This is dummy code, after all.
*/
template<class RES>
class Query
: public Goal
{
/** generic syntactical definition */
mutable lib::QueryText def_;
protected:
static QueryID
defineQueryTypeID (Kind queryType = Goal::GENERIC)
{
QueryID id(queryType, getResultTypeID<RES>());
return id;
}
/**
* Extension point to generate a generic query definition on demand.
* Some specialised kinds of queries, intended to be treated by a specific resolver,
* may choose skip constructing a generic query representation, but are then bound
* to supplement such a generic definition through this function when required.
* The generated query definition must be sufficient to reconstruct the query
* in all respects.
* @return a complete definition of this query in predicate form
* @retval bottom token to indicate failure to comply to this requirement.
*/
virtual lib::QueryText
buildSyntacticRepresentation() const
{
WARN (query, "internal query not outfitted with a suitable query definition");
return string("bottom");
}
/** access the complete syntactical representation of this query.
* May trigger on-demand initialisation */
lib::QueryText
getQueryDefinition() const
{
if (isnil(this->def_))
def_ = this->buildSyntacticRepresentation();
return def_;
}
class Builder;
Query (QueryID typeID, lib::QueryText const& genericQuerySpec)
: Goal (typeID)
, def_(genericQuerySpec)
{ }
Query (QueryID typeID, string querySpec)
: Goal (defineQueryTypeID(typeID.kind))
, def_(querySpec)
{
REQUIRE (this->getQID().type == typeID.type);
}
friend class Builder;
public:
Query()
: Goal (defineQueryTypeID(Goal::EMPTY))
{ }
explicit
Query (string querySpec)
: Goal (defineQueryTypeID(Goal::GENERIC))
, def_(querySpec)
{ }
operator QueryKey() const;
static Builder build (Kind queryType = Goal::GENERIC);
Builder rebuild() const;
string extractID (Symbol predicate) const;
bool usesPredicate (Symbol predicate) const;
/* results retrieval */
class Cursor
: public Goal::Result
{
public:
typedef RES value_type;
typedef RES& reference;
typedef RES* pointer;
RES& operator* () { return access<RES>(); }
RES* operator->() { return & access<RES>(); }
void point_at(RES* r){ Goal::Result::point_at(r);}
void point_at(RES& r){ Goal::Result::point_at(&r);}
};
typedef lib::IterAdapter<Cursor,PReso> iterator;
iterator operator() (QueryResolver const& resolver) const;
iterator resolveBy (QueryResolver const& resolver) const;
friend size_t
hash_value (Query const& q)
{
return taggedHash (hash_value(q.getQueryDefinition()), q.id_);
}
};
/**
* Wrapper for indexing and ordering.
* Defines a synthetic totally ordered index value.
* Implicitly convertible to and from Query instances.
*/
class QueryKey
: boost::totally_ordered<QueryKey>
{
Goal::QueryID id_;
lib::QueryText def_;
public:
QueryKey (Goal::QueryID id, lib::QueryText q)
: id_(id)
, def_(q)
{
ENSURE (!isnil(def_));
}
/** the empty or bottom query key */
QueryKey()
: id_()
, def_("false")
{ }
// default copyable
template<class RES>
operator Query<RES>() const
{
REQUIRE (getResultTypeID<RES>() == id_.type);
return Query<RES>::build(id_.kind).fromText(def_);
}
string
display() const
{
return "kind=" + lexical_cast<string>(id_.kind)
+",type=" + lexical_cast<string>(id_.type)
+",def=" + string(def_);
}
string
getQueryString() const
{
return string(def_);
}
uint
degree() const
{
return def_.degree_of_constriction();
}
bool
empty() const
{
return Goal::EMPTY == id_.kind;
}
friend bool
operator< (QueryKey const& q1, QueryKey const& q2)
{
uint d1 = q1.degree();
uint d2 = q2.degree();
return d1 < d2
||(d1 == d2 && ( q1.def_ < q2.def_
||(q1.def_ == q2.def_ && q1.id_ < q2.id_)));
}
friend bool
operator== (QueryKey const& q1, QueryKey const& q2)
{
return q1.def_ == q2.def_;
}
friend size_t
hash_value (QueryKey const& q)
{
return taggedHash (hash_value(q.def_), q.id_);
}
};
/**
* Helper for establishing,
* reworking and remolding queries.
*/
template<class RES>
class Query<RES>::Builder
{
QueryID typeID_;
string predicateForm_;
Builder (QueryID kind, string baseDef ="")
: typeID_(kind)
, predicateForm_(baseDef)
{ }
friend class Query<RES>;
public:
/** when done with defining or reworking the query,
* the result may be retrieved by type conversion */
operator Query<RES>()
{
return Query<RES>(typeID_, predicateForm_);
}
/** @return a string representation usable for hashing
* @note includes the type parameter of the underlying query
*/
string
asKey() const
{
return "type("
+ lexical_cast<string> (getResultTypeID<RES>())
+ "), "+predicateForm_;
}
/** extract an ID term defined as (single) parameter for the given predicate.
* E.g. when using the query "foo(a), bar(b)", \c extractID("bar") returns "b"
* @param predicate symbol of the predicate to investigate
* @warning preliminary implementation
*/
string
extractID (Symbol predicate) const
{
return lib::query::extractID (predicate, this->predicateForm_);
}
/** remove the first term from this query definition,
* which matches the given predicate symbol
* @warning preliminary implementation
*/
Builder&
removeTerm (Symbol termPredicate)
{
lib::query::removeTerm(termPredicate, this->predicateForm_);
return *this;
}
Builder&
withConditions (string additionalQueryPredicates)
{
this->predicateForm_ =
lib::query::appendTerms(this->predicateForm_, additionalQueryPredicates);
return *this;
}
Builder&
prependConditions (string additionalQueryPredicates)
{
this->predicateForm_ =
lib::query::appendTerms(additionalQueryPredicates, this->predicateForm_);
return *this;
}
Builder&
fromText (string queryPredicates)
{
this->predicateForm_ = queryPredicates;
return *this;
}
};
template<class RES>
inline typename Query<RES>::Builder
Query<RES>::build (Kind queryType)
{
return Builder(defineQueryTypeID (queryType));
}
template<class RES>
inline typename Query<RES>::Builder
Query<RES>::rebuild() const
{
return Builder(this->id_, getQueryDefinition());
}
/** convenience shortcut to extract a desired name-ID.
* @todo used extensively for the mock implementation of query resolution.
* For real resolution queries, such a function is quite nonsensical.
* To be revisited and (likely) to be removed on the long run
* @see Query::Builder#extractID
*/
template<class RES>
inline string
Query<RES>::extractID (Symbol predicate) const
{
return this->rebuild().extractID (predicate);
}
template<class RES>
inline bool
Query<RES>::usesPredicate (Symbol predicate) const
{
return lib::query::hasTerm(predicate, getQueryDefinition());
}
/** automatic conversion from Query to QueryKey for indexing and ordering.
* By defining a parameter of type QueryKey, any provided Query will be
* automatically transformed into an generic representation usable for
* ordered storage in sets, maps and for generation of metrics.
*/
template<class RES>
inline
Query<RES>::operator QueryKey() const
{
return QueryKey (this->id_, getQueryDefinition());
}
} // namespace lumiera
#endif