/*****************************************************************************/ /* File : GENDB.CC */ /* */ /* Authors : Stephen C. Smith / Craig E. Smith */ /* University of Massachusetts - Lowell */ /* */ /* Created : Spring Semester, 1992 (Jan - May) */ /* */ /* Revisions : SCS/CES - 01/25/92 - Initial Version */ /* obde/RJL 92/9/25: bug fix to Delete: to avoid skipping odd rows */ /* RJL 93/2/25: Added Warning to strcopy2 about assigning to *(t+n); */ /* renamed gendb_obde.cc back to gendb.cc; old vers is gendb.cc.bak */ /* RJL 94/2/8: wrapped lines to fit in 80 cols including some quoted strings */ /* RJL 94/2/8: upgraded object_count and userDefinedType syntax for new g++ */ /* resulting in error-free compilation */ /* */ /* Purpose : This module provides all the routines and methods used for the*/ /* GENDB data-modeling tool. It is all that is needed (except, */ /* of course, for the GENDB.HPP include file) to develop */ /* applications using GENDB. */ /* */ /* Test Main : To run a simple test of the system, remove the #if 0/#endif */ /* pair that surround a main() at the end of this file, and then */ /* recompile the module. Also be sure that test.sch and test.dat*/ /* are present in the directory you run from. */ /* */ /* Full Test : A complete functioning GENDB application has been created, */ /* called STUDENTS.CC This menu-driven program implements */ /* a complete university student/course/shedule system, and */ /* is designed (along with it's schema) to demonstrate many */ /* of GENDB's features. */ /* */ /* Platforms : The following compilers have been used successfully : */ /* Turbo C++ (1.01) ( MS-DOS ) */ /* Zortech C++ (2.10) ( MS-DOS ) */ /* GNU C++ (g++) ( UNIX ) */ /* Other compilers should work as well, although it may be */ /* necessary to disable warning messages (usually for not */ /* following the function prototypes found in string.h, */ /* which is a typical problem encountered in C programs). */ /* */ /* Files : The following are files that should be kept with the */ /* system, wherever it goes, for completeness. However, only */ /* the three GENDB.* files are critical : */ /* GENDB.CC - GENDB source code (this file) */ /* GENDB.HPP - Include file for GENDB, all applications */ /* must include this file */ /* GENDB.DOC - User's Manual for GENDB */ /* TEST.SCH - Schema file for the main test-driver at the */ /* end of GENDB.CC */ /* TEST.DAT - Data file used by the main test-driver */ /* STUDENTS.CC - STUDENT Test Application SOURCE for GENDB */ /* STUDENTS.DAT - STUDENT Test Application DATA FILE for GENDB */ /* STUDENTS.SCH - STUDENT Test Application SCHEMA for GENDB */ /* */ /*****************************************************************************/ #include #include #include #include #include "gendb.hpp" /*****************************************************************************/ /* This routine will allocate memory for, and then make a copy of a string. */ /*****************************************************************************/ char *CreateString(char *str) { char *ptr; ptr = (char *) calloc(1,strlen(str)+1); strcpy(ptr,str); return ptr; }; // CreateString /*****************************************************************************/ /* This routine will return a string representation of the passed number. */ /*****************************************************************************/ char *stringize(int x) { static char str[20]; sprintf(str,"%d",x); return str; }; // stringize /*****************************************************************************/ /* This routine is an enhanced version of strcpy. It will first copy the */ /* string t into s. Next, it will either pad the string out to the length */ /* n, or, if the unpad flag is true, it will instead remove any trailing */ /* blanks that might be present. */ /* From RJL: do NOT execute *(t+n)='\0' below! see version in chgen v6.2 */ /*****************************************************************************/ void strcpy2(char *s, char *t, int n, int unpad) { int i; char *s2; s2 = s; *(t+n) = '\0'; while (*s++ = *t++) n--; for (s--; n>1; n--) *s++ = ' '; *s = '\0'; s = s2; if (unpad) for (i=strlen(s)-1; (i>=0) && ((s[i] == ' ') || (s[i] == 9)); i--) s[i] = '\0'; }; // strcpy2 /*****************************************************************************/ /* This routine is a general string parsing routine. It will parse white- */ /* space delimited words out of the string s into the result word w. The */ /* value idx serves two purposes. Upon entry, it marks the starting place */ /* in the string to start looking for the word. Upon exit, it indicates the */ /* starting place for the next word that could be parsed. Thus, callers */ /* should first initialize idx to 0 before the first call to parse with a */ /* string. */ /*****************************************************************************/ void ParseWord(char *s, char *w, int *idx) { int i; w[i=0]='\0'; while (isspace(s[*idx])) (*idx)++; if (!s[*idx]) return; while (s[*idx] && !isspace(s[*idx])) { w[i++] = s[*idx]; (*idx)++; }; w[i] = '\0'; while (isspace(s[*idx])) (*idx)++; }; // ParseWord /*****************************************************************************/ /* This routine will simply set the region of memory pointed to by s with */ /* binary zero. the amount of memory zeroed is n bytes, usually the sizeof */ /* s. */ /*****************************************************************************/ void ClearMem(char *s, int n) { memset((void *)s,'\0',n); // for (; n>0; n--) // *s++ = '\0'; }; // clearmem /*****************************************************************************/ /* This routine will read the next line of text from the specified file. */ /* If the kill_comment flag is zero, and a "C" language style begin comment */ /* symbol is found, the line will be cut short just before it. */ /*****************************************************************************/ void ReadTextLine(FILE *fp, char *line, int kill_comment = TRUE) { int i,x; fgets(line,FILELINE_LEN,fp); x = strlen(line); if (x && (line[x-1] == '\n')) line[--x] = '\0'; if (kill_comment) for (--x,i=0; iroutine); CondFree(err_stack.body[i]->table); CondFree(err_stack.body[i]->err_text); free(err_stack.body[i]); }; ClearMem((char *) &err_stack,sizeof(err_stack)); }; /*****************************************************************************/ /* This function will add another error to an error stack. */ /* In addition, if the fatal flag is set, the entire error stack will be */ /* printed and then the program is terminated. */ /*****************************************************************************/ void error::AddError(int fatal,int err, char *rtn, char *tbl, char *txt) { err_stack.body[err_stack.count] = (struct errmsg_t *) calloc(1,sizeof(struct errmsg_t)); err_stack.body[err_stack.count]->err_num = err; if (rtn[0] != '\0') err_stack.body[err_stack.count]->routine = CreateString(rtn); if (tbl[0] != '\0') err_stack.body[err_stack.count]->table = CreateString(tbl); if (txt[0] != '\0') err_stack.body[err_stack.count]->err_text = CreateString(txt); err_stack.count++; if (fatal) { Print(); printf("now exiting image...\n"); exit(0); }; }; // error::AddError /*****************************************************************************/ /* This function will print all errors in an error stack to the stdout. */ /*****************************************************************************/ void error::Print() { int i; if (!IsError()) return; printf("\nERROR:\n"); for (i=0; ierr_num); if (err_stack.body[i]->routine) printf(", routine = %s",err_stack.body[i]->routine); if (err_stack.body[i]->table) printf(", table = %s",err_stack.body[i]->table); if (err_stack.body[i]->err_text) printf(", info = %s",err_stack.body[i]->err_text); printf("\n"); }; printf("\n"); }; // error::Print /*****************************************************************************/ /* This function will allocate memory for a new element of an index tree. */ /* Next, it places a pointer to the specified tuple into the nodes list. */ /*****************************************************************************/ struct index_elt_t *NewIndexElt(struct tuple_t *item) { struct index_elt_t *elt; elt = (struct index_elt_t *) calloc(1,sizeof(struct index_elt_t)); elt->list = (struct index_list_t *) calloc(1,sizeof(struct index_list_t)); elt->list->data = item; elt->last = elt->list; return elt; }; /*****************************************************************************/ /* This function will add a pointer to the specified tuple to the end of the */ /* tuple list of an existing index tree element. */ /*****************************************************************************/ void AddToIndexElt(struct index_elt_t *elt, struct tuple_t *item) { elt->last->next = (struct index_list_t *) calloc(1,sizeof(struct index_list_t)); elt->last = elt->last->next; elt->last->data = item; return; }; /*****************************************************************************/ /* This function performs a binary-tree insert of the specified tuple into */ /* the specified index tree. */ /*****************************************************************************/ void AddIndex(struct index_t *index, struct tuple_t *item) { struct index_elt_t *p,*q; int x; if (index->type != STRING) // remove when other types implemented return; if (!index->root) // add to empty tree { index->root = NewIndexElt(item); return; }; p = index->root; while (TRUE) { q = p; switch (index->type) { case STRING: x = strcmp(item->fields[index->fld_num]->u.sval, p->list->data->fields[index->fld_num]->u.sval); if (x < 0) { p = p->left; if (p == NULL) // add new elt to left of q; { q->left = NewIndexElt(item); return; }; } else if (x > 0) { p = p->right; if (p == NULL) // add new elt to right of q; { q->right = NewIndexElt(item); return; }; } else // install inside list at p { AddToIndexElt(p,item); return; }; }; // switch }; // while }; /*****************************************************************************/ /* This function will remove the specified list element from an index node. */ /*****************************************************************************/ void DelFromIndexElt(struct index_elt_t *elt, struct index_list_t *list) { struct index_list_t *p,*q; if ((list == elt->list) && (list == elt->last)) // only element { free(list); elt->list = elt->last = NULL; } else if (list == elt->list) // first element { elt->list = list->next; free(list); } else // inside chain somewhere { for (p=elt->list; p->next != list; p=p->next); q = p->next->next; free(p->next); p->next = q; }; return; }; /*****************************************************************************/ /* This function will perform a binary-tree deletion of the specified node */ /* from an index tree. */ /*****************************************************************************/ void DelIndex(struct index_t *index, struct tuple_t *item) { }; /*****************************************************************************/ /* This function will perform a binary-tree search for the specified tuple. */ /*****************************************************************************/ struct index_list_t *FindIndex(struct index_t *index, struct tuple_t *item) { struct index_elt_t *p; int x; if (index->type != STRING) // until other types implemented return NULL; p = index->root; while (p) { switch (index->type) { case STRING: x = strcmp(item->fields[index->fld_num]->u.sval, p->list->data->fields[index->fld_num]->u.sval); if (x==0) return p->list; else if (x < 0) p = p->left; else p = p->right; }; }; // while return NULL; }; /*****************************************************************************/ /* This function is the constructor for a database object. It assigns the */ /* database name, and zero's everything else. */ /*****************************************************************************/ database::database(char *name, char *schema_filename) { memset(tables,0,sizeof(tables)); dbname = CreateString(name); num_tables = 0; strictly_topdown = FALSE; Define(schema_filename); }; // database::database /*****************************************************************************/ /* This function is the destructor for a database object. It frees the */ /* memory for the database name only. */ /*****************************************************************************/ database::~database() { free(dbname); }; // database::~database UserTypes user_types; // declare a single global instance to hold all // UserDefinedType summary data UserTypes::UserTypes() { if (object_count) //static - initialized to zero in gendb.hpp - RJL 94/2/8 { printf("Cannot define multiple UserTypes objects, one is automatically declared for you\n"); exit(0); }; object_count = 1; num_types = 0; memset(types,0,sizeof(types)); }; // UserTypes; /*****************************************************************************/ /* This function will add a user-defined datatype to a database, so it can */ /* be later used in schema definitions. The user specifies routines to */ /* manipulate data fields of the new type. */ /*****************************************************************************/ UserDefinedType::UserDefinedType(char *tname, int tsize) { user_types.types[user_types.num_types] = this; user_types.num_types++; type_name = CreateString(tname); type_name_len = strlen(tname); data_size = tsize; }; // UserDefinedType void *UserDefinedType::Read(char *s) { printf("Error: Called virtual base function for user defined type %s\n",type_name); return(NULL); }; // UserDefinedType::Read (virtual function) char *UserDefinedType::Print(void *p) { printf("Error: Called virtual base function for user defined type %s\n",type_name); return(NULL); }; // UserDefinedType::Print (virtual function) void UserDefinedType::Free(void *p) { printf("Error: Called virtual base function for user defined type %s\n",type_name); return; }; // UserDefinedType::Free (virtual function) /*****************************************************************************/ /* This function will read a schema definition file and create all the tables*/ /* and fields specified in the file. A schema file is composed of 'blocks' */ /* that define each table in the schema. Comments can exist in the file, */ /* using "C" style comments. HOWEVER, the code really simply looks for the */ /* start-comment symbol. Everything on the line after that is ignored. */ /* thus, multi-line comments be complete, self-standing comments. */ /* The following is a sample schema of three tables. The students table has */ /* one row for each student in the school. The courses table has one row */ /* each course offered by the school. The schedule table has one row for */ /* each course each student is taking. The ERD for the schema is also */ /* shown : */ /* */ /* */ /* +----------+ +---------+ */ /* | students | | courses | */ /* +----------+ +---------+ */ /* |(1) | (1) */ /* +-------+ +-------+ */ /* | | */ /* (M) v v (M) */ /* +----------+ */ /* | schedule | */ /* +----------+ */ /* */ /* The schema definition file is shown below. Here, fields are either data */ /* fields or 'parent' fields. In a parent field, you specify the name of */ /* field (like any field), the name of the parent table, the name of the */ /* field that will be created inside the parent (so it can refer to this */ /* new child), and finally the cardinality of the relationship (as two */ /* numbers). The only valid value for the first number (parent cardinality) */ /* is 0 or 1. The child cardinality can be any positive number. Future */ /* versions of the system may enforce this as a limit, but is currently */ /* ingored. */ /* */ /* create table students ST */ /* { */ /* sname c(20) */ /* ss_no i */ /* cume f */ /* comment c(50) */ /* } */ /* */ /* create table courses CS */ /* { */ /* cname c(50) */ /* section i */ /* days c(5) */ /* time f */ /* comment c(50) */ /* } */ /* */ /* create table schedule SD */ /* { */ /* student parent(students,classes,1,50) */ /* course parent(courses,members,1,1000) */ /* } */ /* */ /* Following the table definitions can be any number of access "path" defin- */ /* itions (however, there is a maximum of 5 per table). These paths define */ /* a sequence of 'parent' fields to traverse upwards through the table to */ /* get to other tables (ie, any tables along the path, not simply the end). */ /* Later, when the program wishes to inherit a field from a parent table and */ /* the field is along an ambiguous path, the programmer can specify a path */ /* to follow to get there to remove the ambiguity. For example, in the schema*/ /* shown, both the students and courses table have a field called comment. */ /* If the code accesses the field comment from table schedule, the system */ /* does not know which field is actually being requested. In fact, it will */ /* use the first one found in its breadth-first search of fields up the */ /* tree (ie, classes.comment). To force it to find the comment for the */ /* course, the programmer could create a path with the line: */ /* */ /* create path test_path from schedule via course */ /* */ /* Then, the programmer would specify using path test_path when accessing */ /* field comment in table schedule. */ /* */ /*****************************************************************************/ void database::Define(char *fil_name) { FILE *fp; char line[FILELINE_LEN+1]; char word[FILELINE_LEN+1]; char tbl_name[FILELINE_LEN+1]; char tbl_abbrev[FILELINE_LEN+1]; char path_name[FILELINE_LEN+1]; char fld_name[FILELINE_LEN+1]; char fld_type[FILELINE_LEN+1]; char parent_tbl_name[FILELINE_LEN+1]; char parent_fld_name[FILELINE_LEN+1]; char parent_card[FILELINE_LEN+1]; char child_card[FILELINE_LEN+1]; char dummy_word[FILELINE_LEN+1]; int card_p,card_c; int tbl_num; int tbl_num2; int fld_num; int idx; int i,j; int lc; table *tbl_ptr; struct path_elt_t *path_head; enum { STATE_NULL, STATE_START, STATE_FIELD } state; DeclareFunc("Define"); Error.Reset(); if ((fp = fopen(fil_name,"r")) == NULL) { Error.AddError(1,ERR_FILE_NOT_FOUND,func,"n/a",fil_name); exit(0); }; state = STATE_NULL; lc = 0; ReadTextLine(fp,line,TRUE); lc++; while (!feof(fp)) { if (line[0] == '\0') /* skip blank lines */ { ReadTextLine(fp,line,TRUE); lc++; continue; }; idx = 0; ParseWord(line,word,&idx); if (word[0] == '\0') /* skip basically blank lines */ { ReadTextLine(fp,line,TRUE); lc++; continue; }; switch (state) { case STATE_NULL : /* expect "create" */ if (strcmp(word,"create") != 0) { printf("STATE NULL \n"); fclose(fp); Error.AddError(1,ERR_SCHEMA_EXPECT_CREATE,func,word,stringize(lc)); exit(0); }; ParseWord(line,word,&idx); if (strcmp(word,"table") == 0) /* create table statement */ { ParseWord(line,tbl_name,&idx); /* parse tbl_name */ ParseWord(line,tbl_abbrev,&idx); /* parse tbl_abbrev */ tbl_ptr = new table(this,tbl_name,tbl_abbrev); state = STATE_START; break; } else if (strcmp(word,"path") == 0) /* create path statement */ { ParseWord(line,path_name,&idx); ParseWord(line,dummy_word,&idx); if (strcmp(dummy_word,"from") != 0) { fclose(fp); Error.AddError(1,ERR_SCHEMA_EXPECT_FROM,func, dummy_word,stringize(lc)); exit(0); }; ParseWord(line,tbl_name,&idx); for (tbl_num=0; tbl_numtable_name,tbl_name) == 0) break; if (tbl_num == num_tables) { fclose(fp); Error.AddError(1,ERR_NO_SUCH_TABLE,func,tbl_name,stringize(lc)); exit(0); }; ParseWord(line,dummy_word,&idx); if (strcmp(dummy_word,"via") != 0) { fclose(fp); Error.AddError(1,ERR_SCHEMA_EXPECT_VIA,func, dummy_word,stringize(lc)); exit(0); }; tables[tbl_num]->paths[tables[tbl_num]->num_paths] = (struct path_t *) calloc(1,sizeof(struct path_t)); tables[tbl_num]->paths[tables[tbl_num]->num_paths]->path_name = CreateString(path_name); tables[tbl_num]->paths[tables[tbl_num]->num_paths]->head = NULL; tbl_num2 = tbl_num; path_head = NULL; while (1) { ParseWord(line,fld_name,&idx); if (fld_name[0] == '\0') break; if (tables[tbl_num2]->FindRel(fld_name,&fld_num) != TRUE) { fclose(fp); Error.AddError(1,ERR_FIELD_NOT_FOUND,func, tables[tbl_num2]->table_name,fld_name); exit(0); }; if (tables[tbl_num2]->fields[fld_num]->u.tfield.rel_type != PARENT) { fclose(fp); Error.AddError(1,ERR_NOT_A_PARENT_FIELD,func, tables[tbl_num2]->table_name,fld_name); exit(0); }; if (!path_head) { tables[tbl_num]->paths[tables[tbl_num]->num_paths]->head = (struct path_elt_t *) calloc(1,sizeof(struct path_elt_t)); tables[tbl_num]->paths[tables[tbl_num]->num_paths]->head->fld_num = fld_num; path_head = tables[tbl_num]->paths[tables[tbl_num]->num_paths]->head; } else { path_head->next = (struct path_elt_t *) calloc(1,sizeof(struct path_elt_t)); path_head->next->fld_num = fld_num; path_head = path_head->next; }; tbl_num2 = tables[tbl_num2]->fields[fld_num]->u.tfield.tbl->tbl_num; }; // while tables[tbl_num]->num_paths++; break; }; break; case STATE_START: /* expect "{" */ if (strcmp(word,"{") != 0) { fclose(fp); Error.AddError(1,ERR_SCHEMA_EXPECT_OPEN,func,word,stringize(lc)); exit(0); }; state = STATE_FIELD; break; case STATE_FIELD: /* expect "fldname type" or "}" */ if (strcmp(word,"}") == 0) { state = STATE_NULL; break; }; if (strcmp(word,"table") == 0) { fclose(fp); Error.AddError(1,ERR_SCHEMA_EXPECT_CLOSE,func,word,stringize(lc)); exit(0); }; strcpy(fld_name,word); ParseWord(line,fld_type,&idx); /* parse fld_type */ if (strncmp(fld_type,"parent(",7) == 0) { /* expect parent(parent_tbl_name,parent_fld_name, parent_card,child_card */ for (j=0,i=7; fld_type[i]!=','; i++,j++) parent_tbl_name[j] = fld_type[i]; parent_tbl_name[j] = '\0'; for (i++,j=0; fld_type[i]!=','; i++,j++) parent_fld_name[j] = fld_type[i]; parent_fld_name[j] = '\0'; for (i++,j=0; fld_type[i]!=','; i++,j++) parent_card[j] = fld_type[i]; parent_card[j] = '\0'; card_p = atoi(parent_card); for (i++,j=0; fld_type[i]!=')'; i++,j++) child_card[j] = fld_type[i]; child_card[j] = '\0'; card_c = atoi(child_card); for (i=0; itable_name,parent_tbl_name) == 0) break; if (i==num_tables) { fclose(fp); Error.AddError(1,ERR_SCHEMA_NOSUCHTABLE,func,word,stringize(lc)); exit(0); }; tbl_ptr->AddParent(tables[i],parent_fld_name,fld_name,card_p,card_c); } else /* normal data field */ { tbl_ptr->AddField(fld_name,fld_type); }; break; }; // switch ReadTextLine(fp,line,TRUE); lc++; }; // while fclose(fp); }; // database::Define /*****************************************************************************/ /* This function will print out all the rows of all the tables in the */ /* database. The output goes to the specified file. */ /*****************************************************************************/ int database::Print(FILE *fp) { int i; for (i=0; iPrint(fp); return TRUE; }; // database::Print /*****************************************************************************/ /* This function will dump all rows of all tables to the specified file, */ /* using the specified mode ("a" means append to bottom of existing file, */ /* and "w" means to write to a new file, or overwrite an existing file). */ /*****************************************************************************/ int database::Dump(char *fil_name, char *mode) { char tmp_mode[10]; FILE *fp; DeclareFunc("Dump"); Error.Reset(); if (mode == NULL) strcpy(tmp_mode,"w"); else strcpy(tmp_mode,mode); if ((fp = fopen(fil_name,tmp_mode)) == NULL) { Error.AddError(1,ERR_CANT_OPEN_OUTPUT,func,"n/a",fil_name); return FALSE; }; Print(fp); fclose(fp); }; // database::Dump /*****************************************************************************/ /* This function will print out schema definition data for the database. */ /*****************************************************************************/ int database::Help() { int i; for (i=0; iHelp(); return TRUE; }; // database::Help /*****************************************************************************/ /* This function is the constructor for a database table. */ /*****************************************************************************/ table::table(database *db_ptr, char *tbl_name, char *tbl_abbrev) { int i; memset(fields,0,sizeof(fields)); for (i=0; inum_tables; db->tables[tbl_num] = this; db->num_tables++; num_rows = 0; iter = (iterator *) calloc(1,sizeof(iterator)); num_paths = 0; memset(paths,0,sizeof(paths)); iter->tbl = this; iter->Reset(); }; // table::table /*****************************************************************************/ /* This function is the destructor for a database table. It frees the memory*/ /* held by each field in the table. */ /*****************************************************************************/ table::~table() { int i; struct path_elt_t *p,*q; free(table_name); for (i=0; ifield_name); free(fields[i]); }; for (i=0; ipath_name); p = paths[i]->head; while (p) { q=p; p=p->next; free(q); }; free(paths[i]); }; }; // table::~table /*****************************************************************************/ /* This function will locate the specified DATA field in a table. */ /*****************************************************************************/ int table::FindField(char *fld_name,int *fld_num) { int i; DeclareFunc("FindField"); db->Error.Reset(); *fld_num = -1; if ((fields[last_find_field]->type != TABLE_FIELD) && (strcmp(fld_name,fields[last_find_field]->field_name) == 0)) { *fld_num = last_find_field; return TRUE; } if (((last_find_field+1) < num_fields) && (fields[last_find_field+1]->type != TABLE_FIELD) && (strcmp(fld_name,fields[last_find_field+1]->field_name) == 0)) { last_find_field++; *fld_num = last_find_field; return TRUE; } for (i=0; itype != TABLE_FIELD) && (strcmp(fld_name,fields[i]->field_name) == 0)) { last_find_field = i; *fld_num = i; return TRUE; } last_find_field = 0; db->Error.AddError(0,ERR_FIELD_NOT_FOUND,func,table_name,fld_name); return FALSE; }; // table::FindField /*****************************************************************************/ /* This function will locate the specified TABLE field in a table. */ /*****************************************************************************/ int table::FindRel(char *fld_name,int *fld_num) { int i; DeclareFunc("FindRel"); db->Error.Reset(); *fld_num = -1; for (i=0; itype == TABLE_FIELD) && (strcmp(fld_name,fields[i]->field_name) == 0)) { *fld_num = i; return TRUE; } db->Error.AddError(0,ERR_RELATION_NOT_FOUND,func,table_name,fld_name); return FALSE; }; // table::FindRel /*****************************************************************************/ /* This function will add a new DATA field of the specified type to a table. */ /*****************************************************************************/ int table::AddField(char *fld_name, char *data_type, int indexed) { int i; DeclareFunc("AddField"); db->Error.Reset(); fields[num_fields] = (struct field_t *) calloc(1,sizeof(struct field_t)); fields[num_fields]->field_name = CreateString(fld_name); fields[num_fields]->type = DATA_FIELD; fields[num_fields]->u.dfield.index = NULL; if (strcmp(data_type,"i") == 0) { fields[num_fields]->u.dfield.data_type = INTEGER; fields[num_fields]->u.dfield.data_size = sizeof(int); } else if (strcmp(data_type,"f") == 0) { fields[num_fields]->u.dfield.data_type = FLOAT; fields[num_fields]->u.dfield.data_size = sizeof(float); } else if (strncmp(data_type,"c(",2) ==0) { data_type[strlen(data_type)-1] = '\0'; fields[num_fields]->u.dfield.data_type = STRING; fields[num_fields]->u.dfield.data_size = atoi(data_type+2); } else { for (i=0; itype_name,data_type, user_types.types[i]->type_name_len) == 0) break; if (i == user_types.num_types) { free(fields[num_fields]->field_name); free(fields[num_fields]); db->Error.AddError(1,ERR_UNKNOWN_TYPE,func,table_name,data_type); return FALSE; }; fields[num_fields]->u.dfield.data_type = USER; fields[num_fields]->u.dfield.data_size = user_types.types[i]->data_size; fields[num_fields]->u.dfield.user_routines = user_types.types[i]; }; if (indexed) { fields[num_fields]->u.dfield.index = (struct index_t *) calloc(1,sizeof(struct index_t)); fields[num_fields]->u.dfield.index->db = db; fields[num_fields]->u.dfield.index->type = fields[num_fields]->u.dfield.data_type; fields[num_fields]->u.dfield.index->fld_num = num_fields; fields[num_fields]->u.dfield.index->root = NULL; }; last_data_field = num_fields; num_fields++; return TRUE; }; // table::AddField /*****************************************************************************/ /* This function will form a parent-child relationship between two tables. */ /* This adds a new TABLE field to each table, one of type PARENT and one of */ /* type CHILD. Pointers are set up so the two can find each other easily. */ /*****************************************************************************/ int table::AddParent(table *parent, char *parent_fld_name, char *child_fld_name, int parent_card, int child_card) { int fld_num2; DeclareFunc("AddParent"); db->Error.Reset(); fld_num2 = parent->num_fields; if ((parent_card < 0) || (parent_card > 1)) { db->Error.AddError(1,ERR_PARENT_CARD_1_OR_0,func,table_name,child_fld_name); return FALSE; }; if (child_card < 1) { db->Error.AddError(1,ERR_CHILD_CARD_NOT_POSITIVE,func,table_name,child_fld_name); return FALSE; }; fields[num_fields] = (struct field_t *) calloc(1,sizeof(struct field_t)); fields[num_fields]->field_name = CreateString(child_fld_name); fields[num_fields]->type = TABLE_FIELD; fields[num_fields]->u.tfield.tbl = parent; fields[num_fields]->u.tfield.fld_num = fld_num2; fields[num_fields]->u.tfield.rel_type = PARENT; fields[num_fields]->u.tfield.rel_card = child_card; parent->fields[fld_num2] = (struct field_t *) calloc(1,sizeof(struct field_t)); parent->fields[fld_num2]->field_name = CreateString(parent_fld_name); parent->fields[fld_num2]->type = TABLE_FIELD; parent->fields[fld_num2]->u.tfield.tbl = this; parent->fields[fld_num2]->u.tfield.fld_num = num_fields; parent->fields[fld_num2]->u.tfield.rel_type = CHILD; parent->fields[fld_num2]->u.tfield.rel_card = parent_card; num_fields++; parent->num_fields++; return 1; }; // table::AddParent /*****************************************************************************/ /* This function will print out schema information for a table. */ /*****************************************************************************/ void table::Help() { int i; printf("Help on table :%s\n",table_name); printf("table_abbrev :%s\n",table_abbrev); printf("tbl_num :%d\n",tbl_num); printf("num_fields :%d\n",num_fields); printf("last_data_field :%d\n",last_data_field); for (i=0; ifield_name); switch (fields[i]->type) { case DATA_FIELD: printf("DATA_FIELD, type="); switch (fields[i]->u.dfield.data_type) { case INTEGER: printf("i\n"); break; case FLOAT : printf("f\n"); break; case STRING : printf("c(%d)\n", fields[i]->u.dfield.data_size); break; case USER : printf("%s\n", fields[i]->u.dfield.user_routines->type_name); }; break; case TABLE_FIELD: printf("TABLE_FIELD:\n"); switch (fields[i]->u.tfield.rel_type) { case PARENT: printf(" type = PARENT, up to table %s (tblnum %d)\n", fields[i]->u.tfield.tbl->table_name, fields[i]->u.tfield.tbl->tbl_num); printf(" parents fld_num down to this table is %d\n", fields[i]->u.tfield.fld_num); printf(" rel_card (at child side) is :%d\n", fields[i]->u.tfield.rel_card); break; case CHILD: printf(" type = CHILD, down to table %s (tblnum %d)\n", fields[i]->u.tfield.tbl->table_name, fields[i]->u.tfield.tbl->tbl_num); printf(" childs fld_num up to this table is %d\n", fields[i]->u.tfield.fld_num); printf(" rel_card (at parent side) is :%d\n", fields[i]->u.tfield.rel_card); break; }; break; }; }; }; // table::Help /*****************************************************************************/ /* This function will print out all rows of a table to the specified file. */ /*****************************************************************************/ int table::Print(FILE *fp) { iterator iter(this); table_loop(iter) iter.Print(fp); return TRUE; }; // table::Print /*****************************************************************************/ /* This function is the constructor for a table iterator. Iterators are */ /* used for almost all database operations. In addition, they maintain a */ /* looping context useful for traversing tables. */ /*****************************************************************************/ iterator::iterator(table *t) { type = TABLE_ITER; tbl = t; curr = tbl->first; fld_num = -1; }; // iterator::iterator(tbl_ptr) iterator::iterator(database *db, char *tbl_name) { int i; DeclareFunc("iterator"); db->Error.Reset(); for (i=0; inum_tables; i++) if (strcmp(tbl_name,db->tables[i]->table_name) == 0) break; if (i==db->num_tables) { db->Error.AddError(1,ERR_NO_SUCH_TABLE,func,tbl_name,""); exit(0); }; type = TABLE_ITER; tbl = db->tables[i]; curr = tbl->first; fld_num = -1; }; // iterator::iterator(tbl_name) int iterator::Reset() { type = TABLE_ITER; curr = tbl->first; fld_num = -1; return (curr != NULL); }; // iterator::Reset int iterator::Reset(iterator *parent_iter, char *fld_name) { int parent_fld_num; DeclareFunc("Reset"); tbl->db->Error.Reset(); type = CHILD_ITER; fld_num = -1; curr = NULL; if (parent_iter->curr == NULL) return FALSE; if (!parent_iter->tbl->FindRel(fld_name,&parent_fld_num)) { tbl->db->Error.AddError(1,ERR_NOT_A_PARENT_FIELD, func,tbl->table_name,fld_name); return FALSE; }; if (parent_iter->tbl->fields[parent_fld_num]->u.tfield.rel_type != CHILD) { tbl->db->Error.AddError(1,ERR_CHILD_NOT_A_PARENT, func,tbl->table_name,fld_name); return FALSE; }; if (parent_iter->tbl->fields[parent_fld_num]->u.tfield.tbl != tbl) { tbl->db->Error.AddError(1,ERR_PARENT_NOT_CONNECTED, func,tbl->table_name,fld_name); return FALSE; }; fld_num = parent_fld_num; curr = parent_iter->curr->fcptr[fld_num]; return (curr != NULL); }; // iterator::Reset int iterator::Next() { if (curr == NULL) return FALSE; if (type == TABLE_ITER) curr = curr->next; else curr = curr->next_child[fld_num]; return TRUE; }; // iterator::Next int iterator::Done() { return (curr == NULL); }; // iterator::Done int iterator::FindPkey(char *pkey) { int x; DeclareFunc("FindPkey"); tbl->db->Error.Reset(); if (!IsValidPkey(pkey)) { tbl->db->Error.AddError(0,ERR_BAD_PKEY,func,tbl->table_name,pkey); return FALSE; } if (strncmp(pkey,tbl->table_abbrev,2) != 0) { tbl->db->Error.AddError(0,ERR_PKEY_ABBREV_MISMATCH, func,tbl->table_name,pkey); return FALSE; }; for (Reset(); !Done(); Next()) if ((x=strcmp(curr->pkey,pkey)) >= 0) break; if ((curr == NULL) || (x > 0)) { tbl->db->Error.AddError(0,ERR_PKEY_NOT_FOUND, func,tbl->table_name,pkey); curr = NULL; return FALSE; } else return TRUE; }; // iterator::FindPkey int iterator::Set(char *fld_name, char *value) { int fld_num; DeclareFunc("Set(string)"); tbl->db->Error.Reset(); if (Done()) return FALSE; if (!tbl->FindField(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func,tbl->table_name,""); return FALSE; }; if (tbl->fields[fld_num]->u.dfield.data_type != STRING) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,""); return FALSE; }; if (curr->fields[fld_num]->isnull) // there was no previous value { curr->fields[fld_num]->u.sval = CreateString(value); if (tbl->fields[fld_num]->u.dfield.index) // must add to index AddIndex(tbl->fields[fld_num]->u.dfield.index,curr); } else // there was a previous value { if (tbl->fields[fld_num]->u.dfield.index) // must update index { DelIndex(tbl->fields[fld_num]->u.dfield.index,curr); free(curr->fields[fld_num]->u.sval); curr->fields[fld_num]->u.sval = CreateString(value); AddIndex(tbl->fields[fld_num]->u.dfield.index,curr); } else // no index { free(curr->fields[fld_num]->u.sval); curr->fields[fld_num]->u.sval = CreateString(value); }; }; curr->fields[fld_num]->isnull = FALSE; return TRUE; }; // iterator::Set(char *) int iterator::Set(char *fld_name, int value) { int fld_num; DeclareFunc("Set(int)"); tbl->db->Error.Reset(); if (Done()) return FALSE; if (!tbl->FindField(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func,tbl->table_name,""); return FALSE; }; if (tbl->fields[fld_num]->u.dfield.data_type != INTEGER) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,""); return FALSE; }; curr->fields[fld_num]->u.ival = value; return TRUE; }; // iterator::Set(int) int iterator::Set(char *fld_name, float value) { int fld_num; DeclareFunc("Set(float)"); tbl->db->Error.Reset(); if (Done()) return FALSE; if (!tbl->FindField(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func,tbl->table_name,""); return FALSE; }; if (tbl->fields[fld_num]->u.dfield.data_type != FLOAT) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,""); return FALSE; }; curr->fields[fld_num]->u.fval = value; return TRUE; }; // iterator::Set(float) int iterator::Set(char *fld_name, void *value) { int fld_num; DeclareFunc("Set(user)"); tbl->db->Error.Reset(); if (Done()) return FALSE; if (!tbl->FindField(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func,tbl->table_name,""); return FALSE; }; if (tbl->fields[fld_num]->u.dfield.data_type != USER) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,""); return FALSE; }; if (curr->fields[fld_num]->u.pval) tbl->fields[fld_num]->u.dfield.user_routines-> Free(curr->fields[fld_num]->u.pval); curr->fields[fld_num]->u.pval = value; return TRUE; }; // iterator::Set(void *) int iterator::InheritField(char *fld_name,table *tbl,struct tuple_t *curr, int path_num, table **tbl2, struct tuple_t **curr2, int *fld_num) { int i; table *temp_tbl; struct tuple_t *temp_curr; struct path_elt_t *path_head; if ((curr == NULL) || (tbl == NULL)) return FALSE; if (path_num >= 0) { path_head = tbl->paths[path_num]->head; temp_tbl = tbl; temp_curr = curr; while(1) { for (i=0; inum_fields; i++) if ((temp_tbl->fields[i]->type == DATA_FIELD) && (strcmp(fld_name,temp_tbl->fields[i]->field_name) == 0)) { *tbl2 = temp_tbl; *curr2 = temp_curr; *fld_num = i; return TRUE; }; if (!path_head) break; temp_tbl = temp_tbl->fields[path_head->fld_num]->u.tfield.tbl; temp_curr = temp_curr->pptr[path_head->fld_num]; if (!temp_curr) break; path_head = path_head->next; }; // while return FALSE; }; // if path specified for (i=0; inum_fields; i++) if ((tbl->fields[i]->type == DATA_FIELD) && (strcmp(fld_name,tbl->fields[i]->field_name) == 0)) { *curr2 = curr; *tbl2 = tbl; *fld_num = i; return TRUE; }; for (i=0; inum_fields; i++) if ((tbl->fields[i]->type == TABLE_FIELD) && (tbl->fields[i]->u.tfield.rel_type == PARENT) && (curr->pptr[i] != NULL)) if (InheritField(fld_name,tbl->fields[i]->u.tfield.tbl, curr->pptr[i],-1,tbl2,curr2,fld_num)) return TRUE; return FALSE; }; // iterator::InheritField /*****************************************************************************/ /* This function returns the pkey of the current row in a table. */ /*****************************************************************************/ char *iterator::PkeyVal() { return (Done() ? NULL : curr->pkey); }; // iterator::PkeyVal char *iterator::ParentVal(char *fld_name) { int fld_num; DeclareFunc("ParentVal"); tbl->db->Error.Reset(); if (Done()) return NULL; if (!tbl->FindRel(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_RELATION_NOT_FOUND,func, tbl->table_name,fld_name); return NULL; }; if (tbl->fields[fld_num]->u.tfield.rel_type != PARENT) { tbl->db->Error.AddError(1,ERR_NOT_A_PARENT_FIELD, func,tbl->table_name,fld_name); return NULL; }; return(curr->fields[fld_num]->u.sval); }; // iterator::ParentVal char *iterator::StrVal(char *fld_name, char *path_name) { int fld_num; table *tbl2; int path_num; struct tuple_t *curr2; DeclareFunc("StrVal"); tbl->db->Error.Reset(); if (Done()) return NULL; path_num = -1; if (path_name) { for (path_num = 0; path_num < tbl->num_paths; path_num++) if (strcmp(tbl->paths[path_num]->path_name,path_name) == 0) break; if (path_num == tbl->num_paths) { tbl->db->Error.AddError(1,ERR_UNKNOWN_PATH,func, tbl->table_name,path_name); return NULL; }; }; if (!InheritField(fld_name,tbl,curr,path_num,&tbl2,&curr2,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func, tbl->table_name,fld_name); return NULL; }; if (tbl2->fields[fld_num]->u.dfield.data_type != STRING) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func, tbl->table_name,fld_name); return NULL; }; return(curr2->fields[fld_num]->u.sval); }; // iterator::StrVal int iterator::IntVal(char *fld_name, char *path_name) { int fld_num; table *tbl2; int path_num; struct tuple_t *curr2; DeclareFunc("IntVal"); tbl->db->Error.Reset(); if (Done()) return NULL; path_num = -1; if (path_name) { for (path_num = 0; path_num < tbl->num_paths; path_num++) if (strcmp(tbl->paths[path_num]->path_name,path_name) == 0) break; if (path_num == tbl->num_paths) { tbl->db->Error.AddError(1,ERR_UNKNOWN_PATH,func, tbl->table_name,path_name); return NULL; }; }; if (!InheritField(fld_name,tbl,curr,path_num,&tbl2,&curr2,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func, tbl->table_name,fld_name); return NULL; }; if (tbl2->fields[fld_num]->u.dfield.data_type != INTEGER) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,fld_name); return NULL; }; return(curr2->fields[fld_num]->u.ival); }; // iterator::IntVal float iterator::FloatVal(char *fld_name, char *path_name) { int fld_num; table *tbl2; int path_num; struct tuple_t *curr2; DeclareFunc("FloatVal"); tbl->db->Error.Reset(); if (Done()) return NULL; path_num = -1; if (path_name) { for (path_num = 0; path_num < tbl->num_paths; path_num++) if (strcmp(tbl->paths[path_num]->path_name,path_name) == 0) break; if (path_num == tbl->num_paths) { tbl->db->Error.AddError(1,ERR_UNKNOWN_PATH,func, tbl->table_name,path_name); return NULL; }; }; if (!InheritField(fld_name,tbl,curr,path_num,&tbl2,&curr2,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func, tbl->table_name,fld_name); return NULL; }; if (tbl2->fields[fld_num]->u.dfield.data_type != FLOAT) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func,tbl->table_name,fld_name); return NULL; }; return(curr2->fields[fld_num]->u.fval); }; // iterator::FloatVal void *iterator::UserVal(char *fld_name, char *path_name) { int fld_num; table *tbl2; int path_num; struct tuple_t *curr2; DeclareFunc("UserVal"); tbl->db->Error.Reset(); if (Done()) return NULL; path_num = -1; if (path_name) { for (path_num = 0; path_num < tbl->num_paths; path_num++) if (strcmp(tbl->paths[path_num]->path_name,path_name) == 0) break; if (path_num == tbl->num_paths) { tbl->db->Error.AddError(1,ERR_UNKNOWN_PATH,func, tbl->table_name,path_name); return NULL; }; }; if (!InheritField(fld_name,tbl,curr,path_num,&tbl2,&curr2,&fld_num)) { tbl->db->Error.AddError(1,ERR_FIELD_NOT_FOUND,func, tbl->table_name,fld_name); return NULL; }; if (tbl2->fields[fld_num]->u.dfield.data_type != USER) { tbl->db->Error.AddError(1,ERR_BAD_TYPE,func, tbl->table_name,fld_name); return NULL; }; return(curr2->fields[fld_num]->u.pval); }; // iterator::UserVal /*****************************************************************************/ /* Given a foriegn key and the field (in the child) that it goes in, fill it */ /* in and find the parent. If found, link it in. It is placed at the END */ /* of the parents child-chain. If the parent is not found, it is not fatal. */ /* However, this means that it will have to be linked in from above if and */ /* when the parent is ever appended. It would be inefficient if, every time */ /* a parent is appended, all possible children need to be looked for. To */ /* help optimize this, each table keeps a list of children tables that are */ /* waiting for a parent row to be appended. The value stored in the list is */ /* a count of children waiting. For example, in the AA table, suppose BB is */ /* a child, and its row was appended first. The BB records SetParent call */ /* increments AA's waiting_children[BB]. Whenever a AA row is appended, it */ /* see's the BB counter is non-zero, so it loops over the BB table, finding */ /* rows with unassigned parent pointers to this parent. As they are found, */ /* the counter is decremented. */ /*****************************************************************************/ int iterator::SetParent(char *fld_name, char *value) { int fld_num2; struct tuple_t *p; table *tbl_ptr; DeclareFunc("SetParent"); tbl->db->Error.Reset(); if (Done()) return FALSE; /*********************************************************/ /* Verify that the specified field is a relation field...*/ /*********************************************************/ if (!tbl->FindRel(fld_name,&fld_num)) { tbl->db->Error.AddError(1,ERR_NOT_A_PARENT_FIELD,func, tbl->table_name,fld_name); return FALSE; }; /*********************************************************/ /* and that it is a parent field... */ /*********************************************************/ if (tbl->fields[fld_num]->u.tfield.rel_type != PARENT) { tbl->db->Error.AddError(1,ERR_PARENT_NOT_A_CHILD,func, tbl->table_name,fld_name); return FALSE; }; /*********************************************************/ /* and that the pkey is a valid one... */ /*********************************************************/ if (!IsValidPkey(value)) { tbl->db->Error.AddError(1,ERR_BAD_PKEY,func,tbl->table_name,value); return FALSE; }; /*********************************************************/ /* and that the pkey being added goes with that parent. */ /*********************************************************/ tbl_ptr = tbl->fields[fld_num]->u.tfield.tbl; if (strncmp(tbl_ptr->table_abbrev,value,2) != 0) { tbl->db->Error.AddError(1,ERR_PKEY_ABBREV_MISMATCH,func, tbl->table_name,value); return FALSE; }; /*********************************************************/ /* Now that the validations are done, fill in the pkey. */ /*********************************************************/ curr->fields[fld_num]->u.sval = CreateString(value); fld_num2 = tbl->fields[fld_num]->u.tfield.fld_num; iterator parent_iter(tbl_ptr); /*********************************************************/ /* Now try to find the parent record. */ /* If it cant be found, maybe it just hasn't been loaded */ /* yet, so increment the parents waiting_children count. */ /*********************************************************/ if (!parent_iter.FindPkey(value)) if (tbl->db->strictly_topdown) { tbl->db->Error.AddError(1,ERR_PKEY_NOT_FOUND,func, tbl->table_name,value); return FALSE; } else { parent_iter.tbl->waiting_children[tbl->tbl_num]++; return TRUE; }; if (++parent_iter.curr->num_children[fld_num2] > tbl->fields[fld_num]->u.tfield.rel_card) { tbl->db->Error.AddError(1,ERR_TOO_MANY_CHILDREN,func, tbl->table_name,value); return FALSE; }; /*********************************************************/ /* Since the parent was found, link it in. It is placed */ /* in the parents child-chain in pkey SORTED order. */ /*********************************************************/ curr->pptr[fld_num] = parent_iter.curr; curr->next_child[fld_num2] = curr->prev_child[fld_num2] = NULL; if (parent_iter.curr->fcptr[fld_num2] == NULL) // only child parent_iter.curr->fcptr[fld_num2] = parent_iter.curr->lcptr[fld_num2] = curr; else if (strcmp(curr->pkey,parent_iter.curr->lcptr[fld_num2]->pkey) > 0) {// last child curr->prev_child[fld_num2] = parent_iter.curr->lcptr[fld_num2]; curr->prev_child[fld_num2]->next_child[fld_num2] = curr; parent_iter.curr->lcptr[fld_num2] = curr; } else if (strcmp(curr->pkey,parent_iter.curr->fcptr[fld_num2]->pkey) < 0) {// first child curr->next_child[fld_num2] = parent_iter.curr->fcptr[fld_num2]; curr->next_child[fld_num2]->prev_child[fld_num2] = curr; parent_iter.curr->fcptr[fld_num2] = curr; } else // somewhere in the middle of the list { for (p=parent_iter.curr->fcptr[fld_num2]; strcmp(p->pkey,curr->pkey) < 0; p=p->next_child[fld_num2]); // link in just before p-> curr->prev_child[fld_num2] = p->prev_child[fld_num2]; curr->next_child[fld_num2] = p; p->prev_child[fld_num2]->next_child[fld_num2] = curr; p->prev_child[fld_num2] = curr; }; return TRUE; }; // iterator::SetParent /*****************************************************************************/ /* Append a new record with the specified pkey to a table. Any waiting_ */ /* children will be linked in, if found. If no pkey is specified, the next */ /* sequential one is generated (based off the last record in the list). */ /*****************************************************************************/ int iterator::Append(char *pkey) { struct tuple_t *tuple, *tuple2, *last, *p; int i,j,x,tbl_num2; table *tbl2; char temp_pkey[9]; int last_rec = FALSE; DeclareFunc("Append"); tbl->db->Error.Reset(); if (pkey == NULL) { last_rec = TRUE; GenNextPkey(temp_pkey); } else { if (!IsValidPkey(pkey)) { tbl->db->Error.AddError(1,ERR_BAD_PKEY,func,tbl->table_name,pkey); return FALSE; }; if (strncmp(pkey,tbl->table_abbrev,2) != 0) { tbl->db->Error.AddError(1,ERR_PKEY_ABBREV_MISMATCH,func, tbl->table_name,pkey); return FALSE; }; strcpy(temp_pkey,pkey); }; /****************************************************/ /* Allocate memory for the new record, fill in the */ /* pkey, and allocate memory for each of the fields */ /* in the new tuple. */ /****************************************************/ tuple = (struct tuple_t *) calloc(1,sizeof(struct tuple_t)); curr = tuple; strcpy(tuple->pkey,temp_pkey); for (i=0; inum_fields; i++) { tuple->fields[i] = (struct data_elt_t *) calloc(1,sizeof(struct data_elt_t)); tuple->fields[i]->isnull = TRUE; } tbl->num_rows++; /****************************************************/ /* Now, add the new record to the tables list of */ /* records. It is placed in the list in pkey */ /* SORTED order. */ /****************************************************/ if (tbl->first == NULL) // add to empty list tbl->first = tbl->last = tuple; else if (last_rec || (strcmp(temp_pkey,tbl->last->pkey) > 0)) {// add to end of list tbl->last->next = tuple; tuple->prev = tbl->last; tbl->last = tuple; } else if (strcmp(temp_pkey,tbl->first->pkey) < 0) // add to beginning of list { tbl->first->prev = tuple; tuple->next = tbl->first; tbl->first = tuple; } else { // add to middle of list for (p=tbl->first; (x=strcmp(temp_pkey,p->pkey)) > 0; p=p->next); if (x==0) { tbl->db->Error.AddError(1,ERR_DUPLICATE_PKEY,func, tbl->table_name,temp_pkey); for (i=0; inum_fields; i++) free(tuple->fields[i]); free(tuple); curr = NULL; tbl->num_rows--; return FALSE; }; tuple->next = p; // Node links in before p-> tuple->prev = p->prev; p->prev->next = tuple; p->prev = tuple; }; /****************************************************/ /* If the database is strictly top-down, then there */ /* wont be any waiting children. */ /****************************************************/ if (tbl->db->strictly_topdown) return TRUE; /****************************************************/ /* Finally, if there are any waiting children, */ /* try to find them, and link them in. */ /****************************************************/ for (i=0; inum_fields; i++) if ((tbl->fields[i]->type == TABLE_FIELD) && (tbl->fields[i]->u.tfield.rel_type == CHILD) && (tbl->waiting_children[tbl->fields[i]->u.tfield.tbl->tbl_num] > 0)) { /************************************************/ /* We have a child table that is waiting for us.*/ /* For each parent field in that child that */ /* points to this table, traverse the children */ /* seeing if that parent field's pkey is ours. */ /* If so, link it in and decrement the waiting */ /* count. We can give up entirely if the count */ /* hits zero. */ /************************************************/ tbl2 = tbl->fields[i]->u.tfield.tbl; tbl_num2 = tbl2->tbl_num; for (j=0; jnum_fields; j++) if ((tbl2->fields[j]->type == TABLE_FIELD) && (tbl2->fields[j]->u.tfield.rel_type == PARENT) && (tbl2->fields[j]->u.tfield.tbl == tbl)) for (tuple2 = tbl2->first; tuple2 != NULL; tuple2=tuple2->next) if (strcmp(tuple2->fields[j]->u.sval,temp_pkey) == 0) { tuple2->pptr[j] = tuple; tuple2->next_child[i] = tuple2->prev_child[i] = NULL; if (tuple->fcptr[i] == NULL) tuple->fcptr[i] = tuple->lcptr[i] = tuple2; else { last = tuple->lcptr[i]; last->next_child[i] = tuple2; tuple2->prev_child[i] = last; tuple->lcptr[i] = tuple2; }; if (++tuple->num_children[i] > tbl2->fields[j]->u.tfield.rel_card) { tbl->db->Error.AddError(1,ERR_TOO_MANY_CHILDREN,func, tbl->table_name,temp_pkey); return FALSE; }; if (--tbl->waiting_children[tbl_num2] == 0) return TRUE; }; // for,if,for,if (a waiting child record!) }; return TRUE; }; // iterator::Append int iterator::Delete() { //bug fix at end-4 to avoid skipping every other row- RJL 92/9/25. int i; int f2; table *tbl2; struct tuple_t *curr2; DeclareFunc("Delete"); tbl->db->Error.Reset(); if (Done()) return FALSE; for (i=0; inum_fields; i++) switch(tbl->fields[i]->type) { case DATA_FIELD: if (curr->fields[i]->isnull) // nothing there to free-up break; if (tbl->fields[i]->u.dfield.index) // delete from index DelIndex(tbl->fields[i]->u.dfield.index,curr); switch (tbl->fields[i]->u.dfield.data_type) { case STRING: free(curr->fields[i]->u.sval); break; case USER: tbl->fields[i]->u.dfield.user_routines-> Free(curr->fields[i]->u.pval); break; }; case TABLE_FIELD: switch (tbl->fields[i]->u.tfield.rel_type) { case CHILD: curr2 = curr->fcptr[i]; f2 = tbl->fields[i]->u.tfield.fld_num; while(curr2) { curr2->pptr[f2] = NULL; curr2 = curr2->next_child[i]; }; break; case PARENT: if (curr->pptr[i]) { f2 = tbl->fields[i]->u.tfield.fld_num; curr2 = curr->pptr[i]; if ((curr2->fcptr[f2] == curr) && // only child (curr2->lcptr[f2] == curr)) curr2->fcptr[f2] = curr2->lcptr[f2] = NULL; else if (curr2->fcptr[f2] == curr) // first child { curr->next_child[f2]->prev_child[f2] = NULL; curr2->fcptr[f2] = curr->next_child[f2]; } else if (curr2->lcptr[f2] == curr) // last child { curr->prev_child[f2]->next_child[f2] = NULL; curr2->lcptr[f2] = curr->prev_child[f2]; } else // middle of list { curr->prev_child[f2]->next_child[f2] = curr->next_child[f2]; curr->next_child[f2]->prev_child[f2] = curr->prev_child[f2]; }; } else // parent wasnt loaded, decrement its waiting_children[] tbl->fields[i]->u.tfield.tbl->waiting_children[tbl->tbl_num]--; }; }; if ((curr == tbl->first) && (curr == tbl->last)) // only row tbl->first = tbl->last = NULL; else if (curr == tbl->first) // first row { tbl->first = curr->next; tbl->first->prev = NULL; } else if (curr == tbl->last) // last row { tbl->last = curr->prev; tbl->last->next = NULL; } else // middle of list { curr->prev->next = curr->next; curr->next->prev = curr->prev; }; curr2 = curr; // Next(); <--bug fix 92/9/25 to avoid skipping every other row. RJL free(curr2); tbl->num_rows--; return TRUE; }; // iterator::Delete int iterator::Print(FILE *fp2) { int i; struct tuple_t *tmp; FILE *fp; if (Done()) return FALSE; if (fp2 == NULL) fp = stdout; else fp = fp2; fprintf(fp,"%s",curr->pkey); for (i=0; inum_fields; i++) switch (tbl->fields[i]->type) { case TABLE_FIELD: if (tbl->fields[i]->u.tfield.rel_type == PARENT) { if (curr->pptr[i] != NULL) fprintf(fp," %s",curr->pptr[i]->pkey); else // parent record not present fprintf(fp," %s",curr->fields[i]->u.sval); } else { #if 0 fprintf(fp,"\n children:\n"); tmp = curr->fcptr[i]; while (tmp != NULL) { fprintf(fp," <%s>\n",tmp->pkey); tmp = tmp->next_child[i]; }; #endif }; break; case DATA_FIELD: switch (tbl->fields[i]->u.dfield.data_type) { case INTEGER: fprintf(fp," %d",curr->fields[i]->u.ival); break; case FLOAT: fprintf(fp," %f",curr->fields[i]->u.fval); break; case STRING: fprintf(fp," %s",curr->fields[i]->u.sval); break; case USER: fprintf(fp," %s",tbl->fields[i]->u.dfield.user_routines-> Print(curr->fields[i]->u.pval)); break; }; // switch data_type }; // switch table_type fprintf(fp,"\n"); return TRUE; }; // iterator::Print int iterator::AddRow(char *line) { int idx; int i; char str[FILELINE_LEN+1]; char word[FILELINE_LEN+1]; DeclareFunc("AddRow"); tbl->db->Error.Reset(); strcpy(str,line); idx = 0; ParseWord(str,word,&idx); Append(word); for (i=0; inum_fields; i++) { switch (tbl->fields[i]->type) { case TABLE_FIELD: if (tbl->fields[i]->u.tfield.rel_type == PARENT) { ParseWord(str,word,&idx); SetParent(tbl->fields[i]->field_name,word); }; break; case DATA_FIELD: if (i == tbl->last_data_field) strcpy(word,str+idx); else ParseWord(str,word,&idx); switch (tbl->fields[i]->u.dfield.data_type) { case INTEGER: Set(tbl->fields[i]->field_name,(int) atoi(word)); break; case FLOAT: Set(tbl->fields[i]->field_name,(float) atof(word)); break; case STRING: Set(tbl->fields[i]->field_name,word); break; case USER: Set(tbl->fields[i]->field_name, tbl->fields[i]->u.dfield.user_routines->Read(word)); break; }; break; }; }; // for i return TRUE; }; // iterator::AddRow /*****************************************************************************/ /* Generate the next sequential pkey for use with a table. It is simply the */ /* numeric increment of the integer portion of the pkey (ie, all but the */ /* table abbreviation). */ /*****************************************************************************/ void iterator::GenNextPkey(char *pkey) { int x; if (tbl->last == NULL) x = 0; else x = atoi(&tbl->last->pkey[2]); sprintf(pkey,"%s%06d",tbl->table_abbrev,x+1); }; // GenNextPkey int database::Load(char *fil_name) { FILE *fp; char line[FILELINE_LEN+1]; char word[FILELINE_LEN+1]; int idx; int i; DeclareFunc("Load"); Error.Reset(); if ((fp = fopen(fil_name,"r")) == NULL) { Error.AddError(1,ERR_FILE_NOT_FOUND,func,"n/a",fil_name); return FALSE; }; ReadTextLine(fp,line,FALSE); i = -1; while (!feof(fp)) { idx = 0; ParseWord(line,word,&idx); if (word[0] == '\0') { ReadTextLine(fp,line,FALSE); continue; }; word[2] = '\0'; /**************************************************************************/ /* The first 'if' in the following is an optimization. If there was a */ /* previous table_abbrev looked up, and it was the one we need, dont */ /* bother with the for loop search. This optimization will usually save */ /* lots of time since most data files are grouped by table. */ /**************************************************************************/ if ((i<0) || (strncmp(word,tables[i]->table_abbrev,2) != 0)) for (i=0; itable_abbrev,2) == 0) break; if (i == num_tables) { fclose(fp); Error.AddError(1,ERR_NO_SUCH_TABLE,func,word,line); return FALSE; }; tables[i]->iter->AddRow(line); ReadTextLine(fp,line,FALSE); }; // while fclose(fp); return TRUE; }; // database::Load dbinfo::dbinfo(database *db_ptr) { db = db_ptr; tbl_num = 0; }; // dbinfo::dbinfo void dbinfo::Reset() { tbl_num = 0; }; // dbinfo::Reset void dbinfo::Next() { if (tbl_num < db->num_tables) tbl_num++; }; // dbinfo::Next int dbinfo::Done() { return (tbl_num >= db->num_tables); }; // dbinfo::Done int dbinfo::table_num() { return (Done() ? -1 : tbl_num); }; // dbinfo::table_num char *dbinfo::table_name() { return (Done() ? NULL : db->tables[tbl_num]->table_name); }; // dbinfo::table_name char *dbinfo::table_abbrev() { return (Done() ? NULL : db->tables[tbl_num]->table_abbrev); }; // dbinfo::table_abbrev int dbinfo::num_rows() { return (Done() ? -1 : db->tables[tbl_num]->num_rows); }; // dbinfo::num_rows tableinfo::tableinfo(database *db_ptr) { db = db_ptr; tbl_num = 0; fld_num = 0; }; // tableinfo::tableinfo void tableinfo::Reset(int table_num) { if (table_num < db->num_tables) { tbl_num = table_num; fld_num = 0; } else { tbl_num = 0; fld_num = 0; }; }; // tableinfo::Reset int tableinfo::Done() { return (fld_num >= db->tables[tbl_num]->num_fields); }; // tableinfo::Done void tableinfo::Next() { while(1) { fld_num++; if (Done()) return; if ((db->tables[tbl_num]->fields[fld_num]->type==DATA_FIELD) || (db->tables[tbl_num]->fields[fld_num]->u.tfield.rel_type==PARENT)) return; }; }; // tableinfo::Next int tableinfo::field_num() { return (Done() ? -1 : fld_num); }; // tableinfo::field_num char *tableinfo::field_name() { return (Done() ? NULL : db->tables[tbl_num]->fields[fld_num]->field_name); }; // tableinfo::field_name char tableinfo::field_type() { return (Done() ? NULL : (db->tables[tbl_num]-> fields[fld_num]->type == DATA_FIELD ? 'D' : 'P')); }; // tableinfo::field_type int tableinfo::data_size() { return (field_type()=='P' ? -1 : db->tables[tbl_num]-> fields[fld_num]->u.dfield.data_size); }; // tableinfo::data_size char tableinfo::data_type() { if (field_type()=='P') return ('\0'); switch(db->tables[tbl_num]->fields[fld_num]->u.dfield.data_type) { case INTEGER: return ('i'); break; case FLOAT : return ('f'); break; case STRING : return ('c'); break; case USER : return ('u'); break; default : return ('?'); break; }; }; // tableinfo::data_type char *tableinfo::cardinality() { if (field_type()=='D') return (NULL); sprintf(temp_card,"%d:%d", db->tables[tbl_num]->fields[fld_num]->u.tfield.tbl-> fields[db->tables[tbl_num]->fields[fld_num]-> u.tfield.fld_num]-> u.tfield.rel_card, db->tables[tbl_num]->fields[fld_num]->u.tfield.rel_card); return(temp_card); }; // tableinfo::cardinality char *tableinfo::parent_table_name() { return (field_type()=='D' ? NULL : db->tables[tbl_num]-> fields[fld_num]->u.tfield.tbl->table_name ); }; // tableinfo::parent_table_name int tableinfo::parent_table_num() { return (field_type()=='D' ? -1 : db->tables[tbl_num]-> fields[fld_num]->u.tfield.tbl->tbl_num ); }; // tableinfo::parent_table_num /*****************************************************************************/ struct list_t { int count; int body[50]; }; class list : public UserDefinedType { public: //list(char *tname, int tsize) : (tname,tsize){} //RJL: g++ rejected the above. //Error msg: ANSI C++ forbids old style base class initialization. // gendb was written before later version of g++ tightened up syntax // I added explicit superclass constructor name to list below: // Reference: Ira Pohl's text p. 438; Roger Sessions text 9.9 p. 302 - RJL) list(char *tname, int tsize) : UserDefinedType(tname, tsize) {}; void *Read(char *s); char *Print(void *list); void Free(void *list); }; // class list void *list::Read(char *s) { char value[20]; int i,j; struct list_t *x; x = (struct list_t *) calloc(1,sizeof(struct list_t)); i = 1; while (s[i] != ')') { for (j=0; (s[i] != ',') && (s[i] != ')'); i++,j++) value[j] = s[i]; value[j] = '\0'; x->body[x->count] = atoi(value); x->count++; if (s[i] != ')') i++; }; return (void *) x; }; char *list::Print(void *list) { struct list_t *x; static char s[50]; int i; x = (struct list_t *) list; strcpy(s,"("); if (x->count>0) sprintf(s,"(%d",x->body[0]); for (i=1; i < x->count; i++) { strcat(s,","); strcat(s,stringize(x->body[i])); }; strcat(s,")"); return s; }; void list::Free(void *list) { free(list); }; /****************************************************************************/ /* Remove the following pre-processor 'if' and its corresponding endif to */ /* reveal the test-driver that is embedded within. */ /****************************************************************************/ #if 0 main() { list dummy_list("list",0); database db("test","test.sch"); db.Load("test.dat"); iterator PT(&db,"part"); iterator AP(&db,"apartoper"); iterator MC(&db,"measchar"); table_loop(AP) if (strcmp(AP.StrVal("start"),"Christmas") == 0) AP.Delete(); table_loop(PT) { PT.Print(); child_loop(PT,AP,"oper_list") { printf(" "); AP.Print(); } }; printf("Operations with more than 5 defects :\n"); table_loop(AP) if (AP.IntVal("defect_count") > 5) printf(" Part %s (%s), Oper %s\n", AP.StrVal("part_id"), AP.ParentVal("part"), AP.StrVal("oper_num")); printf("Measurements taken (part,cut_oper,meas_oper,weight,location,person,meas,point):\n"); table_loop(MC) { printf(" %s, %s, %s, %f, %s, %s, %f\n", MC.StrVal("part_id"), MC.StrVal("oper_num","MC_AP2"), MC.StrVal("oper_num","MC_AP1"), MC.FloatVal("weight","MC__PT"), MC.StrVal("loc_cd","MC__PT"), MC.StrVal("person"), MC.FloatVal("meas")); }; db.Dump("test.out"); dbinfo db_info(&db); tableinfo table_info(&db); dbinfo_loop(db_info) { printf("Table %d, name: %s, abbrev: %s, num_rows: %d\n", db_info.table_num(), db_info.table_name(), db_info.table_abbrev(), db_info.num_rows()); tableinfo_loop(table_info,db_info.table_num()) { switch (table_info.field_type()) { case 'D' : printf(" DATA Field %d, name: %s, type: %c, size %d\n", table_info.field_num(), table_info.field_name(), table_info.data_type(), table_info.data_size()); break; case 'P' : printf(" PARENT Field %d, name: %s, card: %s, parent: %s (table num :%d)\n", table_info.field_num(), table_info.field_name(), table_info.cardinality(), table_info.parent_table_name(), table_info.parent_table_num()); break; }; }; }; }; #endif