5171 lines
142 KiB
C++
5171 lines
142 KiB
C++
|
|
/*
|
||
|
|
----
|
||
|
|
This file is part of SECONDO.
|
||
|
|
|
||
|
|
Copyright (C) 2014,
|
||
|
|
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
|
||
|
|
----
|
||
|
|
|
||
|
|
|
||
|
|
//[<] [\ensuremath{<}]
|
||
|
|
//[>] [\ensuremath{>}]
|
||
|
|
|
||
|
|
\setcounter{tocdepth}{2}
|
||
|
|
\tableofcontents
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
0 Preface
|
||
|
|
|
||
|
|
This document describes how to integrate a new algebra into the
|
||
|
|
Secondo DBMS. An algebra consists of types and operators.
|
||
|
|
|
||
|
|
It starts with a description of nested lists, a structure
|
||
|
|
for representing objects externally and types internally.
|
||
|
|
The implementation of different kinds of types are shown, these include
|
||
|
|
|
||
|
|
* simple types
|
||
|
|
|
||
|
|
* attribute data types of fixed size
|
||
|
|
|
||
|
|
* attribute data type of variable size
|
||
|
|
|
||
|
|
* large types, e.g., for implementing indexes
|
||
|
|
|
||
|
|
Some special sections also describe how to reduce the amount of disc storage
|
||
|
|
required for an attribute data type.
|
||
|
|
|
||
|
|
An Operator consists of a type mapping, a set of value mappings,
|
||
|
|
a selection function, and a description for the user. The type mapping checks
|
||
|
|
whether the operator can handle a given set of argument types. The value mapping
|
||
|
|
computes the result of the operator from the arguments. The selection function
|
||
|
|
selects a value mapping for overloaded operators. Operators can occur in a lot
|
||
|
|
of ways. This document describes
|
||
|
|
|
||
|
|
* simple operators
|
||
|
|
|
||
|
|
* overloaded operators
|
||
|
|
|
||
|
|
* stream processing operators
|
||
|
|
|
||
|
|
* operators having a function as an argument
|
||
|
|
|
||
|
|
* update operators
|
||
|
|
|
||
|
|
* operators where results from the type mapping are transferred to the value mapping
|
||
|
|
|
||
|
|
* operators having access to values within the type mapping
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
0.1 The PD-System
|
||
|
|
|
||
|
|
All files in Secondo are commented using comments in PD-style. The PD tool
|
||
|
|
allows the creation of pretty formatted PDF documents from source
|
||
|
|
files. For a complete documentation of this tool, read the file PDSystem1.pdf
|
||
|
|
located in the ~Documents~ directory of Secondo.
|
||
|
|
|
||
|
|
If the PD system is running, a single pdf document from all files within
|
||
|
|
the directory of this algebra can
|
||
|
|
be created by entering ~make doc~ in the algebra directory.
|
||
|
|
|
||
|
|
|
||
|
|
0.2 Nested Lists
|
||
|
|
|
||
|
|
Because nested lists are used very often in Secondo, this document starts with a
|
||
|
|
short description of this structure.
|
||
|
|
Nested lists are required to import/export objects, within type mappings,
|
||
|
|
selection functions, display functions and much more. After reading this
|
||
|
|
document, the meaning of these things will be clear.
|
||
|
|
|
||
|
|
Each element of a nested list can be an atom or a nested list again.
|
||
|
|
Atomar elements are the following:
|
||
|
|
|
||
|
|
----
|
||
|
|
Int (32 bit)
|
||
|
|
Real
|
||
|
|
Bool
|
||
|
|
String (maximum 48 characters)
|
||
|
|
Symbol (maximum 48 characters)
|
||
|
|
Text (a string of arbitrary length)
|
||
|
|
----
|
||
|
|
|
||
|
|
For storing 64 bit integer values, the integer must be separated into a list
|
||
|
|
consisting of two 32-bit integer values. The file ~ListUtils.h~ provides functions
|
||
|
|
for encoding and decoding 64 bit integers.
|
||
|
|
|
||
|
|
Functions manipulating nested lists use a global list storage called *nl*.
|
||
|
|
This list storage is cleaned after each query. This disables the possibility
|
||
|
|
to store some frequently used list as static variables.
|
||
|
|
|
||
|
|
For using the global nested list storage, the following lines within the program code
|
||
|
|
are required:
|
||
|
|
|
||
|
|
----
|
||
|
|
#include "NestedList.h"
|
||
|
|
extern NestedList* nl;
|
||
|
|
----
|
||
|
|
|
||
|
|
The variable *nl* is a singleton pattern defined somewhere in the system.
|
||
|
|
|
||
|
|
For the creation of a list atom, the following functions are available:
|
||
|
|
|
||
|
|
----
|
||
|
|
ListExpr li = nl->IntAtom(23);
|
||
|
|
ListExpr lr = nl->RealAtom(-8.3);
|
||
|
|
ListExpr lsy = nl->SymbolAtom("Symbol");
|
||
|
|
...
|
||
|
|
----
|
||
|
|
|
||
|
|
For creating short lists, several functions are provided.
|
||
|
|
|
||
|
|
----
|
||
|
|
ListExpr l1 = nl->OneElemList(li);
|
||
|
|
ListExpr l2 = nl->TwoElemList(li,lr);
|
||
|
|
...
|
||
|
|
ListExpr l6 = nl->SixElemList(li,lr,lsy,l1,l2,li);
|
||
|
|
bool correct = nl->ReadFromString("a 'b' 1.0 (TRUE 6))", list);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
The return value of ~ReadFromString~ determines the success of
|
||
|
|
this operation, in particular, whether the string represents a
|
||
|
|
valid nested list. The list itself is returned via the output
|
||
|
|
parameter ~list~.
|
||
|
|
|
||
|
|
For the creation of long lists, the following code fragment can be used as a template:
|
||
|
|
|
||
|
|
----
|
||
|
|
ListExpr l1000 = nl->OneElemList(nl->IntAtom(0));
|
||
|
|
ListExpr last = l1000;
|
||
|
|
for(int i=1; i<1000; i++){
|
||
|
|
last = nl->Append(last,nl->IntAtom(i));
|
||
|
|
}
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
The function ~Append~ takes the last element of a list and append its second
|
||
|
|
argument to this list. The new last element is returned.
|
||
|
|
|
||
|
|
A nested list can be read from a file using the ~ReadFromFile~ function.
|
||
|
|
This function works quite similar to ~ReadFromString~. The input is
|
||
|
|
not the nested list as a string, but the name of a file containing the
|
||
|
|
list.
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
bool correct = nl->ReadFromString("mylist.txt", list);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
To request the type of a given list, the function ~AtomType~ ist used:
|
||
|
|
|
||
|
|
----
|
||
|
|
int r = nl->AtomType(list);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
This function returns one of the predefined values ~NoAtom~, ~IntType~, ~RealType~,
|
||
|
|
~BoolType~, ~StringType~, ~SymbolType~, or ~TextType~.
|
||
|
|
|
||
|
|
The value of an atom can be taken by:
|
||
|
|
|
||
|
|
----
|
||
|
|
int i = nl->IntValue(list);
|
||
|
|
string s = nl->SymbolValue(list);
|
||
|
|
....
|
||
|
|
string t = nl->Text2String(list);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Note that the try to get the value of wrong type, e.g. calling
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
int k = nl->IntValue(list);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
where ~list~ is a string, will crash the system by throwing an assertion.
|
||
|
|
|
||
|
|
|
||
|
|
The following functions provide information about the length of a list:
|
||
|
|
|
||
|
|
----
|
||
|
|
nl->IsEmpty(list);
|
||
|
|
nl->ListLength(list); // -1 for an atom
|
||
|
|
nl->hasLength(list,3); // check list to have length 3
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
A list can be separated by:
|
||
|
|
|
||
|
|
----
|
||
|
|
nl->First(list);
|
||
|
|
nl->Second(list);
|
||
|
|
...
|
||
|
|
nl->Sixth(list);
|
||
|
|
|
||
|
|
nl->Rest(list); // returns list without the first element
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Two lists can be checked for equality and a single list can be checked to be a symbol atom
|
||
|
|
having a certain value (the third argument is for case sensitivity) using the functions:
|
||
|
|
|
||
|
|
----
|
||
|
|
bool eq = nl->Equal(list1,list2);
|
||
|
|
bool isSym = nl->IsEqual(list, "symbol", false);
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
For debugging purposes, sometimes an output of a list is required. This can be done by:
|
||
|
|
|
||
|
|
----
|
||
|
|
cout << nl->ToString(list) << endl;
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Within ~ListUtils.h~ a collection of auxiliary functions are implemented, to make the life with
|
||
|
|
nested lists easier, e.g.:
|
||
|
|
|
||
|
|
----
|
||
|
|
#include "NestedList.h"
|
||
|
|
#include "ListUtils.h"
|
||
|
|
|
||
|
|
// example: read in an integer or a real from list
|
||
|
|
if( listutils::isNumeric(list)){
|
||
|
|
double d = listutils::getNumValue(list);
|
||
|
|
}
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Note: the nested list implementation contains a lot of assertions. Thereby any
|
||
|
|
wrong usage of these lists leads to a crash of Secondo. Before a function is
|
||
|
|
called, always the correctness of the list's format,e.g. length and type,
|
||
|
|
has to be checked.
|
||
|
|
|
||
|
|
A more objectoriented interface to nested list is provided in NList.h.
|
||
|
|
Here, exceptions instead of assertions are used to handle wrong calls.
|
||
|
|
If you want to use this interface, please take a look to the
|
||
|
|
file ~NList.h~ within the ~include~ directory of Secondo.
|
||
|
|
|
||
|
|
|
||
|
|
0.3 What is an Algebra
|
||
|
|
|
||
|
|
An algebra contains types and/or operators. It uses the types defined in the algebra
|
||
|
|
itself and --if required-- types of other algebras. Secondo provides a well defined
|
||
|
|
interface for integrating a new algebra. Each activated algebra is linked together
|
||
|
|
with the kernel of Secondo. Each type and each operator extends Secondo's executable
|
||
|
|
language.
|
||
|
|
|
||
|
|
|
||
|
|
0.4 Preliminary Steps
|
||
|
|
|
||
|
|
For creating a new algebra, some steps are required.
|
||
|
|
Firstly, create a new subdirectory having the algebra's name within the
|
||
|
|
~Algebra~ directory of Secondo.
|
||
|
|
|
||
|
|
After that, insert a new entry in the file ~Algebras/Management/AlgebraList.i.cfg~ having the
|
||
|
|
format:
|
||
|
|
|
||
|
|
----
|
||
|
|
ALGEBRA_INCLUDE(<Number>, <Name>)
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Note that number and name must be different to all existing entries.
|
||
|
|
|
||
|
|
|
||
|
|
Now, activate the algebra by modifying the file ~makefile.algebras~ in
|
||
|
|
Secondo's main directory. Insert the two lines:
|
||
|
|
|
||
|
|
----
|
||
|
|
ALGEBRA_DIRS += <DirectoryName>
|
||
|
|
ALGEBRAS += <AlgebraName>
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
in the file. If the algebra uses third party libraries, add the line:
|
||
|
|
|
||
|
|
----
|
||
|
|
ALGEBRA_DEPS += <libname>
|
||
|
|
----
|
||
|
|
|
||
|
|
in the file. If the library is not stored within the standard directories,
|
||
|
|
add the line:
|
||
|
|
|
||
|
|
----
|
||
|
|
ALGEBRA_DEP_DIRS += <directory>
|
||
|
|
----
|
||
|
|
|
||
|
|
If the library required special flags for linking, add the line
|
||
|
|
|
||
|
|
----
|
||
|
|
ALGEBRA_LINK_FLAGS += <flags>
|
||
|
|
----
|
||
|
|
|
||
|
|
After adding the required entries to the files, go back to the newly created algebra
|
||
|
|
directory. Copy the ~makefile~ of the ~StandardAlgebra~ into this directory. If there
|
||
|
|
a dependencies to other algebras, modify the copyied file by inserting the lines:
|
||
|
|
|
||
|
|
----
|
||
|
|
CURRENT_ALGEBRA := <AlgebraName>
|
||
|
|
ALGEBRA_DEPENDENCIES := StandardAlgebra
|
||
|
|
ALGEBRA_DEPENDENCIES += <AlgebraName>
|
||
|
|
...
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
after the line
|
||
|
|
|
||
|
|
----
|
||
|
|
include ../../makefile.env
|
||
|
|
----
|
||
|
|
|
||
|
|
This provides understandable error messages if one of the required algebras is not
|
||
|
|
activated. Without these entries a required but non-activated algebra will
|
||
|
|
lead to strange error messages during linking the system.
|
||
|
|
|
||
|
|
|
||
|
|
After these steps, the algebra file(s) can be implemented.
|
||
|
|
It is not required to implement an algebra within a single file. The algebra
|
||
|
|
files can be split into header and implementation files as usual in C++.
|
||
|
|
This algebra is implemented within a single file for easy creating a single
|
||
|
|
documentation file.
|
||
|
|
|
||
|
|
|
||
|
|
0.5 Includes
|
||
|
|
|
||
|
|
To be able to implement an algebra, different header files must be included.
|
||
|
|
Here, each include is commented with some functionality used.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
#include "Attribute.h" // implementation of attribute types
|
||
|
|
#include "Algebra.h" // definition of the algebra
|
||
|
|
#include "NestedList.h" // required at many places
|
||
|
|
#include "QueryProcessor.h" // needed for implementing value mappings
|
||
|
|
#include "AlgebraManager.h" // e.g., check for a certain kind
|
||
|
|
#include "Operator.h" // for operator creation
|
||
|
|
#include "StandardTypes.h" // priovides int, real, string, bool type
|
||
|
|
#include "Algebras/FText/FTextAlgebra.h"
|
||
|
|
#include "Symbols.h" // predefined strings
|
||
|
|
#include "ListUtils.h" // useful functions for nested lists
|
||
|
|
#include "Stream.h" // wrapper for secondo streams
|
||
|
|
|
||
|
|
#include "GenericTC.h" // use of generic type constructors
|
||
|
|
|
||
|
|
#include "LogMsg.h" // send error messages
|
||
|
|
|
||
|
|
#include "Tools/Flob/DbArray.h" // use of DbArrays
|
||
|
|
|
||
|
|
#include "Algebras/Relation-C++/RelationAlgebra.h" // use of tuples
|
||
|
|
|
||
|
|
#include "FileSystem.h" // deletion of files
|
||
|
|
|
||
|
|
|
||
|
|
#include <SecMath.h> // required for some operators
|
||
|
|
#include <stack>
|
||
|
|
#include <limits>
|
||
|
|
|
||
|
|
/*
|
||
|
|
0.5 Global Variables
|
||
|
|
|
||
|
|
Secondo uses some variables designed as singleton pattern. For accessing these
|
||
|
|
global variables, these variables have to be declared to be extern:
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
extern NestedList *nl;
|
||
|
|
extern QueryProcessor *qp;
|
||
|
|
extern AlgebraManager *am;
|
||
|
|
|
||
|
|
using namespace std;
|
||
|
|
|
||
|
|
/*
|
||
|
|
0.6 Namespace
|
||
|
|
|
||
|
|
Each algebra file defines a lot of functions. Thus, name conflicts may arise
|
||
|
|
with function names defined in other algebra modules during compiling/linking
|
||
|
|
the system. To avoid these conflicts, the algebra implementation should be
|
||
|
|
embedded into a namespace.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace guide{
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
1 Implementation of a simple Type
|
||
|
|
|
||
|
|
In the next section, the creation of a simple (non-attribute) type and it's
|
||
|
|
integration into a Secondo algebra is described. A value of such an object
|
||
|
|
can be stored and accessed within a Secondo database but cannot be part of
|
||
|
|
a relation. In general, a class encapsulating
|
||
|
|
the type and a set of functions must be implemented. These functions are
|
||
|
|
|
||
|
|
* Description of the type
|
||
|
|
|
||
|
|
* Import, Export
|
||
|
|
|
||
|
|
* Creation, Deletion
|
||
|
|
|
||
|
|
* Open, Close, Save
|
||
|
|
|
||
|
|
* Clone
|
||
|
|
|
||
|
|
* Cast
|
||
|
|
|
||
|
|
* required storage size
|
||
|
|
|
||
|
|
* type check
|
||
|
|
|
||
|
|
|
||
|
|
Using these function, an instance of a type constructor is created. This type constructor
|
||
|
|
is added to the algebra within the algebra constructor.
|
||
|
|
Later, the user interfaces can be extended for a pretty output of the newly created type.
|
||
|
|
The extension of user interfaces is out of the scope of this document.
|
||
|
|
|
||
|
|
|
||
|
|
All these steps are now described on an example.
|
||
|
|
|
||
|
|
|
||
|
|
1.1 The Class
|
||
|
|
|
||
|
|
This class encapsulates the Secondo type within C++. The class must provide one
|
||
|
|
constructor doing nothing. This constructor is later used within the cast function
|
||
|
|
and should be called only in the cast function.
|
||
|
|
|
||
|
|
For an easy use of this class in Secondo, two additional functions are implemented,
|
||
|
|
namely ~BasicType~ and ~checkType~. The ~BasicType~ function return Secondo's
|
||
|
|
basic type for this class. We call the type of the example ~scircle~ (standing for simple circle).
|
||
|
|
For non-nested types, a string holding the type's name is the result of the
|
||
|
|
~BasicType~ function.
|
||
|
|
For more complex types, e.g. ~(rel(tuple(...)))~, this function
|
||
|
|
returns only the main type, e.g. ~rel~.
|
||
|
|
The function ~checkType~ takes a nested list
|
||
|
|
and checks whether this list represents a valid type description for this class.
|
||
|
|
Note that the ~checkType~ function not only checks the main type but the complete
|
||
|
|
type expression, e.g. for relations, ~checkType(rel)~ will return ~false~ while
|
||
|
|
~checktype(rel(tuple((A int)(B string))))~ will return ~true~.
|
||
|
|
|
||
|
|
All other functions and members are usual C++ stuff.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
class SCircle{
|
||
|
|
public:
|
||
|
|
// constructor doing nothing
|
||
|
|
SCircle() {}
|
||
|
|
// constructor initializing the object
|
||
|
|
SCircle(const double _x, const double _y, const double _r):
|
||
|
|
x(_x), y(_y), r(_r) {}
|
||
|
|
// copy constructor
|
||
|
|
SCircle(const SCircle& src): x(src.x), y(src.y),r(src.r){}
|
||
|
|
// assignment operator
|
||
|
|
SCircle& operator=(const SCircle& src) {
|
||
|
|
x = src.x;
|
||
|
|
y = src.y;
|
||
|
|
r = src.r;
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
// destructor
|
||
|
|
~SCircle(){}
|
||
|
|
static const string BasicType(){ return "scircle";}
|
||
|
|
// the checktype function for non-nested types looks always
|
||
|
|
// the same
|
||
|
|
static const bool checkType(const ListExpr list) {
|
||
|
|
return listutils::isSymbol(list, BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
double perimeter() const{
|
||
|
|
return 2*M_PI*r;
|
||
|
|
}
|
||
|
|
double getX() const{ return x; }
|
||
|
|
double getY() const{ return y; }
|
||
|
|
double getR() const{ return r; }
|
||
|
|
private:
|
||
|
|
double x;
|
||
|
|
double y;
|
||
|
|
double r;
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.2 The Property Function
|
||
|
|
|
||
|
|
The ~Property~ function provides a description of the Secondo type
|
||
|
|
to the user. It returns a nested list, which must be have exactly
|
||
|
|
the format given in this example. The first element of the list
|
||
|
|
is always the same and the second element of the list contains
|
||
|
|
type specific descriptions.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr SCircleProperty() {
|
||
|
|
return ( nl->TwoElemList (
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom("Signature"),
|
||
|
|
nl->StringAtom("Example Type List"),
|
||
|
|
nl->StringAtom("List Rep"),
|
||
|
|
nl->StringAtom("Example List")),
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom("-> SIMPLE"),
|
||
|
|
nl->StringAtom(SCircle::BasicType()),
|
||
|
|
nl->StringAtom("(real real real) = (x,y,r)"),
|
||
|
|
nl->StringAtom("(13.5 -76.0 1.0)")
|
||
|
|
)));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.3 In Function
|
||
|
|
|
||
|
|
For the creation of a constant value within a query and for
|
||
|
|
importing objects or whole databases from a file, object
|
||
|
|
values are described by nested lists. The task of the ~IN~-function
|
||
|
|
is to convert such a list into the internal object representation, i.e.
|
||
|
|
into an instance of the class above.
|
||
|
|
The list may have an invalid format. If the list does not
|
||
|
|
have the expected format, the output parameter ~correct~ must be set to
|
||
|
|
~false~ and the ~addr~-pointer of the result must be set to 0.
|
||
|
|
A detailed error description can be provided to the user by calling
|
||
|
|
the ~inFunError~ of the global ~cmsg~ object.
|
||
|
|
In case of success, the argument ~correct~ has to be set to ~true~ and
|
||
|
|
the ~addr~ pointer of the result points to an object instance having the
|
||
|
|
value represented by the ~instance~ argument.
|
||
|
|
The parameters of the function are:
|
||
|
|
|
||
|
|
* ~typeInfo~: contains the complete type description and is required for nested types
|
||
|
|
like tuples
|
||
|
|
|
||
|
|
* ~instance~: the value of the object in external (nested list) representation
|
||
|
|
|
||
|
|
* ~errorPos~: output parameter reporting the position of an error within the list (set types)
|
||
|
|
|
||
|
|
* ~errorInfo~: can provide information about an error to the user
|
||
|
|
|
||
|
|
* ~correct~: output parameter returning the success of this call
|
||
|
|
|
||
|
|
|
||
|
|
For the ~scircle~ class, the external representation consists of three numeric values standing for
|
||
|
|
the ~x~ position, the ~y~ position and the radius of the circle. The radius must be greater than
|
||
|
|
zero.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Word InSCircle( const ListExpr typeInfo, const ListExpr instance,
|
||
|
|
const int errorPos, ListExpr& errorInfo, bool& correct ){
|
||
|
|
// create a result with addr pointing to 0
|
||
|
|
Word res((void*)0);
|
||
|
|
// assume an incorrect list
|
||
|
|
correct = false;
|
||
|
|
// check whether the list has three elements
|
||
|
|
if(!nl->HasLength(instance,3)){
|
||
|
|
cmsg.inFunError("expected three numbers");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// check whether all elements are numeric
|
||
|
|
if( !listutils::isNumeric(nl->First(instance))
|
||
|
|
|| !listutils::isNumeric(nl->Second(instance))
|
||
|
|
|| !listutils::isNumeric(nl->Third(instance))){
|
||
|
|
cmsg.inFunError("expected three numbers");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// get the numeric values of the elements
|
||
|
|
double x = listutils::getNumValue(nl->First(instance));
|
||
|
|
double y = listutils::getNumValue(nl->Second(instance));
|
||
|
|
double r = listutils::getNumValue(nl->Third(instance));
|
||
|
|
// check for a valid radius
|
||
|
|
if(r<=0){
|
||
|
|
cmsg.inFunError("invalid radius (<=0)");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// list was correct, create the result
|
||
|
|
correct = true;
|
||
|
|
res.addr = new SCircle(x,y,r);
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.4 Out Function
|
||
|
|
|
||
|
|
This function is used to create the external representation of an object
|
||
|
|
as nested list. Note that the ~IN~ function must be able to read in the
|
||
|
|
result of this function. The arguments are:
|
||
|
|
|
||
|
|
* ~typeInfo~: nested list representing the type of the object (required for
|
||
|
|
complex types)
|
||
|
|
|
||
|
|
* ~value~: the ~addr~ pointer of ~value~ points to the object to export. The Secondo
|
||
|
|
framework ensures that the type of this object is the correct one. The
|
||
|
|
cast in the first line will be successful.
|
||
|
|
|
||
|
|
|
||
|
|
This function must be able to convert *each* instance into a nested list. For this
|
||
|
|
reason, there is no function for error reporting as in the ~IN~ function.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr OutSCircle( ListExpr typeInfo, Word value ) {
|
||
|
|
SCircle* k = (SCircle*) value.addr;
|
||
|
|
return nl->ThreeElemList(
|
||
|
|
nl->RealAtom(k->getX()),
|
||
|
|
nl->RealAtom(k->getY()),
|
||
|
|
nl->RealAtom(k->getR()));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.5 Create Function
|
||
|
|
|
||
|
|
This function creates an object instance having an arbitrary value. The ~typeInfo~
|
||
|
|
argument represents the type of the object and is required for nested types like
|
||
|
|
tuples.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Word CreateSCircle( const ListExpr typeInfo ) {
|
||
|
|
Word w;
|
||
|
|
w.addr = (new SCircle(0,0,1.0));
|
||
|
|
return w;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.6 Delete Function
|
||
|
|
|
||
|
|
Removes the complete object (inclusive disc parts if there are any, see section
|
||
|
|
\ref{largeStructures}).
|
||
|
|
The Secondo framework ensures that the type behind the ~addr~ pointer
|
||
|
|
of ~w~ is the expected one. The arguments are:
|
||
|
|
|
||
|
|
* ~typeInfo~: the type description (for complex types)
|
||
|
|
|
||
|
|
* ~w~: the ~addr~ pointer of this argument points to the object to delete.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void DeleteSCircle( const ListExpr typeInfo, Word& w ) {
|
||
|
|
SCircle *k = (SCircle *)w.addr;
|
||
|
|
delete k;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.7 Open Function
|
||
|
|
|
||
|
|
Reads an object from disc via an ~SmiRecord~.
|
||
|
|
|
||
|
|
* ~valueRecord~: here, the disc representation of the object is stored
|
||
|
|
|
||
|
|
* ~offset~: the object representation starts here in ~valueRecord~ After
|
||
|
|
the call of this function, ~offset~ must be after the object's value
|
||
|
|
|
||
|
|
* ~typeInfo~: the type description (required for complex types)
|
||
|
|
|
||
|
|
* ~value~: output argument
|
||
|
|
|
||
|
|
The function reads data out of the SmiRecord and creates a new object from them
|
||
|
|
in case of success. The created object is stored in the ~addr~-pointer of the
|
||
|
|
~value~ argument. In the case of an error, the ~addr~ pointer has to be set to
|
||
|
|
~NULL~. The result of this functions reports the success of reading.
|
||
|
|
To implement this function, the function ~Read~ of the ~SmiRecord~ is used.
|
||
|
|
Its first argument is a pointer to the storage where the data should be
|
||
|
|
written. The second argument determines how may data should be transferred
|
||
|
|
from the record to the buffer. The last argument indicates the position
|
||
|
|
of the data within the record. The return value of this function corresponds
|
||
|
|
to the actual read amount of data. In case of success, this number is
|
||
|
|
the same as given in the second argument.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool OpenSCircle( SmiRecord& valueRecord,
|
||
|
|
size_t& offset, const ListExpr typeInfo,
|
||
|
|
Word& value ){
|
||
|
|
size_t size = sizeof(double);
|
||
|
|
double x,y,r;
|
||
|
|
bool ok = (valueRecord.Read(&x,size,offset) == size);
|
||
|
|
offset += size;
|
||
|
|
ok = ok && (valueRecord.Read(&y,size,offset) == size);
|
||
|
|
offset += size;
|
||
|
|
ok = ok && (valueRecord.Read(&r, size, offset) == size);
|
||
|
|
offset += size;
|
||
|
|
if(ok){
|
||
|
|
value.addr = new SCircle(x,y,r);
|
||
|
|
} else {
|
||
|
|
value.addr = 0;
|
||
|
|
}
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.8 Save Function
|
||
|
|
|
||
|
|
Saves an object to disc (via SmiRecord). This function has to be symmetrically
|
||
|
|
to the ~OPEN~ function. The result reports the success of the call. The arguments are
|
||
|
|
|
||
|
|
* ~valueRecord~: here the object will be stored
|
||
|
|
|
||
|
|
* ~offset~: the object has to be stored at this position in ~valueRecord~; after the call of this
|
||
|
|
function, ~offset~ must be after the object's representation
|
||
|
|
|
||
|
|
* ~typeInfo~: type description as a nested list (required for complex types)
|
||
|
|
|
||
|
|
* ~value~: the addr pointer of this argument points to the object to save
|
||
|
|
|
||
|
|
The used ~Write~ function of the ~SmiRecord~ works similar to its ~Read~ function but transfers the
|
||
|
|
data into the other direction.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
bool SaveSCircle( SmiRecord& valueRecord, size_t& offset,
|
||
|
|
const ListExpr typeInfo, Word& value ) {
|
||
|
|
SCircle* k = static_cast<SCircle*>( value.addr );
|
||
|
|
size_t size = sizeof(double);
|
||
|
|
double v = k->getX();
|
||
|
|
bool ok = valueRecord.Write( &v, size, offset );
|
||
|
|
offset += size;
|
||
|
|
v = k->getY();
|
||
|
|
ok = ok && valueRecord.Write(&v,size,offset);
|
||
|
|
offset += size;
|
||
|
|
v = k->getR();
|
||
|
|
ok = ok && valueRecord.Write(&v,size,offset);
|
||
|
|
offset += size;
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.9 Close Function
|
||
|
|
|
||
|
|
Removes the main memory part of an object. In contrast to delete, the
|
||
|
|
disc part of the object is untouched (if there is one).
|
||
|
|
|
||
|
|
* ~typeInfo~: type description as a nested list
|
||
|
|
|
||
|
|
* ~w~: the ~addr~ pointer of ~w~ points to the object which is to close
|
||
|
|
|
||
|
|
*/
|
||
|
|
void CloseSCircle( const ListExpr typeInfo, Word& w ) {
|
||
|
|
SCircle *k = (SCircle *)w.addr;
|
||
|
|
delete k;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.10 Clone Function
|
||
|
|
|
||
|
|
Creates a depth copy (inclusive disc parts) of an object.
|
||
|
|
|
||
|
|
* ~typeInfo~: type description as nested list
|
||
|
|
|
||
|
|
* ~w~: holds a pointer to the object which is to clone
|
||
|
|
|
||
|
|
*/
|
||
|
|
Word CloneSCircle( const ListExpr typeInfo, const Word& w ){
|
||
|
|
SCircle* k = (SCircle*) w.addr;
|
||
|
|
Word res;
|
||
|
|
res.addr = new SCircle(k->getX(), k->getY(), k->getR());
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.11 Cast Function
|
||
|
|
|
||
|
|
Casts a void pointer to the type using a special call of new operator.
|
||
|
|
The argument points to a memory block which is to cast to the object.
|
||
|
|
The used C++ constructor cannot initialize the object, e.g. the used
|
||
|
|
constructur must do nothing.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void* CastSCircle( void* addr ) {
|
||
|
|
return (new (addr) SCircle);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.12 Type Check
|
||
|
|
|
||
|
|
Checks whether a given list corresponds to the type. This function
|
||
|
|
is quit similar to the ~checkType~ function within the class.
|
||
|
|
The result is ~true~ if ~type~ represents a valid type description for
|
||
|
|
the type, ~false~ otherwise. The argument ~errorInfo~ can be used
|
||
|
|
to report an error message to the user.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool SCircleTypeCheck(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return nl->IsEqual(type, SCircle::BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.13 SizeOf Function
|
||
|
|
|
||
|
|
Returns the size required to store an instance of this object to disc
|
||
|
|
using the ~Save~ function from above. Because an ~scircle~ is represented
|
||
|
|
by three double numbers, the size is three times the size of a single double.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int SizeOfSCircle() {
|
||
|
|
return 3*sizeof(double);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
1.14 The TypeConstructor Instance
|
||
|
|
|
||
|
|
We define a Secondo type by creating an instance of ~TypeConstructor~ feeded with
|
||
|
|
the functions defined before.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
TypeConstructor SCircleTC(
|
||
|
|
SCircle::BasicType(), // name of the type
|
||
|
|
SCircleProperty, // property function
|
||
|
|
OutSCircle, InSCircle, // out and in function
|
||
|
|
0, 0, // deprecated, don't think about it
|
||
|
|
CreateSCircle, DeleteSCircle, // creation and deletion
|
||
|
|
OpenSCircle, SaveSCircle, // open and save functions
|
||
|
|
CloseSCircle, CloneSCircle, // close and clone functions
|
||
|
|
CastSCircle, // cast function
|
||
|
|
SizeOfSCircle, // sizeOf function
|
||
|
|
SCircleTypeCheck); // type checking function
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
After creating the type constructor, the algebra can be defined and initialized.
|
||
|
|
(please read the code in section \ref{AlgebraDefinition}).
|
||
|
|
After the creation and initialization of the algebra, the algebra containing the
|
||
|
|
type ~scircle~ can be integrated into secondo.
|
||
|
|
|
||
|
|
For compiling the algebra module, just type ~make~ within the algebra
|
||
|
|
directory. For linking the algebra together with the kernel, navigate to
|
||
|
|
Secondo's main directory and enter ~make TTY~. If these calls were
|
||
|
|
successful, you can start with first tests.
|
||
|
|
|
||
|
|
Start SecondoTTYBDB:
|
||
|
|
|
||
|
|
~list algebras~:
|
||
|
|
the new algebra should be part of the result.
|
||
|
|
|
||
|
|
~list algebra GuideAlgebra~:
|
||
|
|
the type constructor for ~scircle~ is displayed
|
||
|
|
|
||
|
|
Open a database (create one if no database exists)
|
||
|
|
|
||
|
|
|
||
|
|
~query [const scircle value (9.0 10.0 20.0)]~:
|
||
|
|
a scircle object is displayed as a nested list
|
||
|
|
|
||
|
|
~query [const scircle value ( a + b) ]~: leads to an error message
|
||
|
|
|
||
|
|
~let k1 = [const scircle value (9.0 10.0 20.0)]~ is successful
|
||
|
|
|
||
|
|
~query k1~: displays the created scircle object as a nested list
|
||
|
|
|
||
|
|
~let k2 = k1~: copies k1
|
||
|
|
|
||
|
|
~delete k1~: removed the object k1
|
||
|
|
|
||
|
|
If running secondo on a linux system with valgrind installed, Secondo can be
|
||
|
|
started with:
|
||
|
|
|
||
|
|
----
|
||
|
|
SecondoTTYBDB --valgrind
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
This call starts Secondo within a valgrind environment and reports
|
||
|
|
memory errors and memory leaks. If there is a memory leak, the
|
||
|
|
reason of it is reported by:
|
||
|
|
|
||
|
|
----
|
||
|
|
SecondoTTYBDB --valgrindlc
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
2 Operator Implementation
|
||
|
|
|
||
|
|
|
||
|
|
Each operator implementation in Secondo contains of a type mapping,
|
||
|
|
a set of value mappings, a selection function,
|
||
|
|
a description, and a creation of an operator instance.
|
||
|
|
|
||
|
|
Furthermore, the syntax of the operator is described in the file
|
||
|
|
~AlgebraName.spec~ and at least one example must be given in the file
|
||
|
|
~AlgebraName.examples~. If there is no example, the operator will be
|
||
|
|
switched off by the Secondo framework.
|
||
|
|
|
||
|
|
|
||
|
|
The following sections present the implementation of a very simple
|
||
|
|
operator without any specials. The operator takes a single ~scircle~
|
||
|
|
value as its argument and returns the perimeter (a real number) of
|
||
|
|
this circle.
|
||
|
|
|
||
|
|
2.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping gets a nested list containing the argument types
|
||
|
|
for this operator. Within the implementation, the number and the
|
||
|
|
types of the arguments are checked to be a valid input for the
|
||
|
|
operator. If the argument types cannot be handled by this operator,
|
||
|
|
a type error is returned. Otherwise, the result of this function
|
||
|
|
is the result type of the operator in nested list format.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr perimeterTM(ListExpr args){
|
||
|
|
string err = "scircle expected";
|
||
|
|
// check the number of arguments
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError(err + " (wrong number of arguments)");
|
||
|
|
}
|
||
|
|
// check type of the argument
|
||
|
|
if(!SCircle::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
// return the result type
|
||
|
|
return listutils::basicSymbol<CcReal>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
2.2 Value Mapping
|
||
|
|
|
||
|
|
The value mapping takes values as arguments and computes the result.
|
||
|
|
If the result of the operator is not a stream as here, the result storage of the
|
||
|
|
operator tree node must be used for returning the result. The arguments are
|
||
|
|
provided as an array of ~Word~ holding the arguments in the
|
||
|
|
~addr~ pointers. The type mapping ensures that only the expected types
|
||
|
|
are behind these pointers and the cast will be successful.
|
||
|
|
The parameters ~message~ and ~local~ are used for stream operators only.
|
||
|
|
The parameter ~s~ is the operator's node within the operator tree.
|
||
|
|
The result of the operator for non-stream operators is always 0.
|
||
|
|
|
||
|
|
The arguments are :
|
||
|
|
|
||
|
|
* ~args~: array with the arguments of the operator
|
||
|
|
|
||
|
|
* ~result~: output parameter, for non stream operators, the
|
||
|
|
result storage must be used
|
||
|
|
|
||
|
|
* ~message~: message used by stream operators
|
||
|
|
|
||
|
|
* ~local~: possibility to store the state of an operator, used in
|
||
|
|
stream operators
|
||
|
|
|
||
|
|
* ~s~: node of this operator within the operator tree
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
int perimeterVM (Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s) {
|
||
|
|
SCircle* k = (SCircle*) args[0].addr; // get the argument and cast it
|
||
|
|
result = qp->ResultStorage(s); // use the result storage
|
||
|
|
CcReal* res = (CcReal*) result.addr; // cast the result
|
||
|
|
res->Set(true, k->perimeter()); // compute and set the result
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
2.3 Specification
|
||
|
|
|
||
|
|
The specification provides an operator description for the user.
|
||
|
|
The first argument of the ~OperatorSpec~ constructor is a description
|
||
|
|
of the type mapping, the second argument describes the syntax of
|
||
|
|
the operator, than comes the operator's meaning and the last argument
|
||
|
|
used here is an example query. If required, an additional
|
||
|
|
argument can provide some remark to this operator.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
OperatorSpec perimeterSpec(
|
||
|
|
"scircle -> real",
|
||
|
|
"perimeter(_)",
|
||
|
|
"Computes the perimeter of a disc.",
|
||
|
|
"query perimeter([const scircle value (1.0 8.0 16.0)])"
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
2.4 Operator Instance
|
||
|
|
|
||
|
|
Here, we create an instance of the operator using a constructor
|
||
|
|
of the class ~Operator~ and feeding it with the defined functions.
|
||
|
|
For non-overloaded operators, always the selection function
|
||
|
|
~Operator::SimpleSelect~ is used.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator perimeterOp(
|
||
|
|
"perimeter", // name of the operator
|
||
|
|
perimeterSpec.getStr(), // specification
|
||
|
|
perimeterVM, // value mapping
|
||
|
|
Operator::SimpleSelect, // selection function
|
||
|
|
perimeterTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
After the creation of the operator instance, the operator must be added to the
|
||
|
|
algebra within the algebra's constructor. Please take a look to
|
||
|
|
section \ref{AlgebraDefinition}.
|
||
|
|
|
||
|
|
Furthermore, the syntax of the operator is described in the file ~Guide.spec~
|
||
|
|
(see section \ref{SpecFile}) and
|
||
|
|
an example query including the result must be inserted into the file ~Guide.examples~
|
||
|
|
(see section \ref{ExampleFile}).
|
||
|
|
|
||
|
|
After that, the operator can be tested. After the start of Secondo, the operator
|
||
|
|
appears if ~list algebra GuideAlgebra~ is entered.
|
||
|
|
|
||
|
|
After opening a database, some queries can be entered, e.g.
|
||
|
|
|
||
|
|
----
|
||
|
|
query perimeter([const scircle value (1 2 3)])
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
The examples are processed by closing a running Secondo and entering:
|
||
|
|
|
||
|
|
----
|
||
|
|
Selftest tmp/Guide.examples
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
within Secondo's ~bin~ directory.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
3 Overloaded Operators
|
||
|
|
|
||
|
|
In this section, we describe the implementation of an overloaded
|
||
|
|
operator using the example of the ~distN~ operator. An overloaded
|
||
|
|
operator can handle more than one type combination as its
|
||
|
|
arguments and may have a different result type for each type
|
||
|
|
combination.
|
||
|
|
|
||
|
|
The ~distN~ operator accepts two integers or two real numbers
|
||
|
|
as arguments and returns the distance of these numbers as its result.
|
||
|
|
If the input types are integers, the result is also an integer.
|
||
|
|
In the case of a real number input, the result is a real number too.
|
||
|
|
|
||
|
|
|
||
|
|
3.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping of an overloaded operator handles all combinations of
|
||
|
|
accepted input types.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr distNTM(ListExpr args){
|
||
|
|
string err = "int x int or real x real expected";
|
||
|
|
// check length
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError(err + " (wrong number of arguments)");
|
||
|
|
}
|
||
|
|
// check for two integers
|
||
|
|
if( CcInt::checkType(nl->First(args))
|
||
|
|
&& CcInt::checkType(nl->Second(args))){
|
||
|
|
return listutils::basicSymbol<CcInt>();
|
||
|
|
}
|
||
|
|
// check for two real numbers
|
||
|
|
if( CcReal::checkType(nl->First(args))
|
||
|
|
&& CcReal::checkType(nl->Second(args))){
|
||
|
|
return listutils::basicSymbol<CcReal>();
|
||
|
|
}
|
||
|
|
// not accepted
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
3.2 Value Mapping
|
||
|
|
|
||
|
|
For each type combination of an overloaded operator exists one
|
||
|
|
value mapping. Here solved by a template parameter. If the handling
|
||
|
|
of other type combinations differs, it is also possible to define more
|
||
|
|
value mapping functions.
|
||
|
|
|
||
|
|
*/
|
||
|
|
template<class T>
|
||
|
|
int distNVMT( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
// get and casts the arguments
|
||
|
|
T* a1 = (T*) args[0].addr;
|
||
|
|
T* a2 = (T*) args[1].addr;
|
||
|
|
// use the result storage of s
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
T* res = (T*) result.addr;
|
||
|
|
// in secondo, integers and reals can be undefined
|
||
|
|
// if one of the arguments is not defined,
|
||
|
|
// set the result to be undefined
|
||
|
|
if(!a1->IsDefined() || !a2->IsDefined()){
|
||
|
|
res->SetDefined(false);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// compute the distance and store it in the
|
||
|
|
// result
|
||
|
|
res->Set(true, a1->GetValue() - a2->GetValue());
|
||
|
|
if(res->GetValue() < 0){
|
||
|
|
res->Set(true, res->GetValue() * -1);
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
3.3 Value Mapping Array and Selection Function
|
||
|
|
|
||
|
|
Each type combination has its own value mapping. Each value mapping
|
||
|
|
is put into an array of value mappings. The Selection function
|
||
|
|
picks the correct index within the value mapping array. Note that the
|
||
|
|
selection function is only called if the type mapping function of the
|
||
|
|
operator is passed. Thereby, here the check for correct list format
|
||
|
|
can be omitted.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ValueMapping distNVM[] = {
|
||
|
|
distNVMT<CcInt>, // value mapping handling two integers
|
||
|
|
distNVMT<CcReal> // value mapping handling two reals
|
||
|
|
};
|
||
|
|
|
||
|
|
int distNSelect(ListExpr args){
|
||
|
|
// int case at index 0
|
||
|
|
if ( CcInt::checkType(nl->First(args)) ){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// real case at index 1
|
||
|
|
if ( CcReal::checkType(nl->First(args)) ){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
// should never be reached
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
3.4 Specification
|
||
|
|
|
||
|
|
In principle, there is no difference between the specification of a
|
||
|
|
non-overloaded operator and an overloaded one. For overloaded operators
|
||
|
|
each accepted type combination must be recognized from the description.
|
||
|
|
|
||
|
|
*/
|
||
|
|
OperatorSpec distNSpec(
|
||
|
|
" t x t -> t , with t in {int,real}",
|
||
|
|
" _ distN _ ",
|
||
|
|
"Computes the distance between two numbers",
|
||
|
|
"query 1 distN 3"
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
3.5 Operator Instance
|
||
|
|
|
||
|
|
For an overloaded operator, another ~Operator~ constructor is used.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator distNOp(
|
||
|
|
"distN", // operator's name
|
||
|
|
distNSpec.getStr(), // specification
|
||
|
|
2, // number of Value Mappings
|
||
|
|
distNVM, // value mapping array
|
||
|
|
distNSelect, // selection function
|
||
|
|
distNTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
After the creation of the operator instance, add the operator to the
|
||
|
|
algebra, define the syntax in the ~spec~ file and give an example in
|
||
|
|
the ~examples~ file. Do not forget to test the operator in Secondo.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
4 Streams as Arguments of Operators
|
||
|
|
|
||
|
|
Streams are used to avoid the materialization of a large sets of objects.
|
||
|
|
Streams can be an argument and/or the result of an operator.
|
||
|
|
|
||
|
|
|
||
|
|
If a stream is an argument of an operator, each element of the stream
|
||
|
|
must be requested (similar to an iterator in a programming language).
|
||
|
|
Before an element can be requested, the stream must be opened. After
|
||
|
|
usage of the stream, the stream must be closed.
|
||
|
|
|
||
|
|
We describe the usage of streams by implementing the operator ~countNumber~.
|
||
|
|
This operator takes a stream of integers as its first argument and
|
||
|
|
a single integer number as the second argument. The result of this operator
|
||
|
|
is an integer value, reporting how often the second argument is contained
|
||
|
|
in the stream.
|
||
|
|
|
||
|
|
For easy usage of streams, we use the ~Stream~ class defined in ~Stream.h~
|
||
|
|
as a wrapper. Please read this file (~include~ directory of Secondo)
|
||
|
|
for more information.
|
||
|
|
|
||
|
|
4.1 Type Mapping
|
||
|
|
|
||
|
|
A stream type representing a stream of type ~X~ has the nested list
|
||
|
|
representation (stream X). The Stream class provides a ~checkType~
|
||
|
|
function doing this test.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr countNumberTM(ListExpr args){
|
||
|
|
// check for correct number of arguments
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError("wrong number of arguments");
|
||
|
|
}
|
||
|
|
// first argument must be a stream of integers
|
||
|
|
// second argument must be a single integer
|
||
|
|
if( !Stream<CcInt>::checkType(nl->First(args))
|
||
|
|
|| !CcInt::checkType(nl->Second(args))){
|
||
|
|
return listutils::typeError("stream(int) expected");
|
||
|
|
}
|
||
|
|
// result is an integer
|
||
|
|
return listutils::basicSymbol<CcInt>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
4.2 Value Mapping
|
||
|
|
|
||
|
|
Firstly, the first argument is
|
||
|
|
put into the ~Stream~ constructor. The second argument is
|
||
|
|
cast to a ~CcInt~. The stream is opened. While the
|
||
|
|
stream is not exhausted, we get the next element from the
|
||
|
|
stream via the ~request~ function. We compare the stream element
|
||
|
|
with the second argument and in the case of equality, we increase
|
||
|
|
a counter. All stream elements are deleted. Because in Secondo
|
||
|
|
attribute data types provide reference counting, we use the function
|
||
|
|
~DeleteIfAllowed~ (instead of direct delete) for this purpose.
|
||
|
|
If the stream is exhausted (~request~ returns null),
|
||
|
|
we set the result ~res~ to the counter's value and close the stream.
|
||
|
|
Because this value mapping produces no stream, the return value is 0.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
int countNumberVM( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
result = qp->ResultStorage(s); // use result storage for the result
|
||
|
|
Stream<CcInt> stream(args[0]); // wrap the stream
|
||
|
|
CcInt* num = (CcInt*) args[1].addr;
|
||
|
|
int count = 0;
|
||
|
|
stream.open(); // open the stream
|
||
|
|
CcInt* elem;
|
||
|
|
while( (elem = stream.request()) ){ // request next element
|
||
|
|
if(num->Compare(elem) == 0){
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
elem->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
CcInt* res = (CcInt*) result.addr;
|
||
|
|
res->Set(true,count);
|
||
|
|
stream.close();
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
4.3 Specification
|
||
|
|
|
||
|
|
The specification is implemented as usual.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
OperatorSpec countNumberSpec(
|
||
|
|
" stream(int) x int -> int",
|
||
|
|
" _ countNumber[_] ",
|
||
|
|
" Computes how often a given number occurs within a stream",
|
||
|
|
"query intstream(1,10) countNumbers[2]"
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
4.4 Operator Instance
|
||
|
|
|
||
|
|
Also the operator instance has no specials.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator countNumberOp(
|
||
|
|
"countNumber",
|
||
|
|
countNumberSpec.getStr(),
|
||
|
|
countNumberVM,
|
||
|
|
Operator::SimpleSelect,
|
||
|
|
countNumberTM
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
After the last steps for the creation of an operator (adding to the algebra,
|
||
|
|
defining syntax and providing an example), do not forget to test the
|
||
|
|
operator. Example queries are:
|
||
|
|
|
||
|
|
----
|
||
|
|
query intstream(1,10) countNumber[4]
|
||
|
|
|
||
|
|
# result is 1
|
||
|
|
|
||
|
|
query intstream(1,10) countNumber[12]
|
||
|
|
|
||
|
|
# result is 0
|
||
|
|
|
||
|
|
query intstream(1,10) intstream(2,10) concat countNumber[8]
|
||
|
|
|
||
|
|
# result is 2
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
5 Streams as Results of Operators (stream operators)
|
||
|
|
|
||
|
|
If a stream is the result of an operator, we call such an operator
|
||
|
|
stream-operator. The main difference to other operators is in the
|
||
|
|
value mapping function.
|
||
|
|
|
||
|
|
We explain the implementation of a stream operator by the
|
||
|
|
operator ~getChars~. This operator gets a single string as
|
||
|
|
its argument and returns a stream of strings where each string
|
||
|
|
corresponds to a single character of the argument.
|
||
|
|
|
||
|
|
5.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping of a stream operator has no specials. The creation
|
||
|
|
of the result is a little bit more complicated as for simple types because
|
||
|
|
the typed stream must be returned.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr getCharsTM(ListExpr args){
|
||
|
|
// check number of arguments
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError("wrong number of arguments");
|
||
|
|
}
|
||
|
|
// argument must be of type string
|
||
|
|
if(!CcString::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError("string expected");
|
||
|
|
}
|
||
|
|
// create the result type (stream string)
|
||
|
|
return nl->TwoElemList( listutils::basicSymbol<Stream<CcString > >(),
|
||
|
|
listutils::basicSymbol<CcString>());
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
5.2 LocalInfo Class
|
||
|
|
|
||
|
|
The value mapping of a stream operator is called many times during the
|
||
|
|
execution of a query. We need a structure, storing the current state
|
||
|
|
of the operator. In the implementation of the ~getChars~ operator, we
|
||
|
|
have to store the current position within the input string. We encapsulate
|
||
|
|
the state of the operator within a class and let do this class the
|
||
|
|
whole work.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class getCharsLI{
|
||
|
|
public:
|
||
|
|
// constructor: initializes the class from the string argument
|
||
|
|
getCharsLI(CcString* arg) : input(""), pos(0){
|
||
|
|
if(arg->IsDefined()){
|
||
|
|
input = arg->GetValue();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// destructor
|
||
|
|
~getCharsLI(){}
|
||
|
|
|
||
|
|
// this function returns the next result or null if the input is
|
||
|
|
// exhausted
|
||
|
|
CcString* getNext(){
|
||
|
|
if(pos >= input.length()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
CcString* res = new CcString(true, input.substr(pos,1));
|
||
|
|
pos++;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
private:
|
||
|
|
string input; // input string
|
||
|
|
size_t pos; // current position
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
5.3 Value Mapping
|
||
|
|
|
||
|
|
The value mapping of stream operators has a lot of differences compared to
|
||
|
|
the value mapping of non-stream operator. One difference is
|
||
|
|
that the ~message~ argument must be used to select the action to do. The messages
|
||
|
|
are OPEN, REQUEST, and CLOSE. (if the operator supports progress estimation,
|
||
|
|
some more messages must be handled).
|
||
|
|
Furthermore, the ~local~ argument is used to store the current state of the
|
||
|
|
operator (and doing the computations). The ~addr~ pointer of ~local~ is
|
||
|
|
null at the first call of this operator. The operator is responsible to this
|
||
|
|
pointer. After receiving a close message, the pointer must be set to null.
|
||
|
|
Another difference to non-stream operators is that the result storage of
|
||
|
|
~s~ is not used. Instead, we write newly created objects into the ~addr~ pointer
|
||
|
|
of ~result~.
|
||
|
|
|
||
|
|
When an OPEN message is received, we firstly check whether a
|
||
|
|
~localInfo~ is already stored by checking the ~addr~ unequal to null.
|
||
|
|
If so, we delete this structure and create a new one.
|
||
|
|
We set the ~addr~ pointer of the ~local~ argument to this structure. The result
|
||
|
|
of an OPEN message is always 0.
|
||
|
|
|
||
|
|
If a REQUEST message is received. We first look, whether we have already created
|
||
|
|
a local info. If not, we set the ~addr~ pointer of ~result~ to null. If there
|
||
|
|
is already such a structure, we compute the next result and store it into the
|
||
|
|
~addr~ pointer of ~result~. The computation of the next result is delegated to
|
||
|
|
the ~getNext~ function of the localInfo class. If there is a next result (addr
|
||
|
|
pointer of result is not null), the operator returns YIELD, otherwise CANCEL.
|
||
|
|
|
||
|
|
In the case of a CLOSE message, we free the memory allocated by the local info class
|
||
|
|
and set the ~addr~ pointer of ~local~ to null. The result to a CLOSE message is
|
||
|
|
always 0.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int getCharsVM( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
getCharsLI* li = (getCharsLI*) local.addr;
|
||
|
|
switch(message){
|
||
|
|
case OPEN : if(li) {
|
||
|
|
delete li;
|
||
|
|
}
|
||
|
|
local.addr = new getCharsLI( (CcString*) args[0].addr);
|
||
|
|
return 0;
|
||
|
|
case REQUEST: result.addr = li?li->getNext():0;
|
||
|
|
return result.addr?YIELD:CANCEL;
|
||
|
|
case CLOSE: if(li){
|
||
|
|
delete li;
|
||
|
|
local.addr = 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
5.4 Specification
|
||
|
|
|
||
|
|
The specification of a stream operator has no specials.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
OperatorSpec getCharsSpec(
|
||
|
|
" string -> stream(string)",
|
||
|
|
" getChars(_) ",
|
||
|
|
" Seperates the characters of a string. ",
|
||
|
|
" query getChars(\"secondo\") count"
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
5.5 Operator instance
|
||
|
|
|
||
|
|
The creation of the operator instance is the same as for non-stream operators.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator getCharsOp(
|
||
|
|
"getChars",
|
||
|
|
getCharsSpec.getStr(),
|
||
|
|
getCharsVM,
|
||
|
|
Operator::SimpleSelect,
|
||
|
|
getCharsTM
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
As usual, the final steps are:
|
||
|
|
|
||
|
|
* add the operator to the algebra
|
||
|
|
|
||
|
|
* define the syntax in the ~spec~ file
|
||
|
|
|
||
|
|
* give an example in the ~examples~ file
|
||
|
|
|
||
|
|
* test the operator in Secondo
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
6 Streams as Both, Arguments and Results
|
||
|
|
|
||
|
|
Some operators have a stream as an argument and return also a stream. The
|
||
|
|
implementation combines stream consuming with stream producing operators.
|
||
|
|
|
||
|
|
We show as an example the operator ~startsWithS~. This is a kind of filter operator.
|
||
|
|
It receives a stream of strings and a single string argument. All elements in the
|
||
|
|
stream starting with the second argument pass the operator, all others are
|
||
|
|
filtered out.
|
||
|
|
|
||
|
|
6.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping is quite usual.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr startsWithSTM(ListExpr args){
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError("wrong number of args");
|
||
|
|
}
|
||
|
|
if( !Stream<CcString>::checkType(nl->First(args))
|
||
|
|
|| !CcString::checkType(nl->Second(args))){
|
||
|
|
return listutils::typeError("stream(string) x string expected");
|
||
|
|
}
|
||
|
|
return nl->First(args);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
6.2 LocalInfo Class
|
||
|
|
|
||
|
|
As for other stream operators, we create a local info class storing the
|
||
|
|
state of this operator and computing the next result element.
|
||
|
|
|
||
|
|
Because we create an instance of this class in case of a OPEN message
|
||
|
|
and delete the instance in case of a CLOSE message, we open the
|
||
|
|
argument stream in the constructor and close it in the destructor.
|
||
|
|
|
||
|
|
Elements passing the test are just returned as the next result.
|
||
|
|
Filtered out strings are deleted.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class startsWithSLI{
|
||
|
|
public:
|
||
|
|
|
||
|
|
// s is the stream argument, st the string argument
|
||
|
|
startsWithSLI(Word s, CcString* st): stream(s), start(""){
|
||
|
|
def = st->IsDefined();
|
||
|
|
if(def){ start = st->GetValue(); }
|
||
|
|
stream.open();
|
||
|
|
}
|
||
|
|
|
||
|
|
~startsWithSLI(){
|
||
|
|
stream.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
CcString* getNext(){
|
||
|
|
if(!def){ return 0; }
|
||
|
|
CcString* k;
|
||
|
|
while( (k = stream.request())){
|
||
|
|
if(k->IsDefined() && stringutils::startsWith(k->GetValue(), start)){
|
||
|
|
return k;
|
||
|
|
}
|
||
|
|
k->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
Stream<CcString> stream;
|
||
|
|
string start;
|
||
|
|
bool def;
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
6.3 Value Mapping
|
||
|
|
|
||
|
|
Because the complete functionality is outsourced to the ~LocalInfo~ class,
|
||
|
|
the implementation of the actual value mapping is straightforward.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int startsWithSVM( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
startsWithSLI* li = (startsWithSLI*) local.addr;
|
||
|
|
switch(message){
|
||
|
|
case OPEN : if(li) {
|
||
|
|
delete li;
|
||
|
|
}
|
||
|
|
local.addr = new startsWithSLI(args[0],
|
||
|
|
(CcString*) args[1].addr);
|
||
|
|
return 0;
|
||
|
|
case REQUEST: result.addr = li?li->getNext():0;
|
||
|
|
return result.addr?YIELD:CANCEL;
|
||
|
|
case CLOSE: if(li){
|
||
|
|
delete li;
|
||
|
|
local.addr = 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
6.4 Specification
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
OperatorSpec startsWithSSpec(
|
||
|
|
" stream(string) x string -> stream(string)",
|
||
|
|
" _ startsWithS[_]",
|
||
|
|
" All strings in the stream not starting with the second "
|
||
|
|
" are filtered out from the stream",
|
||
|
|
" query plz feed projecttransformstream[Ort] startsWithS(\"Ha\") count"
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
6.5 Operator Instance
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator startsWithSOp(
|
||
|
|
"startsWithS",
|
||
|
|
startsWithSSpec.getStr(),
|
||
|
|
startsWithSVM,
|
||
|
|
Operator::SimpleSelect,
|
||
|
|
startsWithSTM
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
The final steps are the same as for other operators.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
7 Functions as Arguments
|
||
|
|
|
||
|
|
Functions are part of the Secondo framework. Sometimes, an operator
|
||
|
|
needs to evaluate a function during its execution. For example, the
|
||
|
|
filter operator of Secondo has a function as its second argument
|
||
|
|
representing the filter condition.
|
||
|
|
|
||
|
|
The usage of functions as arguments to an operator is explained
|
||
|
|
at the example of the ~replaceElem~ operator. This operator has a stream of
|
||
|
|
some attribute data type ~X~ as its first argument while the second
|
||
|
|
argument is a function having the type ~X~ as its only argument and
|
||
|
|
returns another (or may be the same) attribute data type.
|
||
|
|
|
||
|
|
7.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping itself has no speciality. The type of a function is
|
||
|
|
given by ~(map arguments result)~, e.g. ~(map real int bool)~ for
|
||
|
|
a function computing a ~bool~ value from a ~real~ and an ~int~.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
ListExpr replaceElemTM(ListExpr args){
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError("wrong number of arguments");
|
||
|
|
}
|
||
|
|
if(!Stream<Attribute>::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError("first argument has to be a "
|
||
|
|
"stream of attributes");
|
||
|
|
}
|
||
|
|
if(!listutils::isMap<1>(nl->Second(args))){
|
||
|
|
return listutils::typeError("second arg has to be a map "
|
||
|
|
"with 1 argument");
|
||
|
|
}
|
||
|
|
ListExpr StreamElem = nl->Second(nl->First(args));
|
||
|
|
ListExpr MapArg = nl->Second(nl->Second(args));
|
||
|
|
if(!nl->Equal(StreamElem, MapArg)){
|
||
|
|
return listutils::typeError("map arg not equal to stream elem");
|
||
|
|
}
|
||
|
|
ListExpr res = nl->Third(nl->Second(args));
|
||
|
|
// result of the function must be an attribute again, e.g. in
|
||
|
|
// kind DATA
|
||
|
|
if(!Attribute::checkType(res)){
|
||
|
|
return listutils::typeError("map result is not in kind DATA");
|
||
|
|
}
|
||
|
|
return nl->TwoElemList( listutils::basicSymbol<Stream<Attribute> >(),
|
||
|
|
res);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
7.2 LocalInfo
|
||
|
|
|
||
|
|
Before a function can be evaluated, the arguments of this function must be set.
|
||
|
|
All arguments of a function can be stored into the argument vector of the function.
|
||
|
|
The type of this array in called ArgVectorPointer. The argument vector of a
|
||
|
|
specific function can be get using the ~Argument~ function of the query processor.
|
||
|
|
After putting the arguments into the argument vector, the function can be evaluated
|
||
|
|
using the ~Request~ function of the query processor. Note that the result of a
|
||
|
|
function returning a single value (i.e. not a stream)
|
||
|
|
should not be deleted and hence also not put into the output stream. For this reason, the
|
||
|
|
Clone function is used in the ~getNext~ function below.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class replaceElemLI{
|
||
|
|
public:
|
||
|
|
replaceElemLI(Word st, Word f): stream(st), fun(f){
|
||
|
|
// open the input stream
|
||
|
|
stream.open();
|
||
|
|
// get the argument vector
|
||
|
|
funargs = qp->Argument(f.addr);
|
||
|
|
}
|
||
|
|
|
||
|
|
~replaceElemLI(){
|
||
|
|
// close the input stream
|
||
|
|
stream.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
Attribute* getNext(){
|
||
|
|
// get the next element from the input stream
|
||
|
|
Attribute* funarg = stream.request();
|
||
|
|
if(!funarg){ return 0; }
|
||
|
|
// put this argument to the argument vector
|
||
|
|
(*funargs)[0] = funarg;
|
||
|
|
//
|
||
|
|
Word funres;
|
||
|
|
qp->Request(fun.addr, funres);
|
||
|
|
// free the memory of the input element
|
||
|
|
funarg->DeleteIfAllowed();
|
||
|
|
// cast the function result
|
||
|
|
Attribute* res = (Attribute*) funres.addr;
|
||
|
|
// return a clone of the function result to
|
||
|
|
// avoid a deletion of the result
|
||
|
|
return res->Clone();
|
||
|
|
}
|
||
|
|
private:
|
||
|
|
Stream<Attribute> stream;
|
||
|
|
Word fun;
|
||
|
|
ArgVectorPointer funargs;
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
7.3 Value Mapping
|
||
|
|
|
||
|
|
As usual, the local info class does the work and the actual
|
||
|
|
value mapping is quite simple.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int replaceElemVM( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
replaceElemLI* li = (replaceElemLI*) local.addr;
|
||
|
|
switch(message){
|
||
|
|
case OPEN:
|
||
|
|
if(li) { delete li; }
|
||
|
|
local.addr = new replaceElemLI(args[0], args[1]);
|
||
|
|
return 0;
|
||
|
|
case REQUEST:
|
||
|
|
result.addr = li?li->getNext():0;
|
||
|
|
return result.addr?YIELD:CANCEL;
|
||
|
|
case CLOSE:
|
||
|
|
if(li){
|
||
|
|
delete li;
|
||
|
|
local.addr = 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
7.4 Specification
|
||
|
|
|
||
|
|
*/
|
||
|
|
OperatorSpec replaceElemSpec(
|
||
|
|
"stream(X) x (fun : X -> Y) -> stream(Y)",
|
||
|
|
" _ replaceElem[_] ",
|
||
|
|
" replaces the element in the stream by the function results",
|
||
|
|
" query intstream(1,30) replaceElem[fun(i : int) i * 1.5) count"
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
7.5 Operator Instance
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator replaceElemOp(
|
||
|
|
"replaceElem",
|
||
|
|
replaceElemSpec.getStr(),
|
||
|
|
replaceElemVM,
|
||
|
|
Operator::SimpleSelect,
|
||
|
|
replaceElemTM
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
Do not forget the final steps for the operator.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8 Implementing Attribute Data Types
|
||
|
|
|
||
|
|
Relations (tables) of a database systems store sets of tuples which in turn
|
||
|
|
consist of some attributes. Types which can be part of a tuple are called
|
||
|
|
attribute data types. Because relations in Secondo use a generic approach
|
||
|
|
for storing tuples to disc, the internal structure of attribute data types
|
||
|
|
is restricted. Furthermore, attribute data types must provide implementations
|
||
|
|
of a set of functions to support some operators, e.g. a ~compare~ function to
|
||
|
|
support ~sort~ operators. To mark a type as an attribute data type, it must
|
||
|
|
be in the kind ~DATA~. Attributes can be defined or not. In operators using
|
||
|
|
attribute data types, always a check of the defined state is required.
|
||
|
|
|
||
|
|
8.1 Restricted Structure
|
||
|
|
|
||
|
|
Firstly, an attribute data type must be derived from the class ~Attribute~.
|
||
|
|
Because of the frequent use of void pointers in secondo, multiple inheritance
|
||
|
|
is not allowed and may lead to a crash of the system under certain circumstances.
|
||
|
|
But it is allowed to build chains of inheritances having the class ~Attribute~ as
|
||
|
|
the root.
|
||
|
|
|
||
|
|
The use of pointers is forbidden within a class representing an attribute. This
|
||
|
|
restriction also forbids member variables using pointers, for example the most
|
||
|
|
of the STL classes. For implementing attributes of variable size, FLOBs are used
|
||
|
|
(see section \ref{FLOBs}).
|
||
|
|
|
||
|
|
The standard constructor (the one without any argument) cannot do anything.
|
||
|
|
|
||
|
|
All other constructors must call the constructor of the Attribute class taking a
|
||
|
|
~bool~ argument.
|
||
|
|
|
||
|
|
|
||
|
|
8.2 Golden Rules for Implementing Attribute Types
|
||
|
|
|
||
|
|
* Derive from class ~Attribute~
|
||
|
|
|
||
|
|
* Never use pointers within the class (also do not use members having pointers)
|
||
|
|
|
||
|
|
* Always define the standard constructor (the one without any argument) having an
|
||
|
|
empty implementation
|
||
|
|
|
||
|
|
* Always implement at least another constructor
|
||
|
|
|
||
|
|
* In all non-standard constructors, call the constructor of the class ~Attribute~
|
||
|
|
having a boolean argument. Initialize *all* members of your class.
|
||
|
|
If the class has FLOB or DbArray members, use the constructor receiving the initial
|
||
|
|
size.
|
||
|
|
|
||
|
|
* Never use the standard constructor except in the cast function
|
||
|
|
|
||
|
|
|
||
|
|
If you don't take these rules to heart, Secondo will run instable.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
8.2 Additional Functions
|
||
|
|
|
||
|
|
Besides the normal functions of the class, the following functions are required to form the internal structure of an
|
||
|
|
attribute data type.
|
||
|
|
|
||
|
|
* virtual int NumOfFLOBs() const\\
|
||
|
|
When implementing an attribute type having variable size, the class
|
||
|
|
contains a fixed number of FLOBs. This function returns this number.
|
||
|
|
|
||
|
|
|
||
|
|
* virtual Flob[*] GetFLOB( const int i ) \\
|
||
|
|
This funtion returns a pointer to one of the flobs.
|
||
|
|
|
||
|
|
* int Compare(const Attribute[*] arg) const\\
|
||
|
|
This function compares the current instance of the class with another
|
||
|
|
attribute. It is ensured that the ~arg~ argument has the same C++ type
|
||
|
|
as the instance itself. This function is used for example for the
|
||
|
|
~sort~ operator. This function returns a negative value if the called
|
||
|
|
object is smaller than the argument, 0 if both objects are equal and
|
||
|
|
a positive value otherwise. Note that two non-defined objects are equal
|
||
|
|
and an undefined object is smaller than a defined one.
|
||
|
|
|
||
|
|
|
||
|
|
* bool Adjacent(const Attribute[*] arg) const \\
|
||
|
|
This operator checks whether this instance and arg (having the same
|
||
|
|
type as the instance) are adjacent. This operator is used to
|
||
|
|
build generic range types. Because these generic range types was never
|
||
|
|
implemented, this function can just return false if there is no
|
||
|
|
meaningful adjacent relation between instances of this class.
|
||
|
|
|
||
|
|
|
||
|
|
* size\_t Sizeof() const \\
|
||
|
|
This function returns the size of the memory block allocated by this
|
||
|
|
class. The implementation of this function is always: \\
|
||
|
|
\texttt{return sizeof([*]this); }
|
||
|
|
|
||
|
|
* size\_t HashValue() const \\
|
||
|
|
This function returns a hash value for this object and supports
|
||
|
|
among others the ~hashjoin~ operator. If there is no meaningful implementation
|
||
|
|
for this function, the ~hashjoin~ operator will be extremely slow
|
||
|
|
if there is a large input.
|
||
|
|
|
||
|
|
* void CopyFrom(const Attribute[*] arg) \\
|
||
|
|
This operations copyies the value of ~arg~ into the called
|
||
|
|
instance. The C++ type of ~arg~ is the same as the one of the called
|
||
|
|
instance. This operator is used by operations like ~extract~, ~max~, ~min~
|
||
|
|
and many other.
|
||
|
|
|
||
|
|
* Attribute[*] Clone() const \\
|
||
|
|
Returns a depth copy of the attribute. This operation is used for example
|
||
|
|
in the ~extend~ operator.
|
||
|
|
|
||
|
|
Because Secondo is developed since many years, there are several possibilities
|
||
|
|
to define an attribute data type. In this document two of them are presented.
|
||
|
|
|
||
|
|
This first method is the classical one, defining a lot of independent functions
|
||
|
|
as input for a Secondo type constructor. This method should be used if an existing
|
||
|
|
Secondo data type is to tranfer into an attribute data type or in some
|
||
|
|
special cases. If the type is planned
|
||
|
|
to be an attribute data type from the beginning, the second method is to prefer.
|
||
|
|
The second method uses static member functions
|
||
|
|
of the class for defining an attribute data type using a generic template class.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1 The Classical Method
|
||
|
|
|
||
|
|
8.1.1 Defining the Class
|
||
|
|
|
||
|
|
We call the class ~ACircle~ standing for (attribute circle).
|
||
|
|
Because a circle can be represented using three double
|
||
|
|
values, this class has fixed size and hence no pointers
|
||
|
|
are used.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
class ACircle: public Attribute{
|
||
|
|
public:
|
||
|
|
ACircle() {} // cannot do anything
|
||
|
|
|
||
|
|
ACircle(const double _x, const double _y, const double _r):
|
||
|
|
Attribute(true), // do not forget the initialization of
|
||
|
|
// the super class
|
||
|
|
x(_x), y(_y), r(_r) {}
|
||
|
|
|
||
|
|
// copy constructor
|
||
|
|
ACircle(const ACircle & c) : Attribute(c.IsDefined()),
|
||
|
|
x(c.x), y(c.y),r(c.r) {}
|
||
|
|
|
||
|
|
// assignment operator
|
||
|
|
ACircle& operator=(const ACircle& src){
|
||
|
|
SetDefined(src.IsDefined());
|
||
|
|
x = src.x;
|
||
|
|
y = src.y;
|
||
|
|
r = src.r;
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
|
||
|
|
// destructor
|
||
|
|
~ACircle(){}
|
||
|
|
|
||
|
|
// auxiliary functions
|
||
|
|
static const string BasicType(){ return "acircle"; }
|
||
|
|
static const bool checkType(const ListExpr list) {
|
||
|
|
return listutils::isSymbol(list, BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
// perimeter construction
|
||
|
|
double perimeter() const{
|
||
|
|
return 2*M_PI*r;
|
||
|
|
}
|
||
|
|
// here, the functions required for an attribute
|
||
|
|
// data type are defined
|
||
|
|
|
||
|
|
|
||
|
|
// NumOfFLOBs
|
||
|
|
// for class without FLOB members, this function
|
||
|
|
// can be omitted or it returns 0
|
||
|
|
inline virtual int NumOfFLOBs() const {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// this class contains no FLOB to return
|
||
|
|
inline virtual Flob* GetFLOB( const int i ) {
|
||
|
|
assert(false);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// compare: always implement this function
|
||
|
|
// if there is no natural order, just use any
|
||
|
|
// valid order to the objects, in this example,
|
||
|
|
// a lexicographical order is chosen
|
||
|
|
int Compare(const Attribute* arg) const{
|
||
|
|
// first compare defined and undefined flag
|
||
|
|
if(!IsDefined()){
|
||
|
|
return arg->IsDefined()?-1:0;
|
||
|
|
}
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
ACircle* c = (ACircle*) arg;
|
||
|
|
if(x < c->x) return -1;
|
||
|
|
if(x > c->x) return 1;
|
||
|
|
if(y < c->y) return -1;
|
||
|
|
if(y > c->y) return 1;
|
||
|
|
if(r < c->r) return -1;
|
||
|
|
if(r > c->r) return 1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// we dont want to create a range type over acircles,
|
||
|
|
// thus, just false is returned
|
||
|
|
bool Adjacent(const Attribute* arg) const{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// standard implementation of Sizeof
|
||
|
|
size_t Sizeof() const{
|
||
|
|
return sizeof(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
// defines a meaningful hash function e.g., for
|
||
|
|
// support of hash joins
|
||
|
|
size_t HashValue() const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return (size_t) (x + y + r);
|
||
|
|
}
|
||
|
|
|
||
|
|
// takes the values from arg over
|
||
|
|
// delegated to the assignment operator
|
||
|
|
void CopyFrom(const Attribute* arg){
|
||
|
|
*this = *((ACircle*)arg);
|
||
|
|
}
|
||
|
|
|
||
|
|
// returns a depth copy from this object
|
||
|
|
Attribute* Clone() const{
|
||
|
|
return new ACircle(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// usual functions
|
||
|
|
double getX() const{ return x; }
|
||
|
|
double getY() const{ return y; }
|
||
|
|
double getR() const{ return r; }
|
||
|
|
private:
|
||
|
|
double x;
|
||
|
|
double y;
|
||
|
|
double r;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
8.1.2 Secondo Interface Support
|
||
|
|
|
||
|
|
8.1.2.1 Property function
|
||
|
|
|
||
|
|
This function is very similar to the SCircle property function. Note that
|
||
|
|
the name and the signature have been changed.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr ACircleProperty () {
|
||
|
|
return ( nl -> TwoElemList (
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom ( " Signature " ) ,
|
||
|
|
nl->StringAtom ( " Example Type List " ) ,
|
||
|
|
nl->StringAtom ( " List Rep " ) ,
|
||
|
|
nl->StringAtom ( " Example List " )) ,
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom ( " -> DATA " ) ,
|
||
|
|
nl->StringAtom ( ACircle::BasicType ()) ,
|
||
|
|
nl->StringAtom ( " ( real real real ) = (x ,y ,r ) " ) ,
|
||
|
|
nl->StringAtom ( " (13.5 -76.0 1.0) " )
|
||
|
|
)));
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.2 IN function
|
||
|
|
|
||
|
|
Because each attribute may be undefined, a special treatment is
|
||
|
|
necessary for this case.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Word InACircle( const ListExpr typeInfo, const ListExpr instance,
|
||
|
|
const int errorPos, ListExpr& errorInfo, bool& correct ){
|
||
|
|
// create a result with addr pointing to 0
|
||
|
|
Word res((void*)0);
|
||
|
|
// assume an incorrect list
|
||
|
|
correct = false;
|
||
|
|
|
||
|
|
// check for undefined
|
||
|
|
if(listutils::isSymbolUndefined(instance)){
|
||
|
|
correct = true;
|
||
|
|
ACircle* c = new ACircle(1,2,3);
|
||
|
|
c->SetDefined(false);
|
||
|
|
res.addr = c;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
// check whether the list has three elements
|
||
|
|
if(!nl->HasLength(instance,3)){
|
||
|
|
cmsg.inFunError("expected three numbers");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// check whether all elements are numeric
|
||
|
|
if( !listutils::isNumeric(nl->First(instance))
|
||
|
|
|| !listutils::isNumeric(nl->Second(instance))
|
||
|
|
|| !listutils::isNumeric(nl->Third(instance))){
|
||
|
|
cmsg.inFunError("expected three numbers");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// get the numeric values of the elements
|
||
|
|
double x = listutils::getNumValue(nl->First(instance));
|
||
|
|
double y = listutils::getNumValue(nl->Second(instance));
|
||
|
|
double r = listutils::getNumValue(nl->Third(instance));
|
||
|
|
// check for a valid radius
|
||
|
|
if(r<=0){
|
||
|
|
cmsg.inFunError("invalid radius");
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
// list was correct, create the result
|
||
|
|
correct = true;
|
||
|
|
res.addr = new ACircle(x,y,r); // is defined, see constructor
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.3 OUT function
|
||
|
|
|
||
|
|
Also in this function, undefined values must be handled in
|
||
|
|
a special case.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr OutACircle( ListExpr typeInfo, Word value ) {
|
||
|
|
ACircle* k = (ACircle*) value.addr;
|
||
|
|
if(!k->IsDefined()){
|
||
|
|
return listutils::getUndefined();
|
||
|
|
}
|
||
|
|
return nl->ThreeElemList(
|
||
|
|
nl->RealAtom(k->getX()),
|
||
|
|
nl->RealAtom(k->getY()),
|
||
|
|
nl->RealAtom(k->getR()));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.4 Create function, Delete function, Close function
|
||
|
|
|
||
|
|
Again very similar to the ~SCircle~ versions.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Word CreateACircle( const ListExpr typeInfo ) {
|
||
|
|
Word w;
|
||
|
|
w.addr = (new ACircle(0,0,1.0));
|
||
|
|
return w;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
void DeleteACircle ( const ListExpr typeInfo , Word & w ) {
|
||
|
|
ACircle * k = ( ACircle *) w.addr ;
|
||
|
|
delete k ;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void CloseACircle ( const ListExpr typeInfo , Word & w ) {
|
||
|
|
ACircle * k = ( ACircle *) w.addr ;
|
||
|
|
delete k ;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.5 Open and Save Functions
|
||
|
|
|
||
|
|
For attribute data types, generic ~Open~ and ~Save~ functions
|
||
|
|
are available. Thus we don't need an implementation here.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.6 Clone function
|
||
|
|
|
||
|
|
Because a class representing an attribute data type has an own ~Clone~
|
||
|
|
function, the implementation of Secondo's Clone function is simpler
|
||
|
|
than for non-attribute types.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Word CloneACircle ( const ListExpr typeInfo , const Word & w ){
|
||
|
|
ACircle * k = ( ACircle *) w . addr ;
|
||
|
|
Word res(k->Clone());
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
8.1.2.7 Cast function
|
||
|
|
|
||
|
|
The cast function is implemented as before.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void * CastACircle ( void * addr ) {
|
||
|
|
return ( new ( addr ) ACircle );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.7 Type check
|
||
|
|
|
||
|
|
|
||
|
|
Within the ~TypeCheck~ function, we can call the appropriate class function.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool ACircleTypeCheck ( ListExpr type , ListExpr & errorInfo ){
|
||
|
|
return ACircle::checkType(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.8 SizeOf function
|
||
|
|
|
||
|
|
Because of the generic Open and Save functions, we return the size
|
||
|
|
of the class.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
int SizeOfACircle () {
|
||
|
|
return sizeof ( ACircle );
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.1.2.9 Type Constructor instance
|
||
|
|
|
||
|
|
This is the same constructor as for non-attribute data types. Note the
|
||
|
|
usage of the generic open and save functions.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
TypeConstructor ACircleTC(
|
||
|
|
ACircle::BasicType(), // name of the type
|
||
|
|
ACircleProperty, // property function
|
||
|
|
OutACircle, InACircle, // out and in function
|
||
|
|
0, 0, // deprecated, don't think about it
|
||
|
|
CreateACircle, DeleteACircle, // creation and deletion
|
||
|
|
OpenAttribute<ACircle>, // open function
|
||
|
|
SaveAttribute<ACircle>, // save functions
|
||
|
|
CloseACircle, CloneACircle, // close and clone functions
|
||
|
|
CastACircle, // cast function
|
||
|
|
SizeOfACircle, // sizeOf function
|
||
|
|
ACircleTypeCheck); // type checking function
|
||
|
|
|
||
|
|
/*
|
||
|
|
After adding this type constructor to the algebra, the type constructor is
|
||
|
|
inserted into kind ~DATA~ to mark it as an attribute data type. See section
|
||
|
|
\ref{AlgebraDefinition}.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.3 Attribute Data Type -- the Modern Way
|
||
|
|
|
||
|
|
When defining an attribute data type in the way described above, a
|
||
|
|
lot of functions having a standard implementation must be implemented.
|
||
|
|
For attribute data types there is another way for rapid implementing them
|
||
|
|
without the stupid repeatation of code. Here, all functions which cannot be
|
||
|
|
handled automatically are part of the class (the most of them are static
|
||
|
|
member functions). We show the class ~GCircle~ (generic circle) as an example.
|
||
|
|
Note that for using this method the header ~GenericTC.h~ must be included.
|
||
|
|
|
||
|
|
|
||
|
|
8.3.1 The class implementation
|
||
|
|
|
||
|
|
Besides the usual functions required to implement an attribute data type,
|
||
|
|
the following functions must be implemented if the generic approach should
|
||
|
|
be used. In return, no non-class functions must be implemented.
|
||
|
|
|
||
|
|
* a type constructor taking an ~int~ value as an argument
|
||
|
|
|
||
|
|
* a static ~Property~ function
|
||
|
|
|
||
|
|
* a ~ToListExpr~ function replacing the ~OUT~ function
|
||
|
|
|
||
|
|
* a function called ~CheckKind~ checking the type (and having a wrong name ;-) )
|
||
|
|
|
||
|
|
* a static ~ReadFrom~ function replacing the ~IN~ function
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
class GCircle: public Attribute{
|
||
|
|
public:
|
||
|
|
GCircle() {} // cannot do anything
|
||
|
|
|
||
|
|
GCircle(const double _x, const double _y, const double _r):
|
||
|
|
Attribute(true), // do not forget the initialization of
|
||
|
|
// the super class
|
||
|
|
x(_x), y(_y), r(_r) {}
|
||
|
|
|
||
|
|
// copy constructor
|
||
|
|
GCircle(const GCircle & c) : Attribute(c.IsDefined()),
|
||
|
|
x(c.x), y(c.y),r(c.r) {}
|
||
|
|
|
||
|
|
// assignment operator
|
||
|
|
GCircle& operator=(const GCircle& src){
|
||
|
|
SetDefined(src.IsDefined());
|
||
|
|
x = src.x;
|
||
|
|
y = src.y;
|
||
|
|
r = src.r;
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
|
||
|
|
// destructor
|
||
|
|
~GCircle(){}
|
||
|
|
|
||
|
|
// auxiliary functions
|
||
|
|
static const string BasicType(){ return "gcircle"; }
|
||
|
|
static const bool checkType(const ListExpr list) {
|
||
|
|
return listutils::isSymbol(list, BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
// perimeter computation
|
||
|
|
double perimeter() const{
|
||
|
|
return 2*M_PI*r;
|
||
|
|
}
|
||
|
|
// here, the functions required for an attribute
|
||
|
|
// data type are defined
|
||
|
|
|
||
|
|
|
||
|
|
// NumOfFLOBs
|
||
|
|
// for class without FLOB members, this function
|
||
|
|
// can be omitted
|
||
|
|
inline virtual int NumOfFLOBs() const {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// this class contains no FLOB to return
|
||
|
|
inline virtual Flob* GetFLOB( const int i ) {
|
||
|
|
assert(false);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// compare, always implement this function
|
||
|
|
// if there is no natural order, just use any
|
||
|
|
// valid order to the objects, in this example,
|
||
|
|
// a lexicographical order is chosen
|
||
|
|
int Compare(const Attribute* arg) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return arg->IsDefined()?-1:0;
|
||
|
|
}
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
GCircle* c = (GCircle*) arg;
|
||
|
|
if(x < c->x) return -1;
|
||
|
|
if(x > c->x) return 1;
|
||
|
|
if(y < c->y) return -1;
|
||
|
|
if(y > c->y) return 1;
|
||
|
|
if(r < c->r) return -1;
|
||
|
|
if(r > c->r) return 1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// we dont want to create a range type over GCircles,
|
||
|
|
// thus, just false is returned
|
||
|
|
bool Adjacent(const Attribute* arg) const{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// standard implementation of Sizeof
|
||
|
|
size_t Sizeof() const{
|
||
|
|
return sizeof(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
// define a meaningful hash function for
|
||
|
|
// support of hash joins
|
||
|
|
size_t HashValue() const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return (size_t) (x + y + r);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CopyFrom(const Attribute* arg){
|
||
|
|
*this = *((GCircle*)arg);
|
||
|
|
}
|
||
|
|
|
||
|
|
Attribute* Clone() const{
|
||
|
|
return new GCircle(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Here, the additional functions start
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
// Additional type constructor taking some int as argument
|
||
|
|
GCircle(int dummy): Attribute(false), x(0), y(0), r(1) {}
|
||
|
|
|
||
|
|
// Property functions
|
||
|
|
static ListExpr Property(){
|
||
|
|
return gentc::GenProperty("-> DATA", // signature
|
||
|
|
BasicType(), // type description
|
||
|
|
"(real real real)", // list rep
|
||
|
|
"1.0 2.0 3.0)"); // example list
|
||
|
|
}
|
||
|
|
|
||
|
|
// Type check function
|
||
|
|
static bool CheckKind(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return checkType(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
// replacement for the IN function
|
||
|
|
bool ReadFrom(ListExpr LE, const ListExpr typeInfo){
|
||
|
|
// handle undefined value
|
||
|
|
if(listutils::isSymbolUndefined(LE)){
|
||
|
|
SetDefined(false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!nl->HasLength(LE,3)){
|
||
|
|
cmsg.inFunError("three numbers expected");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if( !listutils::isNumeric(nl->First(LE))
|
||
|
|
|| !listutils::isNumeric(nl->Second(LE))
|
||
|
|
|| !listutils::isNumeric(nl->Third(LE))){
|
||
|
|
cmsg.inFunError("three numbers expected");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
double x = listutils::getNumValue(nl->First(LE));
|
||
|
|
double y = listutils::getNumValue(nl->Second(LE));
|
||
|
|
double r = listutils::getNumValue(nl->Third(LE));
|
||
|
|
if( r<=0){
|
||
|
|
cmsg.inFunError("invalid radius");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
SetDefined(true);
|
||
|
|
this->x = x;
|
||
|
|
this->y = y;
|
||
|
|
this->r = r;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// replacement for the out function
|
||
|
|
ListExpr ToListExpr(ListExpr typeInfo) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return listutils::getUndefined();
|
||
|
|
}
|
||
|
|
return nl->ThreeElemList(
|
||
|
|
nl->RealAtom(x),
|
||
|
|
nl->RealAtom(y),
|
||
|
|
nl->RealAtom(r)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// normal stuff
|
||
|
|
|
||
|
|
double getX() const{ return x; }
|
||
|
|
double getY() const{ return y; }
|
||
|
|
double getR() const{ return r; }
|
||
|
|
private:
|
||
|
|
double x;
|
||
|
|
double y;
|
||
|
|
double r;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8.3.4 Creating a type constructor instance
|
||
|
|
|
||
|
|
Because all required functionality
|
||
|
|
is encapsulated within the class, no non-class functions have to be implemented.
|
||
|
|
Just instantiate the ~GenTC~ template class using the class name to define the
|
||
|
|
type constructor instance named ~GCircleTC~.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
GenTC<GCircle> GCircleTC;
|
||
|
|
|
||
|
|
/*
|
||
|
|
9 Attribute Types having Variable Size
|
||
|
|
|
||
|
|
\label{FLOBs}
|
||
|
|
|
||
|
|
Up to now, the defined attribute types have had a fixed size. The problem is
|
||
|
|
how to implement attribute types having variable size although pointers are
|
||
|
|
forbidden. The solution provided in the Secondo system are so called FLOBs (Faked
|
||
|
|
Large Objects) which can be embedded into an attribute data type. Basically a
|
||
|
|
FLOB is a unstructured memory block. For using FLOBs directly see the
|
||
|
|
~BinaryFileAlgebra~ implementation. Mostly, a set of structured data should be
|
||
|
|
part of an attribute. To realize this, Secondo provides a ~DbArray~ implementation
|
||
|
|
derived from the FLOB class. A ~DbArray~ can store an arbitrary number of a structure
|
||
|
|
and offers random access to its elements. Pointers are not allowed to be part
|
||
|
|
of ~DbArray~ elements. If required, use logical pointers (indexes in ~DbArray~s) to
|
||
|
|
realize pointers. ~FLOBs~ and ~DbArrays~ cannot be nested.
|
||
|
|
|
||
|
|
We show the implementation of an integer list as an example. In all constructors
|
||
|
|
except the standard constructor, all ~DBArray~ members have to be initialized. Otherwise
|
||
|
|
Secondo will crash when using it.
|
||
|
|
|
||
|
|
Do not forget the include:
|
||
|
|
|
||
|
|
----
|
||
|
|
#include "../../Tools/Flob/DbArray.h"
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
class IntList: public Attribute{
|
||
|
|
public:
|
||
|
|
IntList() {} // cannot do anything
|
||
|
|
|
||
|
|
IntList(int dummy): // must initialize attribute and the DbArray using
|
||
|
|
// non-standard constructors
|
||
|
|
Attribute(true), content(0) {}
|
||
|
|
|
||
|
|
|
||
|
|
// copy constructor
|
||
|
|
IntList(const IntList & c) : Attribute(c.IsDefined()),
|
||
|
|
content(c.content.Size()){
|
||
|
|
content.copyFrom(c.content);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// assignment operator
|
||
|
|
IntList& operator=(const IntList& src){
|
||
|
|
SetDefined(src.IsDefined());
|
||
|
|
content.copyFrom(src.content);
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
|
||
|
|
// desctructor
|
||
|
|
~IntList(){}
|
||
|
|
|
||
|
|
// auxiliary functions
|
||
|
|
static const string BasicType(){ return "intlist"; }
|
||
|
|
static const bool checkType(const ListExpr list) {
|
||
|
|
return listutils::isSymbol(list, BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
void append(CcInt* i) {
|
||
|
|
if(!i->IsDefined()){
|
||
|
|
SetDefined(false);
|
||
|
|
} else if(IsDefined()){
|
||
|
|
content.Append(i->GetValue());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void append(int i) {
|
||
|
|
content.Append(i);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// NumOfFLOBs
|
||
|
|
// this class has one FLOB in form of a DbArray
|
||
|
|
inline virtual int NumOfFLOBs() const {
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
// return the flob if index is correct
|
||
|
|
inline virtual Flob* GetFLOB( const int i ) {
|
||
|
|
assert(i==0);
|
||
|
|
return &content;
|
||
|
|
}
|
||
|
|
|
||
|
|
// compare, always implement this function
|
||
|
|
// if there is no natural order, just use any
|
||
|
|
// valid order to the objects, in this example,
|
||
|
|
// a lexicographical order is chosen
|
||
|
|
int Compare(const Attribute* arg) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return arg->IsDefined()?-1:0;
|
||
|
|
}
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
IntList* i = (IntList*) arg;
|
||
|
|
// first criterion number of entries
|
||
|
|
if(content.Size() < i->content.Size()){
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
if(content.Size() > i->content.Size()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
for(int k=0;k<content.Size();k++){
|
||
|
|
int i1, i2;
|
||
|
|
content.Get(k,i1);
|
||
|
|
i->content.Get(k,i2);
|
||
|
|
if(i1<i2) return -1;
|
||
|
|
if(i1>i2) return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// there is no meaningful Adjacent implementation
|
||
|
|
bool Adjacent(const Attribute* arg) const{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
// standard implementation of Sizeof
|
||
|
|
size_t Sizeof() const{
|
||
|
|
return sizeof(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
// define a meaningful hash function for
|
||
|
|
// support of hash joins and others
|
||
|
|
size_t HashValue() const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// sum up the first 5 elements
|
||
|
|
int sum = 0;
|
||
|
|
int max = min(5,content.Size());
|
||
|
|
for(int i=0;i<max; i++){
|
||
|
|
int v;
|
||
|
|
content.Get(i,v);
|
||
|
|
sum += v;
|
||
|
|
}
|
||
|
|
return (size_t) sum;
|
||
|
|
}
|
||
|
|
|
||
|
|
void CopyFrom(const Attribute* arg){
|
||
|
|
*this = *((IntList*)arg);
|
||
|
|
}
|
||
|
|
|
||
|
|
Attribute* Clone() const{
|
||
|
|
return new IntList(*this);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// Additionall functions
|
||
|
|
static ListExpr Property(){
|
||
|
|
return gentc::GenProperty("-> DATA", // signature
|
||
|
|
BasicType(), // type description
|
||
|
|
"(int int ...)", // list rep
|
||
|
|
"(1 2 3)"); // example list
|
||
|
|
}
|
||
|
|
|
||
|
|
// Type check function
|
||
|
|
static bool CheckKind(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return checkType(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
// replacement for the IN function
|
||
|
|
bool ReadFrom(ListExpr LE, const ListExpr typeInfo){
|
||
|
|
// handle undefined value
|
||
|
|
if(listutils::isSymbolUndefined(LE)){
|
||
|
|
SetDefined(false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(LE)!=NoAtom){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
SetDefined(true);
|
||
|
|
content.clean();
|
||
|
|
while(!nl->IsEmpty(LE)){
|
||
|
|
ListExpr f = nl->First(LE);
|
||
|
|
LE = nl->Rest(LE);
|
||
|
|
if(nl->AtomType(f)!=IntType){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
append(nl->IntValue(f));
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
// replacement for the out function
|
||
|
|
ListExpr ToListExpr(ListExpr typeInfo) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return listutils::getUndefined();
|
||
|
|
}
|
||
|
|
if(content.Size()==0){
|
||
|
|
return nl->TheEmptyList();
|
||
|
|
}
|
||
|
|
int v;
|
||
|
|
content.Get(0,v);
|
||
|
|
ListExpr res = nl->OneElemList(nl->IntAtom(v));
|
||
|
|
ListExpr last = res;
|
||
|
|
for(int i=1;i<content.Size(); i++){
|
||
|
|
content.Get(i,v);
|
||
|
|
last = nl->Append(last,nl->IntAtom(v));
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
DbArray<int> content;
|
||
|
|
};
|
||
|
|
|
||
|
|
GenTC<IntList> IntListTC;
|
||
|
|
|
||
|
|
/*
|
||
|
|
10 Advanced Type Mappings
|
||
|
|
|
||
|
|
For some operators it is desirable to transfer information
|
||
|
|
computed within the type mapping to the value mapping. The
|
||
|
|
standard case is the index of an attribute within a tuple for
|
||
|
|
a certain attribute name. Another application are default
|
||
|
|
arguments. For this purpose, Secondo provides
|
||
|
|
the so-called APPEND mechanism. In general it works as follows.
|
||
|
|
Instead of returning just the result type within the type mapping,
|
||
|
|
a list of length three of the form (~APPEND args result~)
|
||
|
|
where APPEND is a keyword (symbol), ~args~ is a list containing additional
|
||
|
|
arguments and ~result~ is the normal result type. The content of
|
||
|
|
args is accessible within the value mapping as when the user had given
|
||
|
|
additional arguments directly.
|
||
|
|
|
||
|
|
This mechanism is explained at the ~attrIndex~ operator. This operator
|
||
|
|
gets a stream of tuples and an attribute name. The result of this operator
|
||
|
|
is the index of the attribute with given name in the tuple. The content
|
||
|
|
of the tuple stream remains untouched.
|
||
|
|
|
||
|
|
10.1 Type Mapping
|
||
|
|
|
||
|
|
Here, the APPEND mechanism explained above is used.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr attrIndexTM(ListExpr args){
|
||
|
|
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError("wrong number of arguments");
|
||
|
|
}
|
||
|
|
if(!Stream<Tuple>::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError("first arg is not a tuple stream");
|
||
|
|
}
|
||
|
|
if(nl->AtomType(nl->Second(args))!=SymbolType){
|
||
|
|
return listutils::typeError("second arg is not a valid attribute name");
|
||
|
|
}
|
||
|
|
// extract the attribute list
|
||
|
|
ListExpr attrList = nl->Second(nl->Second(nl->First(args)));
|
||
|
|
ListExpr type;
|
||
|
|
string name = nl->SymbolValue(nl->Second(args));
|
||
|
|
int j = listutils::findAttribute(attrList, name, type);
|
||
|
|
// the append mechanism
|
||
|
|
return nl->ThreeElemList(
|
||
|
|
nl->SymbolAtom(Symbols::APPEND()),
|
||
|
|
nl->OneElemList(nl->IntAtom(j)),
|
||
|
|
listutils::basicSymbol<CcInt>());
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.2 Value Mapping
|
||
|
|
|
||
|
|
Within the value mapping the appended arguments are accessible in the normal way.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int attrIndexVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
CcInt* append = (CcInt*) args[2].addr; // the appended value
|
||
|
|
CcInt* res = (CcInt*) result.addr;
|
||
|
|
int v = append->GetValue();
|
||
|
|
if(v==0){
|
||
|
|
res->SetDefined(false);
|
||
|
|
} else {
|
||
|
|
res->Set(true,v-1);
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.3 Specification
|
||
|
|
|
||
|
|
*/
|
||
|
|
OperatorSpec attrIndexSpec (
|
||
|
|
" stream(tuple(X) ) x symbol -> int " ,
|
||
|
|
" _ attrIndex[ _ ] " ,
|
||
|
|
" Returns the index of the attribute with given name. " ,
|
||
|
|
" query plz feed attrIndex[Ort] "
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.4 Operator Instance
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Operator attrIndexOp (
|
||
|
|
"attrIndex" , // name of the operator
|
||
|
|
attrIndexSpec.getStr() , // specification
|
||
|
|
attrIndexVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
attrIndexTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
11 Implementing Large Structures
|
||
|
|
|
||
|
|
\label{largeStructures}
|
||
|
|
|
||
|
|
Up to now, all implemented data types within this algebra were more or less small.
|
||
|
|
This section deals with the implementation of real big data types. This means, this
|
||
|
|
type can massive exceed the main memory of the underlying system if it would be
|
||
|
|
completely loaded.
|
||
|
|
|
||
|
|
To solve this problem, only small parts of the whole structure are in memory,
|
||
|
|
the main part is located on disc. Using files directly would violate the ACID
|
||
|
|
properties of a database system because there is no synchronization if several
|
||
|
|
users work in parallel. For this reason, file structures
|
||
|
|
provided by the Secondo-SMI have to be used for such types. The SecondoSMI provides several
|
||
|
|
file types, ~RecordFiles~, ~HashFiles~, and ~Queues~. The used example will use the
|
||
|
|
~RecordFile~ class. A ~RecordFile~ stores records within a file where each record is assigned
|
||
|
|
to a unique id (the ~RecordId~). Depending on the used constructor, records within a file may have
|
||
|
|
fixed or variable size.
|
||
|
|
|
||
|
|
We explain the usage of RecordFiles at the example of an AVL-tree, a balanced version of a
|
||
|
|
binary searchtree. Because pointers are not possible within persistent structures, pointers are
|
||
|
|
simulated by ~RecordID~s. For simplicity, we allow to store only integer values within the
|
||
|
|
tree.
|
||
|
|
|
||
|
|
11.1 Node Class
|
||
|
|
|
||
|
|
A node of an AVL-tree consists of its content, pointers to the two sons and a value denoting
|
||
|
|
the height which is used for balancing these nodes. Each node corresponds to a record
|
||
|
|
within the file representing the tree. As mentioned above, we simulate
|
||
|
|
pointers by ~RecordId~s. For technical reasons, each node contains also its own ~RecordId~.
|
||
|
|
This makes it easier to update the node after changes.
|
||
|
|
The ~Node~ class provides methods for reading its value from a record
|
||
|
|
and writing to a record.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
class Node{
|
||
|
|
public:
|
||
|
|
typedef uint16_t htype;
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.1.1 Constructor
|
||
|
|
|
||
|
|
This constructor is used to create a new node not assigned to a record.
|
||
|
|
The node will have no childs (represented by the record id 0.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Node(int v): value(v), left(0), right(0),height(1),myId(0){}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.1.2 Constructor
|
||
|
|
|
||
|
|
This constructor creates the main memory representation of a node
|
||
|
|
from a Record.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Node(SmiRecord& r){
|
||
|
|
myId = r.GetId();
|
||
|
|
size_t offset = 0;
|
||
|
|
r.Read(&value,sizeof(CcInt::inttype),offset);
|
||
|
|
offset += sizeof(CcInt::inttype);
|
||
|
|
r.Read(&left,sizeof(SmiRecordId),offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
r.Read(&right,sizeof(SmiRecordId),offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
r.Read(&height,sizeof(htype),offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
// copy constructor
|
||
|
|
Node(const Node& n):
|
||
|
|
value(n.value), left(n.left), right(n.right),
|
||
|
|
height(n.height),myId(n.myId) {}
|
||
|
|
|
||
|
|
// assignment operator
|
||
|
|
Node& operator=(const Node& n){
|
||
|
|
value = n.value;
|
||
|
|
left = n.left;
|
||
|
|
right = n.right;
|
||
|
|
height = n.height;
|
||
|
|
myId = n.myId;
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
12.1.3 ~getStorageSize~
|
||
|
|
|
||
|
|
This function computes how many space a single node will require on disc.
|
||
|
|
|
||
|
|
*/
|
||
|
|
static size_t getStorageSize(){
|
||
|
|
return sizeof(CcInt::inttype) + 2*sizeof(SmiRecordId)
|
||
|
|
+ sizeof(htype);
|
||
|
|
}
|
||
|
|
|
||
|
|
// getter and setter methods
|
||
|
|
CcInt::inttype getValue()const { return value; }
|
||
|
|
htype getHeight() const { return height; }
|
||
|
|
SmiRecordId getLeft() const{ return left; }
|
||
|
|
SmiRecordId getRight() const { return right; }
|
||
|
|
SmiRecordId getID() const{ return myId; }
|
||
|
|
void setId(SmiRecordId& id) { myId = id; }
|
||
|
|
void setLeft(const SmiRecordId id) { left = id; }
|
||
|
|
void setRight(const SmiRecordId id) { right = id; }
|
||
|
|
void setHeight(const htype h){ height = h; }
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.1.4 ~append~
|
||
|
|
|
||
|
|
This functions appends a node which is not already part of a
|
||
|
|
record file to a record file. The id of the node will be changed
|
||
|
|
to the used record id. This id is also the return value of this
|
||
|
|
method.
|
||
|
|
|
||
|
|
*/
|
||
|
|
SmiRecordId append(SmiRecordFile& file){
|
||
|
|
// allow only non-written node to be appended
|
||
|
|
assert(myId == 0);
|
||
|
|
SmiRecord record;
|
||
|
|
SmiRecordId id;
|
||
|
|
file.AppendRecord(id, record);
|
||
|
|
myId = id;
|
||
|
|
assert(id!=0);
|
||
|
|
writeToRecord(record);
|
||
|
|
record.Finish();
|
||
|
|
return id;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.1.5 ~update~
|
||
|
|
|
||
|
|
Overwrites the data stored on disc by the current values of this node.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void update(SmiRecordFile& file) const{
|
||
|
|
assert(myId != 0);
|
||
|
|
SmiRecord record;
|
||
|
|
file.SelectRecord(myId, record,SmiFile::Update);
|
||
|
|
writeToRecord(record);
|
||
|
|
record.Finish();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private:
|
||
|
|
CcInt::inttype value;
|
||
|
|
SmiRecordId left;
|
||
|
|
SmiRecordId right;
|
||
|
|
htype height;
|
||
|
|
SmiRecordId myId;
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.1.6 ~writeToRecord~
|
||
|
|
|
||
|
|
This function writes the current values of this node to the given record.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void writeToRecord(SmiRecord& r)const{
|
||
|
|
size_t offset = 0;
|
||
|
|
r.Write(&value,sizeof(CcInt::inttype),offset);
|
||
|
|
offset += sizeof(CcInt::inttype);
|
||
|
|
r.Write(&left,sizeof(SmiRecordId),offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
r.Write(&right,sizeof(SmiRecordId),offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
r.Write(&height,sizeof(htype),offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2 Tree Class
|
||
|
|
|
||
|
|
The class tree contains the file and the ~RecordID~ of the root node.
|
||
|
|
If the tree is empty, this id is 0.
|
||
|
|
To be able to store also undefined ~CcInt~ values, a flag is used whether
|
||
|
|
a undefined value is part of the tree or not. Additionally, the number of
|
||
|
|
entries is stored.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class PAVLTree{
|
||
|
|
public:
|
||
|
|
/*
|
||
|
|
11.2.1 Constructors
|
||
|
|
|
||
|
|
The ~PAVLTree~ class provides two constructors. The first one creates a new
|
||
|
|
~PAVLTree~ including the record file storing the tree. The tree will be
|
||
|
|
empty. The second variant is used to open an existing ~PAVLTree~. Details
|
||
|
|
of these constructors can be found at their implementations.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
PAVLTree();
|
||
|
|
|
||
|
|
PAVLTree(SmiFileId fileId,
|
||
|
|
SmiRecordId rootId,
|
||
|
|
bool containsUndef,
|
||
|
|
size_t noEntries);
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.2 Destructor
|
||
|
|
|
||
|
|
Each opened file must be closed at the end of a Secondo session. Otherwise the
|
||
|
|
database directory may be corrupt. Hence is is strongly required to close the
|
||
|
|
file in the destructor.
|
||
|
|
|
||
|
|
*/
|
||
|
|
~PAVLTree(){
|
||
|
|
if(file.IsOpen()){
|
||
|
|
file.Close();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
The usual ~BasicType~ function.
|
||
|
|
|
||
|
|
*/
|
||
|
|
static string BasicType(){
|
||
|
|
return "pavl";
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
The ususal ~checkType~ function.
|
||
|
|
|
||
|
|
*/
|
||
|
|
static bool checkType(ListExpr args){
|
||
|
|
return nl->IsEqual(args,BasicType());
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Declarations of functions for inserting elements and test
|
||
|
|
for containtness.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool insert(CcInt* value);
|
||
|
|
|
||
|
|
bool contains(CcInt* value);
|
||
|
|
|
||
|
|
/*
|
||
|
|
If a database object is deleted, all corresponding files must also be
|
||
|
|
removed from disc. The deletion of the file is done in the next function.
|
||
|
|
Before a file can be removed from disc (using the ~Drop~ function), it must
|
||
|
|
be closed.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void deleteFile(){
|
||
|
|
if(file.IsOpen()){
|
||
|
|
file.Close();
|
||
|
|
}
|
||
|
|
file.Drop();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Functions for converting into and from nested lists.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool readFrom(ListExpr args);
|
||
|
|
ListExpr toListExpr();
|
||
|
|
|
||
|
|
/*
|
||
|
|
The clean function removes all elements from a tree.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void clean();
|
||
|
|
|
||
|
|
/*
|
||
|
|
Some Getter functions.
|
||
|
|
|
||
|
|
*/
|
||
|
|
SmiFileId getFileID() { return file.GetFileId() ; }
|
||
|
|
SmiRecordId getRootID() const { return rootId; }
|
||
|
|
bool getContainsUndef() const { return containsUndef; }
|
||
|
|
size_t getNoEntries() const { return noEntries; }
|
||
|
|
|
||
|
|
// clone function
|
||
|
|
PAVLTree* clone();
|
||
|
|
|
||
|
|
private:
|
||
|
|
SmiRecordFile file;
|
||
|
|
SmiRecordId rootId;
|
||
|
|
bool containsUndef;
|
||
|
|
size_t noEntries;
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Auxiliary functions
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool readFrom(ListExpr args, SmiRecordId& r, int& min, int& max);
|
||
|
|
|
||
|
|
ListExpr toListExpr(SmiRecordId root);
|
||
|
|
|
||
|
|
SmiRecordId clone( SmiRecordId root, PAVLTree* res);
|
||
|
|
|
||
|
|
Node getNode(SmiRecordId id);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
// returns the height of the subtree specified by id
|
||
|
|
Node::htype getHeight(SmiRecordId id){
|
||
|
|
if(id==0) return 0;
|
||
|
|
return getNode(id).getHeight();
|
||
|
|
}
|
||
|
|
|
||
|
|
// computes the balance value for given heights
|
||
|
|
static int balanceVal( const Node::htype left, Node::htype right){
|
||
|
|
if(left > right){
|
||
|
|
return left - right;
|
||
|
|
} else {
|
||
|
|
return -1 * ((int) ( right - left));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// returns the balance value for a specified node
|
||
|
|
int balanceVal(const Node& n){
|
||
|
|
return balanceVal(getHeight(n.getLeft()), getHeight(n.getRight()));
|
||
|
|
}
|
||
|
|
|
||
|
|
// balances an unbalanced subtree
|
||
|
|
void balance(Node& badNode, stack<Node>& parents);
|
||
|
|
|
||
|
|
// recomputes the height of a node and writes the changed node to file
|
||
|
|
void updateHeight(Node n){
|
||
|
|
Node::htype h1 = getHeight(n.getLeft());
|
||
|
|
Node::htype h2 = getHeight(n.getRight());
|
||
|
|
n.setHeight( max(h1,h2) + 1);
|
||
|
|
n.update(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
// sets a new root for the subtree at the top of
|
||
|
|
// predecessors. If the stack is empty, the root of the entire
|
||
|
|
// tree is changed. Additionally, the heights of all nodes
|
||
|
|
// within predecessors are updated.
|
||
|
|
void setRoot(Node& newRoot, stack<Node>& predecessors){
|
||
|
|
// special case: new root of the entire tree
|
||
|
|
if(predecessors.empty()){
|
||
|
|
rootId = newRoot.getID();
|
||
|
|
updateHeight(getNode(rootId));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Node f = predecessors.top();
|
||
|
|
predecessors.pop();
|
||
|
|
if(newRoot.getValue() < f.getValue()){
|
||
|
|
f.setLeft(newRoot.getID());
|
||
|
|
} else {
|
||
|
|
f.setRight(newRoot.getID());
|
||
|
|
}
|
||
|
|
updateHeight(f);
|
||
|
|
while(!predecessors.empty()){
|
||
|
|
Node p = predecessors.top();
|
||
|
|
predecessors.pop();
|
||
|
|
updateHeight(p);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}; // end of class PAVLTree
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.1 First constructor
|
||
|
|
|
||
|
|
This constructor creates a new empty avl-tree. The file containing the
|
||
|
|
nodes of the tree must be created. Because each node has a fixed size,
|
||
|
|
we can use a ~SmiRecordFile~ using fixed size records. The size
|
||
|
|
is requested from the ~Node~ class.
|
||
|
|
|
||
|
|
*/
|
||
|
|
PAVLTree::PAVLTree():
|
||
|
|
file(true, Node::getStorageSize()),
|
||
|
|
rootId(0), containsUndef(false), noEntries(0){
|
||
|
|
file.Create();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.2 Second constructor
|
||
|
|
|
||
|
|
This constructor is used for opening an existing tree.
|
||
|
|
|
||
|
|
*/
|
||
|
|
PAVLTree::PAVLTree(SmiFileId fileID,
|
||
|
|
SmiRecordId _rootId,
|
||
|
|
bool _containsUndef,
|
||
|
|
size_t _noEntries):
|
||
|
|
file(true), rootId(_rootId), noEntries(_noEntries) {
|
||
|
|
file.Open(fileID);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.3 Clone
|
||
|
|
|
||
|
|
This function creates a depth clone of the tree.
|
||
|
|
|
||
|
|
*/
|
||
|
|
PAVLTree* PAVLTree::clone(){
|
||
|
|
PAVLTree* res = new PAVLTree();
|
||
|
|
res->containsUndef = containsUndef;
|
||
|
|
res->noEntries = noEntries;
|
||
|
|
// call recursive clone function
|
||
|
|
res->rootId = clone(rootId, res);
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
This functions clones a subtree given by root.
|
||
|
|
The function returns the ~RecordId~ of the newly created root
|
||
|
|
in ~res~.
|
||
|
|
|
||
|
|
*/
|
||
|
|
SmiRecordId PAVLTree::clone(SmiRecordId root, PAVLTree* res){
|
||
|
|
if(root == 0){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
Node n = getNode(root);
|
||
|
|
n.setLeft( clone(n.getLeft(),res));
|
||
|
|
n.setRight( clone(n.getRight(),res));
|
||
|
|
return n.append(res->file);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.4 getNode
|
||
|
|
|
||
|
|
This function creates the main memory representation of a node
|
||
|
|
stored within a certain record.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Node PAVLTree::getNode(SmiRecordId id){
|
||
|
|
assert(id!=0);
|
||
|
|
SmiRecord record;
|
||
|
|
file.SelectRecord(id,record);
|
||
|
|
Node res(record);
|
||
|
|
record.Finish();
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.5 Insert function
|
||
|
|
|
||
|
|
This function inserts a new value into the tree. Whereas undefined values are
|
||
|
|
handled separately, normal values are inserted into the file. The return value
|
||
|
|
corresponds to the success of this operation, i.e. whether the value was
|
||
|
|
not already part of the tree.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool PAVLTree::insert(CcInt* value){
|
||
|
|
if(!value->IsDefined()){
|
||
|
|
bool res = !containsUndef;
|
||
|
|
containsUndef=true;
|
||
|
|
if(res){ noEntries++;}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
CcInt::inttype v = value->GetValue();
|
||
|
|
// create a new node to be inserted
|
||
|
|
Node n(v);
|
||
|
|
// special case: empty tree
|
||
|
|
if(rootId==0){
|
||
|
|
rootId = n.append(file);
|
||
|
|
noEntries = 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
// search insertion position storing the path into a stack
|
||
|
|
stack<Node> s;
|
||
|
|
Node cn = getNode(rootId);
|
||
|
|
if(cn.getValue()==v){ // value already exists
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
SmiRecordId son = cn.getValue()>v?cn.getLeft():cn.getRight();
|
||
|
|
while(son != 0){
|
||
|
|
s.push(cn);
|
||
|
|
cn = getNode(son);
|
||
|
|
if(cn.getValue()==v){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
son = cn.getValue()>v?cn.getLeft():cn.getRight();
|
||
|
|
}
|
||
|
|
|
||
|
|
// found insertion position, append record
|
||
|
|
SmiRecordId id = n.append(file);
|
||
|
|
if(cn.getValue()>v){
|
||
|
|
cn.setLeft(id);
|
||
|
|
} else {
|
||
|
|
cn.setRight(id);
|
||
|
|
}
|
||
|
|
cn.update(file);
|
||
|
|
s.push(cn);
|
||
|
|
// now, we have to correct the heights of the predecessors
|
||
|
|
while(!s.empty()){
|
||
|
|
Node parent = s.top();
|
||
|
|
s.pop();
|
||
|
|
Node::htype leftHeight = getHeight(parent.getLeft());
|
||
|
|
Node::htype rightHeight = getHeight(parent.getRight());
|
||
|
|
if(abs(balanceVal(leftHeight, rightHeight)) > 1){
|
||
|
|
// correct unbalanced node
|
||
|
|
balance(parent,s);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
parent.setHeight(max(leftHeight,rightHeight)+1);
|
||
|
|
parent.update(file);
|
||
|
|
}
|
||
|
|
noEntries++;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2.6 Balance of an unbalanced subtree
|
||
|
|
|
||
|
|
*/
|
||
|
|
void PAVLTree::balance( Node& root, stack<Node>& predecessors){
|
||
|
|
if(balanceVal(root) == -2){ // right subtree heigher
|
||
|
|
Node r = getNode(root.getRight());
|
||
|
|
if(balanceVal(r) == -1){ // left rotation
|
||
|
|
Node y = r;
|
||
|
|
SmiRecordId b = y.getLeft();
|
||
|
|
root.setRight(b);
|
||
|
|
updateHeight(root);
|
||
|
|
y.setLeft(root.getID());
|
||
|
|
updateHeight(y);
|
||
|
|
setRoot(y,predecessors);
|
||
|
|
return;
|
||
|
|
} else { // right left rotation
|
||
|
|
Node x = root;
|
||
|
|
Node z = getNode(x.getRight());
|
||
|
|
Node y = getNode(z.getLeft());
|
||
|
|
SmiRecordId B1 = y.getLeft();
|
||
|
|
SmiRecordId B2 = y.getRight();
|
||
|
|
x.setRight(B1);;
|
||
|
|
updateHeight(x);
|
||
|
|
z.setLeft(B2);
|
||
|
|
updateHeight(z);
|
||
|
|
y.setLeft(x.getID());
|
||
|
|
y.setRight(z.getID());
|
||
|
|
updateHeight(y);
|
||
|
|
setRoot(y,predecessors);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else if(balanceVal(root)==2){ // left subtree is heigher
|
||
|
|
Node left = getNode(root.getLeft());
|
||
|
|
if(balanceVal(left) == 1){ // right rotation
|
||
|
|
Node y = getNode(root.getLeft());
|
||
|
|
SmiRecordId B = y.getRight();
|
||
|
|
SmiRecordId C = root.getRight();
|
||
|
|
root.setLeft(B);
|
||
|
|
root.setRight(C);
|
||
|
|
updateHeight(root);
|
||
|
|
y.setRight(root.getID());
|
||
|
|
updateHeight(y);
|
||
|
|
setRoot(y,predecessors);
|
||
|
|
return;
|
||
|
|
} else { // leftRightRotation
|
||
|
|
Node x = root;
|
||
|
|
Node z = getNode(root.getLeft());;
|
||
|
|
Node y = getNode(z.getRight());
|
||
|
|
SmiRecordId A = z.getLeft();
|
||
|
|
SmiRecordId B = y.getLeft();
|
||
|
|
SmiRecordId C = y.getRight();
|
||
|
|
z.setLeft(A);
|
||
|
|
z.setRight(B);
|
||
|
|
updateHeight(z);
|
||
|
|
x.setLeft(C);
|
||
|
|
updateHeight(x);
|
||
|
|
y.setLeft(z.getID());
|
||
|
|
y.setRight(x.getID());
|
||
|
|
updateHeight(y);
|
||
|
|
setRoot(y,predecessors);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
assert(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
11.3.4 ~contains~
|
||
|
|
|
||
|
|
This function checks whether a value is part of the tree.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
bool PAVLTree::contains(CcInt* value){
|
||
|
|
|
||
|
|
if(!value->IsDefined()){
|
||
|
|
return containsUndef;
|
||
|
|
}
|
||
|
|
SmiRecordId son = rootId;
|
||
|
|
int v = value->GetValue();
|
||
|
|
while(son){
|
||
|
|
Node n = getNode(son);
|
||
|
|
if(n.getValue()== v){
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
son = n.getValue()>v?n.getLeft():n.getRight();
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
void PAVLTree::clean(){
|
||
|
|
file.Truncate();
|
||
|
|
rootId = 0;
|
||
|
|
noEntries = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.2 ~readFrom~
|
||
|
|
|
||
|
|
This function reads a nested list and stores the tree represented by the
|
||
|
|
list into the file. The id of the subtree's root is returned in the
|
||
|
|
output parameter ~rootId~. Furthermore, the minimum and the maximum value
|
||
|
|
of the tree are returned in the appropriate output parameters. If the list
|
||
|
|
represents a valid AVL-tree, the return value of this function is true, false
|
||
|
|
otherwise.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool PAVLTree::readFrom(ListExpr list, SmiRecordId& rootId,
|
||
|
|
int& min, int& max){
|
||
|
|
if(nl->AtomType(list)!=NoAtom){
|
||
|
|
cmsg.inFunError("wrong list format");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if(nl->IsEmpty(list)){
|
||
|
|
rootId=0;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!nl->HasLength(list,3)){
|
||
|
|
cmsg.inFunError("wrong list format");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(nl->First(list))!=IntType ||
|
||
|
|
nl->AtomType(nl->Second(list))!=NoAtom ||
|
||
|
|
nl->AtomType(nl->Third(list))!=NoAtom){
|
||
|
|
clean();
|
||
|
|
cmsg.inFunError("wrong list format");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
int v = nl->IntValue(nl->First(list));
|
||
|
|
|
||
|
|
min = v;
|
||
|
|
max = v;
|
||
|
|
int min1 = v;
|
||
|
|
|
||
|
|
|
||
|
|
SmiRecordId id1;
|
||
|
|
if(!readFrom(nl->Second(list),id1,min,max)){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if(id1){
|
||
|
|
if(max>v){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
min1 = min;
|
||
|
|
}
|
||
|
|
SmiRecordId id2;
|
||
|
|
if(!readFrom(nl->Third(list),id2,min,max)){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if(id2){
|
||
|
|
if(min<v){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
min = min1;
|
||
|
|
Node n(nl->IntValue(nl->First(list)));
|
||
|
|
n.setLeft(id1);
|
||
|
|
n.setRight(id2);
|
||
|
|
Node::htype h1 = getHeight(n.getLeft());
|
||
|
|
Node::htype h2 = getHeight(n.getRight());
|
||
|
|
n.setHeight(std::max(h1,h2) + 1);
|
||
|
|
if(abs(balanceVal(h1,h2))>1){
|
||
|
|
cmsg.inFunError("unbalanced tree found");
|
||
|
|
clean();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
rootId = n.append(file);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.3 ~readFrom~
|
||
|
|
|
||
|
|
This function converts a nested list into a tree. The return value
|
||
|
|
is ~true~ if the list represents a valid AVL tree, ~false~ otherwise.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
bool PAVLTree::readFrom(ListExpr list){
|
||
|
|
if(rootId){
|
||
|
|
clean();
|
||
|
|
}
|
||
|
|
if(!nl->HasLength(list,2)){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(nl->First(list))!=BoolType){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
containsUndef = nl->BoolValue(nl->First(list));
|
||
|
|
list = nl->Second(list);
|
||
|
|
int min, max;
|
||
|
|
if(!readFrom(list,rootId,min,max)){
|
||
|
|
clean();
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.4 toListExpr
|
||
|
|
|
||
|
|
This function converts the tree into a list.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr PAVLTree::toListExpr(){
|
||
|
|
|
||
|
|
return nl->TwoElemList(nl->BoolAtom(containsUndef),
|
||
|
|
toListExpr(rootId));
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
ListExpr PAVLTree::toListExpr(SmiRecordId root){
|
||
|
|
if(root==0){
|
||
|
|
return nl->TheEmptyList();
|
||
|
|
}
|
||
|
|
Node n = getNode(root);
|
||
|
|
return nl->ThreeElemList(
|
||
|
|
nl->IntAtom(n.getValue()),
|
||
|
|
toListExpr(n.getLeft()),
|
||
|
|
toListExpr(n.getRight()));
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3 Secondo Supporting Functions
|
||
|
|
|
||
|
|
11.3.1 Property function
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr PAVLProperty(){
|
||
|
|
return ( nl->TwoElemList (
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom("Signature"),
|
||
|
|
nl->StringAtom("Example Type List"),
|
||
|
|
nl->StringAtom("List Rep"),
|
||
|
|
nl->StringAtom("Example List")),
|
||
|
|
nl->FourElemList (
|
||
|
|
nl->StringAtom("-> SIMPLE"),
|
||
|
|
nl->StringAtom(PAVLTree::BasicType()),
|
||
|
|
nl->StringAtom("(value left right)"),
|
||
|
|
nl->StringAtom("(5 (3 () 4) (7)) ")
|
||
|
|
)));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.2 In-function
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Word InPAVL(const ListExpr typeInfo, const ListExpr instance,
|
||
|
|
const int errorPos, ListExpr & errorInfo, bool& correct){
|
||
|
|
|
||
|
|
PAVLTree* tree = new PAVLTree();
|
||
|
|
assert(!tree->getRootID());
|
||
|
|
if(!tree->readFrom(instance)){
|
||
|
|
tree->deleteFile();
|
||
|
|
delete tree;
|
||
|
|
correct = false;
|
||
|
|
return Word((void*)0);
|
||
|
|
} else {
|
||
|
|
correct = true;
|
||
|
|
return Word(tree);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.3 Out function
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr OutPAVL(ListExpr typeInfo, Word value){
|
||
|
|
return ((PAVLTree*)value.addr)->toListExpr();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.4 Create
|
||
|
|
|
||
|
|
*/
|
||
|
|
Word CreatePAVL(const ListExpr typeInfo){
|
||
|
|
return Word(new PAVLTree);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.5 Delete
|
||
|
|
|
||
|
|
Here the complete object instance is removed inclusive
|
||
|
|
the file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void DeletePAVL(const ListExpr typeInfo, Word& w){
|
||
|
|
PAVLTree* t = (PAVLTree*) w.addr;
|
||
|
|
t->deleteFile();
|
||
|
|
delete t;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.6 OPEN
|
||
|
|
|
||
|
|
Within the open function, we extract all required information
|
||
|
|
from the record to build the main structure of the tree. The
|
||
|
|
tree itself is keept in the file without loading it into main
|
||
|
|
memory.
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool OpenPAVL ( SmiRecord & valueRecord ,
|
||
|
|
size_t & offset , const ListExpr typeInfo ,
|
||
|
|
Word & value ){
|
||
|
|
SmiFileId fileId;
|
||
|
|
SmiRecordId rootId;
|
||
|
|
bool containsUndef;
|
||
|
|
size_t noEntries;
|
||
|
|
bool ok = valueRecord.Read(&fileId, sizeof(SmiFileId),offset);
|
||
|
|
offset += sizeof(SmiFileId);
|
||
|
|
ok = ok && valueRecord.Read(&rootId, sizeof(SmiRecordId), offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
ok = ok && valueRecord.Read(&containsUndef,sizeof(bool),offset);
|
||
|
|
offset +=sizeof(bool);
|
||
|
|
ok = ok && valueRecord.Read(&noEntries,sizeof(size_t), offset);
|
||
|
|
offset += sizeof(size_t);
|
||
|
|
if(ok){
|
||
|
|
value.addr = new PAVLTree(fileId, rootId, containsUndef,noEntries);
|
||
|
|
} else {
|
||
|
|
value.addr = 0;
|
||
|
|
}
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.7 SAVE
|
||
|
|
|
||
|
|
The save function stores the main information into the record.
|
||
|
|
The tree's file is not required (except its id).
|
||
|
|
|
||
|
|
*/
|
||
|
|
bool SavePAVL( SmiRecord & valueRecord , size_t & offset ,
|
||
|
|
const ListExpr typeInfo , Word & value ) {
|
||
|
|
PAVLTree* t = (PAVLTree*) value.addr;
|
||
|
|
SmiFileId fileId = t->getFileID();
|
||
|
|
SmiRecordId rootId = t->getRootID();
|
||
|
|
bool containsUndef = t->getContainsUndef();;
|
||
|
|
size_t noEntries = t->getNoEntries();
|
||
|
|
bool ok = valueRecord.Write(&fileId, sizeof(SmiFileId), offset);
|
||
|
|
offset += sizeof(SmiFileId);
|
||
|
|
ok = ok && valueRecord.Write(&rootId, sizeof(SmiRecordId), offset);
|
||
|
|
offset += sizeof(SmiRecordId);
|
||
|
|
ok = ok && valueRecord.Write(&containsUndef, sizeof(bool), offset);
|
||
|
|
offset += sizeof(bool);
|
||
|
|
ok = ok && valueRecord.Write(&noEntries, sizeof(size_t), offset);
|
||
|
|
offset += sizeof(size_t);
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.8 CLOSE
|
||
|
|
|
||
|
|
Here, the disc part remains untouched.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
void ClosePAVL ( const ListExpr typeInfo , Word & w ) {
|
||
|
|
PAVLTree* t = (PAVLTree*) w.addr;
|
||
|
|
delete t;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.9 Clone
|
||
|
|
|
||
|
|
*/
|
||
|
|
Word ClonePAVL ( const ListExpr typeInfo , const Word & w ){
|
||
|
|
PAVLTree* t = (PAVLTree*) w.addr;
|
||
|
|
return Word(t->clone());
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.3.10 Cast
|
||
|
|
|
||
|
|
*/
|
||
|
|
void * CastPAVL ( void * addr ) {
|
||
|
|
return addr;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
bool PAVLTypeCheck ( ListExpr type , ListExpr & errorInfo ){
|
||
|
|
return nl->IsEqual ( type , PAVLTree::BasicType ());
|
||
|
|
}
|
||
|
|
|
||
|
|
int SizeOfPAVL () {
|
||
|
|
return sizeof(SmiFileId) + sizeof(SmiRecordId)
|
||
|
|
+ sizeof(bool) + sizeof(size_t);;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
TypeConstructor PAVLTC (
|
||
|
|
PAVLTree::BasicType (), // name of the type
|
||
|
|
PAVLProperty , // property function
|
||
|
|
OutPAVL , InPAVL , // out and in function
|
||
|
|
0, 0, // deprecated , don not think about it
|
||
|
|
CreatePAVL , DeletePAVL , // creation and deletion
|
||
|
|
OpenPAVL , SavePAVL , // open and save functions
|
||
|
|
ClosePAVL , ClonePAVL , // close and clone functions
|
||
|
|
CastPAVL , // cast function
|
||
|
|
SizeOfPAVL , // sizeOf function
|
||
|
|
PAVLTypeCheck );
|
||
|
|
// type checking functi
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.4 Operators Creating a PAVL Tree
|
||
|
|
|
||
|
|
11.4.1 Creation of a pavltree
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr createPAVLTM(ListExpr args){
|
||
|
|
string err = "stream(int) expected";
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
if(!Stream<CcInt>::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
return listutils::basicSymbol<PAVLTree>();
|
||
|
|
}
|
||
|
|
|
||
|
|
int createPAVLVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
PAVLTree* res = (PAVLTree*) result.addr;
|
||
|
|
Stream<CcInt> stream(args[0]);
|
||
|
|
stream.open();
|
||
|
|
CcInt* elem;
|
||
|
|
while( (elem = stream.request()) != 0){
|
||
|
|
res->insert(elem);
|
||
|
|
elem->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
stream.close();
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
OperatorSpec createPAVLSpec (
|
||
|
|
" stream(int) -> pavl " ,
|
||
|
|
" _ createPAVL " ,
|
||
|
|
" creates an avl-tree from an integer stream " ,
|
||
|
|
" query intstream(1,100) createPAVL "
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
Operator createPAVLOp (
|
||
|
|
"createPAVL" , // name of the operator
|
||
|
|
createPAVLSpec.getStr() , // specification
|
||
|
|
createPAVLVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
createPAVLTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.4.2 Checking for containtness
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
ListExpr containsTM(ListExpr args){
|
||
|
|
string err = "pavl x int expected";
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
if(!PAVLTree::checkType(nl->First(args))
|
||
|
|
|| !CcInt::checkType(nl->Second(args))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
return listutils::basicSymbol<CcBool>();
|
||
|
|
}
|
||
|
|
|
||
|
|
int containsVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
CcBool* res = (CcBool*) result.addr;
|
||
|
|
PAVLTree* a1 = (PAVLTree*) args[0].addr;
|
||
|
|
CcInt* a2 = (CcInt*) args[1].addr;
|
||
|
|
res->Set(true, a1->contains(a2));
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
OperatorSpec containsSpec (
|
||
|
|
" -> pavl x int -> bool" ,
|
||
|
|
" _ contains _ " ,
|
||
|
|
" Checks whether an avl tree contains an int " ,
|
||
|
|
" query p1 contains 23 "
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
Operator containsOp (
|
||
|
|
"contains" , // name of the operator
|
||
|
|
containsSpec.getStr() , // specification
|
||
|
|
containsVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
containsTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
12 Update Operators
|
||
|
|
|
||
|
|
The update command in Secondo replaces a stored object
|
||
|
|
completely by another one. Sometimes it is required to modify
|
||
|
|
an existing object, e.g. for inserting new tuples into an
|
||
|
|
existing relation.
|
||
|
|
To realize that, update operators have to be written. Such operators
|
||
|
|
manipulate their arguments, which is quite unsual for Secondo
|
||
|
|
operators. To make the changes persistent, the manipulated argument
|
||
|
|
must be marked. otherwise the changes are not written back to the disc.
|
||
|
|
|
||
|
|
The implementation of update operators is explained at the example of
|
||
|
|
an ~insert~ operator inserting new elementes into an existing avl-tree.
|
||
|
|
|
||
|
|
12.1 Type Mapping
|
||
|
|
|
||
|
|
The type mapping of an update operator has no special features.
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr insertTM(ListExpr args){
|
||
|
|
string err = "stream(int) x pavl expected";
|
||
|
|
if(!nl->HasLength(args,2)){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
if( !Stream<CcInt>::checkType(nl->First(args))
|
||
|
|
|| !PAVLTree::checkType(nl->Second(args))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
// the number of new elements is returned
|
||
|
|
return listutils::basicSymbol<CcInt>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
12.2 Value Mapping
|
||
|
|
|
||
|
|
Within the value mapping the tree argument is manipulated.
|
||
|
|
At the end of this operator, this argument is marked as
|
||
|
|
modified. For stream operators, this marking can be done
|
||
|
|
within the ~CLOSE~ section. In non-stream operators like here,
|
||
|
|
this is done somewhere in the value mapping implementation.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
int insertVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
CcInt* res = (CcInt*) result.addr;
|
||
|
|
Stream<CcInt> stream(args[0]);
|
||
|
|
PAVLTree* avl = (PAVLTree*) args[1].addr;
|
||
|
|
int count = 0;
|
||
|
|
stream.open();
|
||
|
|
CcInt* elem;
|
||
|
|
while( (elem=stream.request())!=0){
|
||
|
|
if(avl->insert(elem)){
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
elem->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
stream.close();
|
||
|
|
// mark the argument as modified
|
||
|
|
qp->SetModified(qp->GetSon(s, 1));
|
||
|
|
res->Set(true,count);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
12.3 Specification and Operator Instance
|
||
|
|
|
||
|
|
Here, no specials must be considered.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
OperatorSpec insertSpec (
|
||
|
|
" stream(int) x pavl -> int " ,
|
||
|
|
" _ _ insert " ,
|
||
|
|
" inserts new elements into an exixisting avl tree" ,
|
||
|
|
" query instream (1,200) p1 insert "
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
Operator insertOp (
|
||
|
|
"insert" , // name of the operator
|
||
|
|
insertSpec.getStr() , // specification
|
||
|
|
insertVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
insertTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
9 Accessing values in Type Mappings
|
||
|
|
|
||
|
|
Sometimes it is necessary to know a value of an object for computing the result type.
|
||
|
|
An example is an import operator reading some object from a file. If the file contains
|
||
|
|
the type, the filename (a string or a text) must be known in the type mapping.
|
||
|
|
Secondo provides a possibility to get not only the types in the argument list of
|
||
|
|
the type mapping but additionally the part of the query forming this type.
|
||
|
|
To enable this feature for an operator, the function ~SetUsesArgsInTypeMapping()~ must
|
||
|
|
be called for this operator. This happens within the Algebra constructor.
|
||
|
|
|
||
|
|
This feature is explained at the example of the ~importObject~ operator. This operator
|
||
|
|
gets a filename and returns the object located in the file. The object is coded
|
||
|
|
in the way like ~save object to ...~ it does.
|
||
|
|
|
||
|
|
9.1 Type Mapping
|
||
|
|
|
||
|
|
If the argument feature is enabled for an operator, each argument is not longer only
|
||
|
|
described by its type, but by a list ([<]type[>] [<]expression[>]) where [<]expression[>] corresponds
|
||
|
|
to the part of the query forming the argument.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr importObjectTM(ListExpr args){
|
||
|
|
string err="text expected";
|
||
|
|
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError("expected one argument");
|
||
|
|
}
|
||
|
|
ListExpr arg = nl->First(args);
|
||
|
|
// the list is coded as (<type> <query part>)
|
||
|
|
if(!nl->HasLength(arg,2)){
|
||
|
|
return listutils::typeError("internal error");
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!FText::checkType(nl->First(arg))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
|
||
|
|
ListExpr fn = nl->Second(arg);
|
||
|
|
if(nl->AtomType(fn)!=TextType){
|
||
|
|
return listutils::typeError("file name not constant");
|
||
|
|
}
|
||
|
|
string fileName = nl->Text2String(fn);
|
||
|
|
|
||
|
|
ListExpr objectList;
|
||
|
|
if(! nl->ReadFromFile(fileName, objectList)){
|
||
|
|
return listutils::typeError("file not found or "
|
||
|
|
"file does not contain a list");
|
||
|
|
}
|
||
|
|
// an object file is formatted as
|
||
|
|
// (OBJECT <name> <typename> <type> <value> () )
|
||
|
|
// or (OBJECT <name> <typename> <type> <value> )
|
||
|
|
// the typename is mostly empty the last, empty list is for future extensions
|
||
|
|
|
||
|
|
// check structure
|
||
|
|
if( (!nl->HasLength(objectList,5) && !nl->HasLength(objectList,6))
|
||
|
|
|| !listutils::isSymbol(nl->First(objectList),"OBJECT")){
|
||
|
|
return listutils::typeError("file does not contain a "
|
||
|
|
"valid object description");
|
||
|
|
}
|
||
|
|
|
||
|
|
ListExpr typeList = nl->Fourth(objectList);
|
||
|
|
|
||
|
|
// check whether this list is a valid type description
|
||
|
|
SecondoCatalog* ctl = SecondoSystem::GetCatalog();
|
||
|
|
string tname;
|
||
|
|
int algId, typeId;
|
||
|
|
if(!ctl->LookUpTypeExpr(typeList, tname, algId, typeId)){
|
||
|
|
return listutils::typeError("Invalid type description in file");
|
||
|
|
}
|
||
|
|
return typeList;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
9.2 Value Mapping
|
||
|
|
|
||
|
|
The value mapping is the same as for normal operators.
|
||
|
|
|
||
|
|
*/
|
||
|
|
int importObjectVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
FText* fileName = (FText*) args[0].addr;
|
||
|
|
if(!fileName->IsDefined()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
string fn = fileName->GetValue();
|
||
|
|
ListExpr objectList;
|
||
|
|
if(!nl->ReadFromFile(fn,objectList)){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if(!nl->HasLength(objectList,6) && !nl->HasLength(objectList,5)){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
ListExpr typeList = nl->Fourth(objectList);
|
||
|
|
ListExpr instance = nl->Fifth(objectList);
|
||
|
|
SecondoCatalog* ctlg = SecondoSystem::GetCatalog();
|
||
|
|
string tname;
|
||
|
|
int algId, typeId;
|
||
|
|
if(!ctlg->LookUpTypeExpr(typeList, tname, algId, typeId)){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
InObject in = am->InObj(algId, typeId);
|
||
|
|
int errorPos=0;
|
||
|
|
ListExpr errorInfo = listutils::emptyErrorInfo();
|
||
|
|
bool correct;
|
||
|
|
|
||
|
|
Word o = in(ctlg->NumericType(typeList), instance,
|
||
|
|
errorPos, errorInfo,correct);
|
||
|
|
if(!correct){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// Because the object may be quite large, we replace it instead
|
||
|
|
// of manipulating the stored object
|
||
|
|
qp->DeleteResultStorage(s);
|
||
|
|
qp->ChangeResultStorage(s, o);
|
||
|
|
qp->SetDeleteFunction(s, am->DeleteObj(algId, typeId));
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
9.3 Specification
|
||
|
|
|
||
|
|
There are no news within this part.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
OperatorSpec importObjectSpec (
|
||
|
|
" text -> X " ,
|
||
|
|
" importObject(_) " ,
|
||
|
|
" Returns the object located in a file named by the argument." ,
|
||
|
|
" query importObject('Kinos.obj') "
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
9.4 Operator Instance
|
||
|
|
|
||
|
|
Also the definition of the operator instance is as usual.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
Operator importObjectOp (
|
||
|
|
"importObject" , // name of the operator
|
||
|
|
importObjectSpec.getStr() , // specification
|
||
|
|
importObjectVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
importObjectTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
/*
|
||
|
|
9.5 More Flexible Variant of Accessing Values in Type Mappings
|
||
|
|
|
||
|
|
The operator importObject only accepts a constant text as its input.
|
||
|
|
Sometimes, the argument is build by an expression build from constants
|
||
|
|
and database objects. For example, if there is a database object
|
||
|
|
called ~basename~ of type text, it would be desirable to import an object
|
||
|
|
using the query :
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
query importObject(basename + 'obj')
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
Because importObject expects that the expression is a constant text, this is not
|
||
|
|
possible. Here, a variant of the ~importObject~ operator is described supporting
|
||
|
|
this kind of input. We call the operator ~importObject2~. Because the value mapping
|
||
|
|
as well as the specification is the same as for ~importObject~, we reuse these
|
||
|
|
implementations when defining the operator instance.
|
||
|
|
|
||
|
|
The main idea behind this operator is to use the Queryprocessor for the
|
||
|
|
evaluation of the expression building the argument. The ~QueryProcessor~
|
||
|
|
provides a function ~ExecuteQuery~ for this job.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr importObject2TM(ListExpr args){
|
||
|
|
string err="text expected";
|
||
|
|
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError("expected one argument");
|
||
|
|
}
|
||
|
|
ListExpr arg = nl->First(args);
|
||
|
|
// the list is codes as (<type> <query part>)
|
||
|
|
if(!nl->HasLength(arg,2)){
|
||
|
|
return listutils::typeError("internal error");
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!FText::checkType(nl->First(arg))){
|
||
|
|
return listutils::typeError(err);
|
||
|
|
}
|
||
|
|
// here, accessing the value is changed
|
||
|
|
|
||
|
|
ListExpr expression = nl->Second(arg);
|
||
|
|
|
||
|
|
// we need some variables for feeding the ExecuteQuery function
|
||
|
|
Word queryResult;
|
||
|
|
string typeString = "";
|
||
|
|
string errorString = "";
|
||
|
|
bool correct;
|
||
|
|
bool evaluable;
|
||
|
|
bool defined;
|
||
|
|
bool isFunction;
|
||
|
|
// use the queryprocessor for executing the expression
|
||
|
|
qp->ExecuteQuery(expression, queryResult,
|
||
|
|
typeString, errorString, correct,
|
||
|
|
evaluable, defined, isFunction);
|
||
|
|
// check correctness of the expression
|
||
|
|
if(!correct || !evaluable || !defined || isFunction){
|
||
|
|
assert(queryResult.addr == 0);
|
||
|
|
return listutils::typeError("could not extract filename ("+
|
||
|
|
errorString + ")");
|
||
|
|
}
|
||
|
|
FText* fn = (FText*) queryResult.addr;
|
||
|
|
assert(fn);
|
||
|
|
if(!fn->IsDefined()){
|
||
|
|
fn->DeleteIfAllowed();
|
||
|
|
return listutils::typeError("filename undefined");
|
||
|
|
}
|
||
|
|
string fileName = fn->GetValue();
|
||
|
|
fn->DeleteIfAllowed();
|
||
|
|
|
||
|
|
// frome here, it's the same as for importObject
|
||
|
|
|
||
|
|
ListExpr objectList;
|
||
|
|
if(! nl->ReadFromFile(fileName, objectList)){
|
||
|
|
return listutils::typeError("file not found or "
|
||
|
|
"file does not contain a list");
|
||
|
|
}
|
||
|
|
// an object file is formatted as
|
||
|
|
// (OBJECT <name> <typename> <type> <value> () )
|
||
|
|
// the typename is mostly empty the last empty list is for future extensions
|
||
|
|
|
||
|
|
// check structure
|
||
|
|
if( !nl->HasLength(objectList,6)
|
||
|
|
|| !listutils::isSymbol(nl->First(objectList),"OBJECT")){
|
||
|
|
return listutils::typeError("file does not contain a "
|
||
|
|
"valid object description");
|
||
|
|
}
|
||
|
|
ListExpr typeList = nl->Fourth(objectList);
|
||
|
|
// check whether this list is a valid type description
|
||
|
|
SecondoCatalog* ctl = SecondoSystem::GetCatalog();
|
||
|
|
string tname;
|
||
|
|
int algId, typeId;
|
||
|
|
if(!ctl->LookUpTypeExpr(typeList, tname, algId, typeId)){
|
||
|
|
return listutils::typeError("Invalid type description in file");
|
||
|
|
}
|
||
|
|
return typeList;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
9.5 Operator Instance
|
||
|
|
|
||
|
|
Here, we use the specification and the value maping for importObject.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
Operator importObject2Op (
|
||
|
|
"importObject2" , // name of the operator
|
||
|
|
importObjectSpec.getStr() , // specification
|
||
|
|
importObjectVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
importObject2TM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
10 Compact Storing of Attribute Data Types
|
||
|
|
|
||
|
|
The default implementation of storing attributes to disc within
|
||
|
|
a relation is to build a byte block from the class and perform
|
||
|
|
a special treatment for the contained flobs. Because an attribute
|
||
|
|
contains some reference counters and other stuff, the storage
|
||
|
|
size is much more than necessary. For example, an 32 bit int value
|
||
|
|
has a storage size of 16 bytes, but needs only 5 bytes (defined + value)
|
||
|
|
for its representation. On the other hand there are small size restricted
|
||
|
|
types, where the actual value varyies in size. For example, a string
|
||
|
|
data type may contain up to 48 characters but the most strings are
|
||
|
|
much shorter.
|
||
|
|
|
||
|
|
Secondo provides mechanisms for storing types within relations more
|
||
|
|
efficently than the standard storage procedure. These mechanisms work
|
||
|
|
only for attributes without any FLOB members.
|
||
|
|
|
||
|
|
For understanding the mechanism, the tuple representation on disc
|
||
|
|
must be explained. A tuple on disc consists of two parts. The first part
|
||
|
|
is called the core part, the second one is the extension part.
|
||
|
|
Both parts are stored together as a single byte block to disc.
|
||
|
|
The core part of
|
||
|
|
the tuple consists of the core parts of the attributes, written direcly
|
||
|
|
one after each other. The extension part contains the extension parts of
|
||
|
|
the attributes and may be empty.
|
||
|
|
|
||
|
|
Obviously, we have to clarify the core and extension part of an attribute.
|
||
|
|
Firstly, we describe the parts if the default mechanism is used. Afterwards,
|
||
|
|
the changes for special storage mechanisms are described.
|
||
|
|
|
||
|
|
The core part of an attribute for the default storage consists of the byte
|
||
|
|
block represented by the attribut's class. This is also called root record
|
||
|
|
of the attribute. The extension part of an attribute without any flobs is
|
||
|
|
empty. If the attribute has flobs with a size smaller than a threshold, these
|
||
|
|
flob data build the extension part. The threshold is defined in the file
|
||
|
|
~RelationCommon.cpp~ in static tuple variable ~Tuple::extensionLimit~.
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
There are three different methods for storing an attribut in a relation:
|
||
|
|
|
||
|
|
* Default: This method corresponds to the storage mechanism above.
|
||
|
|
This mechanism is used for all attribute data types above.
|
||
|
|
|
||
|
|
* Core: The storage size is fixed, there is no extension part.
|
||
|
|
The attribute class provides its own serialization method.
|
||
|
|
This istorage method is suitable for small fixed size attributes
|
||
|
|
like number representations.
|
||
|
|
|
||
|
|
* Extension: The storage size may vary within a small range.
|
||
|
|
The attribute provides functions storing the core part and
|
||
|
|
the extension part. This may be used for attribute data
|
||
|
|
types like string etc.
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
10.1 Using Core Storage
|
||
|
|
|
||
|
|
The next class uses the core storage mechanism. We describe the implementation of
|
||
|
|
the ~ushort~ type representing an unsigned integer with 2 bytes length. We use an
|
||
|
|
additional byte for representing the undefined state.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class UShort: public Attribute{
|
||
|
|
public:
|
||
|
|
typedef uint16_t inttype;
|
||
|
|
// constructors
|
||
|
|
UShort() {}
|
||
|
|
UShort(const inttype v): Attribute(true), value(v) {}
|
||
|
|
UShort(const bool def): Attribute(def), value(0) {}
|
||
|
|
UShort(int dummy): Attribute(false),value(0){}
|
||
|
|
UShort(const UShort& rhs): Attribute(rhs), value(rhs.value){}
|
||
|
|
// assignment operator
|
||
|
|
UShort& operator=(const UShort& src){
|
||
|
|
SetDefined(src.IsDefined());
|
||
|
|
value = src.value;
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
// some class functions
|
||
|
|
inttype GetValue() const{
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
void Set(const bool def, const inttype v){
|
||
|
|
SetDefined(def);
|
||
|
|
value = v;
|
||
|
|
}
|
||
|
|
// auxiliary functions
|
||
|
|
static const string BasicType(){ return "ushort"; }
|
||
|
|
static const bool checkType(const ListExpr e){
|
||
|
|
return listutils::isSymbol(e,BasicType());
|
||
|
|
}
|
||
|
|
// attribute supporting types
|
||
|
|
virtual int NumOfFLOBS() const{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
virtual Flob* GetFLOB(const int i){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
int Compare(const Attribute* arg) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return arg->IsDefined()?-1:0;
|
||
|
|
}
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
inttype v = ((UShort*)arg)->GetValue();
|
||
|
|
if(value < v) return -1;
|
||
|
|
if(value > v) return 1;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
bool Adjacent(const Attribute* arg) const{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
size_t Sizeof() const{
|
||
|
|
return sizeof(*this);
|
||
|
|
}
|
||
|
|
size_t HashValue() const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return 0xFFFFFFu;
|
||
|
|
} else {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void CopyFrom(const Attribute* arg){
|
||
|
|
*this = *((UShort*)arg);
|
||
|
|
}
|
||
|
|
Attribute* Clone() const{
|
||
|
|
return new UShort(*this);
|
||
|
|
}
|
||
|
|
// function supporting generic type constructors
|
||
|
|
static ListExpr Property(){
|
||
|
|
return gentc::GenProperty("-> DATA",
|
||
|
|
BasicType(),
|
||
|
|
"int",
|
||
|
|
"16");
|
||
|
|
}
|
||
|
|
static bool CheckKind(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return checkType(type);
|
||
|
|
}
|
||
|
|
bool ReadFrom(ListExpr instance, const ListExpr typeInfo){
|
||
|
|
if(listutils::isSymbolUndefined(instance)){
|
||
|
|
SetDefined(false);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(instance)!=IntType){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
int v = nl->IntValue(instance);
|
||
|
|
if(v< 0 || v>numeric_limits<inttype>::max()){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
Set(true,(inttype)v);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
ListExpr ToListExpr(ListExpr typeInfo) const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return listutils::getUndefined();
|
||
|
|
}
|
||
|
|
return nl->IntAtom(value);
|
||
|
|
}
|
||
|
|
|
||
|
|
// function for storage management
|
||
|
|
/*
|
||
|
|
10.1.1 Function ~GetStorageType~
|
||
|
|
|
||
|
|
Here, the ~StorageType~ is returned. Possible values are ~Default~, ~Core~,
|
||
|
|
~Extension~, ~Unspecified~, where the last mentioned one makes no sense to use. We want
|
||
|
|
to store the attribute completely within the Core part of the tuple.
|
||
|
|
Thus, the return value is ~Core~.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
inline virtual StorageType GetStorageType() const{
|
||
|
|
return Core;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.1.2 Function ~SerializedSize~
|
||
|
|
|
||
|
|
This function returns the number of bytes required on disc for this type.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
inline virtual size_t SerializedSize() const{
|
||
|
|
return sizeof(inttype) + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.1.3 Function ~Serialize~
|
||
|
|
|
||
|
|
This function writes the serialized version of the value into some
|
||
|
|
buffer. We use the first byte for the defined flag
|
||
|
|
and the remaining bytes store the value. The argument ~sz~ contains
|
||
|
|
the value returned by ~SerializedSize~
|
||
|
|
|
||
|
|
*/
|
||
|
|
inline virtual void Serialize(char* buffer, size_t sz, size_t offset) const{
|
||
|
|
|
||
|
|
uint8_t b = IsDefined()?1:0;
|
||
|
|
memcpy(buffer+offset,&b,1);
|
||
|
|
offset+=1;
|
||
|
|
memcpy(buffer+offset,&value, sizeof(inttype));
|
||
|
|
offset+= sizeof(inttype);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.1.4 ~Rebuild~
|
||
|
|
|
||
|
|
This function reads the value from a buffer. As for the ~Serialize~ function,
|
||
|
|
~sz~ is the size returned by ~SerializedSize~. In contrast to ~Serialize~,
|
||
|
|
there is no offset. The value is read from the beginning of the buffer.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
inline virtual void Rebuild(char* buffer, size_t sz){
|
||
|
|
size_t offset=0;
|
||
|
|
uint8_t b;
|
||
|
|
memcpy(&b, buffer+offset, 1);
|
||
|
|
offset += 1;
|
||
|
|
memcpy(&value, buffer+offset, sizeof(inttype));
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
inttype value;
|
||
|
|
};
|
||
|
|
|
||
|
|
GenTC<UShort> ushortTC;
|
||
|
|
|
||
|
|
/*
|
||
|
|
10.1.5 Testing the success
|
||
|
|
|
||
|
|
There are several operators returning some sizes for attributes.
|
||
|
|
Here, we can use the queries:
|
||
|
|
|
||
|
|
----
|
||
|
|
query [const ushort value 23] memattrsize
|
||
|
|
|
||
|
|
query [const ushort value 23] feed transformstream rootattrsize[Elem]
|
||
|
|
|
||
|
|
----
|
||
|
|
|
||
|
|
and be happy that the first result is greater than the second one.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
11 Variable Size Attributes Without FLOBs
|
||
|
|
|
||
|
|
The storage mechanism describe in the last section can also be used for
|
||
|
|
storing attribute data types of variable length. Even the usage of pointers
|
||
|
|
is possible. Note that this mechanism is only for small values efficient.
|
||
|
|
For bigger values (more than 512 bytes), FLOBs should be used instead of
|
||
|
|
this mechanism.
|
||
|
|
|
||
|
|
We describe the mechanism at the attribute data type ~vstring~ representing
|
||
|
|
a variable length string. Because the core size of an attribute is always
|
||
|
|
fixed, we have to store the string data within the extension part. On the other
|
||
|
|
hand, the generic Open and Save functions defined in the attribute class only
|
||
|
|
support the default mechanism. For this reason, we have to implement the
|
||
|
|
serialisation (Open and Save) functions by ourself.
|
||
|
|
|
||
|
|
*/
|
||
|
|
class VString;
|
||
|
|
ostream& operator<<(ostream& o, const VString& vs);
|
||
|
|
|
||
|
|
class VString: public Attribute{
|
||
|
|
public:
|
||
|
|
VString() {}
|
||
|
|
VString(const bool def): Attribute(def), value(""){}
|
||
|
|
VString(const VString& src):Attribute(src),value(src.value) {}
|
||
|
|
VString(const string& s):Attribute(true),value(s){}
|
||
|
|
VString& operator=(const VString& src){
|
||
|
|
SetDefined(src.IsDefined());
|
||
|
|
value = IsDefined()?src.value:"";
|
||
|
|
return *this;
|
||
|
|
}
|
||
|
|
void Set(const bool def, const string& v){
|
||
|
|
SetDefined(def);
|
||
|
|
value = IsDefined()?v:"";
|
||
|
|
}
|
||
|
|
|
||
|
|
string GetValue() const{
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
|
||
|
|
// auxiliary functions
|
||
|
|
static const string BasicType(){ return "vstring"; }
|
||
|
|
static const bool checkType(ListExpr type){
|
||
|
|
return listutils::isSymbol(type,BasicType());
|
||
|
|
}
|
||
|
|
// attribute related functions
|
||
|
|
inline int NumOfFLOBs() const{
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
inline virtual Flob* GetFLOB(const int i){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
int Compare(const Attribute* arg)const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return arg->IsDefined()?-1:0;
|
||
|
|
}
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return strcmp(value.c_str(),((VString*)arg)->value.c_str());
|
||
|
|
}
|
||
|
|
bool Adjacent(const Attribute* arg) const{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
size_t Sizeof() const{
|
||
|
|
return sizeof(*this);
|
||
|
|
}
|
||
|
|
size_t HashValue() const{
|
||
|
|
if(!IsDefined()){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
size_t res = 0;
|
||
|
|
size_t st = value.length()>5u?5:value.length();
|
||
|
|
for(size_t i=0;i<st; i++){
|
||
|
|
res += i*255 + value[i];
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
void CopyFrom(const Attribute* arg){
|
||
|
|
*this = *((VString*) arg);
|
||
|
|
}
|
||
|
|
Attribute* Clone() const{
|
||
|
|
return new VString(*this);
|
||
|
|
}
|
||
|
|
// functions supporting the embedding into secondo
|
||
|
|
static ListExpr Property(){
|
||
|
|
return gentc::GenProperty( " -> DATA",
|
||
|
|
BasicType(),
|
||
|
|
"string or text",
|
||
|
|
"'lang'");
|
||
|
|
}
|
||
|
|
static bool CheckKind(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return checkType(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
static Word In(const ListExpr typeInfo, const ListExpr le,
|
||
|
|
const int errorPos, ListExpr& errorInfo, bool& correct){
|
||
|
|
Word res((void*)0);
|
||
|
|
|
||
|
|
if(listutils::isSymbolUndefined(le)){
|
||
|
|
res.addr = new VString(false);
|
||
|
|
correct = true;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(le)==StringType){
|
||
|
|
res.addr = new VString(nl->StringValue(le));
|
||
|
|
correct = true;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
if(nl->AtomType(le)==TextType){
|
||
|
|
res.addr = new VString(nl->Text2String(le));
|
||
|
|
correct = true;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
correct = false;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
static ListExpr Out(const ListExpr typeInfo, Word value){
|
||
|
|
VString* v = (VString*) value.addr;
|
||
|
|
if(!v->IsDefined()){
|
||
|
|
return listutils::getUndefined();
|
||
|
|
}
|
||
|
|
if(v->value.length()<MAX_STRINGSIZE){
|
||
|
|
return nl->StringAtom(v->value);
|
||
|
|
} else {
|
||
|
|
return nl->TextAtom(v->value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static Word Create(const ListExpr typeInfo){
|
||
|
|
Word res( new VString(false));
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Delete(const ListExpr typeInfo, Word& v){
|
||
|
|
delete (VString*) v.addr;
|
||
|
|
v.addr = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool Open( SmiRecord& valueRecord, size_t& offset,
|
||
|
|
const ListExpr typeInfo, Word& value){
|
||
|
|
// get Size of the string
|
||
|
|
size_t length;
|
||
|
|
bool ok = valueRecord.Read(&length,sizeof(size_t),offset);
|
||
|
|
if(!ok) { return false; }
|
||
|
|
size_t buffersize = sizeof(size_t) + 1 + length;
|
||
|
|
char* buffer = new char[buffersize];
|
||
|
|
ok = valueRecord.Read(buffer,buffersize,offset);
|
||
|
|
offset += buffersize;
|
||
|
|
if(!ok){
|
||
|
|
delete[] buffer;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
VString* v = new VString(false);
|
||
|
|
v->Rebuild(buffer, buffersize);
|
||
|
|
value.addr = v;
|
||
|
|
delete[] buffer;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool Save(SmiRecord& valueRecord, size_t& offset,
|
||
|
|
const ListExpr typeInfo, Word& value){
|
||
|
|
VString* v = (VString*) value.addr;
|
||
|
|
size_t size = v->SerializedSize();
|
||
|
|
char* buffer = new char[size];
|
||
|
|
v->Serialize(buffer, size,0);
|
||
|
|
bool ok = valueRecord.Write(buffer, size,offset);
|
||
|
|
offset += size;
|
||
|
|
delete[] buffer;
|
||
|
|
return ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void Close(const ListExpr typeInfo, Word& w){
|
||
|
|
delete (VString*) w.addr;
|
||
|
|
w.addr = 0;
|
||
|
|
}
|
||
|
|
static Word Clone(const ListExpr typeInfo, const Word& w){
|
||
|
|
VString* v = (VString*) w.addr;
|
||
|
|
Word res;
|
||
|
|
res.addr = new VString(*v);
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int Size() {
|
||
|
|
return 256;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Because the Serialization does not overwrite internal function pointers as in the
|
||
|
|
default mechanism, here just the argument pointer is returned.
|
||
|
|
Because the Standard constructir of the ~string~ class initilizes the
|
||
|
|
string to be empty, we cannot use the special variant of ~new~ here.
|
||
|
|
|
||
|
|
*/
|
||
|
|
static void* Cast(void* addr){
|
||
|
|
return addr;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool TypeCheck(ListExpr type, ListExpr& errorInfo){
|
||
|
|
return checkType(type);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|
||
|
|
Because of the variable length, we have to store the value within the
|
||
|
|
extension part of a tuple.
|
||
|
|
|
||
|
|
*/
|
||
|
|
inline virtual StorageType GetStorageType() const{
|
||
|
|
return Extension;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
The function ~SerializedSize~ is implemented as for the ~Core~ variant.
|
||
|
|
|
||
|
|
*/
|
||
|
|
inline virtual size_t SerializedSize() const{
|
||
|
|
return sizeof(size_t) + 1 + value.length();
|
||
|
|
}
|
||
|
|
|
||
|
|
inline virtual void Serialize(char* buffer, size_t sz,
|
||
|
|
size_t offset) const{
|
||
|
|
size_t length = value.length();
|
||
|
|
uint8_t def = IsDefined()?1:0;
|
||
|
|
memcpy(buffer+offset, &length , sizeof(size_t));
|
||
|
|
offset += sizeof(size_t);
|
||
|
|
memcpy(buffer+offset, &def, 1);
|
||
|
|
offset += 1;
|
||
|
|
memcpy(buffer+offset, value.c_str(), length);
|
||
|
|
offset += length;
|
||
|
|
}
|
||
|
|
|
||
|
|
inline virtual void Rebuild(char* buffer, size_t sz){
|
||
|
|
size_t length;
|
||
|
|
uint8_t def;
|
||
|
|
size_t offset = 0;
|
||
|
|
memcpy(&length, buffer + offset, sizeof(size_t));
|
||
|
|
offset += sizeof(size_t);
|
||
|
|
memcpy(&def, buffer+offset, 1);
|
||
|
|
offset+=1;
|
||
|
|
if(!def){
|
||
|
|
value = "";
|
||
|
|
SetDefined(false);
|
||
|
|
} else {
|
||
|
|
this->value = string(buffer+offset,length);
|
||
|
|
SetDefined(true);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
~GetMemSize~
|
||
|
|
|
||
|
|
The ~GetMemSize~ function returns the amount of space required for an object in
|
||
|
|
main memory. The default implementation uses the size of the root block and adds
|
||
|
|
the FlobSizes of FLOBs whose data are not controlled by the FLOBCache.
|
||
|
|
Because in the VString class pointers are present, we have to overwrite this
|
||
|
|
function for returning a correct result.
|
||
|
|
|
||
|
|
*/
|
||
|
|
virtual size_t GetMemSize() {
|
||
|
|
return sizeof(*this) + value.length();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
private:
|
||
|
|
string value; // uses pointers internally
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
ostream& operator<<(ostream& o, const VString& vs){
|
||
|
|
if(!vs.IsDefined()){
|
||
|
|
o << "undef";
|
||
|
|
} else {
|
||
|
|
o << "'"<<vs.GetValue()<<"'";
|
||
|
|
}
|
||
|
|
return o;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
11.2 Type constructor instance
|
||
|
|
|
||
|
|
*/
|
||
|
|
TypeConstructor VStringTC(
|
||
|
|
VString::BasicType(),
|
||
|
|
VString::Property, VString::Out, VString::In, 0,0,
|
||
|
|
VString::Create, VString::Delete,
|
||
|
|
VString::Open, VString::Save, VString::Close, VString::Clone,
|
||
|
|
VString::Cast, VString::Size, VString::TypeCheck
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
12 Operator text2string
|
||
|
|
|
||
|
|
This operator is for testing the vstring implementation. It takes a ~text~ and converts
|
||
|
|
it into a ~vstring~.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
ListExpr text2vstringTM(ListExpr args){
|
||
|
|
if(!nl->HasLength(args,1) || !FText::checkType(nl->First(args))){
|
||
|
|
return listutils::typeError("text expected");
|
||
|
|
}
|
||
|
|
return listutils::basicSymbol<VString>();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int text2vstringVM ( Word * args , Word & result , int message ,
|
||
|
|
Word & local , Supplier s ) {
|
||
|
|
FText* arg = (FText*) args[0].addr;
|
||
|
|
result = qp->ResultStorage(s);
|
||
|
|
VString* res = (VString*) result.addr;
|
||
|
|
if(!arg->IsDefined()){
|
||
|
|
res->SetDefined(false);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
res->Set(true, arg->GetValue());
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
OperatorSpec text2vstringSpec (
|
||
|
|
" text -> vstring " ,
|
||
|
|
" text2vstring(_) " ,
|
||
|
|
" Converts a text into a vstring" ,
|
||
|
|
" query text2vstring('This is text') "
|
||
|
|
);
|
||
|
|
|
||
|
|
Operator text2vstringOp (
|
||
|
|
"text2vstring" , // name of the operator
|
||
|
|
text2vstringSpec.getStr() , // specification
|
||
|
|
text2vstringVM , // value mapping
|
||
|
|
Operator::SimpleSelect , // selection function
|
||
|
|
text2vstringTM // type mapping
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
13 Operators using Memory
|
||
|
|
|
||
|
|
In some cases, operators will require parts of the main memory to work
|
||
|
|
faster. In Secondo, such operator have to consider memory limitations
|
||
|
|
that can be requested from the QueryProcessor.
|
||
|
|
|
||
|
|
An instance of a memory consuming operator has to call the function
|
||
|
|
SetUsesMemory. A good place to do it is the algebra constructor.
|
||
|
|
In the value mapping, the available memory for this operator node
|
||
|
|
can be reqeusted by calling the function GetMemorySize(s), where s
|
||
|
|
is the supplier given in the value mapping arguments.
|
||
|
|
|
||
|
|
This mechanism is explained using the operator revert that receives a
|
||
|
|
stream of tuples and returns this stream in reverse order.
|
||
|
|
|
||
|
|
|
||
|
|
13.1 The operator ~reverseStream~
|
||
|
|
|
||
|
|
13.1.1 Type Mapping
|
||
|
|
|
||
|
|
*/
|
||
|
|
ListExpr reverseStreamTM(ListExpr args){
|
||
|
|
if(!nl->HasLength(args,1)){
|
||
|
|
return listutils::typeError("wrong number of arguments");
|
||
|
|
}
|
||
|
|
ListExpr arg1 = nl->First(args);
|
||
|
|
if(!Stream<Tuple>::checkType(arg1)){
|
||
|
|
return listutils::typeError("stream<TUPLE> expected");
|
||
|
|
}
|
||
|
|
return arg1;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
13.1.2 LocalInfo class
|
||
|
|
|
||
|
|
*/
|
||
|
|
size_t reverseStreamLI_FileNumber = 0;
|
||
|
|
|
||
|
|
class reverseStreamLI{
|
||
|
|
public:
|
||
|
|
|
||
|
|
/*
|
||
|
|
~Constructor~
|
||
|
|
|
||
|
|
*/
|
||
|
|
reverseStreamLI(Word _stream, size_t _maxMem) : stream(_stream),
|
||
|
|
maxMem(_maxMem){
|
||
|
|
stream.open();
|
||
|
|
readInput();
|
||
|
|
stream.close();
|
||
|
|
in = 0;
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
~Destructor~
|
||
|
|
|
||
|
|
*/
|
||
|
|
~reverseStreamLI(){
|
||
|
|
clearMem();
|
||
|
|
closeFile();
|
||
|
|
// remove created files
|
||
|
|
while(!filenames.empty()){
|
||
|
|
string fn = filenames.back();
|
||
|
|
filenames.pop_back();
|
||
|
|
cout << "delete file" << fn << endl;
|
||
|
|
FileSystem::DeleteFileOrFolder(fn);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(tt){
|
||
|
|
tt->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
~Computing the next result element~
|
||
|
|
|
||
|
|
*/
|
||
|
|
Tuple* getNext(){
|
||
|
|
if(!mmpart.empty()){
|
||
|
|
Tuple* res = mmpart.top();
|
||
|
|
mmpart.pop();
|
||
|
|
if(mmpart.empty()){
|
||
|
|
openNextFile(true);
|
||
|
|
}
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
return nextFromFile();
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
Stream<Tuple> stream;
|
||
|
|
size_t maxMem;
|
||
|
|
std::stack<Tuple*> mmpart;
|
||
|
|
std::vector<string> filenames;
|
||
|
|
TupleType* tt;
|
||
|
|
ifstream* in;
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Here, we read the complete input stream until the maximal available
|
||
|
|
memory is exhausted. In this case, we write the content of the stack.
|
||
|
|
into a file. If the memory overflows again, the content is written into
|
||
|
|
a new file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void readInput(){
|
||
|
|
size_t mem1 = sizeof(mmpart);
|
||
|
|
size_t mem = mem1;
|
||
|
|
Tuple* tuple;
|
||
|
|
tt = 0;
|
||
|
|
while( (tuple = stream.request()) ){
|
||
|
|
if(!tt){
|
||
|
|
tt = tuple->GetTupleType();
|
||
|
|
tt->IncReference();
|
||
|
|
}
|
||
|
|
mmpart.push(tuple);
|
||
|
|
mem += sizeof(void*) + tuple->GetMemSize();
|
||
|
|
if(mem > maxMem){
|
||
|
|
writeFile();
|
||
|
|
mem = mem1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
cout << "written " << filenames.size() << " files " << endl;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Write the content of the stack into a file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void writeFile(){
|
||
|
|
string name = string("OP_reverseStream_")
|
||
|
|
+stringutils::int2str(WinUnix::getpid())
|
||
|
|
+string("_")
|
||
|
|
+ stringutils::int2str(reverseStreamLI_FileNumber)
|
||
|
|
+ string(".bin");
|
||
|
|
reverseStreamLI_FileNumber++;
|
||
|
|
filenames.push_back(name);
|
||
|
|
ofstream out(name.c_str(),ios::binary|ios::trunc);
|
||
|
|
while(!mmpart.empty()){
|
||
|
|
Tuple* t = mmpart.top();
|
||
|
|
mmpart.pop();
|
||
|
|
writeTuple(out,t);
|
||
|
|
t->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
out.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Write a single tuple into a file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void writeTuple(ofstream& out, Tuple* tuple){
|
||
|
|
size_t coreSize;
|
||
|
|
size_t extensionSize;
|
||
|
|
size_t flobSize;
|
||
|
|
size_t blocksize = tuple->GetBlockSize(coreSize, extensionSize,
|
||
|
|
flobSize);
|
||
|
|
// allocate buffer and write flob into it
|
||
|
|
char* buffer = new char[blocksize];
|
||
|
|
tuple->WriteToBin(buffer, coreSize, extensionSize, flobSize);
|
||
|
|
uint32_t tsize = blocksize;
|
||
|
|
out.write((char*) &tsize, sizeof(uint32_t));
|
||
|
|
out.write(buffer, tsize);
|
||
|
|
delete[] buffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Removes remaining tuples from the stack.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void clearMem(){
|
||
|
|
while(!mmpart.empty()){
|
||
|
|
Tuple* t = mmpart.top();
|
||
|
|
mmpart.pop();
|
||
|
|
t->DeleteIfAllowed();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Reads the next result tuple from a file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
Tuple* nextFromFile(){
|
||
|
|
if(!in){
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
if(in->eof()) {
|
||
|
|
cout << "end of file reached" << endl;
|
||
|
|
openNextFile(false);
|
||
|
|
}
|
||
|
|
if(!in){
|
||
|
|
cout << "no next file" << endl;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
// size of the next tuple
|
||
|
|
uint32_t size;
|
||
|
|
in->read( (char*) &size, sizeof(uint32_t));
|
||
|
|
if(!in->good()){
|
||
|
|
openNextFile(false);
|
||
|
|
if(!in) return 0;
|
||
|
|
in->read( (char*) &size, sizeof(uint32_t));
|
||
|
|
}
|
||
|
|
if(size==0){
|
||
|
|
return 0; // some error occured
|
||
|
|
}
|
||
|
|
char* buffer = new char[size];
|
||
|
|
in->read(buffer, size);
|
||
|
|
if(!in->good()){
|
||
|
|
delete [] buffer;
|
||
|
|
cout << "error during reading file " << endl;
|
||
|
|
cout << "position " << in->tellg() << endl;
|
||
|
|
return 0; // error
|
||
|
|
}
|
||
|
|
Tuple* res = new Tuple(tt);
|
||
|
|
res->ReadFromBin(0, buffer );
|
||
|
|
delete[] buffer;
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Opens the next file. If this is not the first file, the
|
||
|
|
currently opened files is closed and deleted afterward.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void openNextFile(bool first){
|
||
|
|
if(!first){
|
||
|
|
closeFile(); // closes and removes the currently open file
|
||
|
|
}
|
||
|
|
if(filenames.empty()){
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
string name = filenames.back();
|
||
|
|
cout << "Open File " << name << endl;
|
||
|
|
in = new ifstream(name.c_str(), ios::binary);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Closes and deletes the current opened file.
|
||
|
|
|
||
|
|
*/
|
||
|
|
void closeFile(){
|
||
|
|
if(in){
|
||
|
|
in->close();
|
||
|
|
delete in;
|
||
|
|
in = 0;
|
||
|
|
string name = filenames.back();
|
||
|
|
filenames.pop_back();
|
||
|
|
cout << "delete file " << name << endl;
|
||
|
|
FileSystem::DeleteFileOrFolder(name);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
};
|
||
|
|
|
||
|
|
/*
|
||
|
|
13.1.3 The Value Mapping
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
int reverseStreamVM( Word* args, Word& result, int message,
|
||
|
|
Word& local, Supplier s ){
|
||
|
|
|
||
|
|
reverseStreamLI* li = (reverseStreamLI*) local.addr;
|
||
|
|
switch(message){
|
||
|
|
case OPEN: if(li){
|
||
|
|
delete li;
|
||
|
|
}
|
||
|
|
local.addr = new reverseStreamLI(args[0],
|
||
|
|
qp->GetMemorySize(s)*1024*1024);
|
||
|
|
return 0;
|
||
|
|
case REQUEST:
|
||
|
|
result.addr = li?li->getNext():0;
|
||
|
|
return result.addr?YIELD:CANCEL;
|
||
|
|
case CLOSE :
|
||
|
|
if(li){
|
||
|
|
delete li;
|
||
|
|
local.addr = 0;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
13.2.3 The specification.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
OperatorSpec reverseStreamSpec(
|
||
|
|
"stream(tuple) -> stream(tuple) ",
|
||
|
|
" _ reverseStream ",
|
||
|
|
"reverseStreams the order in a tuple stream",
|
||
|
|
"query ten feed reverseStream consume");
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
13.2.4 The operator instance
|
||
|
|
|
||
|
|
*/
|
||
|
|
Operator reverseStreamOp(
|
||
|
|
"reverseStream",
|
||
|
|
reverseStreamSpec.getStr(),
|
||
|
|
reverseStreamVM,
|
||
|
|
Operator::SimpleSelect,
|
||
|
|
reverseStreamTM
|
||
|
|
);
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
8 Definition of the Algebra
|
||
|
|
|
||
|
|
\label{AlgebraDefinition}
|
||
|
|
|
||
|
|
In this step, a new algebra -- a class derived from the ~Algebra~ class --
|
||
|
|
is created. Within the constructor of the algebra, we add the type constructors
|
||
|
|
and assign the corresponding kinds to the types.
|
||
|
|
Furthermore, all operators are added to the algebra.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
class GuideAlgebra : public Algebra {
|
||
|
|
public:
|
||
|
|
GuideAlgebra() : Algebra() {
|
||
|
|
|
||
|
|
AddTypeConstructor( &SCircleTC );
|
||
|
|
SCircleTC.AssociateKind( Kind::SIMPLE() );
|
||
|
|
|
||
|
|
AddTypeConstructor( &ACircleTC );
|
||
|
|
ACircleTC.AssociateKind( Kind::DATA() );
|
||
|
|
|
||
|
|
AddTypeConstructor( &GCircleTC );
|
||
|
|
GCircleTC.AssociateKind( Kind::DATA() );
|
||
|
|
|
||
|
|
AddTypeConstructor( &IntListTC );
|
||
|
|
IntListTC.AssociateKind( Kind::DATA() );
|
||
|
|
|
||
|
|
AddTypeConstructor( &PAVLTC );
|
||
|
|
PAVLTC.AssociateKind( Kind::SIMPLE() );
|
||
|
|
|
||
|
|
|
||
|
|
AddTypeConstructor( &ushortTC);
|
||
|
|
ushortTC.AssociateKind(Kind::DATA());
|
||
|
|
|
||
|
|
AddTypeConstructor( &VStringTC);
|
||
|
|
VStringTC.AssociateKind(Kind::DATA());
|
||
|
|
|
||
|
|
|
||
|
|
AddOperator(&perimeterOp);
|
||
|
|
AddOperator(&distNOp);
|
||
|
|
AddOperator(&countNumberOp);
|
||
|
|
AddOperator(&getCharsOp);
|
||
|
|
AddOperator(&startsWithSOp);
|
||
|
|
AddOperator(&replaceElemOp);
|
||
|
|
AddOperator(&attrIndexOp);
|
||
|
|
AddOperator(&createPAVLOp);
|
||
|
|
AddOperator(&containsOp);
|
||
|
|
AddOperator(&insertOp);
|
||
|
|
|
||
|
|
AddOperator(&importObjectOp);
|
||
|
|
importObjectOp.SetUsesArgsInTypeMapping();
|
||
|
|
|
||
|
|
AddOperator(&importObject2Op);
|
||
|
|
importObject2Op.SetUsesArgsInTypeMapping();
|
||
|
|
|
||
|
|
AddOperator(&text2vstringOp);
|
||
|
|
|
||
|
|
AddOperator(&reverseStreamOp);
|
||
|
|
reverseStreamOp.SetUsesMemory();
|
||
|
|
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
End of the namespace. The following code cannot be embedded into the
|
||
|
|
algebras's namespace. Thus the namespace should end here.
|
||
|
|
|
||
|
|
*/
|
||
|
|
|
||
|
|
} // end of namespace guide
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
9 Initialization of the Algebra
|
||
|
|
|
||
|
|
This piece of code returns a new instance of the algebra.
|
||
|
|
|
||
|
|
|
||
|
|
*/
|
||
|
|
extern "C"
|
||
|
|
Algebra*
|
||
|
|
InitializeGuideAlgebra( NestedList* nlRef,
|
||
|
|
QueryProcessor* qpRef ) {
|
||
|
|
return new guide::GuideAlgebra;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
|