treewide: Replace <name>_cnt by n_<name>s and <name>_cap by allocated_<name>.
[pspp] / src / data / dataset.c
index 1729c2d0530307df762eb9f1d5963fe7e3bd0470..8b3332ec0d78aee2b6636e7147d251ccbf5f4c70 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006, 2007, 2009, 2010, 2011, 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
@@ -32,6 +32,7 @@
 #include "data/casewriter.h"
 #include "data/dictionary.h"
 #include "data/file-handle-def.h"
+#include "data/session.h"
 #include "data/transformations.h"
 #include "data/variable.h"
 #include "libpspp/deque.h"
 #include "gl/xalloc.h"
 
 struct dataset {
+  /* A dataset is usually part of a session.  Within a session its name must
+     unique.  The name must either be a valid PSPP identifier or the empty
+     string.  (It must be unique within the session even if it is the empty
+     string; that is, there may only be a single dataset within a session with
+     the empty string as its name.) */
+  struct session *session;
+  char *name;
+  enum dataset_display display;
+
   /* Cases are read from source,
      their transformation variables are initialized,
      pass through permanent_trns_chain (which transforms them into
@@ -60,11 +70,6 @@ struct dataset {
   struct trns_chain *temporary_trns_chain;
   struct dictionary *dict;
 
-  /* Callback which occurs whenever the transformation chain(s) have
-     been modified */
-  transformation_change_callback_func *xform_callback;
-  void *xform_callback_aux;
-
   /* If true, cases are discarded instead of being written to
      sink. */
   bool discard_output;
@@ -98,68 +103,111 @@ struct dataset {
   bool ok;                      /* Error status. */
   struct casereader_shim *shim; /* Shim on proc_open() casereader. */
 
-  void (*callback) (void *); /* Callback for when the dataset changes */
+  const struct dataset_callbacks *callbacks;
   void *cb_data;
 
-  /* Default encoding for reading syntax files. */
-  char *syntax_encoding;
+  /* Uniquely distinguishes datasets. */
+  unsigned int seqno;
 };
 
+static void dataset_changed__ (struct dataset *);
+static void dataset_transformations_changed__ (struct dataset *,
+                                               bool non_empty);
+
 static void add_case_limit_trns (struct dataset *ds);
 static void add_filter_trns (struct dataset *ds);
 
 static void update_last_proc_invocation (struct dataset *ds);
 
-static void
-dataset_set_unsaved (const struct dataset *ds)
-{
-  if (ds->callback) ds->callback (ds->cb_data);
-}
-
 static void
 dict_callback (struct dictionary *d UNUSED, void *ds_)
 {
   struct dataset *ds = ds_;
-  dataset_set_unsaved (ds);
+  dataset_changed__ (ds);
 }
 \f
-/* Creates and returns a new dataset.  The dataset initially has an empty
-   dictionary and no data source. */
-struct dataset *
-dataset_create (void)
+static void
+dataset_create_finish__ (struct dataset *ds, struct session *session)
 {
-  struct dataset *ds;
+  static unsigned int seqno;
 
-  ds = xzalloc (sizeof *ds);
-  ds->dict = dict_create ();
   dict_set_change_callback (ds->dict, dict_callback, ds);
-  dict_set_encoding (ds->dict, get_default_encoding ());
+  proc_cancel_all_transformations (ds);
+  dataset_set_session (ds, session);
+  ds->seqno = ++seqno;
+}
+
+/* Creates a new dataset named NAME, adds it to SESSION, and returns it.  If
+   SESSION already contains a dataset named NAME, it is deleted and replaced.
+   The dataset initially has an empty dictionary and no data source. */
+struct dataset *
+dataset_create (struct session *session, const char *name)
+{
+  struct dataset *ds = XZALLOC (struct dataset);
+  ds->name = xstrdup (name);
+  ds->display = DATASET_FRONT;
+  ds->dict = dict_create (get_default_encoding ());
 
   ds->caseinit = caseinit_create ();
-  proc_cancel_all_transformations (ds);
-  ds->syntax_encoding = xstrdup ("Auto");
+
+  dataset_create_finish__ (ds, session);
+
   return ds;
 }
 
+/* Creates and returns a new dataset that has the same data and dictionary as
+   OLD named NAME, adds it to the same session as OLD, and returns the new
+   dataset.  If SESSION already contains a dataset named NAME, it is deleted
+   and replaced.
+
+   OLD must not have any active transformations or temporary state and must
+   not be in the middle of a procedure.
+
+   Callbacks are not cloned. */
+struct dataset *
+dataset_clone (struct dataset *old, const char *name)
+{
+  struct dataset *new;
+
+  assert (old->proc_state == PROC_COMMITTED);
+  assert (trns_chain_is_empty (old->permanent_trns_chain));
+  assert (old->permanent_dict == NULL);
+  assert (old->sink == NULL);
+  assert (old->temporary_trns_chain == NULL);
+
+  new = xzalloc (sizeof *new);
+  new->name = xstrdup (name);
+  new->display = DATASET_FRONT;
+  new->source = casereader_clone (old->source);
+  new->dict = dict_clone (old->dict);
+  new->caseinit = caseinit_clone (old->caseinit);
+  new->last_proc_invocation = old->last_proc_invocation;
+  new->ok = old->ok;
+
+  dataset_create_finish__ (new, old->session);
+
+  return new;
+}
+
 /* Destroys DS. */
 void
 dataset_destroy (struct dataset *ds)
 {
   if (ds != NULL)
     {
+      dataset_set_session (ds, NULL);
       dataset_clear (ds);
-      dict_destroy (ds->dict);
+      dict_unref (ds->dict);
+      dict_unref (ds->permanent_dict);
       caseinit_destroy (ds->caseinit);
       trns_chain_destroy (ds->permanent_trns_chain);
-
-      if ( ds->xform_callback)
-        ds->xform_callback (false, ds->xform_callback_aux);
-      free (ds->syntax_encoding);
+      dataset_transformations_changed__ (ds, false);
+      free (ds->name);
       free (ds);
     }
 }
 
-/* Discards the active file dictionary, data, and transformations. */
+/* Discards the active dataset's dictionary, data, and transformations. */
 void
 dataset_clear (struct dataset *ds)
 {
@@ -176,6 +224,55 @@ dataset_clear (struct dataset *ds)
   proc_cancel_all_transformations (ds);
 }
 
+const char *
+dataset_name (const struct dataset *ds)
+{
+  return ds->name;
+}
+
+void
+dataset_set_name (struct dataset *ds, const char *name)
+{
+  struct session *session = ds->session;
+  bool active = false;
+
+  if (session != NULL)
+    {
+      active = session_active_dataset (session) == ds;
+      if (active)
+        session_set_active_dataset (session, NULL);
+      dataset_set_session (ds, NULL);
+    }
+
+  free (ds->name);
+  ds->name = xstrdup (name);
+
+  if (session != NULL)
+    {
+      dataset_set_session (ds, session);
+      if (active)
+        session_set_active_dataset (session, ds);
+    }
+}
+
+struct session *
+dataset_session (const struct dataset *ds)
+{
+  return ds->session;
+}
+
+void
+dataset_set_session (struct dataset *ds, struct session *session)
+{
+  if (session != ds->session)
+    {
+      if (ds->session != NULL)
+        session_remove_dataset (ds->session, ds);
+      if (session != NULL)
+        session_add_dataset (session, ds);
+    }
+}
+
 /* Returns the dictionary within DS.  This is always nonnull, although it
    might not contain any variables. */
 struct dictionary *
@@ -194,7 +291,7 @@ dataset_set_dict (struct dataset *ds, struct dictionary *dict)
 
   dataset_clear (ds);
 
-  dict_destroy (ds->dict);
+  dict_unref (ds->dict);
   ds->dict = dict;
   dict_set_change_callback (ds->dict, dict_callback, ds);
 }
@@ -214,7 +311,7 @@ dataset_has_source (const struct dataset *ds)
   return dataset_source (ds) != NULL;
 }
 
-/* Replaces the active file's data by READER.  READER's cases must have an
+/* Replaces the active dataset's data by READER.  READER's cases must have an
    appropriate format for DS's dictionary. */
 bool
 dataset_set_source (struct dataset *ds, struct casereader *reader)
@@ -238,27 +335,37 @@ dataset_steal_source (struct dataset *ds)
 
   return reader;
 }
-\f
-void
-dataset_set_callback (struct dataset *ds, void (*cb) (void *), void *cb_data)
+
+/* Returns a number unique to DS.  It can be used to distinguish one dataset
+   from any other within a given program run, even datasets that do not exist
+   at the same time. */
+unsigned int
+dataset_seqno (const struct dataset *ds)
 {
-  ds->callback = cb;
-  ds->cb_data = cb_data;
+  return ds->seqno;
 }
 
 void
-dataset_set_default_syntax_encoding (struct dataset *ds, const char *encoding)
+dataset_set_callbacks (struct dataset *ds,
+                       const struct dataset_callbacks *callbacks,
+                       void *cb_data)
 {
-  free (ds->syntax_encoding);
-  ds->syntax_encoding = xstrdup (encoding);
+  ds->callbacks = callbacks;
+  ds->cb_data = cb_data;
 }
 
-const char *
-dataset_get_default_syntax_encoding (const struct dataset *ds)
+enum dataset_display
+dataset_get_display (const struct dataset *ds)
 {
-  return ds->syntax_encoding;
+  return ds->display;
 }
 
+void
+dataset_set_display (struct dataset *ds, enum dataset_display display)
+{
+  ds->display = display;
+}
+\f
 /* Returns the last time the data was read. */
 time_t
 time_of_last_procedure (struct dataset *ds)
@@ -329,8 +436,8 @@ proc_open_filtering (struct dataset *ds, bool filter)
   if (!ds->discard_output)
     {
       struct dictionary *pd = ds->permanent_dict;
-      size_t compacted_value_cnt = dict_count_values (pd, 1u << DC_SCRATCH);
-      if (compacted_value_cnt < dict_get_next_value_idx (pd))
+      size_t compacted_n_values = dict_count_values (pd, 1u << DC_SCRATCH);
+      if (compacted_n_values < dict_get_next_value_idx (pd))
         {
           struct caseproto *compacted_proto;
           compacted_proto = dict_get_compacted_proto (pd, 1u << DC_SCRATCH);
@@ -433,7 +540,7 @@ proc_casereader_read (struct casereader *reader UNUSED, void *ds_)
           ds->lag_cases[deque_push_front (&ds->lag)] = case_ref (c);
         }
 
-      /* Write case to replacement active file. */
+      /* Write case to replacement dataset. */
       ds->cases_written++;
       if (ds->sink != NULL)
         casewriter_write (ds->sink,
@@ -466,7 +573,7 @@ proc_casereader_destroy (struct casereader *reader, void *ds_)
 
   /* Make sure transformations happen for every input case, in
      case they have side effects, and ensure that the replacement
-     active file gets all the cases it should. */
+     active dataset gets all the cases it should. */
   while ((c = casereader_read (reader)) != NULL)
     case_unref (c);
 
@@ -479,7 +586,7 @@ proc_casereader_destroy (struct casereader *reader, void *ds_)
 /* Must return false if the source casereader, a transformation,
    or the sink casewriter signaled an error.  (If a temporary
    transformation signals an error, then the return value is
-   false, but the replacement active file may still be
+   false, but the replacement active dataset may still be
    untainted.) */
 bool
 proc_commit (struct dataset *ds)
@@ -490,7 +597,7 @@ proc_commit (struct dataset *ds)
   assert (ds->proc_state == PROC_CLOSED);
   ds->proc_state = PROC_COMMITTED;
 
-  dataset_set_unsaved (ds);
+  dataset_changed__ (ds);
 
   /* Free memory for lagged cases. */
   while (!deque_is_empty (&ds->lag))
@@ -572,9 +679,7 @@ proc_capture_transformations (struct dataset *ds)
   assert (ds->temporary_trns_chain == NULL);
   chain = ds->permanent_trns_chain;
   ds->cur_trns_chain = ds->permanent_trns_chain = trns_chain_create ();
-
-  if ( ds->xform_callback)
-    ds->xform_callback (false, ds->xform_callback_aux);
+  dataset_transformations_changed__ (ds, false);
 
   return chain;
 }
@@ -586,8 +691,7 @@ void
 add_transformation (struct dataset *ds, trns_proc_func *proc, trns_free_func *free, void *aux)
 {
   trns_chain_append (ds->cur_trns_chain, NULL, proc, free, aux);
-  if ( ds->xform_callback)
-    ds->xform_callback (true, ds->xform_callback_aux);
+  dataset_transformations_changed__ (ds, true);
 }
 
 /* Adds a transformation that processes a case with PROC and
@@ -602,9 +706,7 @@ add_transformation_with_finalizer (struct dataset *ds,
                                    trns_free_func *free, void *aux)
 {
   trns_chain_append (ds->cur_trns_chain, finalize, proc, free, aux);
-
-  if ( ds->xform_callback)
-    ds->xform_callback (true, ds->xform_callback_aux);
+  dataset_transformations_changed__ (ds, true);
 }
 
 /* Returns the index of the next transformation.
@@ -639,15 +741,17 @@ proc_start_temporary_transformations (struct dataset *ds)
 
       trns_chain_finalize (ds->permanent_trns_chain);
       ds->temporary_trns_chain = ds->cur_trns_chain = trns_chain_create ();
-
-      if ( ds->xform_callback)
-       ds->xform_callback (true, ds->xform_callback_aux);
+      dataset_transformations_changed__ (ds, true);
     }
 }
 
-/* Converts all the temporary transformations, if any, to
-   permanent transformations.  Further transformations will be
-   permanent.
+/* Converts all the temporary transformations, if any, to permanent
+   transformations.  Further transformations will be permanent.
+
+   The FILTER command is implemented as a temporary transformation, so a
+   procedure that uses this function should usually use proc_open_filtering()
+   with FILTER false, instead of plain proc_open().
+
    Returns true if anything changed, false otherwise. */
 bool
 proc_make_temporary_transformations_permanent (struct dataset *ds)
@@ -658,7 +762,9 @@ proc_make_temporary_transformations_permanent (struct dataset *ds)
       trns_chain_splice (ds->permanent_trns_chain, ds->temporary_trns_chain);
       ds->temporary_trns_chain = NULL;
 
-      dict_destroy (ds->permanent_dict);
+      ds->cur_trns_chain = ds->permanent_trns_chain;
+
+      dict_unref (ds->permanent_dict);
       ds->permanent_dict = NULL;
 
       return true;
@@ -675,17 +781,14 @@ proc_cancel_temporary_transformations (struct dataset *ds)
 {
   if (proc_in_temporary_transformations (ds))
     {
-      dict_destroy (ds->dict);
+      dict_unref (ds->dict);
       ds->dict = ds->permanent_dict;
       ds->permanent_dict = NULL;
 
       trns_chain_destroy (ds->temporary_trns_chain);
       ds->temporary_trns_chain = NULL;
-
-      if ( ds->xform_callback)
-       ds->xform_callback (!trns_chain_is_empty (ds->permanent_trns_chain),
-                           ds->xform_callback_aux);
-
+      dataset_transformations_changed__ (
+        ds, !trns_chain_is_empty (ds->permanent_trns_chain));
       return true;
     }
   else
@@ -703,22 +806,44 @@ proc_cancel_all_transformations (struct dataset *ds)
   ok = trns_chain_destroy (ds->temporary_trns_chain) && ok;
   ds->permanent_trns_chain = ds->cur_trns_chain = trns_chain_create ();
   ds->temporary_trns_chain = NULL;
-  if ( ds->xform_callback)
-    ds->xform_callback (false, ds->xform_callback_aux);
+  dataset_transformations_changed__ (ds, false);
 
   return ok;
 }
-\f
 
-void
-dataset_add_transform_change_callback (struct dataset *ds,
-                                      transformation_change_callback_func *cb,
-                                      void *aux)
+static int
+store_case_num (void *var_, struct ccase **cc, casenumber case_num)
 {
-  ds->xform_callback = cb;
-  ds->xform_callback_aux = aux;
+  struct variable *var = var_;
+
+  *cc = case_unshare (*cc);
+  *case_num_rw (*cc, var) = case_num;
+
+  return TRNS_CONTINUE;
 }
 
+/* Add a variable which we can sort by to get back the original order. */
+struct variable *
+add_permanent_ordering_transformation (struct dataset *ds)
+{
+  struct variable *temp_var;
+
+  temp_var = dict_create_var_assert (ds->dict, "$ORDER", 0);
+  if (proc_in_temporary_transformations (ds))
+    {
+      struct variable *perm_var;
+
+      perm_var = dict_clone_var_in_place_assert (ds->permanent_dict, temp_var);
+      trns_chain_append (ds->permanent_trns_chain, NULL, store_case_num,
+                         NULL, perm_var);
+      trns_chain_finalize (ds->permanent_trns_chain);
+    }
+  else
+    add_transformation (ds, store_case_num, NULL, temp_var);
+
+  return temp_var;
+}
+\f
 /* Causes output from the next procedure to be discarded, instead
    of being preserved for use as input for the next procedure. */
 void
@@ -728,7 +853,7 @@ proc_discard_output (struct dataset *ds)
 }
 
 
-/* Checks whether DS has a corrupted active file.  If so,
+/* Checks whether DS has a corrupted active dataset.  If so,
    discards it and returns false.  If not, returns true without
    doing anything. */
 bool
@@ -813,7 +938,7 @@ add_filter_trns (struct dataset *ds)
 /* FILTER transformation. */
 static int
 filter_trns_proc (void *filter_var_,
-                  struct ccase **c UNUSED, casenumber case_nr UNUSED)
+                  struct ccase **c, casenumber case_nr UNUSED)
 
 {
   struct variable *filter_var = filter_var_;
@@ -828,3 +953,25 @@ dataset_need_lag (struct dataset *ds, int n_before)
 {
   ds->n_lag = MAX (ds->n_lag, n_before);
 }
+\f
+static void
+dataset_changed__ (struct dataset *ds)
+{
+  if (ds->callbacks != NULL && ds->callbacks->changed != NULL)
+    ds->callbacks->changed (ds->cb_data);
+}
+
+static void
+dataset_transformations_changed__ (struct dataset *ds, bool non_empty)
+{
+  if (ds->callbacks != NULL && ds->callbacks->transformations_changed != NULL)
+    ds->callbacks->transformations_changed (non_empty, ds->cb_data);
+}
+\f
+/* Private interface for use by session code. */
+
+void
+dataset_set_session__ (struct dataset *ds, struct session *session)
+{
+  ds->session = session;
+}