/*  ******************************************************************
 *                                                                   *
 * File             : rs.c                                           *
 *                                                                   *
 * Purpose          :                                                *         
 *                                                                   *
 * Version Number   : 2.0d                                           *
 *                                                                   *
 * Revision History :                                                *
 *                                                                   *
 * Date                              Developer                       *
 * ----                              ---------                       *
 *  used to establish levels in a flat file                          *
*/

#define RS_VERSION "rs  version 2.0d 24 Aug 2014"
/*
 * 24 Aug 14                          wjs                            *
 *	Do not treat an empty command line arg as a selection	     *
 *	  There is a general problem that outer's args are all	     *
 *	  considered to be selections or projections.  Better would  *
 *	  be for rs to assume that if an arg were neither selection  *
 *	  nor projection, ignore it.  To do this "better", we'd have *
 *	  to do our own err handling in the  varnames_in_sel call    *
 *	  Note that an empty command line arg CANNOT be an outer     *
 *	  arg - once outer gets the command args back from inner, it *
 *	  assumes the empty args were processed by inner.  This	     *
 *	  design flaw is why other methods don't need this fix - yet *
 * 11 Jun 10                          wjs                            *
 *	Compile-time variable used to set SELPROJ_OPTIMIZE does not  *
 *	  not match doc.  Doc's way is better - fix		     *
 *	Bug fix: Levelization arguments must be included in	     *
 *	  optimized proj list ... at least as far as desired last    *
 *	  level.  Further?					     *
Work not done.  Although it seems like we should be able to limit
inclusion of levelization vars to the lowest level we want, we'd
need to change the parser somehow not to complain when vars are
missing.  Also, I'm not sure how well the actual levelization works
	An approximation might be to optimize by leaving off the
lowest level when possible.  That clearly is logically consistent.
Problems there would be in the parsing if user chose to name the
vars at the lowest level instead of using *
 * 31 Dec 09                          wjs                            *
 *	The 12 Nov work also fixed a problem due to calling	     *
 *	  lengthen_str_and_free instead of lengthen_str.  No work    *
 *	  in 2.0d is relevant to this fix			     *
 *	    [Begin 2.0d]					     *
 * 12 Nov 09                          wjs                            *
 *	Retain trigrams when passing selections to input object.     *
 *	  There might be a significant wildcard in it.		     *
 *	Grab more memory when building "pass-through" obj spec	     *
 *	Get rid of unused buildstring reference			     *
 *	    [Begin 2.0c]					     *
 *  7 Oct 09                          wjs                            *
 *	Bug fix: Levelization argument parsing was being done	     *
 *	  against the optimized, reduced varlist.  Result was re-    *
 *	  jection of args referencing vars that wouldn't be needed   *
 *	Bug fix: Off-by-1 error counting # elements in list (!)	     *
 *	Bug fix: Off-by-1 error failing to account for trailing NULL *
 *	Reformat some error msgs				     *
 *	    [Begin 2.0b]					     *
 *  2 Jul 09                          wjs                            *
 *	Bug fix: when restructuring orig, no-bracket if statements,  *
 *	  blew a biggie.  Guess brackets are a good thing!	     *
 *	    [Begin 2.0a]					     *
 *								     *
 *  8 Jun 09                          wjs                            *
 *	Remove direct access to parse in favor of varnames_in_sel    *
 *	    [needs varnames_in_sel 1.0]				     *
 *	    [does NOT need parse.c - no longer exists w/that name]   *
 *	    [does NOT need parse.h]				     *
 *		THIS should have been the beginning of 2.0a	     *
 *		2.0 version below was released w/JGOFS 2.1	     *
 * 14 Feb 09                          wjs                            *
 *	Reference a couple of utils functions			     *
 *	Change entry point to parse.				     *
 *	Note that correct rs behavior requires sufficiently new	     *
 *	  versions of the parse package				     *
 * 23 Oct 08                          wjs                            *
 *	ILLEGAL_LEVEL now defined in core.h.  Pull from here	     *
 * 12 Oct 07                          wjs                            *
 *	Rework selection/projection optimization		     *
 *  5 Jul 07                          wjs                            *
 *	Change name of switch that controls optimization	     *
 * 14 Jun 07                          wjs                            *
 *	New operator to signify "levelize but do not compress"	     *
 *	  Needed "forever" since otherwise, 2 consecutive records    *
 *	  with identical, say, temperature values might be com-	     *
 *	  pressed to 1 record.					     *
 *	Similarly, need to deal with consecutive "nd"s per above     *
 *	Rework input arg processing to diagnose and reject multiple  *
 *	  specs of an input variable				     *
 *	Honor level part of input arg even if input args are not     *
 *	  spec'ed in level order				     *
 *	Some common code moved to libraries - use it from there	     *
 *      Remove array that was filled in but never used.  Checked     *
 *	  that it hadn't been used in rs 1.5 ("before our time")     *
 *	    [needs parse 2.1]					     *
 *	    [needs parse.h 2.0]					     *
 *	    [needs parse_utils 1.0]				     *
 *	    [needs path_info_routines 1.3a]			     *
 *	    [Needs utils 2.3]					     *
 *	    [Begin 2.0]						     *
 *								     *
 *  9 Nov 06                          wjs                            *
 *	Add switch for 1.9 optimization feature (along w/option to   *
 *	  establish default state of switch)			     *
 *	Change max # levels to depend on MAXLEVELS (core.h) rather   *
 *	  than on char - '0' (and a hardcoded 5!)		     *
 *	Parametrize the sizes of a couple of arrays		     *
 *  1 Nov 06                          wjs                            *
 *	Pass outer args to rs' input object			     *
 *	    [Begin 1.9]						     *
 *								     *
 *  6 Oct 06                          wjs                            *
 *	Parametrize MISSING_VALUE_REAL				     *
 *	    [Begin 1.8b]					     *
 *  5 Jul 05                          wjs                            *
 *	Get int get_integer_attribute from library		     *
 *	    [Needs utils 1.9a]					     *
 * 30 Jun 05                          wjs                            *
 *	Bug fix: v 1.7 code did not handle situation where object    *
 *	  had no comments.					     *
 *  4 Jun 05                          wjs                            *
 *	Needs jdb function defns				     *
 *	    [Needs jdbfuncdefns.h]				     *
 *	    [Begin 1.8a]					     *
 *  9 Jul 04                          wjs                            *
 *	Typo fix						     *
 *	Add iovaldouble_ entry					     *
 * 23 Apr 04                          wjs                            *
 *	Mods to "version-returning function".			     *
 *	  a) change from local to global to ensure it can't be       *
 *	     "optimized out"					     *
 *	  b) don't want "version" in any function name since such    *
 * 	     names appear when grep'ping for "version"		     *
 * 18 Feb 04                          wjs                            *
 *	Error if variable in levelizing list is not in object being  *
 *	  levelized						     *
 *	Check some potential buffer overflows			     *
 *	#include core.h.  Allows us to throw out stuff, and also     *
 *	  gets rid of some compiler warnings			     *
 *	"Full" error messages					     *
 *	    [Needs utils; path_info_routines]			     *
 *	    [Begin 1.8]						     *
 * 25 Jul 03                          wjs                            *
 *	Dynamically allocate comment buffer size		     *
 *	    [Needs path_info_routines 1.1 or later (.c & .h)]	     *
 *	    [Begin 1.7]						     *
 * 28 Jan 99                          wjs                            *
 *	Use path_info_routines to control PATH_INFO; change          *
 *	  PATH_INFO just before needed; change it back when done     *
 *	Add version string; reorder comments	                     *
 *	    [Needs path_info_routines 1.1 or later (.c & .h)]	     *
 *	    [Begin 1.6]						     *
 * June 1997 grf                                                     *
 *       changed sub names to in_xxxx, fixes for v 1.5               *
 * May 1997                         Glenn Flierl                     *
 *       changed error calls to use error_ in outer.c                *
 * May 21, 1996                       clh                            *
 *  iovalreal change to allow alpha vars to begin with digits        *
 * March 1996                         clh                            *
 *  use minbufsize.h to determine size of buffers                    *
 * January 1996                       clh                            *
 *  fixed rsw to respect the re-ordering of variables                *
 * July 1995                          clh                            *
 *  add subroutine to return column widths - iowidth_()              *
 *********************************************************************
*/
#include INNEROPTIONS
#include "jdbfuncdefns.h"
#include "path_info_routines.h"

double strtod();

/* #define DEBUG */

  /*  Next 2 are the operators rs works with.  LEVELIZE means "put	*/
  /*  named variable at the level following the operator".  CONDENSE,	*/
  /*  the traditional operator, means "LEVELIZE, and, in addition, if	*/
  /*  consecutive records at this level are identical, get rid of 2nd"	*/
  /*    Be careful w/choice of characters, since they are the keys for	*/
  /*  rs to determine if a method argument is for inner or outer.  In	*/
  /*  particular, watch out for outer's selection operators.  See utils	*/
  /*  keyword_pairs_to_wjstbl/set_option_string (used by decimator, for	*/
  /*  one) for an alternate parsing approach if push comes to shove	*/
#define CONDENSE ':'
#define LEVELIZE '#'
  /*  Character that means "all un-named variables" in levelization	*/
  /*  parameters							*/
#define UNNAMED_VARS '*'

  /*  See SELPROJ_OPTIMIZE doc released in outer's distribution		*/
  /*    Next parameter is a SOURCE default; eg, set here or in a .h	*/
  /*    file - NOT a compile-time option.  Obviously somebody CAN set	*/
  /*    it at compile time, but they shouldn't				*/
#ifdef DEFAULT_SELPROJ_OPTIMIZE
#if DEFAULT_SELPROJ_OPTIMIZE
static char *default_optimize_state = "Default optimize state TRUE";
#else
static char *default_optimize_state = "Default optimize state FALSE";
#endif
#else
static char *default_optimize_state = "Undef default optimize state - use TRUE";
#define DEFAULT_SELPROJ_OPTIMIZE TRUE
#endif

#ifdef SELPROJ_OPTIMIZE
#if SELPROJ_OPTIMIZE
static char *optimize_state = "Compile switch SELPROJ_OPTIMIZE TRUE";
#else
static char *optimize_state = "Compile switch SELPROJ_OPTIMIZE FALSE";
#endif
#else
static char *optimize_state = "Unspec switch SELPROJ_OPTIMIZE - use default";
#define SELPROJ_OPTIMIZE DEFAULT_SELPROJ_OPTIMIZE
#endif

char *SELPROJ_OPTIMIZE_env_var_name = "SELPROJ_OPTIMIZE";
Logical do_optimize;


#ifndef DEFAULT_RS_OPTIMIZE
#define DEFAULT_RS_OPTIMIZE TRUE
#endif
#if DEFAULT_RS_OPTIMIZE
static char *rs_optimize_state = "Compiled with DEFAULT_RS_OPTIMIZE on";
#else
static char *rs_optimize_state = "Compiled with DEFAULT_RS_OPTIMIZE off";
#endif
char *RS_OPTIMIZE_env_var_name = "RS_OPTIMIZE";
Logical rs_optimize;

int nlevels,nnames;
  /*  Pre v 1.9, dimension in next line was 6.  Taking guess at new size */
int firstvar[MAXLEVELS+1];
char names[NVAR][VARNAMESIZE];
int fldwidths[NVAR];
int namesize=VARNAMESIZE;
char values[NVAR][DATUMSIZE];
char comp[NVAR][DATUMSIZE];
int valuesize=DATUMSIZE;
int handle;
int minlevelread;
char *comments;

  /* levelization info; derived from level:varname args to program	*/
struct varlevel {
  int level;
  char *var;
} *ss;
int num_ss;

int newlev[NVAR];
int newpntr[NVAR];
char comp_type[NVAR];
int maxoutlev;
  /* removed newlpntr[MAXLEVELS+1];  - see Jun 07 comments		*/

void error_();			/* in outer				*/
char *un_trigram();		/* in outer_utils.c(in jgofs.a library)	*/
  /*  Functions below in utils.c (in jgofs.a library)			*/
Logical add_id_to_err();
void errn();
void free_lengthened_str();
int get_integer_attribute();
Logical get_logical_from_string();
Logical is_a_varname();
char *lengthen_str();
char *strdupl();
Logical string_in_list();
char **string_sets_intersection();
char **string_sets_union();
int varnames_in_sel();

/************************************************************************/

char *rs_return_vers()
/*  Dummy routine.  Exists only to force .h file version string into	*/
/*  this module.  Note string must not be global or we'll have con-	*/
/*  flicts if another routine similarly includes the version string	*/
{
  static char version[] = \
RS_VERSION"/"FULL_PATH_INFO_ROUTINESH_VERSION"/"FULL_JDBFUNCDEFNSH_VERSION;
  return version;
}

void err(s,t)
char *s,*t;
{
  char *ss,*tt;
  add_id_to_err(&ss,&tt,s,t,RS_VERSION);
  error_(ss,tt);
  return;		/*  Not that it should ever get here...		*/
}

int iovarlevel_(vn)
int *vn;
{
  return newlev[newpntr[*vn]];
}

int ioattrout_(vn,str)
int *vn;
char *str;
{
   int j;

   j = newpntr[*vn];

   if ((jdbattributes_(&handle,&j,str)) == 0)
       return 0;
   else {
       if (strncmp(str,"width=",6) == 0)
           fldwidths[j] = get_integer_attribute(str);
       return 1;
   }
}

void iovaldouble_(vn,df)
int *vn;
double *df;
/*	See iovalreal_							*/
{

  int i;
  char *end_char_ptr;

  i=newpntr[*vn];
  if(i<0) {
    *df=MISSING_VALUE_REAL;
    return;
  }


  *df=strtod(values[i],&end_char_ptr);
  if (*end_char_ptr != '\0') *df= MISSING_VALUE_REAL;

return;
}

void iovalreal_(vn,f)
int *vn;
float *f;
/*	"Return real value (f) for variable indexed by vn. -9999"	*/
/*	"for strings"							*/
{
  double df;
  iovaldouble_(*vn,&df);
  *f = df;
  return;
 }

void iovalstr_(vn,tmp)
int *vn;
char *tmp;
{
  char *s;

  s=values[newpntr[*vn]];
  s=s+strspn(s," ");
  strcpy(tmp,s);
}

void ioname_(vn,s)
int *vn;
char *s;
{
  strcpy(s,names[newpntr[*vn]]);
}

int iocommout_(str)
char *str;
{
char *at;

if (comments == NULL) return 0;

if (comments[0]){
  at=strchr(comments,'\n');
  if(at){
    *at = '\0';
    strcpy(str,comments);
    strcpy(comments,at+1);
  } else {
    strcpy(str,comments);
    comments[0]='\0';
  };
  return 1;
} else return 0;
}

int iowidth_(vn)
int *vn;
{
   return  fldwidths[newpntr[*vn]];
}

int ioreadrec_(level)
int *level;
{
  int clevel,i;

  if (*level == minlevelread) {
    minlevelread = *level+1;
    return 1;
  } else if (*level > minlevelread) {
    return 0;
  }

  while (TRUE) {
    clevel = jdbreada_(&handle,values,&valuesize);
    					/* printf("clevel %d\n",clevel); */
    if (clevel < 0)  {
      minlevelread= ILLEGAL_LEVEL;
      return 0;
    }
    
    for  (i=firstvar[clevel]; i<nnames; i++)  {
      if (newlev[i] < minlevelread) { 
	if (comp_type[i] == CONDENSE) {
	  if (strcmp(values[i],comp[i]) != 0) { minlevelread = newlev[i]; }
	} else if (comp_type[i] == LEVELIZE) {
	  minlevelread = newlev[i];
	} else {
	  char s[2];
	  s[0] = comp_type[i];
	  s[1] = '\0';
	  err ("Internal error - illegal comp_type.  Bad value = ",s);
        }
      }
      COPY_INTO_FIXED_LEN_BUFFER(comp[i],values[i],
						"copying into comp buffer");
    }
    
    if (minlevelread < *level) {
      return 0;
    } else if  (minlevelread == *level)  {
      minlevelread++;
      return 1;
    }
  }
}

void ioclose_()
{
  jdbclose_(&handle);
}

Logical is_rs_levelization_arg(str)
char *str;
  /*  rs levelization args are of form level:varname (or level#varname)	*/
{
  char *ptr;
  Logical is_rs_operator;
  if ((*str == '+') || (*str == '-') || isspace(*str)) return FALSE;
  strtol(str,&ptr,10);
  is_rs_operator = ((*ptr == CONDENSE) || (*ptr == LEVELIZE));
    /*  Last check in next line is to ensure that there is a varname	*/
    /*  after the :.  If not, it's probably an error, but we will just	*/
    /*  punt the arg back to outer for processing.  Who knows how/where	*/
    /*  the abnormality will show up?					*/
  return (  is_rs_operator  &&  (ptr != str)  &&  (*(ptr+1) != '\0')  );
}

char *optimize_objspec(in_objspec,
			u_proj,num_u_proj,
			l_proj,num_l_proj,
			sel_list,num_sels,
			vars_in_sels,num_vars_in_sels)
char *in_objspec;
char **u_proj,**l_proj,**sel_list,**vars_in_sels;
int num_u_proj,num_l_proj,num_sels,num_vars_in_sels;
  /*    Returns NULL in memory allocation failure scenarios and the	*/
  /*  far more common scenario where no optimization is being performed	*/
  /*  Idea is to let calling program use the original object spec in	*/
  /*  either case, but since some memory allocation failures auto-	*/
  /*  matically generate error msgs and die, so much for consistency	*/
  /*    Non-NULL returns can be freed via the free_lengthened_str, etc	*/
  /*  calls - see utils							*/
{
  Logical any_u_proj,do_level_optimization;
  char *objspec;
  char **proj_list,**free_it;
  int i,objspec_size_guess;

  any_u_proj = (num_u_proj != 0);
  do_level_optimization = (num_l_proj != 0);
  if ( ! any_u_proj  &&  ! do_level_optimization) return NULL;

  i = strlen(in_objspec);
    /* 50 = slop. Preceding line = punc chars (2 for parens).		*/
    /* 4 = opsize w/punc (eg ' eq ') 10 = guess at sel string size	*/
  objspec_size_guess = i 
			+ (num_u_proj+num_l_proj)*namesize
			+ num_sels*(namesize + 4 + 10)
			+ num_u_proj + num_l_proj + num_sels + 2
			+ 50;
  objspec = lengthen_str(in_objspec,"","",objspec_size_guess,"init objspec");
  if (objspec[i-1] == ')') {
    objspec[i-1] = ',';
  } else { 
    objspec = lengthen_str(objspec,"(","",objspec_size_guess,
							"adding ( to objspec");
  }

    /*  There are 2 possible projection lists we might want to add to	*/
    /*  the object spec to reduce the amount of data being transferred	*/
    /*  The first is the list of projections that the user put in the	*/
    /*  objspec.  The second is a level-based list of projections used	*/
    /*  only in html display situations.				*/
    /*    If either of these projection lists is used, we also want	*/
    /*  to project in all the variables used in user selections.	*/
    /*  (Wrestle with this if you want (sample: obj(a,b,b<c); do we	*/
    /*  need c? Answer: yes.  2 falls out of 3))			*/
    /*  Note we do NOT want to add a projection list due to selections	*/
    /*  if we have no other projections, but we don't get here in that	*/
    /*  case.								*/
    /*    If both projection lists are being used, project in the	*/
    /*  intersection of the lists					*/
  if (any_u_proj && do_level_optimization) {
    proj_list = string_sets_intersection(u_proj,num_u_proj,l_proj,num_l_proj);
    if (proj_list == NULL) return NULL;
    free_it = proj_list;
  } else {
    proj_list = (any_u_proj) ? u_proj : l_proj;
    free_it = NULL;
  }
    /*  Add projections for variables involved in selections to the	*/
    /*  list of variables specifically projected in			*/
  i = 0;
  while (proj_list[i] != NULL) i++;	/* count # entries in proj_list	*/
  proj_list = string_sets_union(proj_list,i,vars_in_sels,num_vars_in_sels);
  if (proj_list == NULL) return NULL;
  if (free_it != NULL) free(free_it);

  i = 0;
  while (proj_list[i] != NULL) {
    objspec = lengthen_str(objspec,proj_list[i++],",",objspec_size_guess,
						"adding projection to objspec");
  }

  for (i=0; i<num_sels; i++) {
      /*  trigramming is NOT needed (empirically determined).		*/
      /*  Now I wonder if the whole trigram business is due to		*/
      /*  some interface that involves a shell ... httpd -> cgi?	*/
      /*  Note that as of 21c, sels have trigramming in them on arrival	*/
      /*    here, rendering arg moot (and casting "empirically deter-	*/
      /*    mined" into doubt, as far as "truth" goes).  To be fair,	*/
      /*    21c mod was due to wildcard character being same as trigram	*/
      /*    character							*/
    objspec = lengthen_str(objspec,sel_list[i],",",objspec_size_guess,
						"adding selection to objspec");
  }

  i = strlen(objspec);
  objspec[i-1] = ')';   
  return objspec;
}

int ioopen_(s,nparams,ntotal)
char *s[];
int *nparams;
int *ntotal;
{
  char tmp[INBUFSIZE],*sp,*objspec,*sel_proj_list_start;
  char *varname_ptr;
  char default_comp_type;
  int i,j,k,m,maxclev;
  int position_in_comments,size_comments;
  int default_level,html_max_level;
  Logical default_specified,any_levelization_args;
  Logical var_at_level[MAXLEVELS+1];

    /* u_proj = user projections; specified as args to program		*/
    /* l_proj = implied projections due to level restrictions; eg,	*/
    /*	  .html0 implies only level 0 vars.  Populated from varlevel	*/
    /*	  struct.  Only used for .html output.				*/
    /* sel_list = user selections; specified as args to program		*/
    /* vars_in_sels = list of variables used in selections		*/
  char **u_proj,**l_proj,**sel_list,**vars_in_sels;
  int num_u_proj,num_l_proj,num_sels,num_vars_in_sels;

    /*  PATH_INFO function and strings.					*/
  int get_level();
  char *get_protocol();
  int new_and_old_path_infos();
      /* "PATH_INFO=" + getenv("PATH_INFO").  Must be static		*/
      /*  since it "survives" in process table after ioopen_ exits)	*/
  static char *PATH_INFO_orig_putenv;
      /* "PATH_INFO=" + getenv("PATH_INFO") w/ protocol = "jgof"	*/
  char *PATH_INFO_jgof_putenv;

  if (  (sp = getenv(SELPROJ_OPTIMIZE_env_var_name)) == NULL  ) {
    do_optimize = SELPROJ_OPTIMIZE;
  } else {
    do_optimize = get_logical_from_string
	(sp,"SELPROJ_OPTIMIZE env var",SELPROJ_OPTIMIZE_env_var_name);
  }

    /*  As of rs 2.0, NOT using rs_optimize.  rs_optimize is a form of	*/
    /*  sel/proj optimization that may be OK even if full sel/proj	*/
    /*  optimization is not.  It allows pass-through projection		*/
    /*  optimization for LEVELIZE vars while not allowing it for	*/
    /*  CONDENSE vars.  Not implemented because it requires pre-jdbopen	*/
    /*  processing of the levelization parameters, which is not hap-	*/
    /*  pening in 2.0.  Let's see how many times we need to turn off	*/
    /*  full optimization first... Once to workaround 20b issue,	*/
    /*  pending its fix (Oct 09) ...					*/
  if (  (sp = getenv(RS_OPTIMIZE_env_var_name)) == NULL  ) {
    rs_optimize = DEFAULT_RS_OPTIMIZE;
  } else {
    rs_optimize = get_logical_from_string
	(sp,"RS_OPTIMIZE env var",RS_OPTIMIZE_env_var_name);
  }

    /*  If in html mode, save maximum level for optimization purposes	*/
    /*  If problems w/PATH_INFO, just bag optimization and let whatever	*/
    /*  used to deal w/this problem continue to deal w/it		*/
  html_max_level = ILLEGAL_LEVEL;
  if (do_optimize) {
    sp = get_protocol(NULL);
    if (sp != NULL) {
      if (   (strcmp(sp,"html") == 0) || (strcmp(sp,"brev") == 0)   )  { 
	i = get_level(NULL);
	if ( (i != LEVEL_NOT_SPECIFIED) && (i != LEVEL_ERROR)   )
							html_max_level = i;
      }
      free (sp);
    }
  }

    /*  Must be sure that methods down the line produce JGOFS	*/
    /*  protocol (or whatever jdbopen requires), and not html,	*/
    /*  flat, or whatever.  Do this by replacing proto string	*/
    /*  with "jgof" (WJS decision at this point... jgof has no	*/
    /*  meaning to system).					*/
    /*    Make 2 strings for putenv.  1 sets up existing 	*/
    /*    PATH_INFO; the other sets up PATH_INFO with "jgof"	*/
    /*    protocol						*/  
    /*  Make sure outers "down the line" don't produce flat, html, etc	*/
  if (new_and_old_path_infos(&PATH_INFO_jgof_putenv,&PATH_INFO_orig_putenv)
	!= PATH_INFO_NEW_AND_OLD_OK) err 
("problem processing PATH_INFO env var.  Memory issues or bad fmt. env var=",
						  getenv(PATH_INFO_ENV_VAR)  );

  if (PATH_INFO_jgof_putenv != NULL) 
    if (  (i = putenv(PATH_INFO_jgof_putenv)) != 0  )
      err ("putenv failure for PATH_INFO",strerror(i));

    /*  Process command line args.					*/
    /*    "Rules" are that inner copies its args and replaces them w/	*/
    /*  null strings so outer can process the rest			*/
    /*  1st arg to rs is object specifier.  Other args to rs are of the	*/
    /*  form level:varlist (or level#varlist), and can (hopefully!) be	*/
    /*  intermixed w/outer args						*/
    /*    In addition to processing the inner args, we process the	*/
    /*  outer args, anticipating optimization is in effect.  The pro-	*/
    /*  cessing consists of making a list of variables known to be	*/
    /*  needed.  These variables are projections + the variables used	*/
    /*  in selections							*/
  if (s[0] == NULL) err ("No input object spec","");
  if (s[0][0] == '\0') err ("Empty input object spec","");
  objspec = NULL;

  i = (*nparams) * sizeof(struct varlevel);
  ss = (struct varlevel *)malloc(i);
  if (ss == NULL) errn ("Could not get memory for varlevel structs. nbytes=",i);
  num_ss = 0;
  any_levelization_args = FALSE;
  default_level = ILLEGAL_LEVEL;
  maxoutlev = ILLEGAL_LEVEL;
  default_comp_type = '\0';

    /*  Since u_proj is NULL-terminated, need possible # proj +1.	*/
    /*  Since argv[0] is obj spec, nparams IS 1 bigger			*/
  i = (*nparams) * sizeof(char *);
  u_proj = (char **)malloc(i);
  if (u_proj == NULL) errn ("Could not get memory for u_proj list. nbytes=",i);
  num_u_proj = 0;

  sel_list = (char **)malloc(i);
  if (sel_list == NULL) errn 
			("Could not get memory for selection list. nbytes=",i);
  num_sels = 0;

  num_vars_in_sels = 0;
  vars_in_sels = NULL;

  for (i=1; i<*nparams; i++) {
      /*  Don't process empty command line args because they will end	*/
      /*  up being tested as selections and the selection parser will	*/
      /*  not like it.							*/
    if (s[i][0] == '\0') continue;
    if (is_rs_levelization_arg(s[i])) {
      j = (int) strtol(s[i],&sp,10);
      if (j >= MAXLEVELS) err ("level too big in levelization parameter ",s[i]);
      if (j < 0) err("level is negative in levelization parameter ",s[i]);
      if ((j > maxoutlev) || (maxoutlev == ILLEGAL_LEVEL)) maxoutlev = j;
      varname_ptr = sp+1;
      if (*varname_ptr == UNNAMED_VARS) {
        if (default_level == ILLEGAL_LEVEL) {
	  default_level = j;
	  default_comp_type = *sp;
	} else {
	  err ("Default (*) set more than once","");
        }
      } else {
	for (k=0; k<num_ss; k++)
	  if (strcmp(varname_ptr,ss[k].var) == 0)
err ("Variable spec'd in more than one levelization parameter.  Var is",
								varname_ptr);
	ss[num_ss].level = j;
	ss[num_ss++].var = varname_ptr;
      }
      any_levelization_args = TRUE;
      s[i][0] = '\0';	/* This wipes a piece of the level, but		*/
			/* should leave varname_ptr's string alone	*/
    } else {
      if ( is_a_varname(s[i]) )  {
	u_proj[num_u_proj++] = s[i];
      } else {
	  /*  Save copy of arg rather than ptr to arg in case outer	*/
	  /*  decides to do something.  outer thinks that args we	*/
	  /*  don't NULL out belong to it				*/
	strdupl(&sp,s[i],"copying selection");
	sel_list[num_sels++] = sp;
	sp = un_trigram(sp,DEFAULT_TRIGRAM_CHAR,NULL);
	num_vars_in_sels += varnames_in_sel(&vars_in_sels,sp,NULL,NULL,NULL);
	free (sp);
      }
    }
  }
  if ( ! any_levelization_args) 
			err ("No levelization arguments passed to rs","");
  u_proj[num_u_proj] = NULL;
  sel_list[num_sels] = NULL;

  if (do_optimize) {
      /*    If default_level is illegal, then it was not specified; ie,	*/
      /*  all variables are named.  If this is true, we can proceed	*/
      /*  with level-based projection optimization.			*/
      /*    If default_level is legal, then we must turn off		*/
      /*  optimization if the default level is requested because we	*/
      /*  don't know what vars are on default_level to be projected in.	*/
      /*    If html_max_level is illegal, we aren't doing optimization	*/
      /*  anyway.							*/
      /*    ACCORDINGLY, only issue is if both are legal		*/
    if ((default_level != ILLEGAL_LEVEL) && (html_max_level != ILLEGAL_LEVEL))
      if (html_max_level >= default_level) html_max_level = ILLEGAL_LEVEL;

    num_l_proj = 0;
    i = (num_ss + 1) * sizeof (char *);		/* +1 for trailing NULL	*/
    l_proj = (char **)malloc(i);
    if (l_proj == NULL) 
		errn ("Could not get memory for l_proj list. nbytes=",i);
    if (html_max_level != ILLEGAL_LEVEL) {
      for (i=0; i<num_ss; i++)
	if (ss[i].level <= html_max_level) l_proj[num_l_proj++] = ss[i].var;
    }
    l_proj[num_l_proj] = NULL;

    objspec = optimize_objspec(s[0],
				u_proj,num_u_proj,
				l_proj,num_l_proj,
/* Need to project in levelization args, which is the ss[].var stuff from above */
/* but didn't do it yet (wjs 2.0d Aug 14)					*/
				sel_list,num_sels,
				vars_in_sels,num_vars_in_sels);
  }

  if (objspec == NULL) objspec = s[0];
  nnames = -NVAR;
  maxclev = jdbopen_(&handle,objspec,names,&namesize,&nnames);
    /*  next error message will probably be in an environment where	*/
    /*  jdbopen has already printed one of its error messages		*/
  if (maxclev < 0) err("Trouble with object specifier ",objspec);
  if (objspec != s[0]) free_lengthened_str(objspec);
  s[0][0] = '\0';

    /*  Restore original PATH_INFO if we changed it		*/
  if (PATH_INFO_jgof_putenv != NULL) {
    if (  (i = putenv(PATH_INFO_orig_putenv)) != 0  )
       err ("putenv failure restoring PATH_INFO",strerror(i));
    free(PATH_INFO_jgof_putenv);
  }

  comments = NULL;
  size_comments = 0;
  position_in_comments = 0;
    /*  Note that jdbcomments could overflow tmp - there is no consis-	*/
    /*  tency check between the buffer in jdbcomments and the parameter	*/
    /*  we use to size tmp						*/
    /*  Note we could jigger this to have jdbcomments_ put stuff di-	*/
    /*  rectly in comments, too						*/
  while (jdbcomments_(&handle,tmp)) {
    i = strlen(tmp);
      /*  +2 because we need a \n per tmp as well as a final \0		*/
    if (size_comments < position_in_comments + i + 2) {
	/*  COMMENTSIZE in next line could be "anything"		*/
      size_comments += (i+2 > COMMENTSIZE) ? i+2 : COMMENTSIZE;
      comments = (char *)realloc(comments,size_comments);
      if (comments == NULL)
	err("Could not allocate space for comments buffer","");
    }
    strncpy (comments + position_in_comments, tmp, i);
    position_in_comments += i;
    *(comments + position_in_comments++) = '\n';
    *(comments + position_in_comments) = '\0';
  }
  
  j = 0;
  firstvar[j] = 0;
  for  (i=0; i<nnames; i++)  {
    newlev[i] = ILLEGAL_LEVEL;
    if (iovarlevel_(&handle,&i) > j) firstvar[++j] = i;
  }

  m = 0;
  for  (k=0; k<num_ss; k++)  {
    varname_ptr = ss[k].var;
    for  (j=0; j<nnames; j++)  {
      if (  strcmp(names[j],varname_ptr) == 0  )   break;
    }
    if (j < nnames)  {
      newlev[j] = ss[k].level;
      comp_type[j] = *(varname_ptr - 1);
      newpntr[m++] = j;
    } else if ( (html_max_level == ILLEGAL_LEVEL) 
				||
		(ss[i].level <= html_max_level)    ) {
	/*  Levelization parameter specifies variable that is not in	*/
	/*  input object.  Error unless said variable was actually IN	*/
	/*  input object but was projected out.				*/
	/*	The "unless" clause is in 2 pieces.			*/
	/*	The for loop, below, takes care of explicit projection.	*/
	/*	The if, above, addresses the implicit			*/
	/*  projection via level by ignoring lev params that aren't	*/
	/*  "in" the level(s) we are about to display.  ILLEGAL_LEVEL	*/
	/*  means no optimization, means check all lev params		*/
	/*	Note that if we are optimizing, bad vars in lev params	*/
	/*  will appear in the proj list in the jdbopen above, causing	*/
	/*  THAT to fail, and we'll never get here (except at bottom	*/
	/*  level (maybe))						*/
      for (i=0; i<num_u_proj; i++)
	if (  strcmp(u_proj[i],varname_ptr) == 0  )   break;
      if (i == num_u_proj) err
        ("Variable in levelization parameter not in input object.  Var is",
								   varname_ptr);
    }
  }
  
  if (default_level == ILLEGAL_LEVEL) {
    default_specified = FALSE;
    default_level = maxoutlev;	/* Don't think default_level is ever	*/
				/* used if not specified		*/
    default_comp_type = CONDENSE;
  } else {
    default_specified = TRUE;
    if (default_level > maxoutlev) maxoutlev = default_level;
  }
  i = 0;
  for (j=0; j<nnames; j++) {
    if (newlev[j] == ILLEGAL_LEVEL)  {
      if ( ! default_specified) err ("No output level for variable ",names[j]);
      i++;
      newlev[j] = default_level;
      comp_type[j] = default_comp_type;
      newpntr[m++] = j;
    }
    var_at_level[newlev[j]] = TRUE;
  }

  /*  Both of the commented out tests below are correct, but not clear	*/
  /*  that detected situations are errors when projections are involved	*/
  /*  outer 3.0 should handle the "no variables on level" anyway...	*/
/*
  if (  (i == 0) && default_specified  )
		err ("No variables affected by * levelization parameter","");
  for (i=0;  i <= maxoutlev;  i++)  
    if ( ! var_at_level[i]) errn ("No variables end up on level ",i);
*/

  *ntotal=nnames;

  minlevelread = maxoutlev+1;
  return maxoutlev;
}
