2

I'm using leveldb to store key-value pairs of integer and MyClass objects. Actually, a key can contain more then one of theses objects. The problem I have appears when retrieving the data from the database. It compiles, however the values of the MyClass members are not the one I put into the database.

std::string value;
leveldb::Slice keySlice = ANYKEY;
levelDBObj->Get(leveldb::ReadOptions(), keySlice, &value);

The std::string value1 can now contain only one MyClass object or more. So how do I get them?

I already tried the following which didn't work;

1.) directly typecasting and memcpy

std::vector<MyClass> vObjects;
MyClass* obj = (MyClass*)malloc( value.size());
memcpy((void*)obj, (void*) (value.c_str()), value.size());
MyClass dummyObj;
int numValues = value.size()/sizeof(MyClass);
for( int i=0; i<numValues; ++i) {
    dummyObj = *(obj+i);
    vObjects.push_back(dummyObj);
}

2.) reinterpret_cast to void pointer

MyClass* obj = (MyClass*)malloc( value.size());
const void* vobj = reinterpret_cast<const void*>( value.c_str() );
int numValues = value.size()/sizeof(MyClass);
for( int i=0; i<numValues; ++i) {
    const MyClass dummyObj = *(reinterpret_cast<const MyClass*>(vobj)+i);
    vObjects.push_back(dummyObj);
}

MyClass is a collection of several public members, e.g. unsigned int and unsigned char and it has a stable size.

I know that there are similar problems with only one object. But in my case the vector can contain more then one and it comes from the leveldb database.

EDIT: SOLUTION

I wrote (de)serialization method for MyClass which then made it working. Thanks for the hint!

void MyClass::serialize( char* outBuff ) {
    memcpy(outBuff, (const void*) &aVar, sizeof(aVar));
    unsigned int c = sizeof(aVar);
    memcpy(outBuff+c, (const void*) &bVar, sizeof(bVar));
    c += sizeof(bVAr);
    /* and so on */
}

void MyClass::deserialize( const char* inBuff ) {
    memcpy((void*) &aVar, inBuff, sizeof(aVar));
    unsigned int c = sizeof(aVar);
    memcpy((void*) &aVar, inBuff+c, sizeof(aVar));
    c += sizeof(aVar);
    /* and so on */
}

The get method is as follows (put analogously):

int getValues(leveldb::Slice keySlice, std::vector<MyObj>& values) const {
    std::string value;  
    leveldb::Status status = levelDBObj->Get(leveldb::ReadOptions(), keySlice, &value);
    if (!status.ok()) {
        values.clear();
            return -1;
    }

    int nValues = value1.size()/sizeof(CHit);
    MyObj dummyObj;
    for( int i=0; i<nValues; ++i) {
        dummyObj.deserialize(value.c_str()+i*sizeof(MyObj));
        values.push_back(dummyObj);
    }

    return 0;
}               
ezdazuzena
  • 6,120
  • 6
  • 41
  • 71
  • Can you also show us a code snippet of how you write to leveldb? I suspect that when you're putting your object into the value slice, you're not serializing it. – Kiril Jan 24 '12 at 16:49
  • Right, I'm not serializing it. I hadn't time to try it (I first have to look how to do this in my case). I'll give feedback tomorrow when I'm back to my code ;) – ezdazuzena Jan 24 '12 at 20:11

1 Answers1

2

You have to serialize your class... otherwise, you're just taking some memory and writing it in leveldb. Whatever you get back is not only going to be different, but it will probably be completely useless too. Check out this question for more info on serialization: How do you serialize an object in C++?

LevelDB does support multiple objects under one key, however, try to avoid doing that unless you have a really good reason. I would recommend that you hash each object with a unique hash (see Google's CityHash if you want a hashing function) and store the serialized objects with their corresponding hash. If your objects is a collection in itself, then you have to serialize all of your objects to an array of bytes and have some method that allows you to determine where each object begins/ends.

Update

A serializable class would look something like this:

class MyClass
{
private:
    int _numeric;
    string _text;
public:
    // constructors 

    // mutators
    void SetNumeric(int num);
    void SetText(string text);

    static unsigned int SerializableSize()
    {
        // returns the serializable size of the class with the schema:
        // 4 bytes for the numeric (integer) 
        // 4 bytes for the unsigned int (the size of the text)
        // n bytes for the text (it has a variable size)
        return sizeof(int) + sizeof(unsigned int) + _text.size();
    }

    // serialization
    int Serialize(const char* buffer, const unsigned int bufferLen, const unsigned int position)
    {
        // check if the object can be serialized in the available buffer space
        if(position+SerializableSize()>bufferLen)
        {
            // don't write anything and return -1 signaling that there was an error
            return -1;
        }

        unsigned int finalPosition = position;

        // write the numeric value
        *(int*)(buffer + finalPosition) = _numeric;

        // move the final position past the numeric value
        finalPosition += sizeof(int);

        // write the size of the text
        *(unsigned int*)(buffer + finalPosition) = (unsigned int)_text.size();

        // move the final position past the size of the string
        finalPosition += sizeof(unsigned int);

        // write the string
        memcpy((void*)(buffer+finalPosition), _text.c_str(), (unsigned int)_text.size());

        // move the final position past the end of the string
        finalPosition += (unsigned int)_text.size();

        // return the number of bytes written to the buffer
        return finalPosition-position;
    }

    // deserialization
    static int Deserialize(MyClass& myObject, 
        const char* buffer, 
        const unsigned int buffSize, 
        const unsigned int position)
    {
        insigned int currPosition = position;

        // copy the numeric value
        int numeric = *(int*)(buffer + currentPosition);

        // increment the current position past the numeric value
        currentPosition += sizeof(int);

        // copy the size of the text
        unsigned int textSize = *(unsigned int*)(buffer + currentPosition);

        // increment the current position past the size of the text
        currentPosition += sizeof(unsigned int);

        // copy the text
        string text((buffer+currentPosition), textSize);

        if(currentPosition > buffSize)
        {
            // you decide what to do here
        }

        // Set your object's values
        myObject.SetNumeric(numeric);
        myObject.SetText(text);

        // return the number of bytes deserialized
        return currentPosition - position;
    }
};
Community
  • 1
  • 1
Kiril
  • 39,672
  • 31
  • 167
  • 226
  • I'd like to avoid using boost, you don't have a code snipped for that at hand? And, I have /really good reasons/ to use multiple objects ;) – ezdazuzena Jan 25 '12 at 08:29
  • Well, if boost is not an option, then you can [serialize by using streams](http://www.functionx.com/cpp/articles/serialization.htm). Depending on your speed requirements, you can also use JSON: http://stackoverflow.com/questions/245973/whats-the-best-c-json-parser (which is more robust and forgiving in case you change the schema). – Kiril Jan 25 '12 at 13:46
  • Thanks for your help. I posted my solution. – ezdazuzena Jan 25 '12 at 16:04
  • @ezdazuzena just added a code example of how you can make your class serializable without any third party libraries. **(edit) Just saw your solution... well, now you have two examples :)** – Kiril Jan 25 '12 at 16:18