// saved from url
// http://www-cs.canisius.edu/PL_TUTORIALS/C++/EXAMPLES/MRD/SRC/relation.c -->

//---------------------------------- relation.c ----------------------------

#include <stdio.h>
#include <string.h>
#include <string>
using namespace std;
#include <stdlib.h>
#include <algorithm>	// std::min template for safe comparisons
#include "relation.h"

bool validfilename(char * filename) {
	int i;
	const int length = strlen(filename);

	for (i = 0; i<length; i++) {
		if (filename[i] == '.')
			break;
	}
	return (i < length);
}

static int contains_blank (char *somestring)
{
     while (*somestring)
          if (*somestring++ == ' ') return 1;

     return 0;
}

void Relation::info ()
{
     printf ("Relation loaded from file: %s\n", relfilename);
     printf ("Number of records = %d\n", numrecords);
     for (int i=0; i<numfields; i++)
          printf ("%3d. %-15s  %4d   %c\n", i, dict[i].name, dict[i].printwidth,
                   dict[i].datatype);
}

int Relation::findfield (char *whichfield)
{
     for (int i=0; i<numfields; i++)
          if (strcmp (whichfield, dict[i].name) == 0) return i;
     return -1;
}

void Relation::copydict (Relation *source, Relation *dest)
{
     for (int i=0; i<source->numfields; i++)
          dest->addfield (source->dict[i].name, source->dict[i].position,
                          source->dict[i].datatype, source->dict[i].printwidth);
}


// The next version is used only for cross product and ejoin.  It deals with
// the problem that the two Relations may have the same field names.  The
// solution is to see if the same field name appears in both Relations, and
// if it does, then the second Relation's field is renamed using _fieldname.
// This relies on the hope that no one used THAT name in the first Relation 
// or in the second.  If they did, the field just can't be found.

void Relation::copydict2 (Relation *source1, Relation *source2, Relation *dest)
{
     // First copy the first Relation's fields over
	 int i;
     for (i=0; i<source1->numfields; i++)
          dest->addfield (source1->dict[i].name, source1->dict[i].position,
                       source1->dict[i].datatype, source1->dict[i].printwidth);

     // Now copy the second's over, but for each one search the dictionary
     // of the first Relation, and if found then rename

     for (i=0; i<source2->numfields; i++) {
          int found = 0;

          for (int j=0; j<source1->numfields; j++)
               if (strcmp(source1->dict[j].name, source2->dict[i].name) == 0) {
                    found = 1;
                    break;
               }

          if (found) {
               char temp[60];
               strcpy (temp, "_");
               strcat (temp, source2->dict[i].name);
               dest->addfield (temp, 
                               source2->dict[i].position + source1->numfields,
                               source2->dict[i].datatype,
                               source2->dict[i].printwidth);
          }
          else
               dest->addfield (source2->dict[i].name, 
                               source2->dict[i].position + source1->numfields,
                               source2->dict[i].datatype,
                               source2->dict[i].printwidth);
     }
}

int Relation::xmatch1c (Parseline *x, int pos, char *comparison, 
                        char *fieldvalueC)
{
     if (strcmp (comparison, "=") == 0)
          return (strcmp (x->word(pos), fieldvalueC)==0);
     if (strcmp (comparison, "!=") == 0)
          return (strcmp (x->word(pos), fieldvalueC)!=0);
     if (strcmp (comparison, "<") == 0)
          return (strcmp (x->word(pos), fieldvalueC)<0);
     if (strcmp (comparison, "<=") == 0)
          return (strcmp (x->word(pos), fieldvalueC)<=0);
     if (strcmp (comparison, ">") == 0)
          return (strcmp (x->word(pos), fieldvalueC)>0);
     if (strcmp (comparison, ">=") == 0)
          return (strcmp (x->word(pos), fieldvalueC)>=0);
	 throw "bad comparision!";
	 return -1;

}

int Relation::xmatch1i (Parseline *x, int pos, char *comparison, 
                        char *fieldvalueC)
{
     if (strcmp (comparison, "=")  == 0)
          return atoi(x->word(pos)) == atoi(fieldvalueC);
     if (strcmp (comparison, "!=") == 0)
          return atoi(x->word(pos)) != atoi(fieldvalueC);
     if (strcmp (comparison, "<")  == 0)
          return atoi(x->word(pos)) <  atoi(fieldvalueC);
     if (strcmp (comparison, "<=") == 0)
          return atoi(x->word(pos)) <= atoi(fieldvalueC);
     if (strcmp (comparison, ">")  == 0)
          return atoi(x->word(pos)) >  atoi(fieldvalueC);
     if (strcmp (comparison, ">=") == 0)
          return atoi(x->word(pos)) >= atoi(fieldvalueC);
	 throw "bad comparision";
	 return -1;
}

int Relation::xmatch1d (Parseline *x, int pos, char *comparison, 
                        char *fieldvalueC)
{
     if (strcmp (comparison, "=")  == 0)
          return atof(x->word(pos)) == atof(fieldvalueC);
     if (strcmp (comparison, "!=") == 0)
          return atof(x->word(pos)) != atof(fieldvalueC);
     if (strcmp (comparison, "<")  == 0)
          return atof(x->word(pos)) <  atof(fieldvalueC);
     if (strcmp (comparison, "<=") == 0)
          return atof(x->word(pos)) <= atof(fieldvalueC);
     if (strcmp (comparison, ">")  == 0)
          return atof(x->word(pos)) >  atof(fieldvalueC);
     if (strcmp (comparison, ">=") == 0)
          return atof(x->word(pos)) >= atof(fieldvalueC);
	 throw "Invalid Comparison";
	 return -1;
}

int Relation::match1 (Parseline *x, Spec comp)
{
     int n = findfield (comp.fieldname);
     if (n == -1) return 0;
     int pos = dict[n].position;

     // See if the given field name even exists

     switch (dict[n].datatype) {
       case 'c': return xmatch1c (x, pos, comp.comparison, comp.fieldvalueC);
       case 'i': return xmatch1i (x, pos, comp.comparison, comp.fieldvalueC);
       case 'd': return xmatch1d (x, pos, comp.comparison, comp.fieldvalueC);
	   default : throw "Invalid datatype"; return -1;
     }
}

int Relation::match (Parseline *x, Speclist comps)
{
     int truth = 0;

     for (int i=0; i<comps.numspecs; i++) {
          int val = match1 (x, comps.specs[i]);
          if (comps.specs[i].relation_to_previous == 'a')
               truth = truth && val;
          else
          if (comps.specs[i].relation_to_previous == 'o')
               truth = truth || val;
          else
               truth = val;
     }
     return truth;
}

Relation* Relation::select (Speclist specs)
{
     Relation *newone;

     newone = new Relation();

     copydict (this, newone);

     for (int i=0; i<numrecords; i++)
          if (match (records[i], specs)) {
               char line[MAXDBLLINE];
               records[i]->makeline(line);
               newone->addrecord (line);
          }
     return newone;
}

void Relation::append (Relation *other)
{
     // What if the dictionaries do not match????

     for (int i=0; i<other->numrecords; i++) {
          char line[MAXDBLLINE];
          other->records[i]->makeline(line);
          addrecord (line);
     }
}

Relation* Relation::copy ()
{
     Relation *newone;

     newone = new Relation();

     copydict (this, newone);

     for (int i=0; i<numrecords; i++) {
          char line[MAXDBLLINE];
          records[i]->makeline(line);
          newone->addrecord (line);
     }

     return newone;
}

void Relation::makewidthline (char *line)
{
     line[0] = 0;
     for (int i=0; i<numfields; i++) {
          char temp[MAXNAME];
          sprintf (temp, "%d ", dict[i].printwidth);
          strcat (line, temp);
     }
}

void Relation::print (FILE *fp)
{
     char field[MAXDBLLINE], line[MAXLINES];

     for (int i = 0; i<numrecords; i++) {
          strcpy (line, "");
          for (int j = 0; j<numfields; j++) {
               formatvalue (i, j, field, sizeof(field));
               strcat (line, field);
               strcat (line, " ");
          }
          fprintf (fp, "%s\n", line);
     }
}

void Relation::printheader (FILE *fp)
{
     char fmtstring[20];
     int width=0, i;

     for (i=0; i<numfields; i++) {
          sprintf (fmtstring, "%%-%ds", dict[i].printwidth);
          width += dict[i].printwidth;
          fprintf (fp, fmtstring, dict[i].name);
          fprintf (fp, " ");
     }
     fprintf (fp, "\n");
     fprintf (fp, "--------------------------------------------------------------------------\n");

}

void Relation::printfields (FILE *fp)
{

     for (int i=0; i<numfields; i++) {
          
          fprintf (fp, "%s (%c)   ", dict[i].name, dict[i].datatype);
          
     }
     
     fprintf (fp, "\n--------------------------------------------------------------------------\n");

}

void Relation::print ()
{
     print (stdout);
}

Relation* Relation::project (char *namelist)
{
     Relation *newone;
     char line[MAXLINE];

     Parseline names(namelist);

     newone = new Relation();

     // Put in only the fields that are mentioned.

     for (int j=0; j<names.size(); j++) {
          int k = findfield(names.word(j));
          newone->addfield (names.word(j), j, dict[k].datatype, 
                            dict[k].printwidth);
     }

     for (int i=0; i<numrecords; i++) {
          strcpy (line, "");
          for (int j=0; j<names.size(); j++) {
               int m = findfield(names.word(j));
               int pos = dict[m].position;
               if (contains_blank (records[i]->word(pos))) {
                    strcat (line, "\"");
                    strcat (line, records[i]->word(pos));
                    strcat (line, "\" ");
               }
               else {
                    strcat (line, records[i]->word(pos));
                    strcat (line, " ");
               }
          }
          newone->addrecord(line);
     }

     return newone;
}

//  For the default case of adding a record, we just make a copy of the
//  previous record.

void Relation::addrecord ()
{
     char temp[MAXDBLLINE];
     records[numrecords-1]->makeline(temp);
     addrecord (temp);
}

void Relation::addrecord (char *someline)
{
     Parseline *newrec = new Parseline (someline);
	 addrecord(newrec);
/*     if (numrecords >= MAXRECORDS) return;   // later set error code
     numrecords++;
     // check for consistency in number of fields???
     records[numrecords-1] = newrec;
*/
}

void Relation::addrecord (Parseline *newrec)
{
     if (numrecords >= MAXRECORDS) return;   // later set error code

     // check for consistency in number of fields???
	 if (numfields == newrec->size()) {
		 numrecords++;
		 records[numrecords-1] = newrec;
	 }
	 else 
//		 throw RelationExcp("addrecord: number of fields not match");
		 throw "Relation::addrecord: number of fields not match";
}

void Relation::addfield (char *name, int pos, char type, int width)
{
     if (numfields >= MAXFIELDS) return;

     strcpy (dict[numfields].name, name);   // check to see if already there??
     dict[numfields].datatype = type;
     dict[numfields].position = pos;
     dict[numfields].printwidth = width;
     numfields++;
}

void Relation::load (char *filename)
{
	
     FILE *fp;
     char line[MAXLINE];
	 if (filename == NULL) {
		 throw "Relation::load(): no filename";
	 }
     string fname(filename);
     if ((fp = fopen (filename, "r")) == NULL) {
          fname += ".txt";
          if ((fp = fopen (fname.data(), "r")) == NULL ) {
              printf ("Cannot open file %s\n", filename);
              printf ("Cannot open file %s\n", fname.data());
              return;
          }
     }

     numfields = 0;

     while (fgets(line, sizeof(line), fp) != NULL) {
          line[strlen(line)-1] = 0;
          if (line[0] == '#') {				
			  break;
		  }
          Parseline pline(line);
		  if (pline.size() == 4) 
	          addfield (pline.word(0), numfields, pline.word(2)[0],
                    atoi(pline.word(3)));
		  else
		      addfield (pline.word(0), numfields, pline.word(1)[0],
                    atoi(pline.word(2)));
     }

     // Now load the data records

     numrecords = 0;
     while (fgets(line, sizeof(line), fp) != NULL) {
          line[strlen(line)-1] = 0;
          addrecord(line);
     }
     fclose (fp);
     strcpy (relfilename, fname.data());
}



void Relation::save (char *filename)
{
     FILE *fp;
     string fname(filename);
//
//

	 if (!validfilename(filename))
		  fname += ".txt";

     if ((fp = fopen (fname.data(), "w")) == NULL) {
          printf ("Cannot open file %s for saving\n", fname.data());
          return;
     }

     // Save the data dictionary and make up the width list for saving the
     // line.

     char widths[MAXWIDTHLIST];
     widths[0] = 0;

     for (int i=0; i<numfields; i++) {
          fprintf (fp, "%s %c %d\n", dict[i].name, dict[i].datatype, 
                                     dict[i].printwidth);
          char temp[MAXNAME];
          sprintf (temp, "%d ", dict[i].printwidth);
          strcat (widths, temp);
     }

     fprintf (fp, "#\n");
     print (fp);
     fclose (fp);
}

Relation* Relation::ejoin (Relation *other, char *whichf1, char *whichf2)
{
     char newline[MAXLINES], newline2[MAXDBLLINE];
     Relation *newone;
     int which1, which2;
     
     int n = this->findfield (whichf1);
     if (n == -1) return (Relation *) NULL;
     which1 = this->dict[n].position;

     n = other->findfield (whichf2);
     if (n == -1) return (Relation *) NULL;
     which2 = other->dict[n].position;

     newone = new Relation();

     copydict2 (this, other, newone);

     for (int i=0; i < this->numrecords; i++)
          for (int j=0; j< other->numrecords; j++) 
               if (strcmp(this->records[i]->word(which1),
                          other->records[j]->word(which2)) == 0) {
                    this->records[i]->makeline(newline);
                    strcat (newline, " ");
                    other->records[j]->makeline(newline2);
                    strcat (newline, newline2);
                    newone->addrecord (newline);
               }

     return newone;
}

Relation* Relation::cross (Relation *other)
{
     char newline[MAXLINES], newline2[MAXDBLLINE];
     Relation *newone;

     newone = new Relation();

     copydict2 (this, other, newone);

     for (int i=0; i < this->numrecords; i++)
          for (int j=0; j < other->numrecords; j++) {
               this->records[i]->makeline(newline);
               strcat (newline, " ");
               other->records[j]->makeline(newline2);
               strcat (newline, newline2);
               newone->addrecord (newline);
          }

     return newone;
}

static int greaterthan (char *s1, char *s2, char datatype)
{
     switch (datatype) {
         case 'c':  return strcmp(s1, s2) > 0;
         case 'i':  return atoi(s1) > atoi(s2);
         case 'd':  return atof(s1) > atof(s2);
		 default :  throw "Invalid datatype"; return -1;
     }
}

void Relation::sort (char *whichfield, bool ascending)
{
     int n = findfield(whichfield);
     if (n == -1) return;
     int pos = dict[n].position;
     char datatype = dict[n].datatype;

     for (int i=0; i<numrecords-1; i++)
          for (int j=i+1; j<numrecords; j++) {
               bool temp = greaterthan (records[i]->word(pos),
                                records[j]->word(pos), datatype);
               if (ascending && temp) {
                    Parseline *temp = records[i];
                    records[i] = records[j];
                    records[j] = temp;
               }
               if (!ascending && !temp) {
                    Parseline *temp = records[i];
                    records[i] = records[j];
                    records[j] = temp;
               }
          }
}

void Relation::sort ()
{
     for (int i=0; i<numrecords-1; i++)
          for (int j=i+1; j<numrecords; j++) {
               char line1[MAXDBLLINE], line2[MAXDBLLINE];
               records[i]->makeline(line1);
               records[j]->makeline(line2);
               if (strcmp (line1, line2) > 0) {
                    Parseline *temp = records[i];
                    records[i] = records[j];
                    records[j] = temp;
               }
          }
}

Relation* Relation::istats (int pos)
{
     Relation *newone;
     char templine[MAXLINE];
     int sum=0, n, max, min;
     double avg, sumsq=0.0;

	 int i;
     for (i=0; i<numrecords; i++) {
          n = atoi(records[i]->word(pos));
          sum += n;
          if (i==0) max = n;
          if (i==0) min = n;
          if (n > max) max = n;
          if (n < min) min = n;
     }

     avg = (float)sum/numrecords;

     for (i=0; i<numrecords; i++) {
          n = atoi(records[i]->word(pos));
          sumsq += (avg - n) * (avg - n);
     }

     sprintf (templine, "%d  %d  %d  %d  %.5f  %.5f",
                  numrecords, sum, min, max, avg, sumsq);

     newone = new Relation();

     newone->addfield ("num", 0, 'i', 9);     // number of records in Relation
     newone->addfield ("sum", 1, 'i', 9);     // sum of the stat field's values
     newone->addfield ("min", 2, 'i', 9);     // min value of stat field
     newone->addfield ("max", 3, 'i', 9);     // max value of stat field
     newone->addfield ("avg", 4, 'd', 9);     // average (floating point)
     newone->addfield ("ssq", 5, 'd', 9);     // sum of squares

     newone->addrecord (templine);

     return newone;
}

Relation* Relation::dstats (int pos)
{
     Relation *newone;
     char templine[MAXLINE];
     double sum=0, n, max, min;
     double avg, sumsq=0.0;

	 int i;
     for (i=0; i<numrecords; i++) {
          n = atof(records[i]->word(pos));
          sum += n;
          if (i==0) max = n;
          if (i==0) min = n;
          if (n > max) max = n;
          if (n < min) min = n;
     }

     avg = sum/numrecords;

     for (i=0; i<numrecords; i++) {
          n = atof(records[i]->word(pos));
          sumsq += (avg - n) * (avg - n);
     }

     sprintf (templine, "%d  %.5f  %.5f  %.5f  %.5f  %.5f",
                  numrecords, sum, min, max, avg, sumsq);

     newone = new Relation();

     newone->addfield ("num", 0, 'i', 9);     // number of records in Relation
     newone->addfield ("sum", 1, 'd', 9);     // sum of the stat field's values
     newone->addfield ("min", 2, 'd', 9);     // min value of stat field
     newone->addfield ("max", 3, 'd', 9);     // max value of stat field
     newone->addfield ("avg", 4, 'd', 9);     // average 
     newone->addfield ("ssq", 5, 'd', 9);     // sum of squares

     newone->addrecord (templine);

     return newone;
}

Relation* Relation::summarystats (char *whichfield)
{
     int n = findfield(whichfield);
     if (n == -1) return (Relation *) NULL;

     if (dict[n].datatype == 'i') 
          return istats (dict[n].position);
     else if (dict[n].datatype == 'd') 
          return dstats (dict[n].position);
	 else
		  return (Relation *) NULL;
}

Relation* Relation::addseqnums ()
{
     Relation *newone;
     char temp[MAXLINE];

     newone = new Relation();

     // Make new data dictionary

     newone->addfield ("seqnum", 0, 'i', 6);

	 int i;
     for (i=0; i<numfields; i++)
          newone->addfield (dict[i].name, i+1, dict[i].datatype,
                            dict[i].printwidth);

     for (i=0; i<numrecords; i++) {
          char line[MAXDBLLINE];
          records[i]->makeline(line);
          sprintf (temp, "%d  %s", i, line);
          newone->addrecord (temp);
     }

     return newone;
}

void Relation::showdict ()
{
     for (int i=0; i<numfields; i++) {
          printf ("%3d. %-20s %3d %3d ", i, dict[i].name, dict[i].position,
                                             dict[i].printwidth);
          switch (dict[i].datatype) {
              case 'c': printf ("character string\n"); break;
              case 'd': printf ("floating point\n"); break;
              case 'i': printf ("integer\n"); break;
          }
     }
}

void Relation::print1record (int recnum)
{
     for (int i=0; i<numfields; i++)
          printf ("%s: %s\n", dict[i], records[recnum]->word(i));
}

static int sametuples (Parseline *x, Parseline *y)
{
     if (x->size() != y->size()) return 0;

     for (int i=0; i<x->size(); i++)
          if (strcmp (x->word(i), y->word(i)) != 0) return 0;

     return 1;
}

void Relation::unique ()
{
     if (numrecords < 2) return;

     sort();

     int nums[MAXLINE], numdels=0;

     int i=0, j=1;
     for (j=1; j<numrecords; j++)
          if (sametuples(records[i], records[j]))
               nums[numdels++] = j;
          else
               i = j;

     if (numdels == 0) return;

     int k=1;
     int delindex = 0;
     for (i=1; i<numrecords; i++)
          if (nums[delindex] == i)
               delindex++;
          else
               records[k++] = records[i];

     numrecords = numrecords - numdels;
}

int Relation::findrecord (char *fieldname, char *value)
{
     int n = findfield (fieldname);
     if (n == -1) return 0;
     for (int i=0; i<numrecords; i++)
          if (strcmp(records[i]->word(dict[n].position), value) == 0)
               return i;
	 return 0;
}

int Relation::findrecord (char *fieldname, int value)
{
     int n = findfield (fieldname);
     if (n == -1) return 0;
     for (int i=0; i<numrecords; i++)
          if (atoi(records[i]->word(dict[n].position)) == value)
               return i;
	 return 0;
}

int Relation::findrecord (char *fieldname, double value)
{
     int n = findfield (fieldname);
     if (n == -1) return 0;
     for (int i=0; i<numrecords; i++)
          if (atof(records[i]->word(dict[n].position)) == value)
               return i;
	 return 0;
}

void Relation::setcurrent (int recnum)
{
     if (recnum >= 0 && recnum < numrecords)
          current = recnum;
}

void Relation::advance ()
{
     if (current < numrecords-1) current++;
}

bool Relation::atend ()
{
     return (current == numrecords-1);
}

char* Relation::getvalueC (char *fieldname)
{
     int n = findfield (fieldname);
     if (n == -1) return (char *) NULL;
     return records[current]->word(dict[n].position);
}

int Relation::getvalueI (char *fieldname)
{
     int n = findfield (fieldname);
     if (n == -1) return 0;
     return atoi(records[current]->word(dict[n].position));
}

double Relation::getvalueD (char *fieldname)
{
     int n = findfield (fieldname);
     if (n == -1) return 0.0;
     return atof(records[current]->word(dict[n].position));
}


void Relation::formatvalue (int recnum, int fieldnum, char *result, 
                            size_t resultmaxlen)
{
     char thisvalue[MAXLINE];
     char format[MAXWORD];
     int  cb, len, j;

     strcpy (thisvalue, records[recnum]->word(dict[fieldnum].position));

     int width = dict[fieldnum].printwidth;
	 int i;
     for (i=0; i<resultmaxlen; i++)
          result[i] = 0;

     switch (dict[fieldnum].datatype) {
        case 'c': cb = contains_blank (thisvalue);
                  len = min(resultmaxlen, strlen(thisvalue));
                  j=0;
                  if (cb) {
                       result[0] = '"';
                       j=1;
                  }
                  for (i=0; i<len; i++)
                       result[j++] = thisvalue[i];
                  if (cb) result[j++] = '"';
                  while (j<width-1)
                       result[j++] = ' ';
                  break;
        case 'i': sprintf (format, "%%%dd", width);
                  sprintf (result, format, atoi(thisvalue));
                  break;
        case 'd': sprintf (format, "%%%d.3lf", width);
                  double x;
                  sscanf (thisvalue, "%lf", &x);
                  sprintf (result, format, x);
                  break;
     }
}

void Relation::setvalue (char *fieldname, char *newvalueC)
{
     int n = findfield (fieldname);
     if (n == -1) return;
     records[current]->setword (dict[n].position, newvalueC);
}

void Relation::setvalue (char *fieldname, int newvalueI)
{
     int n = findfield (fieldname);
     if (n == -1) return;
     char newvalueC[MAXWORD];
     sprintf (newvalueC, "%d", newvalueI);
     records[current]->setword (dict[n].position, newvalueC);
}

void Relation::setvalue (char *fieldname, double newvalueD)
{
     int n = findfield (fieldname);
     if (n == -1) return;
     char newvalueC[MAXWORD];
     sprintf (newvalueC, "%f", newvalueD);
     records[current]->setword (dict[n].position, newvalueC);
}

void Relation::changefieldname (char *fieldname, char *newname)
{
     int n = findfield (fieldname);
     if (n == -1) return;
     strcpy (dict[n].name, newname);
}

void Relation::changefieldwidth (char *fieldname, int newwidth)
{
     int n = findfield (fieldname);
     if (n == -1) return;
     dict[n].printwidth = newwidth;
}

int Relation::getfieldwidth (char *fieldname)
{
     int n = findfield (fieldname);
     if (n == -1) return -1;
     return dict[n].printwidth;
}

void Relation::delrecord (int recnum)
{
     if (recnum >= numrecords) return;
     delete records[recnum];
     for (int i=recnum; i<numrecords-1; i++)
          records[i] = records[i+1];
     numrecords--;
}

void Relation::addfieldvalue (char *newfield, int recnum, char *newvalue)
{
     char small[2];
     small[0] = ' ';
     small[1] = 0;

     char temp[MAXLINE];

     records[recnum]->makeline(temp);
     strcat (temp, small);
     strcat (temp, newvalue);

     delete records[recnum];
     records[recnum] = new Parseline (temp);
}

char Relation::getfieldtype (char *fieldname)
{
     int n = findfield (fieldname);
     if (n == -1) return -1;
     return dict[n].datatype;
}

Relation* Relation::difference (char *field1, Relation *other, char *field2)
{
     int fn1, fn2;
     if ((fn1 = findfield (field1)) == -1) return (Relation *) NULL;
     if ((fn2 = other->findfield (field2)) == -1) return (Relation *) NULL;

     Relation *newrel = new Relation();
     copydict (this, newrel);

     //  Go through our records and try to find a record in other with
     //  same value for "whichfield."  If not found, then add to a third
     //  Relation.

     char value[MAXDBLLINE];
     for (int i=0; i<numrecords; i++) {
          int found = 0;
          strcpy (value, records[i]->word(fn1));
          for (int j=0; j<other->numrecords; j++)
               if (strcmp (value, other->records[j]->word(fn2)) == 0) {
                    found = 1;
                    break;
               }
          if (!found) {
               char line[MAXDBLLINE];
               records[i]->makeline(line);
               newrel->addrecord (line);
          }
     }

     return newrel;
}
