From 584f6f8cecf5cfbb6c014bd75d74ebf676b9e350 Mon Sep 17 00:00:00 2001
From: Per Hedbor <ph@opera.com>
Date: Sat, 16 Aug 2014 12:32:07 +0200
Subject: [PATCH] Did some overhauling of the Gdbm module

o Gdbm.gdbm is now known as Gdbm.DB
o Added support for iteration over gdbm objects
o Added overloading of _m_delete, _indices and _values.
o Added some more documentation
---
 src/modules/Gdbm/gdbmmod.c | 333 ++++++++++++++++++++++++++++++++++---
 1 file changed, 307 insertions(+), 26 deletions(-)

diff --git a/src/modules/Gdbm/gdbmmod.c b/src/modules/Gdbm/gdbmmod.c
index 7db500b589..f98b958a68 100644
--- a/src/modules/Gdbm/gdbmmod.c
+++ b/src/modules/Gdbm/gdbmmod.c
@@ -14,6 +14,7 @@
 #include "svalue.h"
 #include "stralloc.h"
 #include "module.h"
+#include "array.h"
 #include "program.h"
 #include "module_support.h"
 
@@ -26,10 +27,11 @@ static MUTEX_T gdbm_lock STATIC_MUTEX_INIT;
 #endif  
 
 #define sp Pike_sp
-
+static struct program *iterator;
 struct gdbm_glue
 {
   GDBM_FILE dbf;
+  struct pike_string *iter;
 };
 
 #define THIS ((struct gdbm_glue *)(Pike_fp->current_storage))
@@ -48,6 +50,11 @@ static void do_free(void)
     mt_unlock(& gdbm_lock);
     THREADS_DISALLOW();
   }
+  if(THIS->iter)
+  {
+    free_string(THIS->iter);
+    THIS->iter=0;
+  }
 }
 
 /* Compat with old gdbm. */
@@ -105,18 +112,35 @@ static int fixmods(char *mods)
   }
 }
 
-void gdbmmod_fatal(char *err)
+void gdbmmod_fatal(const char *err)
 {
   Pike_error("GDBM: %s\n",err);
 }
 
 /*! @module Gdbm
+ *!
+ *! This module provides an interface to the GNU ddm databae.
+ *!
+ *! The basic use of GDBM is to store key/data pairs in a data
+ *! file. Each key must be unique and each key is paired with only one
+ *! data item.
+ *!
+ *! The library provides primitives for storing key/data pairs,
+ *! searching and retrieving the data by its key and deleting a key
+ *! along with its data. It also support sequential iteration over all
+ *! key/data pairs in a database.
+ *!
+ *! The @[DB] class also overloads enough operators to make it behave
+ *! a lot like a @ref[mapping(string(8bit):string(8bit))@], you can index it,
+ *! assign indices and loop over it using foreach.
+ *!
+ *!
  */
 
-/*! @class gdbm
+/*! @class DB
  */
 
-/*! @decl void create(void|string file, void|string mode)
+/*! @decl protected void create(void|string file, void|string(99..119) mode)
  *!
  *! Without arguments, this function does nothing. With one argument it
  *! opens the given file as a gdbm database, if this fails for some
@@ -152,13 +176,19 @@ void gdbmmod_fatal(char *err)
  *!  closed properly. Unfortunately this will not be the case if Pike
  *!  calls exit() or returns from main(). You should therefore make sure
  *!  you call close or destruct your gdbm objects when exiting your
- *!  program. This will probably be done automatically in the future.
+ *!  program.
+ *! 
+ *!  @[atexit] might be useful.
+ *! 
+ *!  This is very important if the database is used with the 'l' flag.
  */
 
 static void gdbmmod_create(INT32 args)
 {
   struct gdbm_glue *this=THIS;
   do_free();
+  if(!args)
+    Pike_error("Need at least one argument to Gdbm.DB, the filename\n");
   if(args)
   {
     GDBM_FILE tmp;
@@ -205,13 +235,12 @@ static void gdbmmod_create(INT32 args)
 #define STRING_TO_DATUM(dat, st) dat.dptr=st->str,dat.dsize=st->len;
 #define DATUM_TO_STRING(dat) make_shared_binary_string(dat.dptr, dat.dsize)
 
-/*! @decl string fetch(string key)
- *! @decl string `[](string key)
+/*! @decl string(8bit) fetch(string(8bit) key)
+ *! @decl string(8bit) `[](string(8bit) key)
  *!
  *! Return the data associated with the key 'key' in the database.
  *! If there was no such key in the database, zero is returned.
  */
-
 static void gdbmmod_fetch(INT32 args)
 {
   struct gdbm_glue *this=THIS;
@@ -240,7 +269,7 @@ static void gdbmmod_fetch(INT32 args)
     push_string(DATUM_TO_STRING(ret));
     free(ret.dptr);
   }else{
-    push_int(0);
+    push_undefined();
   }
 }
 
@@ -250,7 +279,6 @@ static void gdbmmod_fetch(INT32 args)
  *! otherwise 0, e.g. when the item is not present or the
  *! database is read only.
  */
-
 static void gdbmmod_delete(INT32 args)
 {
   struct gdbm_glue *this=THIS;
@@ -277,10 +305,76 @@ static void gdbmmod_delete(INT32 args)
   push_int( ret==0 );
 }
 
+/*! @decl string(8bit) _m_delete( string(8bit) key )
+ *! 
+ *! Provides overloading of the @[m_delete] function.
+ *! 
+ *! Will return the value the key had before it was removed, if any
+ *!
+ *! If the key exists but deletion fails (usually due to a read only
+ *! database) this function will throw an error.
+ */
+static void gdbmmod_m_delete(INT32 args)
+{
+  struct gdbm_glue *this=THIS;
+  datum key, ret;
+
+  if(TYPEOF(sp[-args]) != T_STRING)
+  {
+    push_undefined();
+    return;
+  }
+
+  if(!this->dbf)
+    Pike_error("GDBM database not open.\n");
+
+  STRING_TO_DATUM(key, sp[-args].u.string);
+
+  THREADS_ALLOW();
+  mt_lock(& gdbm_lock);
+  ret=gdbm_fetch(this->dbf, key);
+  if( ret.dptr )
+    if( gdbm_delete(this->dbf, key) )
+      Pike_error("Failed to delete key from database.\n");
+  mt_unlock(& gdbm_lock);
+  THREADS_DISALLOW();
+  if(ret.dptr)
+  {
+    push_string(DATUM_TO_STRING(ret));
+    free(ret.dptr);
+  }else{
+    push_undefined();
+  }
+}
+
 /*! @decl string firstkey()
  *!
  *! Return the first key in the database, this can be any key in the
  *! database.
+ *!
+ *! Used together with @[nextkey] the databse can be iterated.
+ *!
+ *! @note
+ *! The database also works as an @[Iterator], and can be used as the
+ *! first argument to @[foreach].
+ *!
+ *! Adding or removing keys will change the iteration order,
+ *! this can cause keys to be skipped while iterating.
+ *!
+ *! 
+ *! @example
+ *! @code
+ *!  // Write the contents of the database
+ *!  for(key=gdbm->firstkey(); k; k=gdbm->nextkey(k))
+ *!    write(k+":"+gdbm->fetch(k)+"\n");
+ *! @endcode
+ *!
+ *! Or, using foreach
+ *! @code
+ *!  // Write the contents of the database
+ *! foreach( db; string key; string value )
+ *!    write(key+":"+value+"\n");
+ *! @endcode
  */
 
 static void gdbmmod_firstkey(INT32 args)
@@ -306,15 +400,32 @@ static void gdbmmod_firstkey(INT32 args)
   }
 }
 
-/*! @decl string nextkey(string key)
+/*! @decl string(8bit) nextkey(string(8bit) key)
  *!
  *! This returns the key in database that follows the key 'key' key.
  *! This is of course used to iterate over all keys in the database.
  *!
+ *! @note
+ *! Changing (adding or removing keys) the database while iterating
+ *! can cause keys to be skipped.
+ *!
+ *! The database also works as an @[Iterator], and can be used as the
+ *! first argument to @[foreach].
+ *! 
  *! @example
+ *! @code
  *!  // Write the contents of the database
  *!  for(key=gdbm->firstkey(); k; k=gdbm->nextkey(k))
  *!    write(k+":"+gdbm->fetch(k)+"\n");
+ *! @endcode
+ *!
+ *! Or, using foreach
+ *! @code
+ *!  // Write the contents of the database
+ *! foreach( db; string key; string value )
+ *!    write(key+":"+value+"\n");
+ *! @endcode
+ *!
  */
 
 static void gdbmmod_nextkey(INT32 args)
@@ -348,7 +459,7 @@ static void gdbmmod_nextkey(INT32 args)
   }
 }
 
-/*! @decl string `[]= (string key, string data)
+/*! @decl string `[]= (string(8bit) key, string(8bit) data)
  *!
  *! Associate the contents of 'data' with the key 'key'. If the key 'key'
  *! already exists in the database the data for that key will be replaced.
@@ -443,7 +554,7 @@ static void gdbmmod_store_compat(INT32 args)
  *! Deletions and insertions into the database can cause fragmentation
  *! which will make the database bigger. This routine reorganizes the
  *! contents to get rid of fragmentation. Note however that this function
- *! can take a LOT of time to run.
+ *! can take a LOT of time to run if the database is big.
  */
 
 static void gdbmmod_reorganize(INT32 args)
@@ -470,11 +581,9 @@ static void gdbmmod_reorganize(INT32 args)
  *! on the disk.
  */
 
-static void gdbmmod_sync(INT32 args)
+static void gdbmmod_sync(INT32 UNUSED(args))
 {
   struct gdbm_glue *this=THIS;
-  pop_n_elems(args);
-
   if(!THIS->dbf) Pike_error("GDBM database not open.\n");
   THREADS_ALLOW();
   mt_lock(& gdbm_lock);
@@ -484,9 +593,129 @@ static void gdbmmod_sync(INT32 args)
   push_int(0);
 }
 
+
+static void gdbmmod_iter_first(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  gdbmmod_firstkey(0);
+  if( Pike_sp[-1].u.string )
+    this->iter = Pike_sp[-1].u.string;
+  Pike_sp--;
+  push_int( !!this->iter );
+}
+
+static void gdbmmod_iter_next(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  if(!this->iter) 
+  {
+      push_undefined();
+      return;
+  }
+  push_string( this->iter );
+  gdbmmod_nextkey(1);
+  if( TYPEOF(Pike_sp[-1]) != PIKE_T_STRING )
+  {
+    this->iter = 0;
+    push_undefined();
+    return;
+  }
+  this->iter = Pike_sp[-1].u.string;
+  push_int(1);
+  return;
+
+}
+
+static void gdbmmod_iter_index(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  if( this->iter )
+    ref_push_string( this->iter );
+  else
+    push_undefined();
+}
+
+static void gdbmmod_iter_no_value(INT32 UNUSED(args))
+{
+  push_int( !THIS->iter );
+}
+
+static void gdbmmod_iter_value(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  if( this->iter )
+  {
+    ref_push_string( this->iter );
+    gdbmmod_fetch(1);    
+  }
+  else
+    push_undefined();
+}
+
+/*! @decl array(string(8bit)) _indices()
+ *!
+ *! Provides overloading of @[indices].
+ *! 
+ *! @note
+ *! Mainly useful when debugging, the returned list might not fit in
+ *! memory for large databases.
+ */
+static void gdbmmod_indices(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  struct svalue *start = Pike_sp;
+  gdbmmod_iter_first(0);
+  pop_stack();
+  while( this->iter )
+  {
+    ref_push_string( this->iter );
+    gdbmmod_iter_next(0);
+    pop_stack();
+  }
+  push_array(aggregate_array( Pike_sp-start ));
+}
+
+/*! @decl array(string(8bit)) _values()
+ *!
+ *! Provides overloading of @[values].
+ *! 
+ *! @note
+ *! Mainly useful when debugging, the returned list might not fit in
+ *! memory for large databases.
+ */
+static void gdbmmod_values(INT32 UNUSED(args))
+{
+  struct gdbm_glue *this=THIS;
+  struct svalue *start = Pike_sp;
+  gdbmmod_iter_first(0);
+  pop_stack();
+
+  while( this->iter )
+  {
+    ref_push_string( this->iter );
+    gdbmmod_fetch(1);
+    gdbmmod_iter_next(0);
+    pop_stack();
+  }
+  push_array(aggregate_array( Pike_sp-start ));
+}
+
+static void gdbmmod_get_iterator(INT32 UNUSED(args))
+{
+  push_object( clone_object( iterator, 0 ) );
+  *((struct gdbm_glue *)Pike_sp[-1].u.object->storage) = *THIS;
+  apply(Pike_sp[-1].u.object, "first", 0);
+  pop_stack();
+}
+
+
 /*! @decl void close()
  *!
- *! Closes the database.
+ *! Closes the database. The object is no longer usable after this function has been called.
+ *!
+ *! This is also done automatically when the object is destructed for
+ *! any reason (running out of references or explicit destruct, as an
+ *! example)
  */
 
 static void gdbmmod_close(INT32 args)
@@ -500,6 +729,13 @@ static void gdbmmod_close(INT32 args)
 static void init_gdbm_glue(struct object *UNUSED(o))
 {
   THIS->dbf=0;
+  THIS->iter=0;
+}
+
+static void init_gdbm_iterator(struct object *UNUSED(o))
+{
+  THIS->dbf=0;
+  THIS->iter=0;
 }
 
 static void exit_gdbm_glue(struct object *UNUSED(o))
@@ -507,52 +743,97 @@ static void exit_gdbm_glue(struct object *UNUSED(o))
   do_free();
 }
 
+static void exit_gdbm_iterator(struct object *UNUSED(o))
+{
+  if( THIS->iter )
+    free_string( THIS->iter );
+}
+
 /*! @endclass
  */
 
+/*! @class Iterator
+ *! @inherit predef::Iterator
+ *!
+ *! Object keeping track of an iteration over a @[DB]
+ *!
+ *! @note
+ *! Can not be usefully constructed manually, instead use the database
+ *! as the first argument to @[foreach] or @[predef::get_iterator]
+ *!
+ */
+
+ /*! @endclass
+ */
+
 /*! @endmodule
  */
 
 #endif /* defined(HAVE_GDBM_H) && defined(HAVE_LIBGDBM) */
 
-PIKE_MODULE_EXIT {}
+PIKE_MODULE_EXIT {
+}
 
 PIKE_MODULE_INIT
 {
+  struct program *db;
 #if defined(HAVE_GDBM_H) && defined(HAVE_LIBGDBM)
   start_new_program();
   ADD_STORAGE(struct gdbm_glue);
   
   /* function(void|string,void|string:void) */
   ADD_FUNCTION("create", gdbmmod_create,
-	       tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr), tVoid), 0 /*ID_PROTECTED*/);
+	       tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr), tVoid), ID_PROTECTED);
 
   /* function(:void) */
   ADD_FUNCTION("close",gdbmmod_close,tFunc(tNone,tVoid),0);
   /* function(string, string, int(0..1)|void: int) */
   ADD_FUNCTION("store", gdbmmod_store_compat,
-	       tFunc(tStr tStr tOr(tInt01, tVoid), tInt), 0);
+	       tFunc(tStr8 tStr8 tOr(tInt01, tVoid), tInt), 0);
   /* function(string, string, int(0..1)|void: string) */
   ADD_FUNCTION("`[]=", gdbmmod_store,
-	       tFunc(tStr tSetvar(0, tStr) tOr(tInt01, tVoid), tVar(0)), 0);
+	       tFunc(tStr8 tSetvar(0, tStr8) tOr(tInt01, tVoid), tVar(0)), 0);
   /* function(string:string) */
-  ADD_FUNCTION("fetch",gdbmmod_fetch,tFunc(tStr,tStr),0);
+  ADD_FUNCTION("fetch",gdbmmod_fetch,tFunc(tStr,tStr8),0);
   /* function(string:string) */
-  ADD_FUNCTION("`[]",gdbmmod_fetch,tFunc(tStr,tStr),0);
+  ADD_FUNCTION("`[]",gdbmmod_fetch,tFunc(tStr,tStr8),0);
   /* function(string:int) */
   ADD_FUNCTION("delete",gdbmmod_delete,tFunc(tStr,tInt01),0);
   /* function(:string) */
-  ADD_FUNCTION("firstkey",gdbmmod_firstkey,tFunc(tNone,tStr),0);
+  ADD_FUNCTION("firstkey",gdbmmod_firstkey,tFunc(tNone,tStr8),0);
   /* function(string:string) */
-  ADD_FUNCTION("nextkey",gdbmmod_nextkey,tFunc(tStr,tStr),0);
+  ADD_FUNCTION("nextkey",gdbmmod_nextkey,tFunc(tStr,tStr8),0);
   /* function(:int) */
   ADD_FUNCTION("reorganize",gdbmmod_reorganize,tFunc(tNone,tInt),0);
   /* function(:void) */
   ADD_FUNCTION("sync",gdbmmod_sync,tFunc(tNone,tVoid),0);
 
+  /* iterator API.*/
+  ADD_FUNCTION("_get_iterator", gdbmmod_get_iterator,tFunc(tNone,tObj),0);
+
+  /* some mapping operator overloading */
+  ADD_FUNCTION("_m_delete",gdbmmod_m_delete,tFunc(tStr,tStr8),0);
+  ADD_FUNCTION("_values",gdbmmod_values,tFunc(tNone,tArr(tStr8)),0);
+  ADD_FUNCTION("_indices",gdbmmod_indices,tFunc(tNone,tArr(tStr8)),0);
+
   set_init_callback(init_gdbm_glue);
   set_exit_callback(exit_gdbm_glue);
-  end_class("gdbm",0);
+  db = end_program();
+  start_new_program();
+  ADD_STORAGE(struct gdbm_glue);
+  ADD_FUNCTION("first", gdbmmod_iter_first,tFunc(tNone,tInt01),0);
+  ADD_FUNCTION("next",  gdbmmod_iter_next, tFunc(tNone,tInt01),0);
+  ADD_FUNCTION("index", gdbmmod_iter_index,tFunc(tNone,tStr8),0);
+  ADD_FUNCTION("value", gdbmmod_iter_value,tFunc(tNone,tStr8),0);
+  ADD_FUNCTION("`!",    gdbmmod_iter_no_value,tFunc(tNone,tInt01),0);
+  set_init_callback(init_gdbm_iterator);
+  set_exit_callback(exit_gdbm_iterator);
+  iterator = end_program();
+  add_program_constant( "Iterator", iterator, 0 );
+
+  add_program_constant( "DB", db, 0 );
+  add_program_constant( "gdbm", db, 0 ); /* compat (...-7.8). */
+  free_program(db);
 #else
   if(!TEST_COMPAT(7,6))
     HIDE_MODULE();
-- 
GitLab