Add linked list library to PSPP.
[pspp-builds.git] / src / data / casefile.c
index 8b1ac82838f55ff49d587ec1a14f7d620cad4f62..7681a65f9d4e3e33b1c272760ed33ea0bc78e621 100644 (file)
           during the reading phase.  Each casereader has an
           independent position in the casefile.
 
-          Casereaders may only move forward.  They cannot move
-          backward to arbitrary records or seek randomly.
-          Cloning casereaders is possible, but it is not yet
-          implemented.
+          Ordinary casereaders may only move forward.  They
+          cannot move backward to arbitrary records or seek
+          randomly.  Cloning casereaders is possible, but it is
+          not yet implemented.
 
           Use casefile_get_reader() to create a casereader for
           use in phase 2.  This also transitions from phase 1 to
           a case from a casereader.  Use casereader_destroy() to
           discard a casereader when it is no longer needed.
 
+          "Random" casereaders, which support a seek operation,
+          may also be created.  These should not, generally, be
+          used for statistical procedures, because random access
+          is much slower than sequential access.  They are
+          intended for use by the GUI.
+
        3. Destruction.  This phase is optional.  The casefile is
           also read with casereaders in this phase, but the
           ability to create new casereaders is curtailed.
@@ -129,7 +135,7 @@ struct casefile
     enum { MEMORY, DISK } storage;      /* Where cases are stored. */
     enum { WRITE, READ } mode;          /* Is writing or reading allowed? */
     struct casereader *readers;         /* List of our readers. */
-    int being_destroyed;                /* Does a destructive reader exist? */
+    bool being_destroyed;               /* Does a destructive reader exist? */
     bool ok;                            /* False after I/O error. */
 
     /* Memory storage. */
@@ -149,10 +155,13 @@ struct casereader
     struct casereader *next, *prev;     /* Next, prev in casefile's list. */
     struct casefile *cf;                /* Our casefile. */
     unsigned long case_idx;             /* Case number of current case. */
-    int destructive;                    /* Is this a destructive reader? */
+    bool destructive;                   /* Is this a destructive reader? */
+    bool random;                        /* Is this a random reader? */
 
     /* Disk storage. */
     int fd;                             /* File descriptor. */
+    off_t file_ofs;                     /* Current position in fd. */
+    off_t buffer_ofs;                   /* File offset of buffer start. */
     union value *buffer;                /* I/O buffer. */
     size_t buffer_pos;                  /* Offset of buffer position. */
     struct ccase c;                     /* Current case. */
@@ -174,10 +183,11 @@ static size_t case_bytes;
 static void register_atexit (void);
 static void exit_handler (void);
 
-static void reader_open_file (struct casereader *reader);
-static void write_case_to_disk (struct casefile *cf, const struct ccase *c);
-static void flush_buffer (struct casefile *cf);
-static bool fill_buffer (struct casereader *reader);
+static void reader_open_file (struct casereader *);
+static void write_case_to_disk (struct casefile *, const struct ccase *);
+static void flush_buffer (struct casefile *);
+static void seek_and_fill_buffer (struct casereader *);
+static bool fill_buffer (struct casereader *);
 
 static void io_error (struct casefile *, const char *, ...)
      PRINTF_FORMAT (2, 3);
@@ -272,9 +282,9 @@ casefile_error (const struct casefile *cf)
   return !cf->ok;
 }
 
-/* Returns nonzero only if casefile CF is stored in memory (instead of on
-   disk). */
-int
+/* Returns true only if casefile CF is stored in memory (instead of on
+   disk), false otherwise. */
+bool
 casefile_in_core (const struct casefile *cf) 
 {
   assert (cf != NULL);
@@ -423,8 +433,6 @@ flush_buffer (struct casefile *cf)
                        cf->buffer_size * sizeof *cf->buffer))
         io_error (cf, _("Error writing temporary file: %s."),
                   strerror (errno));
-
-
       cf->buffer_used = 0;
     }
 }
@@ -519,6 +527,7 @@ casefile_get_reader (const struct casefile *cf_)
   reader->cf = cf;
   reader->case_idx = 0;
   reader->destructive = 0;
+  reader->random = false;
   reader->fd = -1;
   reader->buffer = NULL;
   reader->buffer_pos = 0;
@@ -530,6 +539,23 @@ casefile_get_reader (const struct casefile *cf_)
   return reader;
 }
 
+/* Creates and returns a random casereader for CF.  A random
+   casereader can be used to randomly read the cases in a
+   casefile. */
+struct casereader *
+casefile_get_random_reader (const struct casefile *cf) 
+{
+  struct casefile  *mutable_casefile = (struct casefile*) cf;
+  struct casereader *reader;
+
+  enum { WRITE, READ } mode = cf->mode ;
+  reader = casefile_get_reader (cf);
+  reader->random = true;
+  mutable_casefile->mode = mode;
+  
+  return reader;
+}
+
 /* Creates and returns a destructive casereader for CF.  Like a
    normal casereader, a destructive casereader sequentially reads
    the cases in a casefile.  Unlike a normal casereader, a
@@ -556,8 +582,6 @@ static void
 reader_open_file (struct casereader *reader) 
 {
   struct casefile *cf = reader->cf;
-  off_t file_ofs;
-
   if (!cf->ok || reader->case_idx >= cf->case_cnt)
     return;
 
@@ -585,24 +609,42 @@ reader_open_file (struct casereader *reader)
       memset (reader->buffer, 0, cf->buffer_size * sizeof *cf->buffer); 
     }
 
+  case_create (&reader->c, cf->value_cnt);
+
+  reader->buffer_ofs = -1;
+  reader->file_ofs = -1;
+  seek_and_fill_buffer (reader);
+}
+
+/* Seeks the backing file for READER to the proper position and
+   refreshes the buffer contents. */
+static void
+seek_and_fill_buffer (struct casereader *reader) 
+{
+  struct casefile *cf = reader->cf;
+  off_t new_ofs;
+
   if (cf->value_cnt != 0) 
     {
       size_t buffer_case_cnt = cf->buffer_size / cf->value_cnt;
-      file_ofs = ((off_t) reader->case_idx / buffer_case_cnt
+      new_ofs = ((off_t) reader->case_idx / buffer_case_cnt
                   * cf->buffer_size * sizeof *cf->buffer);
       reader->buffer_pos = (reader->case_idx % buffer_case_cnt
                             * cf->value_cnt);
     }
   else 
-    file_ofs = 0;
-  if (lseek (reader->fd, file_ofs, SEEK_SET) != file_ofs)
-    io_error (cf, _("%s: Seeking temporary file: %s."),
-              cf->file_name, strerror (errno));
+    new_ofs = 0;
+  if (new_ofs != reader->file_ofs) 
+    {
+      if (lseek (reader->fd, new_ofs, SEEK_SET) != new_ofs)
+        io_error (cf, _("%s: Seeking temporary file: %s."),
+                  cf->file_name, strerror (errno));
+      else
+        reader->file_ofs = new_ofs;
+    }
 
-  if (cf->case_cnt > 0 && cf->value_cnt > 0)
+  if (cf->case_cnt > 0 && cf->value_cnt > 0 && reader->buffer_ofs != new_ofs)
     fill_buffer (reader);
-
-  case_create (&reader->c, cf->value_cnt);
 }
 
 /* Fills READER's buffer by reading a block from disk. */
@@ -618,7 +660,12 @@ fill_buffer (struct casereader *reader)
                   reader->cf->file_name, strerror (errno));
       else if (bytes != reader->cf->buffer_size * sizeof *reader->buffer) 
         io_error (reader->cf, _("%s: Temporary file ended unexpectedly."),
-                  reader->cf->file_name); 
+                  reader->cf->file_name);
+      else 
+        {
+          reader->buffer_ofs = reader->file_ofs;
+          reader->file_ofs += bytes; 
+        }
     }
   return reader->cf->ok;
 }
@@ -635,13 +682,13 @@ casereader_get_casefile (const struct casereader *reader)
 /* Reads a copy of the next case from READER into C.
    Caller is responsible for destroying C.
    Returns true if successful, false at end of file. */
-int
+bool
 casereader_read (struct casereader *reader, struct ccase *c) 
 {
   assert (reader != NULL);
   
   if (!reader->cf->ok || reader->case_idx >= reader->cf->case_cnt) 
-    return 0;
+    return false;
 
   if (reader->cf->storage == MEMORY) 
     {
@@ -650,14 +697,14 @@ casereader_read (struct casereader *reader, struct ccase *c)
 
       case_clone (c, &reader->cf->cases[block_idx][case_idx]);
       reader->case_idx++;
-      return 1;
+      return true;
     }
   else 
     {
       if (reader->buffer_pos + reader->cf->value_cnt > reader->cf->buffer_size)
         {
           if (!fill_buffer (reader))
-            return 0;
+            return false;
           reader->buffer_pos = 0;
         }
 
@@ -667,7 +714,7 @@ casereader_read (struct casereader *reader, struct ccase *c)
       reader->case_idx++;
 
       case_clone (c, &reader->c);
-      return 1;
+      return true;
     }
 }
 
@@ -675,7 +722,7 @@ casereader_read (struct casereader *reader, struct ccase *c)
    to the caller.  Caller is responsible for destroying C.
    Returns true if successful, false at end of file or on I/O
    error. */
-int
+bool
 casereader_read_xfer (struct casereader *reader, struct ccase *c)
 {
   assert (reader != NULL);
@@ -692,10 +739,25 @@ casereader_read_xfer (struct casereader *reader, struct ccase *c)
 
       case_move (c, read_case);
       reader->case_idx++;
-      return 1;
+      return true;
     }
 }
 
+/* Sets the next case to be read by READER to CASE_IDX,
+   which must be less than the number of cases in the casefile.
+   Allowed only for random readers. */
+void
+casereader_seek (struct casereader *reader, unsigned long case_idx) 
+{
+  assert (reader != NULL);
+  assert (reader->random);
+  assert (case_idx < reader->cf->case_cnt);
+
+  reader->case_idx = case_idx;
+  if (reader->cf->storage == DISK)
+    seek_and_fill_buffer (reader);
+}
+
 /* Destroys READER. */
 void
 casereader_destroy (struct casereader *reader)
@@ -787,16 +849,14 @@ static int safe_close (int fd)
 static void
 register_atexit (void) 
 {
-  static int registered = 0;
+  static bool registered = false;
   if (!registered) 
     {
-      registered = 1;
+      registered = true;
       atexit (exit_handler);
     }
 }
 
-
-
 /* atexit() handler that closes and deletes our temporary
    files. */
 static void