You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- 02111-1307, USA. */
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA. */
#include <config.h>
#include "casefile.h"
#include "alloc.h"
#include "case.h"
#include "error.h"
+#include "full-read.h"
+#include "full-write.h"
#include "misc.h"
#include "mkfile.h"
#include "settings.h"
#include "var.h"
-#ifdef HAVE_VALGRIND_VALGRIND_H
-#include <valgrind/valgrind.h>
-#endif
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
#define IO_BUF_SIZE (8192 / sizeof (union value))
-/* A casefile is a sequentially accessible array of immutable
- cases. It may be stored in memory or on disk as workspace
- allows. Cases may be appended to the end of the file. Cases
- may be read sequentially starting from the beginning of the
- file. Once any cases have been read, no more cases may be
- appended. The entire file is discarded at once. */
+/* A casefile represents a sequentially accessible stream of
+ immutable cases.
+
+ If workspace allows, a casefile is maintained in memory. If
+ workspace overflows, then the casefile is pushed to disk. In
+ either case the interface presented to callers is kept the
+ same.
+
+ The life cycle of a casefile consists of up to three phases:
+
+ 1. Writing. The casefile initially contains no cases. In
+ this phase, any number of cases may be appended to the
+ end of a casefile. (Cases are never inserted in the
+ middle or before the beginning of a casefile.)
+
+ Use casefile_append() or casefile_append_xfer() to
+ append a case to a casefile.
+
+ 2. Reading. The casefile may be read sequentially,
+ starting from the beginning, by "casereaders". Any
+ number of casereaders may be created, at any time,
+ 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.
+
+ Use casefile_get_reader() to create a casereader for
+ use in phase 2. This also transitions from phase 1 to
+ phase 2. Calling casefile_mode_reader() makes the same
+ transition, without creating a casereader.
+
+ Use casereader_read(), casereader_read_xfer(), or
+ casereader_read_xfer_assert() to read a case from a
+ casereader. Use casereader_destroy() to discard a
+ casereader when it is no longer needed.
+
+ 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.
+
+ In this phase, casereaders could still be cloned (once
+ we eventually implement cloning).
+
+ To transition from phase 1 or 2 to phase 3 and create a
+ casereader, call casefile_get_destructive_reader().
+ The same functions apply to the casereader obtained
+ this way as apply to casereaders obtained in phase 2.
+
+ After casefile_get_destructive_reader() is called, no
+ more casereaders may be created with
+ casefile_get_reader() or
+ casefile_get_destructive_reader(). (If cloning of
+ casereaders were implemented, it would still be
+ possible.)
+
+ The purpose of the limitations applied to casereaders
+ in phase 3 is to allow in-memory casefiles to fully
+ transfer ownership of cases to the casereaders,
+ avoiding the need for extra copies of case data. For
+ relatively static data sets with many variables, I
+ suspect (without evidence) that this may be a big
+ performance boost.
+
+ When a casefile is no longer needed, it may be destroyed with
+ casefile_destroy(). This function will also destroy any
+ remaining casereaders. */
/* In-memory cases are arranged in an array of arrays. The top
level is variable size and the size of each bottom level array
static int safe_open (const char *filename, int flags);
static int safe_close (int fd);
-static int full_read (int fd, void *buffer, size_t size);
-static int full_write (int fd, const void *buffer, size_t size);
/* Creates and returns a casefile to store cases of VALUE_CNT
`union value's each. */
/* Try memory first. */
if (cf->storage == MEMORY)
{
- if (case_bytes < get_max_workspace ())
+ if (case_bytes < get_workspace ())
{
size_t block_idx = cf->case_cnt / CASES_PER_BLOCK;
size_t case_idx = cf->case_cnt % CASES_PER_BLOCK;
if ((block_idx & (block_idx - 1)) == 0)
{
size_t block_cap = block_idx == 0 ? 1 : block_idx * 2;
- cf->cases = xrealloc (cf->cases,
- sizeof *cf->cases * block_cap);
+ cf->cases = xnrealloc (cf->cases,
+ block_cap, sizeof *cf->cases);
}
- cf->cases[block_idx] = xmalloc (sizeof **cf->cases
- * CASES_PER_BLOCK);
+ cf->cases[block_idx] = xnmalloc (CASES_PER_BLOCK,
+ sizeof **cf->cases);
}
case_move (&cf->cases[block_idx][case_idx], &new_case);
cf->storage = DISK;
if (!make_temp_file (&cf->fd, &cf->filename))
err_failure ();
- cf->buffer = xmalloc (cf->buffer_size * sizeof *cf->buffer);
+ cf->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer);
memset (cf->buffer, 0, cf->buffer_size * sizeof *cf->buffer);
case_bytes -= cf->case_cnt * cf->case_acct_size;
}
else
{
- reader->buffer = xmalloc (cf->buffer_size * sizeof *cf->buffer);
+ reader->buffer = xnmalloc (cf->buffer_size, sizeof *cf->buffer);
memset (reader->buffer, 0, cf->buffer_size * sizeof *cf->buffer);
}
}
/* Reads a copy of the next case from READER into C.
- Caller is responsible for destroying C. */
+ Caller is responsible for destroying C.
+ Returns true if successful, false at end of file. */
int
casereader_read (struct casereader *reader, struct ccase *c)
{
}
/* Reads the next case from READER into C and transfers ownership
- to the caller. Caller is responsible for destroying C. */
+ to the caller. Caller is responsible for destroying C.
+ Returns true if successful, false at end of file. */
int
casereader_read_xfer (struct casereader *reader, struct ccase *c)
{
}
}
+/* Reads the next case from READER into C and transfers ownership
+ to the caller. Caller is responsible for destroying C.
+ Assert-fails at end of file. */
+void
+casereader_read_xfer_assert (struct casereader *reader, struct ccase *c)
+{
+ bool success = casereader_read_xfer (reader, c);
+ assert (success);
+}
+
/* Destroys READER. */
void
casereader_destroy (struct casereader *reader)
return retval;
}
-/* Calls read(), passing FD, BUFFER, and SIZE, repeating as
- necessary to deal with interrupted calls. */
-static int
-full_read (int fd, void *buffer_, size_t size)
-{
- char *buffer = buffer_;
- size_t bytes_read = 0;
-
- while (bytes_read < size)
- {
- int retval = read (fd, buffer + bytes_read, size - bytes_read);
- if (retval > 0)
- bytes_read += retval;
- else if (retval == 0)
- return bytes_read;
- else if (errno != EINTR)
- return -1;
- }
-
- return bytes_read;
-}
-
-/* Calls write(), passing FD, BUFFER, and SIZE, repeating as
- necessary to deal with interrupted calls. */
-static int
-full_write (int fd, const void *buffer_, size_t size)
-{
- const char *buffer = buffer_;
- size_t bytes_written = 0;
-
- while (bytes_written < size)
- {
- int retval = write (fd, buffer + bytes_written, size - bytes_written);
- if (retval >= 0)
- bytes_written += retval;
- else if (errno != EINTR)
- return -1;
- }
-
- return bytes_written;
-}
-
-
/* Registers our exit handler with atexit() if it has not already
been registered. */
static void