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

1549 lines
39 KiB
C++

/*
----
This file is part of SECONDO.
Copyright (C) 2004, University in Hagen, Department of Computer Science,
Database Systems for New Applications.
SECONDO is free software{} you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation{} either version 2 of the License, or
(at your option) any later version.
SECONDO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY{} without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SECONDO{} if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
----
//paragraph [1] Title: [{\Large \bf \begin {center}] [\end {center}}]
//[TOC] [\tableofcontents]
[1] .cpp file of type Record
December 2009, B. Franzkowiak
[TOC]
1 Overview
Within this class a new structured data type named ~Record~ is implemented into
SECONDO. Elements of already existing attributes of kind DATA can be summerized
to a new data type likewise a structure. A Record object is of kind DATA as
well and therefore a record can contain other records.
In particular records should be used to import geographical data respectively
of data type given by EOO and NMEA0183 data protocolls. An object of type Record
should have the ability to hold the specified data given by a E00 or NMEA0183
geodata object.
Additionally the fact that elements that belong to a record can contain FLOBs
has to be considered.
To avoid uncontrolled groth of such FLOBs they have to be handled
by the record administration.
4 Concept
Figure 1: Representation of a ~Record~ [Record.cpp.Figure1.eps]
4.1 Record type structure
A ~Record~ is derived from ~Attribute~ as it has to be used in relations.
Therefore it
is of type ~DATA~ as well and contains amongst others information about:
* Its hash value ~size\_t Hashvalue~ for the whole record
* Its numbers of elements ~int noElements~ in the record
* A structure ~struct ElemInfo~ to describe the record elements in detail
(see below)
* An array ~DBArray elemInfoArray~ contains all structured meta information
about the elements (see below)
* Two ~FLOB~s (see below)
1 ~elemData~ containing in fact the data of the elements
2 ~elemExData~ containing in fact all Flobs of all elements as far as
elements hold FLOBs
4.2 Structure ~struct ElemInfo~
Using a structure to describe the specific details of each element a flexible
and generic build up of a record is possible. For each element a related
structure is allocated and saved in the DBArray ~elemInfoArray~.
The structure
contains the following detailed information:
* ~char elemName~ name of the element, e.g. age
* ~char elemType~ type of the element, e.g. int
* ~int algebraId~ id of the algebra of the given type, e.g. 0
* ~int typeId~ id of typeId of the given type, e.g. 3
* ~bool hasData~ information if the element contains data or is empty
* ~size\_t dataOffSet~ position where the saved data is located in the FLOB
~elemData~
* ~size\_t extDataOffSet~ position where the FLOBS owned by the element
are located in the FLOB ~elemExData~
4.3 Array ~DBArray elemInfoArray~
This array is used to save the element information data. For each element a new
structure element ~ElemenInfo~ is created and saved in the array
~elemInfoArray~. So the record itself always knows about its metadata.
This information is saved in an own array independend of the data by itself.
Therefore it is possible to administrate the data FLOBs.
4.4 Element FLOB ~elemData~
The element data in fact is stored in a FLOB called ~elemData~.
To assure that the
element or respective the ~elemInfoArray~ knows where the related data is
saved in the FLOB, the position in the FLOB the so called offset is stored
in the structure ~ElemInfo~ in ~dataOffSet~. Together with the
~dataOffSet~ and the ~elemData~ FLOB the element can be saved
and rebuilt.
4.5 External element FLOB ~elemExData~
Apparently an element can consist of FLOBs as well.
To administrate an effective
repository management fot all given FLOBs of all existing
record elements another FLOB
~elemExData~is required. Within this additional FLOB all occurring
FLOBs are saved. To assure that the element or respective the
~elemInfoArray~ knows where the related FLOBs are saved, the offset
of the beginning of the elements FLOB area is stored in the structure
~ElemInfo~ in ~extDataOffSet~.
3 handy Record example
List expression definition of the Record:
Type Description: (record (name1 typ1) (name2 type2) ... (nameN typeN))
Elements: ($<elem1> <elem2> ... <elemN>$)
Handy example:
Description: (record (name string) (age int) (amount real))
Elements: ("Mayer" 42 1023.08)
let rec3 = [const record (name: string, age: int, amount: real)
value ("Schulz" 23 28.0)]
4 Source code area
4.1 Defines and includes
The following includes are neccessary:
*/
#include "Record.h"
#include "Algebra.h"
#include "NestedList.h"
#include "NList.h"
#include "LogMsg.h"
#include "QueryProcessor.h"
#include "StandardTypes.h"
#include "SecondoCatalog.h"
#include "ListIterator.h"
#include <cctype>
#include "Symbols.h"
#include "ListUtils.h"
using namespace std;
// enable/disable debug output to standard error
#undef RECORD_DEBUG
//#define RECORD_DEBUG
/*
External references to a nested list and query processor instance are needed:
*/
extern NestedList* nl;
extern QueryProcessor* qp;
/*
To assure a correct hashing two constants are needed:
*/
static const int HASH_INIT = 17;
static const int HASH_MULTI = 59;
/*
5.1 Area with Record constructor and destructor
The class Record provides the following constructors and deconstructor:
* Record
* Record(int initSize)
* Record()
Standard constructor:
*/
Record::Record()
{
#ifdef RECORD_DEBUG
cerr << "Record::Record()" << endl;
#endif
}
/*
Constructor ~Record(int initSize)~ initialises the record with a given
constant hash value HASHINIT = 17 for a given number of elements.
*/
Record::Record(int initSize)
: Attribute(true)
, hashValue(HASH_INIT)
, noElements(0)
, elemInfoArray(initSize)
, elemData(0)
, elemExtData(0)
{
#ifdef RECORD_DEBUG
cerr << "Record::Record(" << initSize << ")" << endl;
#endif
// set as defined
this->SetDefined(true);
}
void Record::Clear() {
hashValue=HASH_INIT;
noElements=0;
elemInfoArray.clean();
elemData.clean();
elemExtData.clean();
}
/*
Deconstructor ~[~]Record()~
Destroys the Record and cleans up the memory of ~elemInfoArray~,
~elemData~, ~elemExtData~.
*/
Record::~Record() { }
/*
6.1 Public record method area
The class Record provides the following public methods:
* GetNoElements
* GetElement
* GetElementName
* GetElementTypeName
* SetElement
* AppendElement
*/
/*
~GetNoElements~ returns the number of elements in this record.
*/
int
Record::GetNoElements() const
{
#ifdef RECORD_DEBUG
cerr << "Record::GetNoElements(): noElements=" << this->noElements << endl;
#endif
return this->noElements;
}
/*
~GetElement~ returns the element at position pos.
With the help of the element info ~elemInfo~ it is possible to rebuild the
attribute that is stored at a given record position. First the ~elemInfo~
is refilled with the information of ~elemInfoArray~ at the given position.
It is now possible to create the attribute with the correct type and name.
As the position in ~elemData~ is known as well the data can be assigned to
the attribute. Finally the external data if available can be assigned to the
attribute as well.
*/
Attribute*
Record::GetElement(int pos) const
{
assert(pos >=0);
assert(pos < this->elemInfoArray.Size());
// get element info by position
ElemInfo elemInfo;
elemInfoArray.Get(pos, elemInfo);
if(!elemInfo.hasData){
return 0;
} else {
// create requested element
// TODO: It's not a good idea to have no type information during
// object creation for some types
Attribute* elem = static_cast<Attribute*>
((am->CreateObj(elemInfo.algebraId, elemInfo.typeId))(0).addr);
// save the flob states
vector<Flob> savedFlobs;
for(int i=0;i<elem->NumOfFLOBs();i++){
savedFlobs.push_back(*elem->GetFLOB(i));
}
this->elemData.read((char*)elem, elem->Sizeof(),elemInfo.dataOffset);
// assign retrieved data to element
elem = static_cast<Attribute*>(
(am->Cast(elemInfo.algebraId, elemInfo.typeId))((void*) elem));
// restore the flob states
for(int i=0;i<elem->NumOfFLOBs();i++){
int size = elem->GetFLOB(i)->getSize();
elem->GetFLOB(i)->kill();
*(elem->GetFLOB(i)) = savedFlobs[i];
elem->GetFLOB(i)->resize(size);
}
// assign external data offset
size_t offset = elemInfo.extDataOffset;
// restore flob data
for (int i = 0; i < elem->NumOfFLOBs(); i++) {
Flob* flob = elem->GetFLOB(i);
char* buffer = new char[flob->getSize()];
bool ok = elemExtData.read(buffer, flob->getSize(), offset);
assert(ok);
// assign retrieved data to target flob
flob->write(buffer, flob->getSize(), 0);
delete[] buffer;
// update external data offset
offset += flob->getSize();
}
return elem;
}
}
/*
~GetElementName~ returns the element name at position pos.
With the help of the element info ~elemInfo~ it is possible to select the
element name of an element given at a certain record position.
*/
const string
Record::GetElementName(int pos) const
{
#ifdef RECORD_DEBUG
cerr << "Record::GetElementName(" << pos << ")";
#endif
// check precondition
assert(pos >=0);
assert(pos < this->elemInfoArray.Size());
// get element info by position
ElemInfo elemInfo;
this->elemInfoArray.Get(pos, elemInfo);
#ifdef RECORD_DEBUG
cerr << ": elemName=" << elemInfo.elemName << endl;
#endif
return elemInfo.elemName;
}
/*
~GetElementTypeName~ returns the element type name at position pos.
With the help of the element info ~elemInfo~ it is possible to select the
type name of an element given at a certain record position.
*/
const string
Record::GetElementTypeName(int pos) const
{
#ifdef RECORD_DEBUG
cerr << "Record::GetElementTypeName(" << pos << ")";
#endif
// check precondition
assert(pos >=0);
assert(pos < this->elemInfoArray.Size());
// get element info by position
ElemInfo elemInfo;
this->elemInfoArray.Get(pos, elemInfo);
#ifdef RECORD_DEBUG
cerr << ": typeName=" << elemInfo.typeName << endl;
#endif
return elemInfo.typeName;
}
/*
~SetElement~ stores the given element at the given position
in this record.
The element name has to be filled in and it has to be a known type name.
Otherwise the action will be rejected.
If everything is correct the new element info has to be saved in
~elemInfoArray~. Therefore the size of the array needs potentially
to be resized to assure that the new ~elemInfo~ fits in the array. After that
the element info data is set and the element data is stored in the
~elemData~ FLOB. If the element contains FLOBS oneself these FLOBs will be
stored in the elemExData FLOB.
*/
bool
Record::SetElement(int pos, Attribute* elem,
const string& typeName, const string& elemName)
{
#ifdef RECORD_DEBUG
cerr << "Record::SetElement(" << pos
<< ", " << (elem == NULL ? "NULL" : typeid(*elem).name())
<< ", " << typeName << ", " << elemName << ")" << endl;
#endif
// check preconditons
assert(pos >= 0);
assert(elem != NULL);
// check of empty element name
if (elemName.size() == 0) {
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: empty element name" << endl;
#endif
cmsg.typeError("Record::SetElement: empty element name is not allowed.");
return false;
}
// get a secondo catalog instance
SecondoCatalog* sc = SecondoSystem::GetCatalog();
// check given type name
if (sc->IsTypeName(typeName) == false) {
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: unknown type name" << endl;
#endif
cmsg.typeError("Record::SetElement: unknown type name " + typeName);
return false;
}
// the element name has to start with a capital letter
if (isupper(elemName[0]) == 0) {
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: element name has to start with a "
<< "capital letter: " << elemName << endl;
#endif
cmsg.inFunError("Record::SetElement: element name has to start with a "
"capital letter: " + elemName);
return false;
}
// create an empty element info instance
ElemInfo elemInfo;
// check if the requested position fits into the element info array
if (pos >= this->elemInfoArray.Size()) {
// save size of array (before resize operation)
int size = this->elemInfoArray.Size();
// resize array
this->elemInfoArray.resize(pos + 1);
// padding the array with empty element info objects to fill the gap
for (; size < (this->elemInfoArray.Size() - 1); size++) {
this->elemInfoArray.Put(size, elemInfo);
}
}
// assign element name and type
strncpy(elemInfo.elemName, elemName.c_str(), MAX_STRINGSIZE);
strncpy(elemInfo.typeName, typeName.c_str(), MAX_STRINGSIZE);
// assign element type related ids to the element info object
sc->GetTypeId(typeName, elemInfo.algebraId, elemInfo.typeId);
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: pos=" << pos
<< ", algebraId=" << elemInfo.algebraId
<< ", typeId=" << elemInfo.typeId << endl;
#endif
// mark element as available and assign remaining offset values
elemInfo.hasData = true;
elemInfo.dataOffset = this->elemData.getSize();
elemInfo.extDataOffset = this->elemExtData.getSize();
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: pos=" << pos
<< ", elemInfo.dataOffset=" << elemInfo.dataOffset
<< ", elemInfo.extDataOffset=" << elemInfo.extDataOffset
<< ", elemData.Size=" << elemData.Size()
<< ", elemData.Info=" << this->elemData.ToString()
<< ", elem->Sizeof()=" << elem->Sizeof()
<< endl;
#endif
// store element
this->elemData.write((char*) elem, elem->Sizeof(), elemInfo.dataOffset);
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: pos=" << pos
<< ", elemData.Size=" << elemData.Size()
<< ", elemData.Info=" << this->elemData.ToString() << endl;
#endif
// store element flob(s)
// first of all, assign starting point of external data as offset
size_t offset = elemInfo.extDataOffset;
// iterate througth all flobs of this element
for (int i = 0; i < elem->NumOfFLOBs(); i++) {
// get current flob
Flob* flob = elem->GetFLOB(i);
// get current flob size
size_t size = flob->getSize();
// resize elem external data flob to the required size
this->elemExtData.resize(offset + size);
#ifdef RECORD_DEBUG
cerr << "Record::SetElement: pos=" << pos
<< ", FLOB #" << i << ", offset=" << offset
<< ", FLOB size=" << size
<< ", elemExtData size=" << this->elemExtData.Size() << endl;
#endif
// get data from current flob and store it
char* buffer = new char[size];
flob->read(buffer, size, 0);
this->elemExtData.write(buffer, size, offset);
delete[] buffer;
// update offset
offset += size;
}
// store element info into the array
this->elemInfoArray.Put(pos, elemInfo);
// update number of elements
this->noElements = pos + 1;
// update hash value
this->hashValue = (this->hashValue * HASH_MULTI) + elem->HashValue();
return true;
}
/*
~AppendElement~ appends the given element at the end of this record.
It is possible to append new elements at the end of the record. Therefore the
method ~SetElement~ is used. The position of the new element equates
the number of already existing elements.
*/
bool
Record::AppendElement(Attribute* elem,
const string& typeName, const string& elemName)
{
#ifdef RECORD_DEBUG
cerr << "Record::AppendElement("
<< (elem == NULL ? "NULL" : typeid(*elem).name())
<< ", " << typeName << ", " << elemName << ")" << endl;
#endif
// delegate work to the set element methode
return (this->SetElement(this->noElements, elem, typeName, elemName));
}
/*
7.1 Area with only abstract(virtual) ~Attribute~ methods
These methods need to be implemented to assure that the new data type can
be used in Secondo algebras. This includes the following methods:
* Sizeof
* Compare
* Adjacent
* Clone
* Hashvalue
* CopyFrom
*/
/*
~Sizeof~ returns the memory size that is needen to store a Record.
*/
size_t
Record::Sizeof() const
{
#ifdef RECORD_DEBUG
cerr << "Record::Sizeof(): sizeof=" << sizeof(Record) << endl;
#endif
return sizeof(Record);
}
/*
~Compare~ compares to records and returns an appropriate int value.
0 = objects are equal
$>$0= argument is smaller than ~this~
$<$0 = ~this~ is smaller than the argument
*/
int
Record::Compare(const Attribute* rhs) const
{
#ifdef RECORD_DEBUG
cerr << "Record::Compare()";
#endif
int cmp;
// validate the address of the right handle side
if (rhs == NULL) {
cmp = 1;
}
// compare the instance adresses
else if (this == rhs) {
cmp = 0;
} else {
// try to cast the given attribute type into a record type
const Record* record = dynamic_cast<const Record*>(rhs);
if (record == NULL) {
// cast failure, given attribute type isn't of type record
cmp = 1;
} else {
// compare by element size
cmp = this->GetNoElements() - record->GetNoElements();
// compare the elements if both have the same count of elements
// (loop until cmp != 0 or all elements are compared)
for (int i = 0; i < this->noElements && cmp == 0; i++) {
Attribute* attr1 = this->GetElement(i);
Attribute* attr2 = record->GetElement(i);
cmp = attr1->Compare(attr2);
attr1->DeleteIfAllowed();
attr2->DeleteIfAllowed();
}
}
}
#ifdef RECORD_DEBUG
cerr << ": cmp=" << cmp << endl;
#endif
// return the comare result
return cmp;
}
/*
~Adjacent~ returns true if the current record instance is adjecent to
annother instance of the same type otherwise returns false.
*/
bool
Record::Adjacent(const Attribute* attr) const
{
// this method returns always false as it is senseless in this context
return false;
}
/*
~Clone~ returns a depth copy of the record object.
*/
Attribute *
Record::Clone() const
{
#ifdef RECORD_DEBUG
cerr << "Record::Clone()" << endl;
#endif
Record* clone = new Record(0);
// copy all values from this to clone
clone->hashValue = this->hashValue;
clone->noElements = this->noElements;
clone->elemInfoArray.copyFrom(elemInfoArray);
clone->elemData.copyFrom(elemData);
clone->elemExtData.copyFrom(elemExtData);
return clone;
}
/*
~HashValue~ returns the hash value of the record.
The ~HashValue~ of the record starts with HASH\_INIT = 17 and changes
with every added element in this way of doing that the old ~HashValue~
is multiplied with a hash constant HASH\_MULTI = 57 and added to the
elements HashValue.
*/
size_t
Record::HashValue() const
{
#ifdef RECORD_DEBUG
cerr << "Record::NumOfFLOBs(): hashValue=" << this->hashValue << endl;
#endif
return this->hashValue;
}
/*
~CopyFrom~ copies the value of a given attribute into this object.
*/
void
Record::CopyFrom(const Attribute* attr)
{
#ifdef RECORD_DEBUG
cerr << "Record::CopyFrom("
<< (attr == NULL ? "NULL" : typeid(*attr).name()) << ")" << endl;
#endif
const Record* record = dynamic_cast<const Record*>(attr);
if (record != NULL) {
// copy all values from given record to this
this->hashValue = record->hashValue;
this->noElements = record->noElements;
elemInfoArray.copyFrom(record->elemInfoArray);
elemData.copyFrom(record->elemData);
elemExtData.copyFrom(record->elemExtData);
}
return;
}
/*
8.1 Area with only virtual ~Attribute~ methods
These methods need to be implemented for this data type because
a record owns FLOBs. This includes the following methods:
* NumOfFLOBs
* GetFLOB
* Print
*/
/*
~NumOfFLOBs~ returns the number of FLOBs used in this record class.
As a Record type needs a DBArray ~elemInfoArray~, a FLOB ~elemData~ and
a FLOB ~elemExData~, 3 is returned.
*/
int
Record::NumOfFLOBs() const
{
#ifdef RECORD_DEBUG
cerr << "Record::NumOfFLOBs(): numOfFLOBs=3" << endl;
#endif
return 3;
}
/*
~GetFLOB~ returns a pointer to one of the three available FLOB objects.
0 = ~elemInfoArray~
1 = ~elemData~
2 = ~elemExData~
*/
Flob*
Record::GetFLOB(const int i)
{
#ifdef RECORD_DEBUG
cerr << "Record::GetFLOB(" << i << ")" << endl;
#endif
// check preconditions
assert(i >= 0 && i < 3);
switch(i) {
case 0:
return &elemInfoArray;
case 1:
return &elemData;
case 2:
default:
return &elemExtData;
}
}
/*
~Print~ prints out useful information about the record for debugging purpose
as there are the ~hashValue~, ~noElements~, current elements.
*/
ostream&
Record::Print(ostream& os) const
{
os << "Record[" << endl;
// << "addr=" << (void*) this
// << ", hashValue=" << this->hashValue
// << ", noElements=" << this->noElements;
for (int pos = 0; pos < this->noElements; pos++) {
// get element info by position
ElemInfo elemInfo;
this->elemInfoArray.Get(pos, elemInfo);
Attribute* elem = GetElement(pos);
os << elemInfo.elemName << " : " ;
if(elem!=nullptr){
elem->Print(os);
elem->DeleteIfAllowed();
} else {
os << "null";
}
os << endl;
}
os << "]";
return os;
}
/*
9.1 Area with only static ~SECONDO~ methods
This area includes the standard ~SECONDO~ methods as there are:
* In
* Out
* Create
* Delete
* Clone
* Cast
* KindCheck
* SizeOfObj
* Property
*/
/*
~In~ function to create a record instance through a nested list.
According to the assigned type and value list the record is created.
Before that the lists are checked regarding there correctness in
the following order:
1 check whether the given type list is a record type list at all
2 check if in case of a non empty value list the two lists
have the same number of elements.
3 If the value list is empty an empty record with only the given
element types is created
In all other cases an appropriate error message is populated and
the creation aborts.
After preliminary research both lists will be run through parallel
in order to create the single elements. To simplify the parallel
run through a list iterator ~ListIterator~ class has been created.
More information about the iterator can be found in ~ListIterator.h~
document.
At the run through the following cases have to be differentiated:
First of all the current type list has to contain 2 elements. Example:
* case 1: (string (int int))
1 element name
1 element type list, with two integer values
1.1 algebraId
1.2 typeId
or
* case 2: (string ((int int) ...))
2 element name
2 element type as list, first list item contains
two integer values (see above)
The first possibility reflects a simple Secondo type whereas the second
illustrates a complex type likewise a ~record~.
The element name has to start with a capital letter.
The list elements have to conform these guidelines otherwise an error is
detected.
Once the list item is correct the new element is created. Therefore the
belonging algebraId and typeId is read out from the
Secondo catalogue.
Elements that are marked with NULL are created as well but are ~undefined~.
After the successful element creation this element is appended to the
record by AppendElement method.
The whole procedure is repeated as long as element information is
available in the given ~typeInfo~ list.
*/
Word
Record::In(const ListExpr typeInfo, const ListExpr instance,
const int errorPos, ListExpr& errorInfo, bool& correct)
{
#ifdef RECORD_DEBUG
cerr << "Record::In(" << nl->ToString(typeInfo) << ", "
<< nl->ToString(instance) << ", ..., ..., " << correct << ")" << endl;
#endif
Word w = SetWord(Address(0));
correct = false;
const string nullSymbol = "NULL";
if (Record::IsRecordTypeList(typeInfo)) {
// create an empty record instance
Record* record = new Record(0);
bool hasValueList;
if(listutils::isSymbolUndefined(instance)){ // an undefined record:
record->SetDefined(false);
correct = true;
return w;
}
// in case of a not empty value list:
// case 1: value list has to be a list
// case 2: type list and value list have to be of the same length
if (nl->ListLength(instance) == 0) {
hasValueList = false;
} else {
hasValueList = true;
// case 1
if (nl->IsAtom(instance)){
#ifdef RECORD_DEBUG
cerr << "Record::In: value list is of kind atom but "
"a list is expected!" << endl;
#endif
cmsg.inFunError("Record::In: Value list is of kind atom but "
"a list is expected! ");
return w;
}
// case 2
if (nl->ListLength(instance) != nl->ListLength(nl->Rest(typeInfo))) {
#ifdef RECORD_DEBUG
cerr << "Record::In: different number of elements in "
"type list and value list " << endl;
#endif
cmsg.inFunError("Record::In: different number of elements in "
"type list and Value list ");
return w;
}
}
// create type and value list iteratoren
ListIterator typeIter = nl->Rest(typeInfo);
ListIterator valueIter = instance;
// variables used inside the iteration loop
string elemName;
string elemTypeName;
int elemAlgebraId;
int elemTypeId;
Word elemWord;
Attribute* elem;
// iterate synchrone through the type and value list elements
while(typeIter.HasNext() && valueIter.HasNext()) {
// assign the current type list element
NList curType = typeIter.NextNList();
// assign the current value list element
ListExpr curValue;
if (hasValueList) {
curValue = valueIter.NextListExpr();
} else {
curValue = nl->OneElemList(nl->SymbolAtom(nullSymbol));
}
#ifdef RECORD_DEBUG
cerr << "Record::In: curType=" << curType.convertToString() << endl;
cerr << "Record::In: curValue=" << nl->ToString(curValue) << endl;
#endif
// the current type list has to contain 2 elements
// case 1: (string (int int))
// 1. element name
// 2. element type list with two integer values
// 2.1 algebraId
// 2.2 typeId
// case 2: (string ((int int) ...))
// 1. element name
// 2. element type as list, first list item contains
// two integer values
if ( !( curType.length() == 2
&& curType.second().length() == 2
&& curType.second().first().isInt()
&& curType.second().second().isInt())
&& !( curType.length() == 2
&& curType.second().length() > 0
&& curType.second().first().length() == 2
&& curType.second().first().first().isInt()
&& curType.second().first().second().isInt()))
{
#ifdef RECORD_DEBUG
cerr << "Record::In: wrong subtype info" << endl;
#endif
cmsg.inFunError("Record::In: wrong subtype info: " +
curType.convertToString());
return w;
}
// extraxt the element name from current type list element
elemName = curType.first().convertToString();
// extraxt the element type ids from current type list element
if (curType.second().first().isAtom()) {
// case 1
elemAlgebraId = curType.second().first().intval();
elemTypeId = curType.second().second().intval();
} else {
// case 2
elemAlgebraId = curType.second().first().first().intval();
elemTypeId = curType.second().first().second().intval();
}
// retrieve the type name of the ids from secondo catalog
SecondoCatalog* sc = SecondoSystem::GetCatalog();
elemTypeName = sc->GetTypeName(elemAlgebraId, elemTypeId);
// the element name has to start with a capital letter
if (isupper(elemName[0]) == 0) {
cmsg.inFunError("Record::In: element name has to start with a "
"capital letter: " + elemName);
return w;
}
// check if curValue is a Atom of value NULL (TEXT or Symbol)
// if true -> create a default object
if (nullSymbol.compare(nl->ToString(curValue)) == 0) {
elemWord = (am->CreateObj(elemAlgebraId, elemTypeId))
(curType.second().listExpr());
// cast the read type instance to an attribute
elem = static_cast<Attribute*>(elemWord.addr);
// make elem as undefined
elem->SetDefined(false);
} else {
// read element by registered IN function of the current type
elemWord = (am->InObj(elemAlgebraId, elemTypeId))
(curType.second().listExpr(),
curValue,
errorPos,
errorInfo,
correct);
// cast the read type instance to an attribute
elem = static_cast<Attribute*>(elemWord.addr);
}
// check of existing object elem
if (elem == NULL) {
cmsg.inFunError("Record::In: In function of type "
+ elemTypeName
+" for element "
+ elemName
+ " has delivered a NULL pointer for value "
+ nl->ToString(curValue));
return w;
}
// append the read attribute to the record and check the result
if (record->AppendElement(elem, elemTypeName, elemName) == false) {
cmsg.inFunError("Record::In: Cannot append element "
+ elemName
+ " of type "
+ elemTypeName);
elem->DeleteIfAllowed();
return w;
}
elem->DeleteIfAllowed();
elem=0;
} // End of: iterate synchrone through the type and value list elements
// set the created record as return value
w = SetWord(record);
correct = true;
} //IsRecordTypeList
#ifdef RECORD_DEBUG
cerr << "Record::In: correct=" << correct
<< ", w.addr=" << w.addr << endl;
#endif
return w;
}
/*
~Out~ function to create a nested list representation for a record type
object.
First of all the given ~typeInfo~ list has to be verified whether
the list is correct. Therfore an private method ~IsRecordTypeList~ (see below)
is used. After that it is checked if the record
is defined at all. If not an adequate information is given back as a
nested list.
If the record does not contain any element that is to say the record
is empty, an empty list is returned.
Finally the private method ~GetElementValueList~ (see below)
is called for every
remaining elment. This method creates a proper ~ListExpr~ for the
element which is added to the related record nested list.
*/
ListExpr
Record::Out(ListExpr typeInfo, Word value)
{
#ifdef RECORD_DEBUG
cerr << "Record::Out(" << nl->ToString(typeInfo) << ", ...)" << endl;
#endif
ListExpr result;
// check type info list expression first
if (Record::IsRecordTypeList(typeInfo) == false) {
// result = nl->GetErrorList();
result = nl->OneElemList(nl->StringAtom("type info failure"));
} if (value.addr == NULL) {
// result = nl->GetErrorList();
result = nl->OneElemList(nl->StringAtom("value is NULL failure"));
} else {
// cast the given address to a record instance
Record* record = static_cast<Record*>(value.addr);
#ifdef RECORD_DEBUG
cerr << "Record::Out: " << *record << endl;
#endif
if(record->IsDefined() == false) {
// record isn't defined
// result = nl->GetErrorList();
result = nl->SymbolAtom(Symbol::UNDEFINED());
} else if (record->noElements == 0) {
// record has no elements
result = nl->TheEmptyList();
} else {
// create list with value of the first element
result = nl->OneElemList(record->GetElementValueList(0));
// iterate through the remaining elements
ListExpr last = result;
for(int i = 1; i < record->noElements; i++) {
// append value of current element to the list
last = nl->Append(last, record->GetElementValueList(i));
}
}
}
#ifdef RECORD_DEBUG
cerr << "Record::Out: result=" << nl->ToString(result) << endl;
#endif
return result;
}
/*
~Create~ creates an empty, undefined ~record~ without subtype for
the Query Processor.
*/
Word
Record::Create(const ListExpr typeInfo)
{
#ifdef RECORD_DEBUG
cerr << "Record::Create(" << nl->ToString(typeInfo) << ")" << endl;
#endif
Word w = SetWord(Address(0));
// create a record instance
w = SetWord(Address(new Record(0)));
return w;
}
/*
~Delete~
*/
void
Record::Delete(const ListExpr typeInfo, Word& w)
{
#ifdef RECORD_DEBUG
cerr << "Record::Delete(" << nl->ToString(typeInfo)
<< ", " << w.addr << ")" << endl;
#endif
if (w.addr != NULL) {
Record* record = static_cast<Record*>(w.addr);
delete record;
w.addr = 0;
}
}
/*
~Close~
*/
void
Record::Close(const ListExpr typeInfo, Word& w)
{
#ifdef RECORD_DEBUG
cerr << "Record::Close(" << nl->ToString(typeInfo)
<< ", " << w.addr << ")" << endl;
#endif
if (w.addr != NULL) {
Record* record = static_cast<Record*>(w.addr);
delete record;
w.addr = 0;
}
}
/*
~Clone~
*/
Word
Record::Clone(const ListExpr typeInfo, const Word& w)
{
#ifdef RECORD_DEBUG
cerr << "Record::Clone(" << nl->ToString(typeInfo)
<< ", " << w.addr << ")" << endl;
#endif
Word clone = SetWord(Address(0));
if (w.addr != NULL) {
Record* record = static_cast<Record*>(w.addr);
clone = SetWord(record->Clone());
}
return clone;
}
/*
~Cast~
*/
void*
Record::Cast(void* addr)
{
#ifdef RECORD_DEBUG
cerr << "Record::Cast(" << addr << ")" << endl;
#endif
return new (addr) Record();
}
/*
Kind Checking Function
This function checks whether the type constructor is correct applied.
A Record ~typeInfo~ has to look like: (record (...)) thus a list of minimum
two elements where the first element has to be keyword ~record~.
All remaining elements in the ~typeInfo~ list can be simple elements with a
simple type or can be records again. Thus they have to look like
* case 1: (string string)
1 element name as string
2 element type name as string
* case 2: (string (list)), e.g. record in record
1 element name as string
2 element type as list
The type name has to be of kind ~DATA~.
*/
bool
Record::KindCheck(ListExpr typeInfo, ListExpr& errorInfo)
{
if(nl->ListLength(typeInfo)<2){ // symbol record and at least one element
return false;
}
if(!listutils::isSymbol(nl->First(typeInfo), Record::BasicType())){
return false;
}
ListExpr rest = nl->Rest(typeInfo);
// rest must be a list of pairs (symbol DATA)
set<string> usedNames;
while(!nl->IsEmpty(rest)){
ListExpr attr = nl->First(rest);
rest = nl->Rest(rest);
if(!nl->HasLength(attr,2)){
cmsg.typeError("invalid field description in record");
return false;
}
ListExpr attrName = nl->First(attr);
if(!listutils::isSymbol(attrName)){
cmsg.typeError("invalid field name in record");
return false;
}
string name = nl->SymbolValue(attrName);
if(usedNames.find(name)!=usedNames.end()){
cmsg.typeError("field name " + name + " is used twice ");
return false;
}
usedNames.insert(name);
string errmsg;
if(!SecondoSystem::GetCatalog()->IsValidIdentifier(name,errmsg,true)){
cmsg.typeError("field name " + name + " is not allowed: " + errmsg);
return false;
}
// ok, check the type of this field
if(!listutils::isDATA(nl->Second(attr))){
cmsg.typeError("field " + name + " is not in kind DATA");
return false;
}
}
return true;
}
/*
~SizeOfObj~
*/
int
Record::SizeOfObj()
{
#ifdef RECORD_DEBUG
cerr << "Record::SizeOfObj(): sizeOfObj=" << sizeof(Record) << endl;
#endif
return sizeof(Record);
}
/*
~Property~
*/
ListExpr
Record::Property()
{
#ifdef RECORD_DEBUG
cerr << "Record::Property()" << endl;
#endif
return (nl->TwoElemList(
nl->FiveElemList(nl->StringAtom("Signature"),
nl->StringAtom("Example Type List"),
nl->StringAtom("List Rep"),
nl->StringAtom("Example List"),
nl->StringAtom("Remarks")),
nl->FiveElemList(nl->StringAtom("-> DATA"),
nl->StringAtom("(" + Record::BasicType() +
"(name string) "
"(age int) (amount real))"),
nl->StringAtom("(stringElem intElem realElem)"),
nl->StringAtom("(\"Meyer\" 42 1023.08)"),
nl->StringAtom("All elements have to be of type"
" attribute."))));
}
/*
10.1 Area with private record methods
This area includes all private record methods as there are:
* CopyFlob
* CopyElemInfos
* GetElementValueList
* IsRecordTypeList
*/
/*
~GetElementValueList~ returns a proper ~ListExpr~ for the element
at the given record position.
This is needed in ~Out~ (see above).
*/
ListExpr
Record::GetElementValueList(int pos) const
{
// check preconditions
assert(pos >=0);
assert(pos < this->elemInfoArray.Size());
// get element info by position
ElemInfo elemInfo;
this->elemInfoArray.Get(pos, elemInfo);
// get element by position
Attribute* elem = this->GetElement(pos);
// create type info list for the element
ListExpr subtypeInfo = nl->TwoElemList(nl->IntAtom(elemInfo.algebraId),
nl->IntAtom(elemInfo.typeId));
// return list expression for the requested element
ListExpr res = (am->OutObj(elemInfo.algebraId, elemInfo.typeId))
(subtypeInfo, SetWord(elem));
delete elem;
return res;
}
/*
~IsRecordTypeList~ checks if the given type list is a record type list.
This is needed in several places like ~In~, ~Out~, ~Save~, ~Open~....
Three type lists a common nowadays:
1 (72 0)
2 ((72 0))
3 ((72 0) ...)
With this information a crosscheck is done with the Secondo catalogue.
*/
bool
Record::IsRecordTypeList(ListExpr typeInfo)
{
bool isRecord = false;
NList list = typeInfo;
#ifdef RECORD_DEBUG
cerr << "Record::IsRecordTypeList(" << nl->ToString(typeInfo) << ")" << endl;
#endif
// check type info list expression, it has to be
// "(72 0)"
// or "((72 0))"
// or "((72 0) ...)"
if (list.length() >= 1) {
// extact the algebra and type id from the given list
int listAlgebraId;
int listTypeId;
if (list.first().isAtom()) {
// case: "(72 0)"
listAlgebraId = list.first().intval();
listTypeId = list.second().intval();
} else {
// case: "((72 0))" or "((72 0) ...)"
listAlgebraId = list.first().first().intval();
listTypeId = list.first().second().intval();
}
// retrieve record ids from secondo cataloge
int scAlgebraId;
int scTypeId;
SecondoCatalog* sc = SecondoSystem::GetCatalog();
sc->GetTypeId(Record::BasicType(), scAlgebraId, scTypeId);
// compare the list ids with secondo record ids
if (listAlgebraId == scAlgebraId && listTypeId == scTypeId) {
isRecord = true;
}
}
return isRecord;
}