diff options
Diffstat (limited to 'relblobif')
-rw-r--r-- | relblobif/Makefile.am | 52 | ||||
-rw-r--r-- | relblobif/blobtile.file.pgc | 402 | ||||
-rw-r--r-- | relblobif/blobtile.hh | 160 | ||||
-rw-r--r-- | relblobif/blobtile.pgc | 765 | ||||
-rw-r--r-- | relblobif/blobtilecommon.cc | 172 | ||||
-rw-r--r-- | relblobif/dbtile.cc | 329 | ||||
-rw-r--r-- | relblobif/dbtile.hh | 222 | ||||
-rw-r--r-- | relblobif/filestorageexception.hh | 42 | ||||
-rw-r--r-- | relblobif/ifilestorage.hh | 56 | ||||
-rw-r--r-- | relblobif/inlinetile.cc | 259 | ||||
-rw-r--r-- | relblobif/inlinetile.hh | 176 | ||||
-rw-r--r-- | relblobif/simplefilestorage.cc | 178 | ||||
-rw-r--r-- | relblobif/simplefilestorage.hh | 66 | ||||
-rw-r--r-- | relblobif/test/Makefile | 65 | ||||
-rw-r--r-- | relblobif/test/README.txt | 5 | ||||
-rw-r--r-- | relblobif/test/blobdump.c | 323 | ||||
-rw-r--r-- | relblobif/test/blobdump.pgc | 315 | ||||
-rw-r--r-- | relblobif/test/dblob.C | 242 | ||||
-rw-r--r-- | relblobif/tileid.hh | 36 |
19 files changed, 3865 insertions, 0 deletions
diff --git a/relblobif/Makefile.am b/relblobif/Makefile.am new file mode 100644 index 0000000..36b7bf8 --- /dev/null +++ b/relblobif/Makefile.am @@ -0,0 +1,52 @@ +# -*-Makefile-*- (for Emacs) +# +# This file is part of rasdaman community. +# +# Rasdaman community 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 3 of the License, or +# (at your option) any later version. +# +# Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +# +# Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +# rasdaman GmbH. +# +# For more information please see <http://www.rasdaman.org> +# or contact Peter Baumann via <baumann@rasdaman.com>. +# +# MAKEFILE FOR: +# relblobif +# +# COMMENTS: +# uses Oracle precompiler +# +################################################################## + +AM_CXXFLAGS=@BASEDBCXXFLAGS@ +AM_LDFLAGS=@BASEDBLDFLAGS@ + +.SUFFIXES= .@EMBEDDEDSQLEXT@ .@EMBEDDEDSQLOUT@ +.@EMBEDDEDSQLEXT@.@EMBEDDEDSQLOUT@: + $(EMBEDDEDSQLPRECOMPILER) $@ $< + + +noinst_LIBRARIES=librelblobif.a +librelblobif_a_SOURCES= blobtile.hh \ + dbtile.cc dbtile.hh inlinetile.cc inlinetile.hh \ + blobtilecommon.cc tileid.hh +EXTRA_librelblobif_a_SOURCES=blobtile.pgc +librelblobif_a_LIBADD=blobtile.$(OBJEXT) +librelblobif_a_DEPENDENCIES=blobtile.$(OBJEXT) + +BUILT_SOURCES=blobtile.@EMBEDDEDSQLOUT@ + +CLEANFILES=blobtile.@EMBEDDEDSQLOUT@ client.bm client.dbg client.log ir.out \ + blobtile.$(OBJEXT) + diff --git a/relblobif/blobtile.file.pgc b/relblobif/blobtile.file.pgc new file mode 100644 index 0000000..0547fdf --- /dev/null +++ b/relblobif/blobtile.file.pgc @@ -0,0 +1,402 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" +// This is -*- C++ -*- +/************************************************************* + * + * + * PURPOSE: + * implements blobtile interface using the PostgreSQL DBMS. + * + * + * COMMENTS: + * - uses LO blob method, this has been tested to be faster than bytea + * - RMDBG macros generate so weird output that I added DEBUG macros + * although this is very ugly (2 trace facilities in parallel) + * - exceptions thrown are r_Error, either directly or through common + * function generateException() (reladminif) which throws r_Ebase_dbms + * + ************************************************************/ + +using namespace std; + +static const char rcsid[] = "@(#)blobif,BLOBTile: $Id: blobtile.ec,v 1.8 2003/12/27 23:19:03 rasdev Exp $"; + +#include <stdio.h> +#include <stdlib.h> /* atoi */ + +// PG stuff: +#include "libpq-fe.h" /* C interface to PgSQL */ +#include "libpq/libpq-fs.h" /* large object (lo) api */ + +// simple trace facility +// trace macros are activated through externally defined compile variable DEBUG +#include "debug-srv.hh" + +// general embedded SQL related definitions +EXEC SQL include "../reladminif/sqlglobals.h"; + +// libpg connection maintenance +// must have been initiated before use (see databasif.pgc) +extern PGconn *pgConn; + +// all the DBMS independent code is factored out and +// will be included in the resulting .c file +#include "blobtile.hh" +#include "simplefilestorage.hh" +#include "raslib/error.hh" +#include "externs.h" +#include "raslib/rmdebug.hh" +#include "sqlerror.hh" +#include "tileid.hh" +#include "inlinetile.hh" +#include "objectbroker.hh" +#include "dbref.hh" + + +IFileStorage* initFileStorage() { + SimpleFileStorage *fileStorage; + char *path = getenv("RASDATA"); + if (path == NULL) + path="/tmp"; + fileStorage = new SimpleFileStorage(path); + return fileStorage; +} + +// update blob in ras_tiles table, identified by variable myOId (from blobtile.cc), update map ref +void +BLOBTile::updateInDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "updateInDb() " << myOId); + ENTER( "BLOBTile::updateInDb" ); + if (fileStorage == NULL) + fileStorage = initFileStorage(); + + Oid tile; + long indbmyoId = 0; + long indbmyoid = 0; + short dataformat = 0; + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; + + // (1) --- get tuple + dataformat = dataFormat; + indbmyoid = myOId.getCounter(); + + fileStorage->update(cells, size, indbmyoid); + + // alternative solution for now: + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "UPDATE RAS_TILES SET DataFormat = %d WHERE BlobId = %d", dataformat, indbmyoid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::updateInDb() libpq 'update dataformat' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + + // (4) --- update map ref + DBObject::updateInDb(); + + LEAVE( "BLOBTile::updateInDb, myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "updateInDb() " << myOId); +} // updateInDb() + +// insert new blob into ras_tiles table, update map ref +// tuple is identified by blobtile.cc var myOId +// data is taken from buffer 'cells' containing 'size' bytes +void +BLOBTile::insertInDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "insertInDb() " << myOId); + ENTER( "BLOBTile::insertInDb" ); + + if (fileStorage == NULL) + fileStorage = initFileStorage(); + + Oid blobOid; + long indbmyOId2; + short dataformat2; + Oid tile2; + dataformat2 = dataFormat; + + // prelim: + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; + + indbmyOId2 = myOId.getCounter(); + TALK( "myOId.getCounter = " << indbmyOId2 ); + + fileStorage->insert(cells, size, indbmyOId2); + + // (2) --- insert tuple into db + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "INSERT INTO RAS_TILES ( BlobId, DataFormat, Tile) VALUES ( %d, %d, %d )", indbmyOId2, dataformat2, indbmyOId2 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::insertInDb() libpq 'insert' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + // FIXME: other sources have here an updateInDb(); + + // (3) --- update map ref + DBObject::insertInDb(); + + LEAVE( "BLOBTile::insertInDb(), myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "insertInDb() " << myOId); +} // insertInDb() + +// delete one tuple from ras_tiles table, update map ref +// tuple is identified by blobtile.cc var myOId +void +BLOBTile::deleteFromDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "deleteFromDb() " << myOId); + ENTER( "BLOBTile::deleteFromDb" ); + if (fileStorage == NULL) + fileStorage = initFileStorage(); + + long blobId; // blob tuple primary key + Oid blobOid; // blob oid "ptr" + + // get counter value (primary key) from oid + blobId = myOId.getCounter(); + + fileStorage->remove(blobId); + PGresult *pgResult = NULL; + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE BlobId = ", blobOid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::deleteFromDb() libpq 'delete tuple' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + // FIXME: other sources have here an updateInDb(); + + // update map ref + DBObject::deleteFromDb(); + + LEAVE( "BLOBTile::deleteFromDb, myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "deleteFromDb() " << myOId); +} + +// delete a range of tuple(s) from ras_tiles table, update map ref +// tuples are identified by target and a range +void +BLOBTile::kill(const OId& target, unsigned int range) +{ + RMDBGENTER(0, RMDebug::module_blobif, "BLOBTile", "kill(" << target << ", " << range <<")"); + ENTER( "BLOBTile::kill, target=" << target << ", range=" << range ); + + long indbmyOId5; + long indbmyOId6; + long blobId; // blob tuple primary key + Oid blobOid; // blob oid "ptr" + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; // query result + + DBObject* targetobj = NULL; + IFileStorage *fileStorage = initFileStorage(); + + if (range == 0) // single tuple + { + // (1) --- delete form cache + targetobj = ObjectBroker::isInMemory(target); + if (targetobj) + { + targetobj->setPersistent(false); + } + + // (2) --- free blob + indbmyOId5 = target.getCounter(); + fileStorage->remove(indbmyOId5); + + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE BlobId = %d", indbmyOId5 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::kill() libpq 'delete single' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) == 0) + { + if (target.getType() == OId::INLINETILEOID) + { + InlineTileId t(target); + if (!t.is_null()) + t->setPersistent(false); + } + //else: this tile has been deleted before + } + PQclear( pgResult ); + } + else + { + // (1) --- iterate over cache and remove + DBObjectPMap& mapRef = ObjectBroker::getMap(target.getType()); + DBObjectPMap::iterator it = mapRef.begin(); + DBObjectPMap::iterator theEnd = mapRef.end(); + OId end(target.getCounter() + range, target.getType()); + while (it != theEnd) + { + if (target <= (const OId&)(*it).first && (*it).first <= (const OId&)end) + { + (*it).second->setPersistent(false); + } + } + + // (2) --- iterate over db and remove + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DECLARE DeleteLoop CURSOR FOR SELECT Tile FROM RAS_TILES WHERE %d <= BlobId AND BlobId <= %d", indbmyOId5, indbmyOId6 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::kill() libpq 'select for deleteLoop' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + + // loop over elements & delete each one + do + { + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "FETCH NEXT FROM DeleteLoop" ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::kill() libpq 'fetch next' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::kill() libpq 'fetch' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + generateException(); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract value from result + + // delete blob identified by blobOid + fileStorage->remove(blobOid); + // FIXME: what about errors? continue?? + } while (PQresultStatus(pgResult) != PGRES_TUPLES_OK); + PQclear( pgResult ); + + // (3) --- delete tuples in db + indbmyOId5 = target.getCounter(); + indbmyOId6 = end.getCounter(); + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE %d <= BlobId AND BlobId <= %d", indbmyOId5, indbmyOId6 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::kill() libpq 'delete range' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + } + + LEAVE( "BLOBTile::kill" ); + RMDBGEXIT(0, RMDebug::module_blobif, "BLOBTile", "kill(" << target << " " << target.getType() << ")"); +} + +// read tuple from ras_tiles, identified by blobtile.cc var myOId +// allocates necessary mem into ptr 'cells' and fills it; must be freed elsewhere +// external var 'size' is set to the number of bytes read +void +BLOBTile::readFromDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "readFromDb() " << myOId); + ENTER( "BLOBTile::readFromDb" ); + if (fileStorage == NULL) + fileStorage = initFileStorage(); + +#ifdef RMANBENCHMARK + DBObject::readTimer.resume(); +#endif + + Oid blobOid; + long indbmyOId3; + short dataformat3; + short indicatorr3; + PGresult *pgResult = NULL; // PostgreSQL call return values + + indbmyOId3 = myOId.getCounter(); + + // (1) --- access tuple + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile, DataFormat FROM RAS_TILES WHERE BlobId = %d", indbmyOId3 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::readFromDb() libpq 'select' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::readFromDb() libpq 'select' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + throw r_Error(r_Error::r_Error_ObjectUnknown); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract 1st value from result + dataformat3 = atoi( PQgetvalue( pgResult, 0, 1 ) ); // extract 2nd value from result + PQclear( pgResult ); + + // we have a tuple, extract data format + dataFormat = (r_Data_Format)dataformat3; + currentFormat = (r_Data_Format)dataformat3; + TALK( "got dataFormat " << dataFormat ); + + fileStorage->retrieve(indbmyOId3, &cells, &size); + + RMDBGIF(20, RMDebug::module_blobif, "BLOBTileOutput", for (int a = 0; a < size; a++)\ + RMInit::dbgOut << " " << hex << (int)(cells[a]); RMInit::dbgOut << dec << endl;) + + DBObject::readFromDb(); + +#ifdef RMANBENCHMARK + DBObject::readTimer.pause(); +#endif + + LEAVE( "BLOBTile::readFromDb" ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "readFromDb() " << myOId); +} + diff --git a/relblobif/blobtile.hh b/relblobif/blobtile.hh new file mode 100644 index 0000000..227ea1e --- /dev/null +++ b/relblobif/blobtile.hh @@ -0,0 +1,160 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +// -*-C++-*- (for Emacs) + +/************************************************************* + * + * + * PURPOSE: + * The Blobtile class is used to store the tiles in the database. + * + * + * COMMENTS: + * + ************************************************************/ + +#ifndef _BLOBTILE_HH_ +#define _BLOBTILE_HH_ + +class OId; +class r_Error; + +#include "dbtile.hh" +#include "raslib/mddtypes.hh" + +//@ManMemo: Module: {\bf relblobif}. + +/*@Doc: + +BLOBTile is the persistent class for storing the contents of MDD tiles +in the database. Each instance represents the contents of a tile of a MDD Object +from the database. BLOBTiles are just arrays of unsigned characters. +In main memory they are encapsulated in the class \Ref{PersTile}. + +At the moment a BLOBTile is loaded into main memory, when it is +accessed the first time. This usually happens, when the RasDaMan DBMS +accesses the contents of a \Ref{PersTile}. + +{\bf Interdependencies} + +BLOBTile is an interface class with the base DBMS. It is, therefore, +highly dependent on the base DBMS used. +*/ + +class BLOBTile : public DBTile + { + public: + //@Man: constructors + //@{ + BLOBTile(r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new empty BLOBTile and gets an id for it. + */ + + BLOBTile(const OId& BlobId) throw (r_Error); + /*@Doc: + constructs a BlobTile out of the database + */ + + BLOBTile(r_Bytes newSize, char c = 0, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new BLOBTile of size newSize filled with c. + */ + + BLOBTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat = r_Array); + /*@Doc: + Constructs a new BLOB Tile of size newSize filled with the repeated + char array pat of size patSize. If after filling some chars are + left, they are filled with 0 + */ + /*@ManMemo: constructs a new BLOB Tile with the char array newCells + with newSize elements as contents. */ + + BLOBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new BLOBTile of size newSize filled with the contents of newCells. + */ + + BLOBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat, const OId& myOId); + /*@Doc: + constructs a new BLOBTile of size newSize filled with the contents of newCells. + the oid will be assigned to this blob. used by regular computed index. + */ + + BLOBTile(const OId& BlobId, r_Bytes newSize, r_Data_Format newFmt); + /*@Doc: + constructs a new BLOBTile of size newSize filled with zeros. + the tile will think it is not modified and also not in the db but persistent. + this is used by the rc index. + */ + + //@} + + virtual ~BLOBTile(); + /*@Doc: + validates the object. deletes it cells. + */ + + static void kill(const OId& target, unsigned int range = 0); + /*@Doc: + delete a blobtile without loading it first into memory. + is used by the indexes. + delete the blobtile and range consecutive tiles. + */ + + static r_Bytes BLOBBufferLength; + /*@Doc: + info on the length of the BLOBBuffer + */ + + protected: + + virtual void updateInDb() throw (r_Error); + /*@Doc: + update the contents of a Tile in the db + */ + + virtual void insertInDb() throw (r_Error); + /*@Doc: + inserts the Blob into the db. + */ + + virtual void readFromDb() throw (r_Error); + /*@Doc: + read blob from db into blobtile + */ + + virtual void deleteFromDb() throw (r_Error); + /*@Doc: + deletes a blob from TILES, sets size to 0 and flags to -1 + */ + + private: + static char* BLOBBuffer; + /*@Doc: + for writing into the DB. currently not needed by oracle. + */ + + }; + +#endif diff --git a/relblobif/blobtile.pgc b/relblobif/blobtile.pgc new file mode 100644 index 0000000..7f3aef4 --- /dev/null +++ b/relblobif/blobtile.pgc @@ -0,0 +1,765 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" +// This is -*- C++ -*- +/************************************************************* + * + * + * PURPOSE: + * implements blobtile interface using the PostgreSQL DBMS. + * + * + * COMMENTS: + * - uses LO blob method, this has been tested to be faster than bytea + * - RMDBG macros generate so weird output that I added DEBUG macros + * although this is very ugly (2 trace facilities in parallel) + * - exceptions thrown are r_Error, either directly or through common + * function generateException() (reladminif) which throws r_Ebase_dbms + * + ************************************************************/ + +using namespace std; + +static const char rcsid[] = "@(#)blobif,BLOBTile: $Id: blobtile.ec,v 1.8 2003/12/27 23:19:03 rasdev Exp $"; + +#include <stdio.h> +#include <stdlib.h> /* atoi */ + +// PG stuff: +#include "libpq-fe.h" /* C interface to PgSQL */ +#include "libpq/libpq-fs.h" /* large object (lo) api */ + +// simple trace facility +// trace macros are activated through externally defined compile variable DEBUG +#include "debug-srv.hh" + +// general embedded SQL related definitions +EXEC SQL include "../reladminif/sqlglobals.h"; + +// libpg connection maintenance +// must have been initiated before use (see databasif.pgc) +extern PGconn *pgConn; + +// all the DBMS independent code is factored out and +// will be included in the resulting .c file +//#include "blobtile.cc" +#include "blobtile.hh" +#include "raslib/rmdebug.hh" +#include "reladminif/objectbroker.hh" +#include "reladminif/dbref.hh" +#include "reladminif/sqlerror.hh" +#include "inlinetile.hh" + +// update blob in ras_tiles table, identified by variable myOId (from blobtile.cc), update map ref +void +BLOBTile::updateInDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "updateInDb() " << myOId); + ENTER( "BLOBTile::updateInDb" ); + +#ifdef NOTYET // should be in future +/* + EXEC SQL BEGIN DECLARE SECTION; + Oid tile; + long indbmyoId; + long indbmyoid = 0; + short dataformat = 0; + EXEC SQL END DECLARE SECTION; + + dataformat = dataFormat; + indbmyoid = myOId.getCounter(); + + // (1) --- get tuple + TALK( "EXEC SQL SELECT Tile INTO :tile FROM RAS_TILES WHERE BlobId = " << indbmyoid ); + EXEC SQL SELECT TILE + INTO :tile + FROM RAS_TILES + WHERE BlobId = :indbmyoid; + if (SQLCODE < 0) + { + RMInit::logOut << "BLOBTile::updateInDb(): error: cannot get blob, SQLCODE " << SQLCODE << endl; + LEAVE( "BLOBTile::updateInDb(): error: cannot get blob, SQLCODE " << SQLCODE ); + check("Select blob oid"); + generateException(); + } + else if (SQLCODE == SQLNODATAFOUND) + { + RMInit::logOut << "BLOBTile::updateInDb(): error: object not in database" << endl; + LEAVE( "BLOBTile::updateInDb(): error: object not in database" ); + throw r_Error(r_Error::r_Error_ObjectUnknown); + } +*/ +#endif //NOTYET + // alternative solution for now: + Oid tile; + long indbmyoId = 0; + long indbmyoid = 0; + short dataformat = 0; + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; + + // (1) --- get tuple + dataformat = dataFormat; + indbmyoid = myOId.getCounter(); + + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile FROM RAS_TILES WHERE BlobId = %d", indbmyoid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::updateInDb() libpq 'select tile' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::updateInDb() libpq 'select' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + generateException(); + } + tile = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract value from result + PQclear( pgResult ); + + // (2) --- open, write, close blob + TALK( "lo_open()" ); + int fd = lo_open( pgConn, tile, INV_WRITE ); // no error code mentioned in manual + TALK( "lo_write() for fd " << fd << " and " << size << " bytes" ); + int loResult = lo_write( pgConn, fd, cells, size ); + if (loResult < 0) + { + RMInit::logOut << "BLOBTile::updateInDb() cannot write blob, error: " << loResult << endl; + LEAVE( "BLOBTile::updateInDb: cannot write blob, error " << loResult ); + generateException(); + } + else if (loResult != size) // did not get all + { + RMInit::dbgOut << "BLOB (" << myOId << ") insert: wrote " << loResult << " instead of " << size << " bytes" << endl; + LEAVE( "BLOBTile::updateInDb: wrote " << loResult << " instead of " << size << " bytes" ); + generateException(); + } + TALK( "lo_close()" ); + loResult = lo_close( pgConn, fd ); + if (loResult < 0) // can't close, don't know if data are written + { + RMInit::logOut << "BLOBTile::updateInDb() ignoring lo_close() error: " << PQerrorMessage(pgConn) << endl; + LEAVE( "BLOBTile::updateInDb: cannot lo_close(): " << PQerrorMessage(pgConn) ); + generateException(); + } + +#ifdef NOTYET // should be in future +/* + // (3) --- update data format + // NOTE: we assume the blob already exists and do not change the oid + TALK( "EXEC SQL UPDATE RAS_TILES SET DataFormat = " << dataformat << " WHERE BlobId = " << indbmyoid ); + EXEC SQL UPDATE RAS_TILES + SET DataFormat = :dataformat + WHERE BlobId = :indbmyoid; + if (check("Update Dataformat")) + generateException(); +*/ +#endif //NOTYET + // alternative solution for now: + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "UPDATE RAS_TILES SET DataFormat = %d WHERE BlobId = %d", dataformat, indbmyoid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::updateInDb() libpq 'update dataformat' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + + // (4) --- update map ref + DBObject::updateInDb(); + + LEAVE( "BLOBTile::updateInDb, myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "updateInDb() " << myOId); +} // updateInDb() + +// insert new blob into ras_tiles table, update map ref +// tuple is identified by blobtile.cc var myOId +// data is taken from buffer 'cells' containing 'size' bytes +void +BLOBTile::insertInDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "insertInDb() " << myOId); + ENTER( "BLOBTile::insertInDb" ); + +/* +#ifdef NOTYET + EXEC SQL BEGIN DECLARE SECTION; +#endif NOTYET +*/ + Oid blobOid; + long indbmyOId2; + short dataformat2; + Oid tile2; +/* +#ifdef NOTYET + EXEC SQL END DECLARE SECTION; +#endif NOTYET +*/ + dataformat2 = dataFormat; + + // prelim: + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; + + indbmyOId2 = myOId.getCounter(); + TALK( "myOId.getCounter = " << indbmyOId2 ); + + // (1) --- open, write, close blob; generates new 'oid' for subsequent storage in tuple + TALK( "lo_creat()" ); + blobOid = lo_creat( pgConn, INV_READ|INV_WRITE ); // create -- not clear what INV_* here means so indicate all + if (blobOid == 0) + { + RMInit::logOut << "BLOBTile::insertInDb() cannot create blob, error: " << PQerrorMessage(pgConn) << endl; + LEAVE( "BLOBTile::insertInDb(): cannot create blob, error " << PQerrorMessage(pgConn) ); + generateException(); + } + TALK( "lo_open() for oid " << blobOid ); + int fd = lo_open( pgConn, blobOid, INV_WRITE ); // no error code indicated, 0 seems to be no error + TALK( "lo_write() for fd " << fd << " and " << size << " bytes" ); + int loResult = lo_write( pgConn, fd, cells, size ); + if (loResult < 0) + { + RMInit::logOut << "BLOBTile::insertInDb() cannot write blob, error: " << PQerrorMessage(pgConn) << endl; + LEAVE( "BLOBTile::insertInDb(): cannot write blob, error " << PQerrorMessage(pgConn) ); + generateException(); + } + else if (loResult != size) // did not get all + { + RMInit::dbgOut << "BLOB (" << myOId << ") insert: wrote " << loResult << " instead of " << size << " bytes" << endl; + LEAVE( "BLOBTile::insertInDb(): wrote " << loResult << " instead of " << size << " bytes" ); + generateException(); + } + TALK( "lo_close()" ); + loResult = lo_close( pgConn, fd ); + if (loResult < 0) // cannot close, don't know if data are there + { + RMInit::logOut << "BLOBTile::insertInDb() ignoring lo_close() error: " << PQerrorMessage(pgConn) << endl; + LEAVE( "BLOBTile::insertInDb(): ignoring lo_close() error: " << PQerrorMessage(pgConn) ); + generateException(); + } + +/* +#ifdef NOTYET + // (2) --- insert tuple into db + TALK( "EXEC SQL INSERT INTO RAS_TILES ( BlobId, DataFormat, Tile ) VALUES (" << indbmyOId2 << "," << dataformat2 << ", " << blobOid << ")" ); + EXEC SQL INSERT INTO RAS_TILES ( BlobId, DataFormat, Tile) + VALUES ( :indbmyOId2, :dataformat2, :blobOid ); +#endif NOTYET +*/ + // alternative solution for now: + // (2) --- insert tuple into db + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "INSERT INTO RAS_TILES ( BlobId, DataFormat, Tile) VALUES ( %d, %d, %d )", indbmyOId2, dataformat2, blobOid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::insertInDb() libpq 'insert' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + // FIXME: other sources have here an updateInDb(); + + // (3) --- update map ref + DBObject::insertInDb(); + + LEAVE( "BLOBTile::insertInDb(), myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "insertInDb() " << myOId); +} // insertInDb() + +// delete one tuple from ras_tiles table, update map ref +// tuple is identified by blobtile.cc var myOId +void +BLOBTile::deleteFromDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "deleteFromDb() " << myOId); + ENTER( "BLOBTile::deleteFromDb" ); + +/* +#ifdef NOTYET + EXEC SQL BEGIN DECLARE SECTION; +#endif NOTYET +*/ + long blobId; // blob tuple primary key + Oid blobOid; // blob oid "ptr" +/* +#ifdef NOTYET + EXEC SQL END DECLARE SECTION; +#endif NOTYET +*/ + + // get counter value (primary key) from oid + blobId = myOId.getCounter(); + +/* +#ifdef NOTYET + // (1) --- get tile tuple + TALK( "EXEC SQL SELECT Tile INTO :blobOid FROM RAS_TILES WHERE BlobId = " << blobId ); + EXEC SQL SELECT Tile INTO :blobOid FROM RAS_TILES WHERE BlobId = :blobId; + if (SQLCODE < 0) + { + LEAVE( "BLOBTile::deleteFromDb(): Fatal error: cannot find tuple for oid " << blobId ); + check("BLOBTile::deleteFromDb() - find tuple\0"); + generateException(); + } +#endif NOTYET +*/ + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile FROM RAS_TILES WHERE BlobId = %d", blobId ); + TALK( pgQuery ); + PGresult *pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult)) + { + LEAVE( "BLOBTile::deleteFromDb() libpq 'select tile' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::deleteFromDb() libpq 'select' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + generateException(); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract value from result + PQclear( pgResult ); + + // FIXME: other sources have here an updateInDb(); + + // (2) --- delete blob identified by blobOid + TALK( "lo_unlink()" ); + int loResult = lo_unlink( pgConn, blobOid ); + if (loResult < 0) + { + LEAVE( "BLOBTile::deleteFromDb(): cannot unlink blob with id '" << blobOid << "' for oid " << blobId << PQerrorMessage(pgConn) ); + generateException(); + } + +/* +#ifdef NOTYET + // (3) --- delete tuple from table + TALK( "EXEC SQL DELETE FROM RAS_TILES WHERE BlobId = " << blobId ); + EXEC SQL DELETE FROM RAS_TILES WHERE BlobId = :blobId; + if (SQLCODE < 0) + { + LEAVE( "BLOBTile::deleteFromDb: Fatal error: cannot delete tuple for oid " << blobId << ", error: " << SQLCODE ); + check("BLOBTile::deleteFromDb() - delete tuple\0"); + generateException(); + } +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE BlobId = ", blobOid ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::deleteFromDb() libpq 'delete tuple' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + // FIXME: other sources have here an updateInDb(); + + // update map ref + DBObject::deleteFromDb(); + + LEAVE( "BLOBTile::deleteFromDb, myOId=" << myOId ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "deleteFromDb() " << myOId); +} + +// delete a range of tuple(s) from ras_tiles table, update map ref +// tuples are identified by target and a range +void +BLOBTile::kill(const OId& target, unsigned int range) +{ + RMDBGENTER(0, RMDebug::module_blobif, "BLOBTile", "kill(" << target << ", " << range <<")"); + ENTER( "BLOBTile::kill, target=" << target << ", range=" << range ); + +/* +#ifdef NOTYET + EXEC SQL BEGIN DECLARE SECTION; +#endif NOTYET +*/ + long indbmyOId5; + long indbmyOId6; + long blobId; // blob tuple primary key + Oid blobOid; // blob oid "ptr" +/* +#ifdef NOTYET + EXEC SQL END DECLARE SECTION; +#endif NOTYET +*/ + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + PGresult *pgResult = NULL; // query result + + DBObject* targetobj = NULL; + + if (range == 0) // single tuple + { + // (1) --- delete form cache + targetobj = ObjectBroker::isInMemory(target); + if (targetobj) + { + targetobj->setPersistent(false); + } + + // (2) --- free blob + indbmyOId5 = target.getCounter(); +/* +#ifdef NOTYET + TALK( "EXEC SQL SELECT Tile INTO :blobOid FROM RAS_TILES WHERE BlobId = " << indbmyOId5 ); + EXEC SQL SELECT Tile INTO :blobOid FROM RAS_TILES WHERE BlobId = :indbmyOId5; + if (SQLCODE < 0) + { + LEAVE( "BLOBTile::kill(): Fatal error: cannot find tuple for oid " << blobId ); + check("BLOBTile::kill() - find tuple\0"); + generateException(); + } + else if (SQLCODE != SQLNODATAFOUND) // we've got a tuple + { + // delete blob identified by blobOid + } + // else there is no tuple - this is inconsistent, but nothing to delete +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile FROM RAS_TILES WHERE BlobId = %d", indbmyOId5 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::kill() libpq 'select tile' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::kill() libpq 'select' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + generateException(); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract value from result + PQclear( pgResult ); + + // delete blob identified by blobOid + int loResult = lo_unlink( pgConn, blobOid ); + if (loResult < 0) + { + LEAVE( "BLOBTile::kill(): cannot unlink blob with id '" << blobOid << "' for oid " << blobId << ": " << PQerrorMessage(pgConn) ); + check("BLOBTile::kill() - unlink blob\0"); + generateException(); + } + +/* +#ifdef NOTYET + // (3) --- delete tuple + TALK( "EXEC SQL DELETE FROM RAS_TILES WHERE BlobId = " << indbmyOId5 ); + EXEC SQL DELETE FROM RAS_TILES WHERE BlobId = :indbmyOId5; + if (SQLCODE != SQLOK) + { + if (SQLCODE == SQLNODATAFOUND) // was: NODATA -- PB 2005-feb-13 + { + if (target.getType() == OId::INLINETILEOID) + { + InlineTileId t(target); + if (!t.is_null()) + t->setPersistent(false); + } + //else: this tile has been deleted before + } + else + { + check("BLOBTile::kill()\0"); + generateException(); + } + } +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE BlobId = %d", indbmyOId5 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::kill() libpq 'delete single' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) == 0) + { + if (target.getType() == OId::INLINETILEOID) + { + InlineTileId t(target); + if (!t.is_null()) + t->setPersistent(false); + } + //else: this tile has been deleted before + } + PQclear( pgResult ); + } + else + { + // (1) --- iterate over cache and remove + DBObjectPMap& mapRef = ObjectBroker::getMap(target.getType()); + DBObjectPMap::iterator it = mapRef.begin(); + DBObjectPMap::iterator theEnd = mapRef.end(); + OId end(target.getCounter() + range, target.getType()); + while (it != theEnd) + { + if (target <= (const OId&)(*it).first && (*it).first <= (const OId&)end) + { + (*it).second->setPersistent(false); + } + } + + // (2) --- iterate over db and remove +/* +#ifdef NOTYET + TALK( "EXEC SQL DECLARE DeleteLoop CURSOR FOR SELECT Tile FROM RAS_TILES WHERE " << indbmyOId5 << " <= BlobId AND BlobId <= " << indbmyOId6 ); + EXEC SQL DECLARE DeleteLoop CURSOR FOR SELECT Tile FROM RAS_TILES WHERE :indbmyOId5 <= BlobId AND BlobId <= :indbmyOId6; + if (SQLCODE < 0) + { + LEAVE( "BLOBTile::kill: Fatal error: cannot prepare cursor for oid " << blobId << " to " << indbmyOId6 ); + check("BLOBTile::kill() - prepare cursor\0"); + generateException(); + } +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DECLARE DeleteLoop CURSOR FOR SELECT Tile FROM RAS_TILES WHERE %d <= BlobId AND BlobId <= %d", indbmyOId5, indbmyOId6 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::kill() libpq 'select for deleteLoop' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + + // loop over elements & delete each one + do + { +/* +#ifdef NOTYET + TALK( "EXEC SQL FETCH NEXT FROM DeleteLoop INTO :blobOid" ); + EXEC SQL FETCH NEXT FROM DeleteLoop INTO :blobOid; + if (SQLCODE < 0) + { + LEAVE( "BLOBTile::kill: Fatal error: cannot fetch next tuple" ); + check("BLOBTile::kill() - fetch next tuple\0"); + generateException(); + } +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "FETCH NEXT FROM DeleteLoop" ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::kill() libpq 'fetch next' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::kill() libpq 'fetch' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + generateException(); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract value from result + + // delete blob identified by blobOid + int loResult = lo_unlink( pgConn, blobOid ); + if (loResult < 0) + { + LEAVE( "BLOBTile::kill: Fatal error: cannot unlink blob with id '" << blobOid << "' for oid " << blobId ); + check("BLOBTile::kill() - unlink blob\0"); + generateException(); + } +/* +#ifdef NOTYET + } while (SQLCODE == SQLOK || SQLCODE < 0); +#endif NOTYET +*/ + // FIXME: what about errors? continue?? + } while (PQresultStatus(pgResult) != PGRES_TUPLES_OK); + PQclear( pgResult ); + + // (3) --- delete tuples in db + indbmyOId5 = target.getCounter(); + indbmyOId6 = end.getCounter(); +/* +#ifdef NOTYET + TALK( "EXEC SQL DELETE FROM RAS_TILES WHERE " << indbmyOId5 << " <= BlobId AND BlobId <= " << indbmyOId6 ); + EXEC SQL DELETE FROM RAS_TILES WHERE :indbmyOId5 <= BlobId AND BlobId <= :indbmyOId6; + if ((SQLCODE != SQLOK) && (SQLCODE != SQLNODATAFOUND)) + { + check("BLOBTile::kill()\0"); + generateException(); + } +#endif NOTYET +*/ + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "DELETE FROM RAS_TILES WHERE %d <= BlobId AND BlobId <= %d", indbmyOId5, indbmyOId6 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + LEAVE( "BLOBTile::kill() libpq 'delete range' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + PQclear( pgResult ); + + } + + LEAVE( "BLOBTile::kill" ); + RMDBGEXIT(0, RMDebug::module_blobif, "BLOBTile", "kill(" << target << " " << target.getType() << ")"); +} + +// read tuple from ras_tiles, identified by blobtile.cc var myOId +// allocates necessary mem into ptr 'cells' and fills it; must be freed elsewhere +// external var 'size' is set to the number of bytes read +void +BLOBTile::readFromDb() throw (r_Error) +{ + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "readFromDb() " << myOId); + ENTER( "BLOBTile::readFromDb" ); + +#ifdef RMANBENCHMARK + DBObject::readTimer.resume(); +#endif + +/* +#ifdef NOTYET + EXEC SQL BEGIN DECLARE SECTION; +#endif NOTYET +*/ + Oid blobOid; + long indbmyOId3; + short dataformat3; + short indicatorr3; +/* +#ifdef NOTYET + EXEC SQL END DECLARE SECTION; +#endif NOTYET +*/ + PGresult *pgResult = NULL; // PostgreSQL call return values + + indbmyOId3 = myOId.getCounter(); + + // (1) --- access tuple +/* +#ifdef NOTYET + TALK( "EXEC SQL SELECT Tile, DataFormat INTO :blobOid, :dataformat3 FROM RAS_TILES WHERE BlobId = " << indbmyOId3 ); + EXEC SQL SELECT Tile, DataFormat INTO :blobOid, :dataformat3 FROM RAS_TILES WHERE BlobId = :indbmyOId3; + if (SQLCODE < 0) + { + RMInit::logOut << "BLOBTile::readFromDb() Fatal error during RAS_TILES read: got SQLCODE " << SQLCODE << " for oid " << indbmyOId3 << endl; + LEAVE( "BLOBTile::readFromDb: Fatal error during RAS_TILES read: got SQLCODE " << SQLCODE << " for oid " << indbmyOId3 ); + check("BLOBTile::readFromDb() - find tuple\0"); + generateException(); + } + else if (SQLCODE == SQLNODATAFOUND) // we've got no tuple + { + RMInit::logOut << "BLOBTile::readFromDb() error: object unknown for oid " << indbmyOId3 << endl; + LEAVE( "BLOBTile::readFromDb: Error: object unknown for oid " << indbmyOId3 ); + throw r_Error(r_Error::r_Error_ObjectUnknown); + } +#endif NOTYET +*/ + char pgQuery[SQL_QUERY_BUFFER_SIZE]; + (void) snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile, DataFormat FROM RAS_TILES WHERE BlobId = %d", indbmyOId3 ); + TALK( pgQuery ); + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + LEAVE( "BLOBTile::readFromDb() libpq 'select' error: " << PQerrorMessage(pgConn) ); + PQclear( pgResult ); + generateException(); + } + else if (PQntuples(pgResult) != 1) + { + LEAVE( "BLOBTile::readFromDb() libpq 'select' did not yield 1 result but " << PQntuples(pgResult) ); + PQclear( pgResult ); + throw r_Error(r_Error::r_Error_ObjectUnknown); + } + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); // extract 1st value from result + dataformat3 = atoi( PQgetvalue( pgResult, 0, 1 ) ); // extract 2nd value from result + PQclear( pgResult ); + + // we have a tuple, extract data format + dataFormat = (r_Data_Format)dataformat3; + currentFormat = (r_Data_Format)dataformat3; + TALK( "got dataFormat " << dataFormat ); + + // (2) --- open, read, close blob + TALK( "lo_open()" ); + int fd = lo_open( pgConn, blobOid, INV_READ ); // open; manual tells no error indication + TALK( "lo_lseek() end" ); + size = lo_lseek( pgConn, fd, 0, SEEK_END ); // determine blob size; FIXME: more efficient method?? + TALK( "lo_lseek() start" ); + (void) lo_lseek( pgConn, fd, 0, SEEK_SET ); // rewind for reading + cells = (char*) mymalloc( size * sizeof(char) ); // allocate buffer for blob + if (cells == NULL) + { + RMInit::logOut << "BLOBTile::readFromDb() error: cannot allocate " << size << " bytes " << endl; + LEAVE( "BLOBTile::readFromDb: error: cannot allocate " << size << " bytes" ); + generateException(); + } + + TALK( "lo_read()" ); // read blob + int loResult = lo_read( pgConn, fd, cells, size ); + if (loResult < 0) + { + RMInit::logOut << "BLOBTile::readFromDb() cannot read blob, error: " << PQerrorMessage(pgConn) << endl; + LEAVE( "BLOBTile::readFromDb: cannot read blob, error " << PQerrorMessage(pgConn) ); + generateException(); + } + else if (loResult != size) // did not get all + { + RMInit::dbgOut << "BLOB (" << myOId << ") read: want to read (" << size << " bytes, but got " << loResult << " bytes" << endl; + LEAVE( "BLOBTile::readFromDb: want to read " << size << " bytes, but got " << loResult << " bytes" ); + generateException(); + } + TALK( "lo_close()" ); + int ignoredPgResult = lo_close( pgConn, fd ); // close blob + if (ignoredPgResult < 0) // we note, but ignore errors, as we have the data + { + RMInit::logOut << "BLOBTile::readFromDb() ignoring lo_close() error: " << ignoredPgResult << endl; + TALK( "BLOBTile::readFromDb: ignoring lo_close() error: " << ignoredPgResult ); + } + RMDBGIF(20, RMDebug::module_blobif, "BLOBTileOutput", for (int a = 0; a < size; a++)\ + RMInit::dbgOut << " " << hex << (int)(cells[a]); RMInit::dbgOut << dec << endl;) + + DBObject::readFromDb(); + +#ifdef RMANBENCHMARK + DBObject::readTimer.pause(); +#endif + + LEAVE( "BLOBTile::readFromDb" ); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "readFromDb() " << myOId); +} + diff --git a/relblobif/blobtilecommon.cc b/relblobif/blobtilecommon.cc new file mode 100644 index 0000000..d134687 --- /dev/null +++ b/relblobif/blobtilecommon.cc @@ -0,0 +1,172 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" + +// This is -*- C++ -*- +/************************************************************* + * + * + * PURPOSE: + * + * + * COMMENTS: + * uses embedded SQL + * + * PURPOSE + * has common code for all database interface implementations + */ + +#include <unistd.h> +#include <stdio.h> +#include <vector> +#include <iostream> +#include <cstring> + +#include "blobtile.hh" + +#include "raslib/error.hh" +#include "reladminif/externs.h" +#include "raslib/rmdebug.hh" +#include "reladminif/sqlerror.hh" +#include "tileid.hh" +#include "inlinetile.hh" +#include "reladminif/objectbroker.hh" +#include "reladminif/dbref.hh" + +// defined in rasserver.cc +extern char globalConnectId[256]; + +BLOBTile::BLOBTile(r_Data_Format dataformat) + : DBTile(dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << dataformat << ")"); + objecttype = OId::BLOBOID; + } + +/************************************************************* + * Method name...: BLOBTile(r_Bytes newSize, char c) + * + * Arguments.....: + * newSize: size in number of chars + * c: value for all cells + * Return value..: none + * Description...: creates a new BLOBTile containing with all + * cells set to c. + ************************************************************/ + +BLOBTile::BLOBTile(r_Bytes newSize, char c, r_Data_Format dataformat) + : DBTile(newSize, c, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << newSize << ", data, " << dataformat << ")"); + objecttype = OId::BLOBOID; + } + + +/************************************************************* + * Method name...: BLOBTile(r_Bytes newSize, + * int patSize, char* pat); + * + * Arguments.....: + * newSize: size in number of chars + * patSize: number of chars in pattern + * pat: char array with the pattern + * Return value..: none + * Description...: creates a new BLOBTile containing the + * repeated pattern pat. newSize shoud be + * a multiply of patSize, otherwise the + * cells are filled up with 0. + ************************************************************/ + +BLOBTile::BLOBTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat) + : DBTile(newSize, patSize, pat, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << newSize << ", " << patSize << ", pattern, " << dataformat << ")"); + objecttype = OId::BLOBOID; + } + +/************************************************************* + * Method name...: BLOBTile(r_Bytes newSize, + * char* newCells) + * + * Arguments.....: + * newSize: size in number of chars + * newCells: char array with the new cells (must + * have at least newSize elements) + * Return value..: none + * Description...: creates a new BLOBTile. The elements of + * the char array are copied! + ************************************************************/ + +BLOBTile::BLOBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat) + : DBTile(newSize, newCells, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << size << ", data, " << dataformat << ")"); + objecttype = OId::BLOBOID; + } + +BLOBTile::BLOBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat, const OId& id) + : DBTile(newSize, newCells, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << size << ", data, " << dataformat << ", " << id << ")"); + objecttype = OId::BLOBOID; + myOId = id; + // copied from DBObject::setPersistent() + // if we don't do this the blob will get a new oid + _isPersistent = true; + _isModified = true; + ObjectBroker::registerDBObject(this); + } + +BLOBTile::BLOBTile(const OId& id) throw (r_Error) + : DBTile(id) + { + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << id <<")"); + readFromDb(); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "BLOBTile(" << id << ")"); + } + +BLOBTile::BLOBTile(const OId& id, r_Bytes newSize, r_Data_Format newFmt) + : DBTile(id) + { + _isInDatabase = false; + _isPersistent = true; + _isModified = false; + _isCached = false; + dataFormat = newFmt; + currentFormat = r_Array; + size = newSize; + cells = (char*)mymalloc(size * sizeof(char)); + memset(cells, 0, size); + ObjectBroker::registerDBObject(this); + } + +BLOBTile::~BLOBTile() + { + RMDBGENTER(3, RMDebug::module_blobif, "BLOBTile", "~BLOBTile() " << myOId); + validate(); + RMDBGEXIT(3, RMDebug::module_blobif, "BLOBTile", "~BLOBTile() " << myOId << ")"); + } + +char* BLOBTile::BLOBBuffer = NULL; +r_Bytes BLOBTile::BLOBBufferLength = 131072; + diff --git a/relblobif/dbtile.cc b/relblobif/dbtile.cc new file mode 100644 index 0000000..4c9d928 --- /dev/null +++ b/relblobif/dbtile.cc @@ -0,0 +1,329 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" +// This is -*- C++ -*- +/************************************************************* + * + * + * PURPOSE: + * + * + * COMMENTS: + * uses embedded SQL + * + ************************************************************/ + +static const char rcsid[] = "@(#)blobif,DBTile: $Id: dbtile.cc,v 1.12 2005/09/03 20:41:40 rasdev Exp $"; + +#include "dbtile.hh" +#include "raslib/rmdebug.hh" +#include "reladminif/externs.h" +#include "reladminif/sqlerror.hh" +#include "raslib/error.hh" +#include "reladminif/objectbroker.hh" +#include "blobtile.hh" +#include "inlinetile.hh" +#include "reladminif/dbref.hh" +#include "debug.hh" // for ENTER/LEAVE/TALK macros + +#include "unistd.h" +#include <iostream> +#include <cstring> +#include <vector> + +#ifdef BASEDB_ORACLE + #include <oratypes.h> + #include <oci.h> + #include <sqlca.h> +#endif + + +r_Data_Format +DBTile::getDataFormat() const + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "getDataFormat() const " << myOId << " " << dataFormat); + return dataFormat; + } + +r_Data_Format +DBTile::getCurrentFormat() const + { + return currentFormat; + } + +void +DBTile::setCurrentFormat(const r_Data_Format& dataformat) const + { + currentFormat = dataformat; + } + +void +DBTile::setDataFormat(const r_Data_Format& dataformat) + { + dataFormat = dataformat; + setModified(); + } + +r_Bytes +DBTile::getMemorySize() const + { + return size * sizeof(char) + sizeof(char*) + sizeof(r_Data_Format) + DBObject::getMemorySize() + sizeof(r_Bytes); + } + +void +DBTile::setCells(char* newCells) + { + if(cells != newCells) { + if(cells != NULL) { + TALK( "DBTile::setCells() freeing blob cells" ); + free(cells); + // cells = NULL; // added PB 2005-jan-10 + } + cells = newCells; + setModified(); + } + } + +void +DBTile::setNoModificationData(char* newCells) const + { + if(cells != newCells) { + if(cells != NULL) { + TALK( "DBTile::setNoModificationData() freeing blob cells" ); + free(cells); + // cells = NULL; // added PB 2005-jan-10 + } + cells = newCells; + } + } + +void +DBTile::setNoModificationSize(r_Bytes newSize) const + { + size = newSize; + } + +char* +DBTile::getCells() + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "getCells() " << myOId); + setModified(); + return cells; + } + +const char* +DBTile::getCells() const + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "getCells() const " << myOId); + return cells; + } + +char +DBTile::getCell(r_Bytes index) const + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "getCell(" << index << ") const " << myOId); + return cells[index]; + } + +r_Bytes +DBTile::getSize() const + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "getSize() const " << myOId << " " << size); + return size; + } + +void +DBTile::setCell(r_Bytes index, char newCell) + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "setCell(" << index << ", " << (int)newCell << ") " << myOId); + setModified(); + cells[index] = newCell; + } + +DBTile::DBTile(r_Data_Format dataformat) + : DBObject(), + size(0), + cells(NULL), + dataFormat(dataformat), + currentFormat(r_Array) + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "DBTile(" << dataFormat << ")"); + objecttype = OId::INVALID; + } + +DBTile::DBTile(r_Bytes newSize, char c, r_Data_Format dataformat) + : DBObject(), + size(newSize), + cells(NULL), + currentFormat(r_Array), + dataFormat(dataformat) + { + RMDBGENTER(3, RMDebug::module_blobif, "DBTile", "DBTile(" << newSize << ", '(int)" << (int) c << "', " << dataFormat << ")"); + + TALK( "DBTile::DBTile() allocating " << newSize << " bytes for blob cells, previous ptr was " << (long) cells ); + cells = (char*)mymalloc(newSize * sizeof(char)); + objecttype = OId::INVALID; + memset(cells, c, size); + + RMDBGEXIT(3, RMDebug::module_blobif, "DBTile", "DBTile(" << newSize << ", '(int)" << (int) c << "', " << dataFormat << ") " << myOId); + } + +DBTile::DBTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat) + : DBObject(), + size(newSize), + cells(NULL), + currentFormat(r_Array), + dataFormat(dataformat) + { + RMDBGENTER(3, RMDebug::module_blobif, "DBTile", "DBTile(" << newSize << ", " << patSize << ", pattern, " << dataFormat << ")"); + objecttype = OId::INVALID; + + TALK( "DBTile::DBTile() allocating " << newSize << " bytes for blob cells, previous ptr was " << (long) cells ); + cells = (char*)mymalloc(newSize * sizeof(char)); + + r_Bytes i = 0; + r_Bytes j = 0; + + if (patSize >= size) + { + // fill cells with pattern + for (j = 0; j < size; j++) + { + cells[j] = pat[j]; + } + } + else { + if (patSize >= 0) + { + // fill cells with repeated pattern + for (i = 0; i < size; i += patSize) + { + for (j = 0; j < patSize; j++) + { + cells[(i+j)]= pat[j]; + } + } + // pad end with 0 + if (i != size) + { + // no padding necessary + i -= patSize; + for (; i < size; i++) + { + cells[i]=0; + } + } + } + else { + // fill cells with 0 + for (i = 0; i < size; i++) + { + cells[i] = 0; + } + } + } + + RMDBGEXIT(3, RMDebug::module_blobif, "DBTile", "DBTile(" << newSize << ", " << patSize << ", pattern, " << dataFormat << ") " << myOId); + } + +DBTile::DBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat) + : DBObject(), + size(newSize), + cells(0), + dataFormat(dataformat), + currentFormat(r_Array) + { + RMDBGENTER(3, RMDebug::module_blobif, "DBTile", "DBTile(" << size << ", data, " << dataFormat << ")"); + + TALK( "DBTile::DBTile() allocating " << newSize << " bytes for blob cells, previous ptr was " << (long) cells ); + + cells = (char*)mymalloc(size * sizeof(char)); + objecttype = OId::INVALID; + memcpy(cells, newCells, newSize); + + RMDBGEXIT(3, RMDebug::module_blobif, "DBTile", "DBTile(" << size << ", data, " << dataFormat << ") " << myOId); + } + +DBTile::DBTile(const OId& id) throw (r_Error) + : DBObject(id), + size(0), + cells(NULL), + currentFormat(r_Array) + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "DBTile(" << id <<")"); + } + +DBTile::~DBTile() + { + RMDBGENTER(3, RMDebug::module_blobif, "DBTile", "~DBTile() " << myOId); + if (cells) + { + TALK( "DBTile::~DBTile() freeing blob cells" ); + free(cells); + } + cells = NULL; + RMDBGEXIT(3, RMDebug::module_blobif, "DBTile", "~DBTile() " << myOId); + } + +void +DBTile::resize(r_Bytes newSize) + { + RMDBGONCE(3, RMDebug::module_blobif, "DBTile", "resize(" << newSize << ") " << myOId); + if (size != newSize) + { + setModified(); + if (cells) + { + TALK( "DBTile::resize() freeing blob cells" ); + free(cells); + // cells = NULL; // added PB 2005-jan-10 + } + TALK( "DBTile::resize() allocating " << newSize << " bytes for blob cells, previous ptr was " << (long) cells ); + cells = (char*)mymalloc(newSize * sizeof(char)); + size = newSize; + } + } + +void +DBTile::printStatus(unsigned int level, std::ostream& stream) const + { + DBObject::printStatus(level, stream); + stream << " r_Data_Format " << dataFormat << " size " << size << " "; + RMDBGIF(20, RMDebug::module_blobif, "DBTile", for (int a = 0; a < size; a++)\ + stream << " " << (int)(cells[a]); stream << endl;) + } + +std::ostream& +operator << (std::ostream& stream, DBTile& b) + { + stream << "\tDBTile at " << &b << endl; + stream << "\t\tOId\t\t:" << b.myOId << endl; + stream << "\t\tId\t\t:" << b.myOId.getCounter() << endl; + stream << "\t\tSize\t\t:" << b.size << endl; + stream << "\t\tModified\t:" << (int)b._isModified << endl; + stream << "\t\tCells\t\t:"; + RMDBGIF(20, RMDebug::module_blobif, "DBTile", for (int a = 0; a < b.size; a++)\ + stream << " " << (int)(b.cells[a]); stream << endl;) + return stream; + } + diff --git a/relblobif/dbtile.hh b/relblobif/dbtile.hh new file mode 100644 index 0000000..d95d86b --- /dev/null +++ b/relblobif/dbtile.hh @@ -0,0 +1,222 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#ifndef _DBTILE_HH_ +#define _DBTILE_HH_ + +// -*-C++-*- (for Emacs) + +/************************************************************* + * + * + * PURPOSE: + * + * + * COMMENTS: + * + ************************************************************/ + +class OId; +class r_Error; +#include "reladminif/dbobject.hh" +#include "raslib/mddtypes.hh" +#include "tileid.hh" +#include "relindexif/indexid.hh" + +//@ManMemo: Module: {\bf reldbif}. + +/*@Doc: +This class is used in tilemgr as an interface to the persistent tiles. There are subclasses of dbtile which store themselves differently. This class can also be used to get rid of tilemgr/TransTile. When no persistent tile is needed then a dbtile and not a blobtile/inlinetile can be used. +*/ + + +class DBTile : public DBObject + { + public: + friend std::ostream& operator << (std::ostream& stream, DBTile& b); + /*@Doc: + prints info about the dbtile (flags, id, dbdata, size, modified) + */ + //@Man: read methods + //@{ + /// get contents as array of chars for read access. + char* getCells(); + /*@Doc: + Returns a pointer to the contents of the DBTile. This pointer + can be used as a char array with getSize() elements. + Retriving the contents with this method will mark this object dirty. + */ + + /// get contents as array of chars for read access. + const char* getCells() const; + /*@Doc: + Returns a pointer to the contents of the DBTile. This pointer + can be used as a char array with getSize() elements. + */ + + /// get one char in the contents of the DBTile. + char getCell(r_Bytes index) const; + /*@Doc: + Returns a copy of the char. This functions function should usually + not be used because of performance considerations. + */ + + r_Data_Format getDataFormat() const; + /*@Doc: + Returns the Data Format of this tile (r_Tiff, r_Array, ...) + */ + + r_Data_Format getCurrentFormat() const; + /*@Doc: + Returns the Data Format of the tiles contents (r_Tiff, r_Array, ...) + */ + + void setDataFormat(const r_Data_Format& dataformat); + /*@Doc: + Sets the Data Format of this tile (r_Tiff, r_Array, ...) + This method will mark this object dirty. + */ + + void setCurrentFormat(const r_Data_Format& dataformat) const; + /*@Doc: + Sets the Data Format of the contents of this tile (r_Array, or the compressed stuff) + This method will _not_ mark this object dirty. + */ + + /// get size of contents of DBTile in chars. + r_Bytes getSize() const; + /*@Doc: + Returns the size of this Tile in bytes. + */ + //@} + + //@Man: write methods + //@{ + /// change one char in the contents of the DBTile. + void setCell(r_Bytes index, char newCell); + /*@Doc: + Usually modifications should be done directly on the char array + returned by getCells() because of much better performance. + This method will mark this object dirty. + */ + + void setCells(char* newCells); + /*@Doc: + Completely replaces the character array of this tile. + The old arry is deleted. + Modification is set to dirty. + */ + + //@} + + //@Man: constructors + //@{ + DBTile(r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new empty DBTile. Length is 0. + */ + + DBTile(const OId& BlobId) throw (r_Error); + /*@Doc: + Passes to DBObject. + */ + + DBTile(r_Bytes newSize, char c = 0, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new DBTile of size newSize filled with c. + */ + + DBTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat = r_Array); + /*@Doc: + Constructs a new DB Tile of size newSize filled with the repeated + char array pat of size patSize. If after filling some chars are + left, they are filled with 0 + */ + /*@ManMemo: constructs a new DB Tile with the char array newCells + with newSize elements as contents. */ + + DBTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new DBTile of size newSize filled with the contents of newCells. + the newCells are copied. + */ + //@} + + virtual ~DBTile(); + /*@Doc: + validates the object. deletes it cells. + */ + + void resize(r_Bytes newSize); + /*@Doc: + resize DBTile. previous contents are lost. + */ + + virtual r_Bytes getMemorySize() const; + /*@Doc: + returns the space taken up by this object in memory: + size * sizeof(char) + sizeof(char*) + + DBObject::getMemorySize() + sizeof(r_Data_Format) + sizeof(r_Bytes) + */ + + virtual void printStatus(unsigned int level = 0, std::ostream& stream = std::cout) const; + /*@Doc: + prints the status of DBObject, the dataformat, the size and the contents as (int) + */ + + void setNoModificationData(char* data) const; + /*@Doc: + Completely replaces the array. + The object will not be marked dirty. + This is used for transparent compression/decompression. + */ + + void setNoModificationSize(r_Bytes newSize) const; + /*@Doc: + Sets the size of this tile. + The object will not be marked dirty. + This is used for transparent compression/decompression. + */ + + protected: + mutable r_Bytes size; + /*@Doc: + total size of the contents of DBTile in number of chars. + */ + + mutable char* cells; + /*@Doc: + the data is allocated by malloc + */ + + r_Data_Format dataFormat; + /*@Doc: + data format to construct a PersTile + */ + + mutable r_Data_Format currentFormat; + /*@Doc: + the current format of the contents. This is neccessary to know when getting mixed up compressed contents. + */ + }; + +#endif diff --git a/relblobif/filestorageexception.hh b/relblobif/filestorageexception.hh new file mode 100644 index 0000000..b39cb69 --- /dev/null +++ b/relblobif/filestorageexception.hh @@ -0,0 +1,42 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +/************************************************************* + * + * + * PURPOSE: + * The interface used by the file storage modules. + * + * + * COMMENTS: + * + ************************************************************/ + +#ifndef _FILESTORAGEEXCEPTION_HH_ +#define _FILESTORAGEEXCEPTION_HH_ + +class FileStorageException +{ + +}; // class FileStorageException + +#endif // _FILESTORAGEEXCEPTION_HH_ diff --git a/relblobif/ifilestorage.hh b/relblobif/ifilestorage.hh new file mode 100644 index 0000000..1a3cb69 --- /dev/null +++ b/relblobif/ifilestorage.hh @@ -0,0 +1,56 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +/************************************************************* + * + * + * PURPOSE: + * The interface used by the file storage modules. + * + * + * COMMENTS: + * + ************************************************************/ + +#ifndef _IFILESTORAGE_H__ +#define _IFILESTORAGE_H__ + +#include "raslib/error.hh" +class OId; + +// The class is the interface for the file storage modules. +class IFileStorage +{ + public: + // Update the content of a blob. The blob should exist already. + virtual void update(const char* data, r_Bytes size, int BlobId) throw (r_Error) = 0; + // Store the content of a new blob. + virtual void insert(const char* data, r_Bytes size, int BlobId) throw (r_Error) = 0; + // Retrive the content of a previously stored blob + virtual void retrieve(int BlobId, char** data, r_Bytes *size) throw (r_Error) = 0; + // Delete a previously stored blob. + virtual void remove(int BlobId) throw (r_Error) = 0; + + //virtual ~IFileStorage() = 0; +}; // class IFileStorage + +#endif // _IFILESTORAGE_H__ diff --git a/relblobif/inlinetile.cc b/relblobif/inlinetile.cc new file mode 100644 index 0000000..bfff1ca --- /dev/null +++ b/relblobif/inlinetile.cc @@ -0,0 +1,259 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" +// This is -*- C++ -*- +/************************************************************* + * + * + * PURPOSE: + * + + * + * COMMENTS: + * uses embedded SQL + * + ************************************************************/ + +static const char rcsid[] = "@(#)blobif,BLOBTile: $Id: inlinetile.cc,v 1.5 2002/06/15 16:47:29 coman Exp $"; + +#include <iostream> + +#include "inlinetile.hh" +#include "raslib/rmdebug.hh" +#include "reladminif/externs.h" +#include "raslib/error.hh" +#include "reladminif/objectbroker.hh" +#include "blobtile.hh" +#include "relindexif/dbtcindex.hh" +#include "reladminif/dbref.hh" +#include "storagemgr/sstoragelayout.hh" + +#include <cstring> + +InlineTile::InlineTile(r_Data_Format dataformat) + : BLOBTile(dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "InlineTile", "InlineTile()"); + objecttype = OId::INLINETILEOID; + } + +InlineTile::InlineTile(r_Bytes newSize, char c, r_Data_Format dataformat) + : BLOBTile(newSize, c, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "InlineTile", "InlineTile(" << newSize << ", data)"); + objecttype = OId::INLINETILEOID; + } + +InlineTile::InlineTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat) + : BLOBTile(newSize, patSize, pat, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "InlineTile", "InlineTile(" << newSize << ", " << patSize << ", pattern)"); + objecttype = OId::INLINETILEOID; + } + +InlineTile::InlineTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat) + : BLOBTile(newSize, newCells, dataformat) + { + RMDBGONCE(3, RMDebug::module_blobif, "InlineTile", "InlineTile(" << size << ", data)"); + objecttype = OId::INLINETILEOID; + } + +InlineTile::InlineTile(const OId& id) throw (r_Error) + : BLOBTile(id) + { + RMDBGONCE(5, RMDebug::module_blobif, "InlineTile", "InlineTile(" << id <<")"); + objecttype = OId::INLINETILEOID; + } + +InlineTile::InlineTile(const OId& id, char*& thecells) + : BLOBTile(r_Array) + { + RMDBGENTER(5, RMDebug::module_blobif, "InlineTile", "InlineTile(" << id << ", cells " << (r_Ptr)thecells << ")"); + objecttype = OId::INLINETILEOID; + myIndexOId = id; + + //restore the size of the blob + memcpy(&size, thecells, sizeof(r_Bytes)); + thecells = thecells + sizeof(r_Bytes); + + //restore the dataformat + memcpy(&dataFormat, thecells, sizeof(r_Data_Format)); + thecells = thecells + sizeof(r_Data_Format); + + //restore my own oid + memcpy(&myOId, thecells, sizeof(OId)); + thecells = thecells + sizeof(OId); + + //restore the cells + cells = (char*)mymalloc(size * sizeof(char)); + memcpy(cells, thecells, size); + thecells = thecells + size; + RMDBGMIDDLE(5, RMDebug::module_blobif, "InlineTile", "OId " << myOId << " size " << size << " DataFormat " << dataFormat); + + DBObject::readFromDb(); + _isInDatabase = false; + ObjectBroker::registerDBObject(this); + RMDBGEXIT(5, RMDebug::module_blobif, "InlineTile", "InlineTile(" << id << ", cells " << (r_Ptr)thecells << ") " << myOId); + } + +void +InlineTile::printStatus(unsigned int level, std::ostream& stream) const + { + char* indent = new char[level*2 +1]; + for (unsigned int j = 0; j < level*2 ; j++) + indent[j] = ' '; + indent[level*2] = '\0'; + + stream << indent << "InlineTile "; + if (isInlined()) + stream << "is Inlined at " << myIndexOId << endl; + else + stream << "is not Inlined " << endl; + BLOBTile::printStatus(level + 1, stream); + delete[] indent; + } + +void +InlineTile::destroy() + { + if (isCached()) + return; + else + DBObject::destroy(); + } + +bool +InlineTile::isCached() const + { + char retval = true; + //not previously cached + if (!_isCached) + if (!isInlined()) + {//outlined + if (getSize() > StorageLayout::DefaultMinimalTileSize)//size is ok + retval = false; + } + else {//inlined + if (getSize() < StorageLayout::DefaultPCTMax)//size is ok + retval = false; + } + return retval; + } + +void +InlineTile::setModified() throw(r_Error) + { + RMDBGENTER(4, RMDebug::module_blobif, "InlineTile", "setModified() " << myOId); + DBObject::setModified(); + if (isInlined()) + { + RMDBGMIDDLE(4, RMDebug::module_blobif, "InlineTile", " index will be modified"); + DBTCIndexId t(myIndexOId); + t->setInlineTileHasChanged(); + } + else { + RMDBGMIDDLE(4, RMDebug::module_blobif, "InlineTile", "index will not be modified"); + } + RMDBGEXIT(4, RMDebug::module_blobif, "InlineTile", "setModified() " << myOId); + } + +const OId& +InlineTile::getIndexOId() const + { + return myIndexOId; + } + +void +InlineTile::setIndexOId(const OId& oid) + { + myIndexOId = oid; + } + +r_Bytes +InlineTile::getStorageSize() const + { + return size + sizeof(r_Bytes) + sizeof(r_Data_Format) + sizeof(OId); + } + +void +InlineTile::outlineTile() + { + DBTCIndexId t(myIndexOId); + t->removeInlineTile(this); + setIndexOId(OId(0, OId::INVALID)); + _isInDatabase = false; + _isModified = true; + } + +void +InlineTile::inlineTile(const OId& ixId) + { + setIndexOId(ixId); + DBTCIndexId t(myIndexOId); + t->addInlineTile(this); + if (_isInDatabase) + { + bool pers = _isPersistent; + bool modi = _isModified; + BLOBTile::deleteFromDb(); + _isPersistent = pers; + _isModified = modi; + } + } + +bool +InlineTile::isInlined() const + { + return (myIndexOId.getType() == OId::DBTCINDEXOID); + } + +char* +InlineTile::insertInMemBlock(char* thecontents) + { + RMDBGENTER(5, RMDebug::module_blobif, "InlineTile", "insertInMemBlock(" << (void*)thecontents << ")"); + //store size of blob + memcpy(thecontents, &size, sizeof(r_Bytes)); + thecontents = thecontents + sizeof(r_Bytes); + + //store the dataformat + memcpy(thecontents, &dataFormat, sizeof(r_Data_Format)); + thecontents = thecontents + sizeof(r_Data_Format); + + //store my own oid + memcpy(thecontents, &myOId, sizeof(OId)); + thecontents = thecontents + sizeof(OId); + + //store the blob + memcpy(thecontents, cells, size * sizeof(char)); + thecontents = thecontents + size * sizeof(char); + RMDBGMIDDLE(5, RMDebug::module_blobif, "InlineTile", "OId " << myOId << " size " << size << " DataFormat " << dataFormat); + RMDBGIF(20, RMDebug::module_blobif, "InlineTile", for (int i = 0; i < size; i++) RMInit::dbgOut << (unsigned int)(cells[i]) << " "; RMInit::dbgOut << endl;) + DBObject::updateInDb(); + RMDBGEXIT(5, RMDebug::module_blobif, "InlineTile", "insertInMemBlock(" << (void*)thecontents << ")"); + return thecontents; + } + +InlineTile::~InlineTile() + { + } + diff --git a/relblobif/inlinetile.hh b/relblobif/inlinetile.hh new file mode 100644 index 0000000..0e833f2 --- /dev/null +++ b/relblobif/inlinetile.hh @@ -0,0 +1,176 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#ifndef _INLINETILE_HH_ +#define _INLINETILE_HH_ +// -*-C++-*- (for Emacs) + +/************************************************************* + * + * + * PURPOSE: + * The inlinetile class is used to store small tiles efficiently. + * Potentially many inlinetiles are grouped together in a blob and + * stored in the database. highly dependend on DBTCIndex. + * + * + * COMMENTS: + * + ************************************************************/ + +class OId; +class r_Error; + +#include "raslib/mddtypes.hh" +#include "tileid.hh" +#include "blobtile.hh" + +//@ManMemo: Module: {\bf relblobif}. + +/*@Doc: + +InlineTile is the persistent class for storing the contents of MDD tiles +in the database. it can be stored as a blobtile or inlined: +in inlined mode multiple inlinetiles are stored as one blob in the database. +memory management and modification management is critical. +there are special functions in objectbroker to retrieve inlinetiles. +they can only be inlined by a dbtcindex. + +*/ + + +class InlineTile : public BLOBTile + { + public: + + //@Man: constructors + //@{ + InlineTile(const OId& id, char*& thecells); + /*@Doc: + construct a new inline tile with the oid of + the dbtilecontainerindex and the array which + holds the contents of the tile. + thecells will be automagically forwarded to the beginning of the next inline tile. + */ + + InlineTile(r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new empty InlineTile and gets an id for it. + */ + + InlineTile(const OId& BlobId) throw (r_Error); + /*@Doc: + constructs a InlineTile out of the database + */ + + InlineTile(r_Bytes newSize, char c = 0, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new InlineTile of size newSize filled with c. + */ + + InlineTile(r_Bytes newSize, r_Bytes patSize, const char* pat, r_Data_Format dataformat = r_Array); + /*@Doc: + Constructs a new InlineTile of size newSize filled with the repeated + char array pat of size patSize. If after filling some chars are + left, they are filled with 0 + */ + /*@ManMemo: constructs a new InlineTile with the char array newCells + with newSize elements as contents. */ + + InlineTile(r_Bytes newSize, const char* newCells, r_Data_Format dataformat = r_Array); + /*@Doc: + constructs a new InlineTile of size newSize filled with the contents of newCells. + */ + + //@} + virtual void destroy(); + /*@Doc: + may not destroy the object because it is inlined and therefore depending on its parent index. + */ + + const OId& getIndexOId() const; + /*@Doc: + returns the oid of the index which contains the inlined tile. if the tile is outlined then this oid is invalid. + */ + + void setIndexOId(const OId& oid); + /*@Doc: + make the inlinetile use this index as its parent and storage structure. + */ + + r_Bytes getStorageSize() const; + /*@Doc: + returns the size this tile will consume in as an inlined array. + */ + + virtual char* insertInMemBlock(char* test); + /*@Doc: + inserts the Blob into the char. + the returned pointer is after the end of this tiles data. + */ + + virtual void setModified() throw(r_Error); + /*@Doc: + does not only set itself modified but also informs its parent of changes. + */ + + virtual bool isCached() const; + /*@Doc: + returns true if it is inlined. + */ + + virtual void inlineTile(const OId& ixOId); + /*@Doc: + do everything so that this tile is inlined and uses ixOId as its index parent. + it will not check if this tile is already inlined. + */ + + virtual void outlineTile(); + /*@Doc: + does everything necessary to act as a blobtile: + remove it from the index parent. + */ + + virtual bool isInlined() const; + /*@Doc: + checks if it has a valid index parent. + */ + + virtual ~InlineTile(); + /*@Doc: + no functionality. if it is inlined the dbtcindex will take care of storing it. + if it is not inlined the blobtile functionality will take over. + */ + + virtual void printStatus(unsigned int level = 0, std::ostream& stream = std::cout) const; + + protected: + + OId myIndexOId; + /*@Doc: + when this inlinetile is in inlined mode the myIndexOId points to the parent index. + if this oid is invalid the inlinetile is not in inline mode. + */ + + }; + +#endif diff --git a/relblobif/simplefilestorage.cc b/relblobif/simplefilestorage.cc new file mode 100644 index 0000000..02548bc --- /dev/null +++ b/relblobif/simplefilestorage.cc @@ -0,0 +1,178 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +/************************************************************* + * + * + * PURPOSE: + * The interface used by the file storage modules. + * + * + * COMMENTS: + * + ************************************************************/ + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include "raslib/rminit.hh" +#include "../reladminif/oidif.hh" +#include "simplefilestorage.hh" +#include "debug-srv.hh" + +using namespace std; + +SimpleFileStorage::~SimpleFileStorage() +{ +} + +SimpleFileStorage::SimpleFileStorage(const string& path) throw (r_Error) : root_path(path) +{ + // Check if the path exist is readable/writable etc. + struct stat status; + if (stat(path.c_str(), &status)<0 || !S_ISDIR(status.st_mode)) { + throw 1; // Root directory not found + } + TALK("SimpleFileStorage initialized on root path" << path); +} + +void SimpleFileStorage::insert(const char* data, r_Bytes size, int BlobId) throw (r_Error) +{ + ENTER("SimpleFileStorage::insert with BlobID="<<BlobId); + vector<string> path; + getPath(BlobId, &path); + string file_path = path[0]; // Root path + // Iterate trough the levels and create all directories needed. + for (int i = 1; i < path.size() - 1; ++i) + { + file_path += '/' + path[i]; + struct stat status; + if (!stat(file_path.c_str(), &status)) + if (!mkdir(file_path.c_str(), 0770)) + throw 2; // Cannot create directory + } + file_path += '/' + path[path.size() - 1]; + int fd = open(file_path.c_str(), O_CREAT | O_WRONLY, 0770); + if (fd < 0) + throw 3; // Cannot create file + int offset = 0; + // Send the data to the disk + while (offset < size) { + int count = write(fd, data + offset, size - offset); + if (count == -1) + throw 4; // Error while writing data to the disk + offset += count; + } + if (close(fd)<0) + throw 4; // Error while writing data to the disk + LEAVE("SimpleFileStorage::insert"); +} + +void SimpleFileStorage::update(const char* data, r_Bytes size, int BlobId) throw (r_Error) +{ + ENTER("SimpleFileStorage::update"); + vector<string> path; + getPath(BlobId, &path); + string file_path = path[0]; // Root path + // Iterate trough the levels and create all directories needed. + for (int i = 1; i < path.size() - 1; ++i) + { + file_path += '/' + path[i]; + struct stat status; + if (!stat(file_path.c_str(), &status)) + if (!mkdir(file_path.c_str(), 0770)) + throw 2; // Cannot create directory + } + file_path += '/' + path[path.size() - 1]; + + int fd = open(file_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC); + if (fd < 0) + throw 6; // Cannot open file + int offset = 0; + // Send the data to the disk + while (offset < size) { + int count = write(fd, data + offset, size - offset); + if (count == -1) + throw 4; // Error while writing data to the disk + offset += count; + } + if (close(fd)<0) + throw 4; // Error while writing data to the disk + LEAVE("SimpleFileStorage::update"); +} + +void SimpleFileStorage::retrieve(int BlobId, char** data, r_Bytes* size) throw (r_Error) +{ + ENTER("SimpleFileStorage::read"); + string path; + getPath(BlobId, &path); + struct stat status; + if (stat(path.c_str(), &status) < 0) + throw 5; // File does not exist, the blob does not exist. + + *size = status.st_size; + *data = (char*)malloc(status.st_size); + + int fd = open(path.c_str(), O_RDONLY); + if (fd < 0) + throw 6; // Cannot open file + int offset = 0; + // Send the data to the disk + while (offset < *size) { + int count = read(fd, *data + offset, *size - offset); + if (count == -1) + throw 7; // Error while reading data from the disk + offset += count; + } + if (close(fd) < 0) + throw 7; // Error while reading data from the disk + LEAVE("SimpleFileStorage::read"); +} + +void SimpleFileStorage::remove(int BlobId) throw (r_Error) +{ + ENTER("SimpleFileStorage::remove"); + string path; + getPath(BlobId, &path); + if (unlink(path.c_str()) < 0) + throw 8; // Error while removing the file + LEAVE("SimpleFileStorage::remove"); +} + +void SimpleFileStorage::getPath(int BlobId, vector<string> *path) { + ENTER("SimpleFileStorage::getPath"); + path->clear(); + path->push_back(root_path); + stringstream aux; + aux << BlobId; + path->push_back(aux.str()); + LEAVE("SimpleFileStorage::getPath"); +} + +void SimpleFileStorage::getPath(int BlobId, string *path) { + vector<string> segments; + getPath(BlobId, &segments); + *path = segments[0]; + for (int i = 1; i < segments.size(); ++i) + *path += '/' + segments[i]; +} diff --git a/relblobif/simplefilestorage.hh b/relblobif/simplefilestorage.hh new file mode 100644 index 0000000..7978486 --- /dev/null +++ b/relblobif/simplefilestorage.hh @@ -0,0 +1,66 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +/************************************************************* + * + * + * PURPOSE: + * The interface used by the file storage modules. + * + * + * COMMENTS: + * + ************************************************************/ + +#ifndef _SIMPLEFILESTORAGE_HH_ +#define _SIMPLEFILESTORAGE_HH_ + +#include <vector> +#include <string> +#include "ifilestorage.hh" + +class SimpleFileStorage : public IFileStorage +{ + public: + SimpleFileStorage(const std::string& storage_path) throw (r_Error); + // Update the content of a blob. The blob should exist already. + void update(const char* data, r_Bytes size, int BlobId) throw (r_Error); + // Store the content of a new blob. + virtual void insert(const char* data, r_Bytes size, int BlobId) throw (r_Error); + // Retrive the content of a previously stored blob + virtual void retrieve(int BlobId, char** data, r_Bytes* size) throw (r_Error); + // Delete a previously stored blob. + virtual void remove(int BlobId) throw (r_Error); + + virtual ~SimpleFileStorage(); + private: + // Given on BlobId will return the path where the blob should be stored. + // It's returned as a vector so that other functions can iterate trough it and check + // or create the directories on each level. + void getPath(int BlobId, std::vector<std::string>* path); + // Similar to the one above but returns the agregated path + void getPath(int BlobId, std::string* path); + std::string root_path; + +}; // class SimpleFileStorage + +#endif // _SIMPLEFILESTORAGE_HH_ diff --git a/relblobif/test/Makefile b/relblobif/test/Makefile new file mode 100644 index 0000000..fe98b32 --- /dev/null +++ b/relblobif/test/Makefile @@ -0,0 +1,65 @@ +# -*-Makefile-*- +# +# This file is part of rasdaman community. +# +# Rasdaman community 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 3 of the License, or +# (at your option) any later version. +# +# Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +# +# Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +# rasdaman GmbH. +# +# For more information please see <http://www.rasdaman.org> +# or contact Peter Baumann via <baumann@rasdaman.com>. # Top Level makefile. This points to the various modules that have to be build +# and/or deployed +# +# +# MAKEFILE FOR: +# test programs of module relblobif +# +# COMMENTS: +# List environment dependencies, known bugs, specialities etc. +# +################################################################## +######################### Definitions ############################ + +LDFLAGS += $(L_SYM)$(RMANHOME)/lib $(LIBAKINSIDE) $(RMANBASE)/mymalloc/mymalloc_cln.o -lcrypt + +# source files +CPPSOURCES = updateblob.c deleteblob.c createtable.c droptable.c insertblob.c readblob.c r.c r2.c # copytile.c + +# all test programs +ALLTESTS = ${CPPSOURCES:%.c=%} + +# object files +OBJS = ${CPPSOURCES:%.c=%.o} + +NEEDSTL = + +# Needed rasdaman libraries for linking. To be completed. +#NEEDEDLIBS = $(QLPARSER) $(RELBLOBIF) $(RELADMINIF) $(RASLIB) $(RELCATALOGIF) $(RELMDDIF) $(RASLIB) $(CACHETAMGR) $(RELINDEXIF) $(CACHETAMGR) $(RASQL) $(QLPARSER) + +MISCCLEAN = core client.bm client.dbg client.log ir.out + +########################### Targets ############################## + +# make all tests +.PHONY: test +test: $(ALLTESTS) + +########################### Dependencies ######################### + +blobdump: blobdump.o + $(CXX) -o $@ $^ $(LDFLAGS) $(BASEDBLDFLAGS) $(L_SYM)$(PGSQL_ROOT)/lib $(l_SYM)ecpg +blobdump.o: blobdump.c +blobdump.c: blobdump.pgc + diff --git a/relblobif/test/README.txt b/relblobif/test/README.txt new file mode 100644 index 0000000..ea20ca4 --- /dev/null +++ b/relblobif/test/README.txt @@ -0,0 +1,5 @@ +relblobif/test: + +Strictly speaking these are not rasdaman tests, they do not contribute to systemtest. +Rather they allow to test a DBMS for how it behaves wrt. long fields (char, varchar, blobs) if attacked with binary contents. + diff --git a/relblobif/test/blobdump.c b/relblobif/test/blobdump.c new file mode 100644 index 0000000..bc2f44f --- /dev/null +++ b/relblobif/test/blobdump.c @@ -0,0 +1,323 @@ +#include "mymalloc/mymalloc.h" +/* Processed by ecpg (3.1.1) */ +/* These include files are added by the preprocessor */ +#include <ecpgtype.h> +#include <ecpglib.h> +#include <ecpgerrno.h> +#include <sqlca.h> +#line 1 "blobdump.pgc" +/* End of automatic include section */ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +/************************************************************* + * + * + * PURPOSE: + * dump RAS_TILES blob table. + * Depending on parameters, either a particular tuple is printed + * or the whole table. Optionally also the blob is dumped in hex. + * Parameters: see usage(). + * + * + * COMMENTS: + * - assumes RAS_TILES + * + ************************************************************/ + +using namespace std; + +#include <iostream> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> /* getopt */ + +#include <sqlca.h> + +// SQL error codes: +#include "externs.h" + +// PG stuff +#include "libpq-fe.h" // C interface to PgSQL +#include "libpq/libpq-fs.h" /* large object (lo) api */ +// libq-style connection ptr taken from ECPG connect (needed for libq functions): +// (currently used by relblobif/blobtile.pgc) +PGconn *pgConn = NULL; +PGresult *pgResult = NULL; + +#define RC_OK 0 +#define RC_ERROR -1 + +#define QUERYSIZE 2000 +static char pgQuery[QUERYSIZE]; + +void exitNicely() +{ + PQclear( pgResult ); + (void) PQexec( pgConn, "ROLLBACK WORK" ); + PQfinish( pgConn ); + exit( RC_ERROR ); +} + + +void +disconnect() +{ + pgResult = PQexec( pgConn, "ROLLBACK WORK" ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during disconnect: " << PQerrorMessage(pgConn) << endl << flush; + PQclear( pgResult ); + exit( RC_ERROR ); + } + PQclear( pgResult ); + + PQfinish( pgConn ); + pgConn = NULL; +} + +void +connect( const char * db ) +{ + char pgConnInfo[200]; + + (void) snprintf( pgConnInfo, (size_t) sizeof(pgConnInfo), "dbname = %s", db ); + pgConn = PQconnectdb( pgConnInfo ); + if (PQstatus(pgConn) != CONNECTION_OK) + { + cerr << "error during connect: " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + + pgResult = PQexec( pgConn, "BEGIN TRANSACTION" ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during being ta: " << PQerrorMessage(pgConn) << endl << flush; + PQclear( pgResult ); + exit( RC_ERROR ); + } + PQclear( pgResult ); + +} + +// read one tuple from ras_tiles, identified by myOId, and display it (with contents if withDump==true) +void +readFromDb( long myOId, bool withDump ) +{ + unsigned int blobOid = 0; + short dataFormat = 0; + unsigned long size = 0; + char *cells = NULL; + + // (1) --- access tuple + int result = snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile, DataFormat FROM RAS_TILES WHERE BlobId = %d", myOId ); + + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + cerr << "error during 'select': " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + else if (PQntuples(pgResult) < 1) + { + cerr << "no tuple with id " << myOId << endl << flush; + exitNicely(); + } + + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); + dataFormat = atoi( PQgetvalue( pgResult, 0, 1 ) ); + PQclear( pgResult ); + + // (2) --- open, read, close blob + int fd = lo_open( pgConn, blobOid, INV_READ ); // open; manual tells no error indication + size = lo_lseek( pgConn, fd, 0, SEEK_END ); // determine blob size; FIXME: more efficient method?? + (void) lo_lseek( pgConn, fd, 0, SEEK_SET ); // rewind for reading + cells = (char*) mymalloc( size * sizeof(char) ); // allocate buffer for blob + if (cells == NULL) + { + cerr << "error during malloc()" << endl << flush; + exitNicely(); + } + int loResult = lo_read( pgConn, fd, cells, size ); + if (loResult < 0) + { + cerr << "error during lo_read(): " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + else if (loResult != size) // did not get all + { + cerr << "error (did not get all bytes): " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + int ignoredPgResult = lo_close( pgConn, fd ); // close blob + + if (withDump) + { + cout << " id = " << myOId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << ", contents = "; + for (int a = 0; a < size; a++) + cout << " " << hex << (0xff & (unsigned int)(cells[a])) << dec; + cout << dec << endl; + } + else + { + cout << " id = " << myOId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << endl; + } + + free( cells ); +} + +// read all tuples from ras_tiles and display them (with contents if withDump==true) +void +readAllFromDb( bool withDump ) +{ + unsigned int blobOid; + long myId; + short dataFormat; + unsigned long size = 0; + char *cells = NULL; + int rowNumber = 0; + + // (1) --- declare cursor + pgResult = PQexec( pgConn, "SELECT BlobId, Tile, DataFormat FROM RAS_TILES" ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK && PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during 'declare cursor': " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + + do + { + myId = atoi( PQgetvalue( pgResult, rowNumber, 0 ) ); + blobOid = atoi( PQgetvalue( pgResult, rowNumber, 1 ) ); + dataFormat = atoi( PQgetvalue( pgResult, rowNumber, 2 ) ); + + // (2) --- open, read, close blob + int fd = lo_open( pgConn, blobOid, INV_READ ); // open; manual tells no error indication + size = lo_lseek( pgConn, fd, 0, SEEK_END ); // determine blob size; FIXME: more efficient method?? + (void) lo_lseek( pgConn, fd, 0, SEEK_SET ); // rewind for reading + cells = (char*) mymalloc( size * sizeof(char) ); // allocate buffer for blob + if (cells == NULL) + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + int loResult = lo_read( pgConn, fd, cells, size ); + if (loResult < 0) + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + else if (loResult != size) // did not get all + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + int ignoredPgResult = lo_close( pgConn, fd ); // close blob + + if (withDump) + { + cout << " id = " << myId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << ", contents = "; + for (int a = 0; a < size; a++) + cout << " " << hex << (0xff & (unsigned int)(cells[a])) << dec; + cout << endl; + } + else + { + cout << " id = " << myId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << endl; + } + + rowNumber++; + } while (PQresultStatus(pgResult) == PGRES_TUPLES_OK || PQresultStatus(pgResult) == PGRES_COMMAND_OK); + + (void) PQexec( pgConn, "CLOSE TileLoop" ); + PQclear( pgResult ); + free( cells ); +} + +void usage(const char *prog) +{ + cout << "Usage: " << prog << " -d db [-i id] [-c]" << endl; + cout << "where:" << endl; + cout << " -d db log into PG server using PG database name db (mandatory)" << endl; + cout << " -i id dump blob with identifier id (default: dump whole table)" << endl; + cout << " -c dump blob contents too (default: descriptor data only)" << endl; + cout << " -h (help) print this overview" << endl; +} + +int +main(int argc, char* argv[]) +{ + char *prog = argv[0]; // prog name + char db[100]; // DB connect string + int blobOid = 0; // tuple to be dumped + bool withDump = false; // by default, don't dump blob contents + char c; // getop var + int result; // for sscanf() + + cout << prog << ": dump rasdaman blob table." << endl; + + opterr = 0; + while ((c = getopt(argc, argv, "d:i:c")) != -1) + { + switch (c) + { + case 'd': + (void) strncpy( db, optarg, sizeof(db) ); + break; + case 'i': + result = sscanf( optarg, "%d", &blobOid ); + if (result != 1) + { + cerr << prog << ": positive integer expected: " << optarg << endl; + exit( RC_ERROR ); + } + break; + case 'c': + withDump = true; + break; + case 'h': + case '?': + case ':': + usage(prog); + exit( RC_OK ); + break; + } + } + if (optind < argc || argc == 1) + { + usage( prog ); + exit( RC_ERROR ); + } + + connect( db ); + + if (blobOid > 0) + readFromDb( blobOid, withDump ); + else + readAllFromDb( withDump ); + + disconnect(); + + cout << prog << ": done." << endl; +} + diff --git a/relblobif/test/blobdump.pgc b/relblobif/test/blobdump.pgc new file mode 100644 index 0000000..69b35b7 --- /dev/null +++ b/relblobif/test/blobdump.pgc @@ -0,0 +1,315 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "mymalloc/mymalloc.h" +/************************************************************* + * + * + * PURPOSE: + * dump RAS_TILES blob table. + * Depending on parameters, either a particular tuple is printed + * or the whole table. Optionally also the blob is dumped in hex. + * Parameters: see usage(). + * + * + * COMMENTS: + * - assumes RAS_TILES + * + ************************************************************/ + +using namespace std; + +#include <iostream> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> /* getopt */ + +#include <sqlca.h> + +// SQL error codes: +#include "externs.h" + +// PG stuff +#include "libpq-fe.h" // C interface to PgSQL +#include "libpq/libpq-fs.h" /* large object (lo) api */ +// libq-style connection ptr taken from ECPG connect (needed for libq functions): +// (currently used by relblobif/blobtile.pgc) +PGconn *pgConn = NULL; +PGresult *pgResult = NULL; + +#define RC_OK 0 +#define RC_ERROR -1 + +#define QUERYSIZE 2000 +static char pgQuery[QUERYSIZE]; + +void exitNicely() +{ + PQclear( pgResult ); + (void) PQexec( pgConn, "ROLLBACK WORK" ); + PQfinish( pgConn ); + exit( RC_ERROR ); +} + + +void +disconnect() +{ + pgResult = PQexec( pgConn, "ROLLBACK WORK" ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during disconnect: " << PQerrorMessage(pgConn) << endl << flush; + PQclear( pgResult ); + exit( RC_ERROR ); + } + PQclear( pgResult ); + + PQfinish( pgConn ); + pgConn = NULL; +} + +void +connect( const char * db ) +{ + char pgConnInfo[200]; + + (void) snprintf( pgConnInfo, (size_t) sizeof(pgConnInfo), "dbname = %s", db ); + pgConn = PQconnectdb( pgConnInfo ); + if (PQstatus(pgConn) != CONNECTION_OK) + { + cerr << "error during connect: " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + + pgResult = PQexec( pgConn, "BEGIN TRANSACTION" ); + if (PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during being ta: " << PQerrorMessage(pgConn) << endl << flush; + PQclear( pgResult ); + exit( RC_ERROR ); + } + PQclear( pgResult ); + +} + +// read one tuple from ras_tiles, identified by myOId, and display it (with contents if withDump==true) +void +readFromDb( long myOId, bool withDump ) +{ + unsigned int blobOid = 0; + short dataFormat = 0; + unsigned long size = 0; + char *cells = NULL; + + // (1) --- access tuple + int result = snprintf( pgQuery, (size_t) sizeof(pgQuery), "SELECT Tile, DataFormat FROM RAS_TILES WHERE BlobId = %d", myOId ); + + pgResult = PQexec( pgConn, pgQuery ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK) + { + cerr << "error during 'select': " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + else if (PQntuples(pgResult) < 1) + { + cerr << "no tuple with id " << myOId << endl << flush; + exitNicely(); + } + + blobOid = atoi( PQgetvalue( pgResult, 0, 0 ) ); + dataFormat = atoi( PQgetvalue( pgResult, 0, 1 ) ); + PQclear( pgResult ); + + // (2) --- open, read, close blob + int fd = lo_open( pgConn, blobOid, INV_READ ); // open; manual tells no error indication + size = lo_lseek( pgConn, fd, 0, SEEK_END ); // determine blob size; FIXME: more efficient method?? + (void) lo_lseek( pgConn, fd, 0, SEEK_SET ); // rewind for reading + cells = (char*) mymalloc( size * sizeof(char) ); // allocate buffer for blob + if (cells == NULL) + { + cerr << "error during malloc()" << endl << flush; + exitNicely(); + } + int loResult = lo_read( pgConn, fd, cells, size ); + if (loResult < 0) + { + cerr << "error during lo_read(): " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + else if (loResult != size) // did not get all + { + cerr << "error (did not get all bytes): " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + int ignoredPgResult = lo_close( pgConn, fd ); // close blob + + if (withDump) + { + cout << " id = " << myOId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << ", contents = "; + for (int a = 0; a < size; a++) + cout << " " << hex << (0xff & (unsigned int)(cells[a])) << dec; + cout << dec << endl; + } + else + { + cout << " id = " << myOId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << endl; + } + + free( cells ); +} + +// read all tuples from ras_tiles and display them (with contents if withDump==true) +void +readAllFromDb( bool withDump ) +{ + unsigned int blobOid; + long myId; + short dataFormat; + unsigned long size = 0; + char *cells = NULL; + int rowNumber = 0; + + // (1) --- declare cursor + pgResult = PQexec( pgConn, "SELECT BlobId, Tile, DataFormat FROM RAS_TILES" ); + if (PQresultStatus(pgResult) != PGRES_TUPLES_OK && PQresultStatus(pgResult) != PGRES_COMMAND_OK) + { + cerr << "error during 'declare cursor': " << PQerrorMessage(pgConn) << endl << flush; + exitNicely(); + } + + do + { + myId = atoi( PQgetvalue( pgResult, rowNumber, 0 ) ); + blobOid = atoi( PQgetvalue( pgResult, rowNumber, 1 ) ); + dataFormat = atoi( PQgetvalue( pgResult, rowNumber, 2 ) ); + + // (2) --- open, read, close blob + int fd = lo_open( pgConn, blobOid, INV_READ ); // open; manual tells no error indication + size = lo_lseek( pgConn, fd, 0, SEEK_END ); // determine blob size; FIXME: more efficient method?? + (void) lo_lseek( pgConn, fd, 0, SEEK_SET ); // rewind for reading + cells = (char*) mymalloc( size * sizeof(char) ); // allocate buffer for blob + if (cells == NULL) + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + int loResult = lo_read( pgConn, fd, cells, size ); + if (loResult < 0) + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + else if (loResult != size) // did not get all + { + cerr << "error: readFromDb() - no tuples found " << endl; + return; + } + int ignoredPgResult = lo_close( pgConn, fd ); // close blob + + if (withDump) + { + cout << " id = " << myId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << ", contents = "; + for (int a = 0; a < size; a++) + cout << " " << hex << (0xff & (unsigned int)(cells[a])) << dec; + cout << endl; + } + else + { + cout << " id = " << myId << ", dataFormat = " << dataFormat << ", bloboid = " << blobOid << ", blob length = " << size << endl; + } + + rowNumber++; + } while (PQresultStatus(pgResult) == PGRES_TUPLES_OK || PQresultStatus(pgResult) == PGRES_COMMAND_OK); + + (void) PQexec( pgConn, "CLOSE TileLoop" ); + PQclear( pgResult ); + free( cells ); +} + +void usage(const char *prog) +{ + cout << "Usage: " << prog << " -d db [-i id] [-c]" << endl; + cout << "where:" << endl; + cout << " -d db log into PG server using PG database name db (mandatory)" << endl; + cout << " -i id dump blob with identifier id (default: dump whole table)" << endl; + cout << " -c dump blob contents too (default: descriptor data only)" << endl; + cout << " -h (help) print this overview" << endl; +} + +int +main(int argc, char* argv[]) +{ + char *prog = argv[0]; // prog name + char db[100]; // DB connect string + int blobOid = 0; // tuple to be dumped + bool withDump = false; // by default, don't dump blob contents + char c; // getop var + int result; // for sscanf() + + cout << prog << ": dump rasdaman blob table." << endl; + + opterr = 0; + while ((c = getopt(argc, argv, "d:i:c")) != -1) + { + switch (c) + { + case 'd': + (void) strncpy( db, optarg, sizeof(db) ); + break; + case 'i': + result = sscanf( optarg, "%d", &blobOid ); + if (result != 1) + { + cerr << prog << ": positive integer expected: " << optarg << endl; + exit( RC_ERROR ); + } + break; + case 'c': + withDump = true; + break; + case 'h': + case '?': + case ':': + usage(prog); + exit( RC_OK ); + break; + } + } + if (optind < argc || argc == 1) + { + usage( prog ); + exit( RC_ERROR ); + } + + connect( db ); + + if (blobOid > 0) + readFromDb( blobOid, withDump ); + else + readAllFromDb( withDump ); + + disconnect(); + + cout << prog << ": done." << endl; +} + diff --git a/relblobif/test/dblob.C b/relblobif/test/dblob.C new file mode 100644 index 0000000..c9e2f3d --- /dev/null +++ b/relblobif/test/dblob.C @@ -0,0 +1,242 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#include "databaseif.hh" +#include "transactionif.hh" +#include "adminif.hh" +#include "typefactory.hh" +#include "blobtile.hh" +#include "raslib/rmdebug.hh" +#include <iostream.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "externs.h" +#include "dbref.hh" + +RMINITGLOBALS('C') + + +int +main(int argc, char *argv[]) + { + try { + RManDebug = 12; + RMInit::dbgOut = cout.rdbuf(); + if (argc == 1) + { + printf("Usage:\n"); + printf("\t%s DBNAME TEST ARGS\n", argv[0]); + printf("\twith DBNAME the name of the Database\n"); + printf("\tand TEST of:\n"); + printf("\t\t1 : Read BLOBTile ARGS=BlobId\n"); + printf("\t\t2 : Insert new BLOBTile ARGS=Blobdata\n"); + printf("\t\t3 : same as above with commit\n"); + printf("\t\t4 : Update BLOBTile ARGS=BlobId Blobdata\n"); + printf("\t\t5 : Delete BLOBTile ARGS=BlobId\n"); + printf("\t\t6 : Insert new BLOBTile ARGS=Blobdatalength\n"); + } + else { + if (argc >= 3) + { + AdminIf* myAdmin; + DatabaseIf database; + TransactionIf ta; + OId* id1 = 0; + BLOBTile* b = 0; + DbRef<BLOBTile> d; + + short test = atoi(argv[2]); + RManDebug = 12; + switch (test) + { + case 1: + if (argc == 4) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + id1 = new OId(atof(argv[3])); + cout << "Reading BLOBTile with Id " << *id1 << endl; + cout << "scop1" << endl; + { + d = DbRef<BLOBTile>(*id1); + cout << "Got BLOBTile" << endl; + cout << *d; + cout << "Refcount\t:" << d->getReferenceCount() << endl; + } + cout << "scop1" << endl; + ta.abort(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + break; + case 2: + if (argc == 4) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + b = new BLOBTile(strlen(argv[3]), argv[3]); + cout << *b; + //unneeded b->setPersistent(1); + b->validate(); + + cout << "Aborting Transaction" << endl; + ta.abort(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + break; + + case 3: + if (argc == 4) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + b = new BLOBTile(strlen(argv[3]), argv[3]); + //unneeded b->setPersistent(1); + cout << *b; + b->validate(); + + cout << "Commiting Transaction" << endl; + ta.commit(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + break; + + case 4: + if (argc == 5) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + id1 = new OId(atof(argv[3])); + cout << "Reading BLOBTile with Id " << *id1 << endl; + d = DbRef<BLOBTile>(*id1); + cout << *d; + + cout << "Resizing BLOBTile to " << strlen(argv[4]) << endl; + d->resize(strlen(argv[4])); + cout << *d; + + cout << "Overwriting BLOBTile data" << endl; + memcpy(d->getCells(), argv[4], d->getSize()); + cout << *d; + + cout << "Updating Database" << endl; + d->setModified(); + + cout << "Commiting Transaction" << endl; + ta.commit(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + + + case 5: + if (argc == 4) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + id1 = new OId(atof(argv[3])); + cout << "Reading BLOBTile with Id " << *id1 << endl; + d = DbRef<BLOBTile>(*id1); + cout << *d; + + cout << "Deleting BLOBTile from Database" << endl; + d->setPersistent(0); + cout << *d; + + cout << "Commiting Transaction" << endl; + ta.commit(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + case 6: + if (argc == 4) + { + cout << "Connect to Database: " << argv[1] << endl; + myAdmin = AdminIf::instance(); + database.open(argv[1]); + ta.begin(&database); + + b = new BLOBTile(atol(argv[3]), 'c'); + cout << *b; + //unneeded b->setPersistent(1); + b->validate(); + + cout << "Commiting Transaction" << endl; + ta.commit(); + database.close(); + delete myAdmin; + } + else { + printf("YOU DIRTY ASHOLE! NOT THE RIGHT COUNT OF ARGS!\n"); + } + break; + + } + } + else { + printf("TOO FEW ARGS\n"); + if (argc == 0) + { + TypeFactory type_(); + } + } + } + } + catch (r_Error e) + { + cout << "caught " << e.what() << " kind " << e.get_kind() << " #" << e.get_errorno() << endl; + } + return 0; + } diff --git a/relblobif/tileid.hh b/relblobif/tileid.hh new file mode 100644 index 0000000..da63043 --- /dev/null +++ b/relblobif/tileid.hh @@ -0,0 +1,36 @@ +/* +* This file is part of rasdaman community. +* +* Rasdaman community 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 3 of the License, or +* (at your option) any later version. +* +* Rasdaman community 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 rasdaman community. If not, see <http://www.gnu.org/licenses/>. +* +* Copyright 2003, 2004, 2005, 2006, 2007, 2008, 2009 Peter Baumann / +rasdaman GmbH. +* +* For more information please see <http://www.rasdaman.org> +* or contact Peter Baumann via <baumann@rasdaman.com>. +*/ +#ifndef _TILEID_HH_ +#define _TILEID_HH_ + +class DBTile; +class BLOBTile; +class InlineTile; + +template <class T> class DBRef; + +typedef DBRef<BLOBTile> BLOBTileId; +typedef DBRef<InlineTile> InlineTileId; +typedef DBRef<DBTile> DBTileId; + +#endif |