pxd: initial work
[pspp] / src / libpspp / pxd.c
diff --git a/src/libpspp/pxd.c b/src/libpspp/pxd.c
new file mode 100644 (file)
index 0000000..00bc888
--- /dev/null
@@ -0,0 +1,692 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2010, 2013 Free Software Foundation, Inc.
+
+   This program 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.
+
+   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "libpspp/pxd.h"
+
+#include <gdbm.h>
+#include <stdlib.h>
+
+#include "data/value.h"
+#include "libpspp/cast.h"
+#include "libpspp/compiler.h"
+#include "libpspp/float-format.h"
+#include "libpspp/integer-format.h"
+#include "libpspp/intern.h"
+
+#include "gl/error.h"
+#include "gl/minmax.h"
+#include "gl/sha1.h"
+#include "gl/xalloc.h"
+#include "gl/xmemdup0.h"
+
+struct pxd
+  {
+    GDBM_FILE dbf;
+  };
+
+static struct pxd_object *pxd_object_lookup__ (const struct pxd_id *id);
+static struct pxd_object *pxd_object_create_raw__ (char *raw, size_t n_links,
+                                                   size_t size,
+                                                   const struct pxd_id *);
+
+static datum
+make_datum (const void *data, size_t size)
+{
+  datum d;
+
+  d.dptr = CONST_CAST (void *, data);
+  d.dsize = size;
+
+  return d;
+}
+
+static datum
+string_datum (const char *s)
+{
+  return make_datum (s, strlen (s));
+}
+
+static datum
+id_datum (const struct pxd_id *id)
+{
+  return make_datum (id, sizeof *id);
+}
+
+static datum
+root_key (void)
+{
+  return string_datum ("root");
+}
+
+void
+pxd_get_root (const struct pxd *pxd, struct pxd_id *id)
+{
+  datum content;
+
+  content = gdbm_fetch (pxd->dbf, root_key ());
+  if (content.dptr != NULL && content.dsize == sizeof *id)
+    memcpy (id, content.dptr, sizeof *id);
+  else
+    memset (id, 0, sizeof *id);
+  free (content.dptr);
+}
+
+struct pxd *
+pxd_open (const char *name, bool may_create)
+{
+  struct pxd *pxd;
+  GDBM_FILE dbf;
+
+  dbf = gdbm_open (CONST_CAST (char *, name), 4096,
+                   may_create ? GDBM_WRCREAT : GDBM_WRITER, 0777, NULL);
+  if (dbf == NULL)
+    {
+      error (0, 0, "%s: open failed (%s)", name, gdbm_strerror (gdbm_errno));
+      return NULL;
+    }
+
+  pxd = xmalloc (sizeof *pxd);
+  pxd->dbf = dbf;
+  return pxd;
+}
+
+void
+pxd_close (struct pxd *pxd)
+{
+  if (pxd != NULL)
+    {
+      gdbm_close (pxd->dbf);
+      free (pxd);
+    }
+}
+
+struct pxd_object *
+pxd_fetch (const struct pxd *pxd, const struct pxd_id *id)
+{
+  struct pxd_object *obj;
+  datum content;
+  uint32_t n_links;
+  size_t header_size;
+
+  obj = pxd_object_lookup__ (id);
+  if (obj != NULL)
+    return obj;
+
+  content = gdbm_fetch (pxd->dbf, id_datum (id));
+  if (content.dptr == NULL)
+    error (1, 0, PXD_ID_FMT": not found in database", PXD_ID_ARGS (id));
+
+  if (content.dsize < sizeof n_links)
+    error (1, 0, PXD_ID_FMT": size %d is less than minimum size 4",
+           PXD_ID_ARGS(id), content.dsize);
+
+  n_links = le32_to_cpu (*(uint32_t *) content.dptr);
+  header_size = 4 + n_links * sizeof (struct pxd_id);
+  if (header_size > content.dsize)
+    error (1, 0, PXD_ID_FMT": size %d is less than minimum size %zu for "
+           "object with %"PRIu32" links",
+           PXD_ID_ARGS (id), content.dsize, header_size, n_links);
+
+  return pxd_object_create_raw__ (content.dptr, n_links,
+                                  content.dsize - header_size, id);
+}
+
+void
+pxd_store (struct pxd *pxd, const struct pxd_object *object)
+{
+  datum content;
+
+  content = make_datum (pxd_object_raw_data__ (object),
+                        pxd_object_raw_size__ (object));
+  if (gdbm_store (pxd->dbf, id_datum (&object->id), content, GDBM_INSERT) < 0)
+    error (1, 0, PXD_ID_FMT": storing object failed",
+           PXD_ID_ARGS (&object->id));
+}
+
+bool
+pxd_swap_root (struct pxd *pxd, const struct pxd_id *old_root,
+               struct pxd_object *new_root)
+{
+  struct pxd_id cur_root;
+
+  pxd_get_root (pxd, &cur_root);
+  if (pxd_id_equals (&cur_root, old_root))
+    {
+      if (gdbm_store (pxd->dbf, root_key (), id_datum (&new_root->id),
+                      GDBM_INSERT) < 0)
+        error (1, 0, "storing root object failed");
+      pxd_object_unref (new_root);
+      return true;
+    }
+  else
+    return false;
+}
+\f
+static struct hmap object_table = HMAP_INITIALIZER (object_table);
+
+static struct pxd_object *
+pxd_object_lookup__ (const struct pxd_id *id)
+{
+  const struct pxd_object *obj;
+
+  HMAP_FOR_EACH_IN_BUCKET (obj, struct pxd_object, hmap_node,
+                           pxd_id_hash (id), &object_table)
+    if (pxd_id_equals (id, &obj->id))
+      return pxd_object_ref (obj);
+
+  return NULL;
+}
+
+static struct pxd_object *
+pxd_object_create_raw__ (char *raw, size_t n_links, size_t size,
+                         const struct pxd_id *id)
+{
+  struct pxd_object *obj;
+
+  obj = xmalloc (sizeof *obj);
+  hmap_insert (&object_table, &obj->hmap_node, pxd_id_hash (id));
+  obj->n_refs = 1;
+  obj->id = *id;
+  obj->n_links = n_links;
+  obj->size = size;
+  obj->links = (struct pxd_id *) (raw + sizeof (uint32_t));
+  obj->data = (uint8_t *) (obj->links + obj->n_links);
+
+  return obj;
+}
+
+struct pxd_object *
+pxd_object_create (const struct pxd_id links[], size_t n_links,
+                   const void *data, size_t size)
+{
+  struct pxd_object *obj;
+  struct sha1_ctx sha;
+  uint32_t le_n_links;
+  struct pxd_id id;
+  char *raw;
+
+  /* Hash the raw form of the object. */
+  sha1_init_ctx (&sha);
+  le_n_links = cpu_to_le32 (n_links);
+  sha1_process_bytes (&le_n_links, sizeof le_n_links, &sha);
+  sha1_process_bytes (links, n_links * sizeof *links, &sha);
+  sha1_process_bytes (data, size, &sha);
+  sha1_finish_ctx (&sha, &id);
+
+  /* Reuse an existing copy if there is one. */
+  obj = pxd_object_lookup__ (&id);
+  if (obj != NULL)
+    return obj;
+
+  /* Build a new object. */
+  raw = xmalloc (sizeof le_n_links + n_links * sizeof *links +
+                 size);
+  memcpy (raw, &le_n_links, sizeof le_n_links);
+  memcpy (raw + sizeof le_n_links, links, n_links * sizeof *links);
+  memcpy (raw + sizeof le_n_links + n_links * sizeof *links, data, size);
+
+  return pxd_object_create_raw__ (raw, n_links, size, &id);
+}
+
+struct pxd_object *
+pxd_object_ref (const struct pxd_object *const_obj)
+{
+  struct pxd_object *obj = CONST_CAST (struct pxd_object *, const_obj);
+  obj->n_refs++;
+  return obj;
+}
+
+void
+pxd_object_unref (struct pxd_object *obj)
+{
+  if (obj != NULL && --obj->n_refs == 0)
+    {
+      hmap_delete (&object_table, &obj->hmap_node);
+      free (pxd_object_raw_data__ (obj));
+      free (obj);
+    }
+}
+
+void *
+pxd_object_raw_data__ (const struct pxd_object *obj)
+{
+  return ((uint32_t *) obj->links) - 1;
+}
+
+size_t
+pxd_object_raw_size__ (const struct pxd_object *obj)
+{
+  return sizeof (uint32_t *) + obj->n_links * sizeof *obj->links + obj->size;
+}
+\f
+void
+pxd_builder_init (struct pxd_builder *b, struct pxd *pxd)
+{
+  b->pxd = pxd;
+
+  b->links = NULL;
+  b->n_links = b->links_allocated = 0;
+
+  b->data = NULL;
+  b->size = b->data_allocated = 0;
+}
+
+void
+pxd_builder_destroy (struct pxd_builder *b)
+{
+  if (b != NULL)
+    {
+      free (b->links);
+      b->links = NULL;
+
+      free (b->data);
+      b->data = NULL;
+    }
+}
+
+struct pxd_object *
+pxd_builder_commit (struct pxd_builder *b)
+{
+  struct pxd_object *obj;
+
+  obj = pxd_object_create (b->links, b->n_links, b->data, b->size);
+  pxd_store (b->pxd, obj);
+  pxd_builder_destroy (b);
+
+  return obj;
+}
+
+void *
+pxd_builder_put_uninit (struct pxd_builder *b, size_t n)
+{
+  void *p;
+
+  if (b->size + n > b->data_allocated)
+    {
+      b->data_allocated = MAX (b->size + n + 64, b->data_allocated * 2);
+      b->data = xrealloc (b->data, b->data_allocated);
+    }
+
+  p = &b->data[b->size];
+  b->size += n;
+  return p;
+}
+
+void
+pxd_builder_put (struct pxd_builder *b, const void *p, size_t n)
+{
+  memcpy (pxd_builder_put_uninit (b, n), p, n);
+}
+
+void
+pxd_builder_put_value (struct pxd_builder *b,
+                       const union value *value, int width)
+{
+  if (width == 0)
+    pxd_builder_put_double (b, value->f);
+  else
+    {
+      const uint8_t *s = value_str (value, width);
+      if (width < sizeof (struct pxd_id))
+        pxd_builder_put (b, s, width);
+      else
+        pxd_builder_put_link (b, pxd_object_create (NULL, 0, s, width));
+    }
+}
+
+static void
+pxd_builder_put_string__ (struct pxd_builder *b, const char *s, size_t length)
+{
+  pxd_builder_put_u32 (b, length);
+  if (length < sizeof (struct pxd_id))
+    pxd_builder_put (b, s, length);
+  else
+    pxd_builder_put_link (b, pxd_object_create (NULL, 0, s, length));
+}
+
+void
+pxd_builder_put_string (struct pxd_builder *b, const char *s)
+{
+  return pxd_builder_put_string__ (b, s, strlen (s));
+}
+
+void
+pxd_builder_put_interned_string (struct pxd_builder *b, const char *s)
+{
+  return pxd_builder_put_string__ (b, s, intern_strlen (s));
+}
+
+void
+pxd_builder_put_bool (struct pxd_builder *b, bool x)
+{
+  pxd_builder_put_u8 (b, x != 0);
+}
+
+void
+pxd_builder_put_u8 (struct pxd_builder *b, unsigned char x)
+{
+  pxd_builder_put (b, &x, 1);
+}
+
+void
+pxd_builder_put_u16 (struct pxd_builder *b, unsigned short int x)
+{
+  uint16_t le_x = cpu_to_le16 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void pxd_builder_put_u32 (struct pxd_builder *b, unsigned int x)
+{
+  uint32_t le_x = cpu_to_le32 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_u64 (struct pxd_builder *b, unsigned long long int x)
+{
+  uint64_t le_x = cpu_to_le64 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_s8 (struct pxd_builder *b, signed char x)
+{
+  pxd_builder_put (b, &x, 1);
+}
+
+void
+pxd_builder_put_s16 (struct pxd_builder *b, short int x)
+{
+  uint16_t le_x = cpu_to_le16 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void pxd_builder_put_s32 (struct pxd_builder *b, int x)
+{
+  uint32_t le_x = cpu_to_le32 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_s64 (struct pxd_builder *b, long long int x)
+{
+  uint64_t le_x = cpu_to_le64 (x);
+  pxd_builder_put (b, &le_x, sizeof (le_x));
+}
+
+void
+pxd_builder_put_size_t (struct pxd_builder *b, size_t x)
+{
+  pxd_builder_put_u64 (b, x);
+}
+
+void
+pxd_builder_put_casenumber (struct pxd_builder *b, casenumber x)
+{
+  pxd_builder_put_u64 (b, x);
+}
+
+void
+pxd_builder_put_double (struct pxd_builder *b, double x)
+{
+  pxd_builder_put_u64 (b, double_to_ieee64le (x));
+}
+
+void
+pxd_builder_put_link (struct pxd_builder *b, struct pxd_object *obj)
+{
+  if (b->n_links >= b->links_allocated)
+    b->links = x2nrealloc (b->links, &b->links_allocated, sizeof *b->links);
+
+  b->links[b->n_links++] = obj->id;
+}
+\f
+void
+pxd_parser_init (struct pxd_parser *p,
+                 struct pxd_object *obj, const struct pxd *pxd)
+{
+  p->obj = obj;
+  p->pxd = pxd;
+  p->offset = 0;
+  p->link = 0;
+}
+
+void
+pxd_parser_destroy (struct pxd_parser *p)
+{
+  if (p != NULL)
+    pxd_object_unref (p->obj);
+}
+
+
+const void *
+pxd_parser_get (struct pxd_parser *p, size_t n)
+{
+  const void *data;
+
+  assert (p->offset + n <= p->obj->size);
+
+  data = &p->obj->data[p->offset];
+  p->offset += n;
+  return data;
+}
+
+void
+pxd_parser_get_copy (struct pxd_parser *p, void *dst, size_t n)
+{
+  memcpy (dst, pxd_parser_get (p, n), n);
+}
+
+void
+pxd_parser_get_value (struct pxd_parser *p, union value *value, int width)
+{
+  value_init (value, width);
+  if (width == 0)
+    value->f = ieee64le_to_double (pxd_parser_get_u64 (p));
+  else
+    {
+      uint8_t *s = value_str_rw (value, width);
+      if (width < sizeof (struct pxd_id))
+        pxd_parser_get_copy (p, s, width);
+      else
+        {
+          struct pxd_object *link = pxd_parser_get_link (p);
+          assert (link->size == width);
+          memcpy (s, link->data, width);
+          pxd_object_unref (link);
+        }
+    }
+}
+
+char *
+pxd_parser_get_string (struct pxd_parser *p)
+{
+  uint32_t length = pxd_parser_get_u32 (p);
+  if (length < sizeof (struct pxd_id))
+    return xmemdup0 (pxd_parser_get (p, length), length);
+  else
+    {
+      struct pxd_object *link;
+      char *s;
+
+      link = pxd_parser_get_link (p);
+      assert (link->size == length);
+      s = xmemdup0 (link->data, link->size);
+      pxd_object_unref (link);
+
+      return s;
+    }
+}
+
+const char *
+pxd_parser_get_interned_string (struct pxd_parser *p)
+{
+  uint32_t length = pxd_parser_get_u32 (p);
+  if (length < sizeof (struct pxd_id))
+    return intern_buffer (pxd_parser_get (p, length), length);
+  else
+    {
+      struct pxd_object *link;
+      const char *s;
+
+      link = pxd_parser_get_link (p);
+      assert (link->size == length);
+      s = intern_buffer ((const void *) link->data, link->size);
+      pxd_object_unref (link);
+
+      return s;
+    }
+}
+
+bool
+pxd_parser_get_bool (struct pxd_parser *p)
+{
+  return pxd_parser_get_u8 (p) != 0;
+}
+
+unsigned char
+pxd_parser_get_u8 (struct pxd_parser *p)
+{
+  uint8_t x;
+  pxd_parser_get_copy (p, &x, 1);
+  return x;
+}
+
+unsigned short int
+pxd_parser_get_u16 (struct pxd_parser *p)
+{
+  uint16_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le16_to_cpu (x);
+}
+
+unsigned int
+pxd_parser_get_u32 (struct pxd_parser *p)
+{
+  uint32_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le32_to_cpu (x);
+}
+
+unsigned long long int
+pxd_parser_get_u64 (struct pxd_parser *p)
+{
+  uint64_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le64_to_cpu (x);
+}
+
+signed char
+pxd_parser_get_s8 (struct pxd_parser *p)
+{
+  int8_t x;
+  pxd_parser_get_copy (p, &x, 1);
+  return x;
+}
+
+short int
+pxd_parser_get_s16 (struct pxd_parser *p)
+{
+  uint16_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le16_to_cpu (x);
+}
+
+int
+pxd_parser_get_s32 (struct pxd_parser *p)
+{
+  uint32_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le32_to_cpu (x);
+}
+
+long long int
+pxd_parser_get_s64 (struct pxd_parser *p)
+{
+  uint64_t x;
+  pxd_parser_get_copy (p, &x, sizeof x);
+  return le64_to_cpu (x);
+}
+
+size_t
+pxd_parser_get_size_t (struct pxd_parser *p)
+{
+  uint64_t x = pxd_parser_get_u64 (p);
+  assert (x <= SIZE_MAX);
+  return x;
+}
+
+casenumber
+pxd_parser_get_casenumber (struct pxd_parser *p)
+{
+  uint64_t x = pxd_parser_get_u64 (p);
+  assert (x <= CASENUMBER_MAX);
+  return x;
+}
+
+double
+pxd_parser_get_double (struct pxd_parser *p)
+{
+  return ieee64le_to_double (pxd_parser_get_u64 (p));
+}
+
+struct pxd_object *
+pxd_parser_get_link (struct pxd_parser *p)
+{
+  assert (p->link < p->obj->n_links);
+
+  return pxd_fetch (p->pxd, &p->obj->links[p->link++]);
+}
+\f
+void
+pxd_array_builder_init (struct pxd_array_builder *ab, struct pxd *pxd)
+{
+  pxd_builder_init (&ab->b, pxd);
+}
+
+void
+pxd_array_builder_destroy (struct pxd_array_builder *ab)
+{
+  pxd_builder_destroy (&ab->b);
+}
+
+struct pxd_object *
+pxd_array_builder_commit (struct pxd_array_builder *ab)
+{
+  return pxd_builder_commit (&ab->b);
+}
+
+void
+pxd_array_builder_add (struct pxd_array_builder *ab, struct pxd_object *obj)
+{
+  pxd_builder_put_link (&ab->b, obj);
+}
+\f
+void
+pxd_array_init (struct pxd_array *array, struct pxd_object *obj,
+                const struct pxd *pxd)
+{
+  array->pxd = pxd;
+  array->obj = obj;
+}
+
+void
+pxd_array_destroy (struct pxd_array *array)
+{
+  pxd_object_unref (array->obj);
+}