Change "union value" to dynamically allocate long strings.
[pspp-builds.git] / src / data / case-tmpfile.c
index ca3a6a564421552f4241900caa2fb21708f08226..5744786611a737fb467f8f7c4c9673164696cc40 100644 (file)
 
 #include <libpspp/assertion.h>
 #include <libpspp/taint.h>
+#include <libpspp/tmpfile.h>
 
 #include "error.h"
 #include "xalloc.h"
 
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-
 /* A temporary file that stores an array of cases. */
 struct case_tmpfile
   {
     struct taint *taint;        /* Taint. */
-    FILE *file;                 /* Underlying file. */
-    size_t value_cnt;           /* Number of `union value's per case. */
-
-    /* Current byte offset in file.  We track this manually,
-       instead of using ftello, because in glibc ftello flushes
-       the stream buffer, making the common case of sequential
-       access to cases unreasonably slow. */
-    off_t position;
+    struct caseproto *proto;    /* Format of cases in the tmpfile. */
+    size_t case_size;           /* Number of bytes per case. */
+    size_t *offsets;            /* Offset to each value. */
+    struct tmpfile *tmpfile;    /* Temporary file. */
   };
 
-/* Creates and returns a new case_tmpfile. */
+/* Returns the number of bytes needed to store a value with the
+   given WIDTH on disk. */
+static size_t
+width_to_n_bytes (int width)
+{
+  return width == 0 ? sizeof (double) : width;
+}
+
+/* Returns the address of the data in VALUE (for reading or
+   writing to/from disk).  VALUE must have the given WIDTH. */
+static void *
+value_to_data (const union value *value_, int width)
+{
+  union value *value = (union value *) value_;
+  assert (sizeof value->f == sizeof (double));
+  if (width == 0)
+    return &value->f;
+  else
+    return value_str_rw (value, width);
+}
+
+/* Creates and returns a new case_tmpfile that will store cases
+   that match case prototype PROTO.  The caller retains
+   ownership of PROTO. */
 struct case_tmpfile *
-case_tmpfile_create (size_t value_cnt)
+case_tmpfile_create (const struct caseproto *proto)
 {
-  struct case_tmpfile *ctf = xmalloc (sizeof *ctf);
+  struct case_tmpfile *ctf;
+  size_t n_values;
+  size_t i;
+
+  ctf = xmalloc (sizeof *ctf);
   ctf->taint = taint_create ();
-  ctf->file = tmpfile ();
-  if (ctf->file == NULL)
+  ctf->tmpfile = tmpfile_create ();
+  ctf->proto = caseproto_ref (proto);
+  ctf->case_size = 0;
+  n_values = caseproto_get_n_widths (proto);
+  ctf->offsets = xmalloc (n_values * sizeof *ctf->offsets);
+  for (i = 0; i < n_values; i++)
     {
-      error (0, errno, _("failed to create temporary file"));
-      taint_set_taint (ctf->taint);
+      size_t width = caseproto_get_width (proto, i);
+      ctf->offsets[i] = ctf->case_size;
+      ctf->case_size += width == -1 ? 0 : width == 0 ? sizeof (double) : width;
     }
-  ctf->value_cnt = value_cnt;
-  ctf->position = 0;
   return ctf;
 }
 
@@ -73,8 +97,9 @@ case_tmpfile_destroy (struct case_tmpfile *ctf)
   if (ctf != NULL)
     {
       struct taint *taint = ctf->taint;
-      if (ctf->file != NULL)
-        fclose (ctf->file);
+      tmpfile_destroy (ctf->tmpfile);
+      caseproto_unref (ctf->proto);
+      free (ctf->offsets);
       free (ctf);
       ok = taint_destroy (taint);
     }
@@ -104,98 +129,32 @@ case_tmpfile_get_taint (const struct case_tmpfile *ctf)
   return ctf->taint;
 }
 
-/* Seeks CTF's underlying file to the start of `union value'
-   VALUE_IDX within case CASE_IDX.
-   Returns true if the seek is successful and CTF is not
-   otherwise tainted, false otherwise. */
-static bool
-do_seek (const struct case_tmpfile *ctf_,
-         casenumber case_idx, size_t value_idx)
-{
-  struct case_tmpfile *ctf = (struct case_tmpfile *) ctf_;
-
-  if (!case_tmpfile_error (ctf))
-    {
-      off_t value_ofs = value_idx + (off_t) ctf->value_cnt * case_idx;
-      off_t byte_ofs = sizeof (union value) * value_ofs;
-
-      if (ctf->position == byte_ofs)
-        return true;
-      else if (fseeko (ctf->file, byte_ofs, SEEK_SET) == 0)
-        {
-          ctf->position = byte_ofs;
-          return true;
-        }
-      else
-        {
-          error (0, errno, _("seeking in temporary file"));
-          case_tmpfile_force_error (ctf);
-        }
-    }
-
-  return false;
-}
-
-/* Reads BYTES bytes from CTF's underlying file into BUFFER.
-   CTF must not be tainted upon entry into this function.
-   Returns true if successful, false upon an I/O error (in which
-   case CTF is marked tainted). */
-static bool
-do_read (const struct case_tmpfile *ctf_, size_t bytes, void *buffer)
-{
-  struct case_tmpfile *ctf = (struct case_tmpfile *) ctf_;
-
-  assert (!case_tmpfile_error (ctf));
-  if (fread (buffer, bytes, 1, ctf->file) != 1)
-    {
-      case_tmpfile_force_error (ctf);
-      if (ferror (ctf->file))
-        error (0, errno, _("reading temporary file"));
-      else if (feof (ctf->file))
-        error (0, 0, _("unexpected end of file reading temporary file"));
-      else
-        NOT_REACHED ();
-      return false;
-    }
-  ctf->position += bytes;
-  return true;
-}
-
-/* Writes BYTES bytes from BUFFER into CTF's underlying file.
-   CTF must not be tainted upon entry into this function.
-   Returns true if successful, false upon an I/O error (in which
-   case CTF is marked tainted). */
-static bool
-do_write (struct case_tmpfile *ctf, size_t bytes, const void *buffer)
-{
-  assert (!case_tmpfile_error (ctf));
-  if (fwrite (buffer, bytes, 1, ctf->file) != 1)
-    {
-      case_tmpfile_force_error (ctf);
-      error (0, errno, _("writing to temporary file"));
-      return false;
-    }
-  ctf->position += bytes;
-  return true;
-}
-
-/* Reads VALUE_CNT values into VALUES, from the case numbered
-   CASE_IDX starting START_VALUE values into that case.
-   Returns true if successful, false if CTF is tainted or an I/O
-   error occurs during the operation.
+/* Reads N_VALUES values into VALUES, from the case numbered
+   CASE_IDX starting START_VALUE values into that case.  Returns
+   true if successful, false if CTF is tainted or an I/O error
+   occurs during the operation.
 
    The results of this function are undefined if any of the
    values read have not been previously written to CTF. */
 bool
 case_tmpfile_get_values (const struct case_tmpfile *ctf,
                          casenumber case_idx, size_t start_value,
-                         union value values[], size_t value_cnt)
+                         union value values[], size_t n_values)
 {
-  assert (value_cnt <= ctf->value_cnt);
-  assert (value_cnt + start_value <= ctf->value_cnt);
+  off_t case_offset = (off_t) ctf->case_size * case_idx;
+  size_t i;
 
-  return (do_seek (ctf, case_idx, start_value)
-          && do_read (ctf, sizeof *values * value_cnt, values));
+  assert (caseproto_range_is_valid (ctf->proto, start_value, n_values));
+  for (i = start_value; i < start_value + n_values; i++)
+    {
+      int width = caseproto_get_width (ctf->proto, i);
+      if (width != -1
+          && !tmpfile_read (ctf->tmpfile, case_offset + ctf->offsets[i],
+                            width_to_n_bytes (width),
+                            value_to_data (&values[i], width)))
+          return false;
+    }
+  return true;
 }
 
 /* Reads the case numbered CASE_IDX from CTF.
@@ -207,9 +166,9 @@ case_tmpfile_get_values (const struct case_tmpfile *ctf,
 struct ccase *
 case_tmpfile_get_case (const struct case_tmpfile *ctf, casenumber case_idx)
 {
-  struct ccase *c = case_create (ctf->value_cnt);
-  if (case_tmpfile_get_values (ctf, case_idx, 0,
-                               case_data_all_rw (c), ctf->value_cnt))
+  struct ccase *c = case_create (ctf->proto);
+  if (case_tmpfile_get_values (ctf, case_idx, 0, case_data_all_rw (c),
+                               caseproto_get_n_widths (ctf->proto)))
     return c;
   else
     {
@@ -218,21 +177,29 @@ case_tmpfile_get_case (const struct case_tmpfile *ctf, casenumber case_idx)
     }
 }
 
-/* Writes VALUE_CNT values from VALUES, into the case numbered
+/* Writes N_VALUES values from VALUES, into the case numbered
    CASE_IDX starting START_VALUE values into that case.
    Returns true if successful, false if CTF is tainted or an I/O
    error occurs during the operation. */
 bool
 case_tmpfile_put_values (struct case_tmpfile *ctf,
                          casenumber case_idx, size_t start_value,
-                         const union value values[], size_t value_cnt)
-
+                         const union value values[], size_t n_values)
 {
-  assert (value_cnt <= ctf->value_cnt);
-  assert (value_cnt + start_value <= ctf->value_cnt);
+  off_t case_offset = (off_t) ctf->case_size * case_idx;
+  size_t i;
 
-  return (do_seek (ctf, case_idx, start_value)
-          && do_write (ctf, sizeof *values * value_cnt, values));
+  assert (caseproto_range_is_valid (ctf->proto, start_value, n_values));
+  for (i = start_value; i < start_value + n_values; i++)
+    {
+      int width = caseproto_get_width (ctf->proto, i);
+      if (width != -1
+          && !tmpfile_write (ctf->tmpfile, case_offset + ctf->offsets[i],
+                             width_to_n_bytes (width),
+                             value_to_data (values++, width)))
+          return false;
+    }
+  return true;
 }
 
 /* Writes C to CTF as the case numbered CASE_IDX.
@@ -242,8 +209,8 @@ bool
 case_tmpfile_put_case (struct case_tmpfile *ctf, casenumber case_idx,
                        struct ccase *c)
 {
-  bool ok = case_tmpfile_put_values (ctf, case_idx, 0,
-                                     case_data_all (c), ctf->value_cnt);
+  bool ok = case_tmpfile_put_values (ctf, case_idx, 0, case_data_all (c),
+                                     caseproto_get_n_widths (ctf->proto));
   case_unref (c);
   return ok;
 }