DB-Berkeley

 view release on metacpan or  search on metacpan

Berkeley.xs  view on Meta::CPAN

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <db.h>
#include <string.h>
#include <stdio.h>

// #define DEBUG_LOG(fmt, ...) fprintf(stderr, "[DB::Berkeley DEBUG] " fmt "\n", ##__VA_ARGS__)
#define	DEBUG_LOG(fmt, ...)

/*
 * Internal C struct to wrap a Berkeley DB handle.
 * We'll store a pointer to this inside a Perl scalar reference.
 */
typedef struct {
	DB	*dbp;  // Pointer to Berkeley DB handle
	DBC	*cursor; // for iterator
	int	readonly;
	int	sync_on_put; /* boolean flag */
	int	iteration_done;	// Reached the end of an each() loop
} Berk;

typedef struct {
	DBC *cursor;
	DB *dbp;
} BerkIter;

/*
 * Helper function to open a Berkeley DB file as a HASH.
 * Takes filename, flags, and mode.
 * Dies (croaks) on failure.
 */
static DB *
_bdb_open(const char *file, u_int32_t flags, int mode)
{
	DB *dbp;
	int ret;

	// Create the database handle
	ret = db_create(&dbp, NULL, 0);
	if (ret != 0) {
		croak("db_create failed: %s", db_strerror(ret));
	}

	// Open the database file as a HASH type
	if (flags & DB_RDONLY) {
		// Do NOT include DB_CREATE
		ret = dbp->open(dbp, NULL, file, NULL, DB_HASH, flags, mode);
	} else {
		// Include DB_CREATE for writeable DBs
		ret = dbp->open(dbp, NULL, file, NULL, DB_HASH, flags | DB_CREATE, mode);
	}

	if (ret != 0) {
		dbp->close(dbp, 0);
		croak("db->open failed: %s", db_strerror(ret));
	}

	return dbp;
}

static SV *
_bdb_get(SV *self, SV *key)
{
    const Berk *obj;
    DB *dbp;
    DBT k, v;
    char *kptr;
    STRLEN klen;
    int ret;

    obj = (Berk*)SvIV((SV*)SvRV(self));
    dbp = obj->dbp;

    kptr = SvPV(key, klen);

    memset(&k, 0, sizeof(DBT));
    k.data = kptr;
    k.size = klen;

    memset(&v, 0, sizeof(DBT));
    v.flags = DB_DBT_MALLOC;

    ret = dbp->get(dbp, NULL, &k, &v, 0);

Berkeley.xs  view on Meta::CPAN


    obj = (Berk*)SvIV((SV*)SvRV(self));
    dbp = obj->dbp;

    if (obj->readonly) {
        croak("DB is opened read-only; cannot perform put operation");
    }

    kptr = SvPV(key, klen);
    vptr = SvPV(value, vlen);

    memset(&k, 0, sizeof(DBT));
    k.data = kptr;
    k.size = klen;

    memset(&v, 0, sizeof(DBT));
    v.data = vptr;
    v.size = vlen;

    ret = dbp->put(dbp, NULL, &k, &v, 0);
    if (ret != 0) {
        croak("DB->put error: %s", db_strerror(ret));
    }
	if (obj->sync_on_put) {
		dbp->sync(dbp, 0);
	}

    return 1;
}

MODULE = DB::Berkeley    PACKAGE = DB::Berkeley
PROTOTYPES: ENABLE

SV *
new(class, file, flags, mode, sync_on_put = 0)
    char *class
    char *file
    int flags
    int mode
    int sync_on_put
PREINIT:
    Berk *obj;
    DB *dbp;
    SV *ret_sv;
CODE:

    DEBUG_LOG("new() called with file='%s', flags=%d, mode=%o", file, flags, mode);

    // Use default file mode if not specified
    if (mode == 0)
        mode = 0666;

    dbp = _bdb_open(file, flags, mode);  // Open Berkeley DB file

    obj = (Berk *)malloc(sizeof(Berk));
    if (!obj) {
        dbp->close(dbp, 0);
        croak("Out of memory");
    }
    obj->dbp = dbp;
    obj->cursor = NULL;
    obj->iteration_done = 0;
    obj->sync_on_put = sync_on_put;

    if(flags&DB_RDONLY) {
	obj->readonly = 1;
    } else {
	obj->readonly = 0;
    }

    // Bless the object reference
    ret_sv = sv_setref_pv(newSV(0), class, (void *)obj);
    DEBUG_LOG("DB handle created at %p", obj);

    RETVAL = ret_sv;
OUTPUT:
    RETVAL

int
put(self, key, value)
    SV *self
    SV *key
    SV *value
CODE:
    RETVAL = _bdb_put(self, key, value);
OUTPUT:
    RETVAL

SV *
get(self, key)
    SV *self
    SV *key
CODE:
    RETVAL = _bdb_get(self, key);
OUTPUT:
    RETVAL

int
delete(self, key)
    SV *self
    SV *key
PREINIT:
    Berk *obj;
    DB *dbp;
    DBT k;
    char *kptr;
    STRLEN klen;
    int ret;
CODE:
    obj = (Berk*)SvIV(SvRV(self));

    if(obj->readonly) {
        croak("DB is opened read-only; cannot perform delete operation");
    }
    dbp = obj->dbp;
    kptr = SvPV(key, klen);

    memset(&k, 0, sizeof(DBT));
    k.data = kptr;
    k.size = klen;

    ret = dbp->del(dbp, NULL, &k, 0);
    RETVAL = (ret == 0) ? 1 : 0;
OUTPUT:
    RETVAL

int
exists(self, key)
    SV *self
    SV *key
PREINIT:
    Berk *obj;
    DB *dbp;
    DBT k, v;
    char *kptr;
    STRLEN klen;
    int ret;
CODE:
    obj = (Berk*)SvIV(SvRV(self));
    dbp = obj->dbp;
    kptr = SvPV(key, klen);

    memset(&k, 0, sizeof(DBT));
    memset(&v, 0, sizeof(DBT));
    k.data = kptr;
    k.size = klen;
    v.flags = DB_DBT_PARTIAL;  // No need to retrieve full value

    ret = dbp->get(dbp, NULL, &k, &v, 0);
    RETVAL = (ret == 0) ? 1 : 0;
OUTPUT:
    RETVAL

AV *
keys(self)
    SV *self
PREINIT:
    Berk *obj;
    DB *dbp;
    DBC *cursor;
    DBT key, val;
    AV *av;
    int ret;
CODE:
    obj = (Berk *)SvIV(SvRV(self));
    dbp = obj->dbp;
    av = newAV();

    // Clear key and value structures
    memset(&key, 0, sizeof(DBT));
    memset(&val, 0, sizeof(DBT));

    // Open a cursor
    ret = dbp->cursor(dbp, NULL, &cursor, 0);
    if (ret != 0) {
        croak("DB::Berkeley keys(): cursor open failed: %s", db_strerror(ret));
    }

    // Iterate over the database using the cursor
    while ((ret = cursor->get(cursor, &key, &val, DB_NEXT)) == 0) {
        av_push(av, newSVpvn((char *)key.data, key.size));
    }

    // DB_NOTFOUND means clean end
    if (ret != DB_NOTFOUND) {
        cursor->close(cursor);
        croak("DB::Berkeley keys(): cursor iteration failed: %s", db_strerror(ret));
    }

    cursor->close(cursor);
    RETVAL = av;
OUTPUT:
    RETVAL

AV *
values(self)
    SV *self
PREINIT:
    Berk *obj;
    DBC *cursor;
    DBT k, v;
    int ret;
    AV *av;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    ret = obj->dbp->cursor(obj->dbp, NULL, &cursor, 0);
    if (ret != 0) {
        croak("db->cursor failed: %s", db_strerror(ret));
    }

    av = newAV();

    memset(&k, 0, sizeof(DBT));
    memset(&v, 0, sizeof(DBT));

    while ((ret = cursor->get(cursor, &k, &v, DB_NEXT)) == 0) {
        SV *sv = newSVpvn((char *)v.data, v.size);
        av_push(av, sv);
    }

    if (ret != DB_NOTFOUND) {
        cursor->close(cursor);
        croak("cursor->get failed: %s", db_strerror(ret));
    }

    cursor->close(cursor);
    RETVAL = av;
OUTPUT:
    RETVAL

void
rewind(self)
    SV *self
PREINIT:
    Berk *obj;
    int ret;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    // Close previous cursor if it exists
    if (obj->cursor) {
        obj->cursor->close(obj->cursor);
        obj->cursor = NULL;
    }

    ret = obj->dbp->cursor(obj->dbp, NULL, &obj->cursor, 0);
    if (ret != 0) {
        croak("Failed to create cursor: %s", db_strerror(ret));
    }

void
iterator_reset(self)
    SV *self
PREINIT:
    Berk *obj;
    int ret;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    if (obj->cursor) {
        obj->cursor->close(obj->cursor);
        obj->cursor = NULL;
    }
    obj->iteration_done = 0;

    ret = obj->dbp->cursor(obj->dbp, NULL, &obj->cursor, 0);
    if (ret != 0) {
        croak("iterator_reset: Failed to create cursor: %s", db_strerror(ret));
    }

SV *
next_key(self)
    SV *self
PREINIT:
    Berk *obj;
    DBT k, v;
    int ret;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    if (!obj->cursor) {
        croak("Iterator not initialized. Call iterator_reset() first.");
    }

    memset(&k, 0, sizeof(DBT));
    memset(&v, 0, sizeof(DBT));

    ret = obj->cursor->get(obj->cursor, &k, &v, DB_NEXT);
    if (ret == DB_NOTFOUND) {
        RETVAL = &PL_sv_undef;
    } else if (ret != 0) {
        croak("next_key: cursor->get failed: %s", db_strerror(ret));
    } else {
        RETVAL = newSVpvn((char *)k.data, k.size);
    }
OUTPUT:
    RETVAL

SV *
each(self)
    SV *self
PREINIT:
    Berk *obj;
    DBT k, v;
    int ret;
    AV *av;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    if(obj->iteration_done) {
	DEBUG_LOG("end() attempt to read beyond end of loop");
	XSRETURN_EMPTY;
	return;
    }

    if (!obj->cursor) {
        // First call to each() – create cursor
        ret = obj->dbp->cursor(obj->dbp, NULL, &obj->cursor, 0);
        if (ret != 0) {
            croak("each: cursor creation failed: %s", db_strerror(ret));
        }
    }

    memset(&k, 0, sizeof(DBT));
    memset(&v, 0, sizeof(DBT));
    v.flags = DB_DBT_MALLOC;

    ret = obj->cursor->get(obj->cursor, &k, &v, DB_NEXT);
    if (ret == DB_NOTFOUND) {
        obj->cursor->close(obj->cursor);
        obj->cursor = NULL;
	obj->iteration_done = 1;
		DEBUG_LOG("end() end of loop");
	XSRETURN_EMPTY;
    } else if (ret != 0) {
        croak("each: cursor->get failed: %s", db_strerror(ret));
    } else {
        av = newAV();
        av_push(av, newSVpvn((char *)k.data, k.size));
        av_push(av, newSVpvn((char *)v.data, v.size));
        free(v.data);
        RETVAL = newRV_noinc((SV *)av);
    }
OUTPUT:
    RETVAL

int
store(self, key, value)
    SV *self
    SV *key
    SV *value
CODE:
    RETVAL = _bdb_put(self, key, value);
OUTPUT:
    RETVAL

int
set(self, key, value)
    SV *self
    SV *key
    SV *value
CODE:
    RETVAL = _bdb_put(self, key, value);
OUTPUT:
    RETVAL

SV *
fetch(self, key)
    SV *self
    SV *key
CODE:
    RETVAL = _bdb_get(self, key);
OUTPUT:
    RETVAL

int
sync(self)
    SV *self
PREINIT:
    const Berk *obj;
    DB *dbp;
    int ret;
CODE:
{
    obj = (Berk*)SvIV((SV*)SvRV(self));
    dbp = obj->dbp;

    if(obj->readonly) {
        croak("DB is opened read-only; cannot perform sync operation");
    }
    ret = dbp->sync(dbp, 0);
    if (ret != 0) {
        croak("DB->sync error: %s", db_strerror(ret));
    }
    RETVAL = 1;
}
OUTPUT:
    RETVAL

int
sync_on_put(self, newval = -1)
    SV *self
    int newval
PREINIT:
    Berk *obj;
CODE:
{
    obj = (Berk*)SvIV((SV*)SvRV(self));

    if (newval != -1) {
        obj->sync_on_put = newval ? 1 : 0;
    }

    RETVAL = obj->sync_on_put;
}
OUTPUT:
    RETVAL

SV *
iterator(self)
    SV *self
PREINIT:
    const Berk *obj;
    BerkIter *it;
    SV *ret;
    int retcode;
CODE:
    obj = (Berk *)SvIV(SvRV(self));

    it = malloc(sizeof(BerkIter));
    if (!it) croak("Out of memory");

    it->dbp = obj->dbp;
    it->cursor = NULL;

    retcode = obj->dbp->cursor(obj->dbp, NULL, &it->cursor, 0);
    if (retcode != 0) {
        free(it);
        croak("iterator: cursor creation failed: %s", db_strerror(retcode));
    }

    ret = sv_setref_pv(newSV(0), "DB::Berkeley::Iterator", (void*)it);
    RETVAL = ret;
OUTPUT:
    RETVAL

void
DESTROY(self)
	SV *self
PREINIT:
	Berk *obj;
	int ret;
CODE:
	obj = (Berk *)SvIV(SvRV(self));
	DEBUG_LOG("DESTROY() called");
	if (obj) {
		if (obj->cursor) {
			DEBUG_LOG("DESTROY() closing cursor");
			obj->cursor->close(obj->cursor);
			obj->cursor = NULL;
		}
		if (obj->dbp) {
			DEBUG_LOG("DESTROY() closing handle");
			obj->dbp->close(obj->dbp, 0);  // Close DB handle
		}
		DEBUG_LOG("DESTROY() freeing the structure");
		free(obj);  // Free the struct
	}
	DEBUG_LOG("DESTROY() left");

MODULE = DB::Berkeley     PACKAGE = DB::Berkeley::Iterator

SV *
each(self)
	SV *self
PREINIT:
	BerkIter *it;
	DBT k, v;
	AV *av;
	int ret;
CODE:
	it = (BerkIter *)SvIV(SvRV(self));

	memset(&k, 0, sizeof(DBT));
	memset(&v, 0, sizeof(DBT));
	v.flags = DB_DBT_MALLOC;

	ret = it->cursor->get(it->cursor, &k, &v, DB_NEXT);
	if (ret == DB_NOTFOUND) {
		RETVAL = &PL_sv_undef;
	} else if (ret != 0) {
		croak("each(): cursor get failed: %s", db_strerror(ret));
	} else {
		av = newAV();
		av_push(av, newSVpvn((char *)k.data, k.size));
		av_push(av, newSVpvn((char *)v.data, v.size));
		free(v.data);
		RETVAL = newRV_noinc((SV *)av);
	}
OUTPUT:
	RETVAL

void
iterator_reset(self)
	SV *self
PREINIT:
	BerkIter *iter;
	int ret;
CODE:
	iter = (BerkIter *)SvIV(SvRV(self));
	if (iter->cursor) {
		iter->cursor->close(iter->cursor);
		iter->cursor = NULL;
	}
	ret = iter->dbp->cursor(iter->dbp, NULL, &iter->cursor, 0);
	if (ret != 0) {
		croak("iterator_reset: cursor creation failed: %s", db_strerror(ret));
	}

void
DESTROY(self)
	SV *self
PREINIT:
	BerkIter *it;
CODE:
	it = (BerkIter *)SvIV(SvRV(self));
	if (it->cursor) {
		it->cursor->close(it->cursor);
	}
	free(it);



( run in 0.960 second using v1.01-cache-2.11-cpan-39bf76dae61 )