/* qi_query - module for query command */ /* Bruce Tanner - Cerritos College */ /* 1993/09/14 - JLW@psulias.psu.edu fixed misextracted ATTR byte in display_id() that caused sequence numbers to spill over into ATTR */ #include stdio #include string #include ctype #include ssdef #include descrip #include rms #include psldef #include "qi.h" struct FAB idxfab, datfab; struct RAB idxrab, datrab; struct XABKEY idxxab, datxab; char idx_record[IDX_RECORD_SIZE + 1]; char idx_key[IDX_KEY_SIZE + 1]; char dat_record[DAT_RECORD_SIZE + 1]; char dat_key[DAT_KEY_SIZE + 1]; int db_status; typedef struct rstruct { int id, field; } Result; #define NotRet(x) ((x->type & TYPE_RETURN) == 0) void writestring(int, char *); void swrite(int, char *, ...); char *new_string(char *); Arg *make_arg(char *, int, char *, int); void free_args(Arg *); void qilog(int, char *, ...); void *my_realloc(void *, int); char *getlogical(char *); extern Fields fields[]; extern int mode; char *get_value(int sock, char *key, char *in_field, char *out_field) { char *cp, temp[KEYWORD_SIZE + 1]; int status; idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$l_rop = 0; /* set up exact match */ idxrab.rab$b_ksz = KEYWORD_SIZE + FIELD_SIZE; if (cp = strchr(key, ' ')) *cp = '\0'; /* strip trailing spaces */ strcpy(temp, key); for (cp = temp; *cp; cp++) *cp = _tolower(*cp); /* force lowercase */ sprintf(idx_key, "%-*s%s", KEYWORD_SIZE, temp, in_field); status = sys$get(&idxrab); if ((status & 1) == 0) { if (DEBUG) swrite(sock, "get_value doesn't find key %s\r\n", idx_key); return NULL; } if (DEBUG) swrite(sock, "get_value finds key %s\r\n", idx_record); sprintf(dat_key, "%s%s%0*d", idx_record + KEYWORD_SIZE + FIELD_SIZE, out_field, SEQ_SIZE, 0); idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$l_rop = 0; idxrab.rab$b_ksz = strlen(dat_key); if (DEBUG) swrite(sock, "get_value lookup %s\r\n", dat_key); status = sys$get(&datrab); if ((status & 1) == 0) { if (DEBUG) swrite(sock, "get_value doesn't find field %s, status %d\r\n", out_field, status); return NULL; } dat_record[datrab.rab$w_rsz] = '\0'; /* terminate string */ if (DEBUG) if (strcmp(out_field, PASSWORD_FIELD)) swrite(sock, "get_value finds field %s value %s\r\n", out_field, dat_record); else swrite(sock, "get_value finds password but I'm not going to show it to you.\r\n"); return (dat_record + ID_SIZE + FIELD_SIZE + SEQ_SIZE + ATTR_SIZE); } int find_exact(int sock, char *query, int field) { char *cp; int status; idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$l_rop = 0; /* set up exact match */ idxrab.rab$b_ksz = KEYWORD_SIZE + FIELD_SIZE; if (cp = strchr(query, ' ')) *cp = '\0'; /* strip trailing spaces */ if (field == atoi(SOUNDEX_FIELD)) /* soundex field means convert */ soundex(query, query, SOUNDEX_SIZE); /* and store soundex in query */ sprintf(idx_key, "%-*s%0*d", KEYWORD_SIZE, query, FIELD_SIZE, field); status = sys$get(&idxrab); if (DEBUG) swrite(sock, "exact match returns %d\r\n", status); return (status & 1); } /* find index that starts with the query string */ int find_approx(int sock, char *query, int field) { int status; idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$l_rop = RAB$M_KGE; /* set up approximate generic match */ idxrab.rab$b_ksz = strlen(query); /* actual key size */ sprintf(idx_key, "%-*s", KEYWORD_SIZE, query); status = sys$get(&idxrab); if ((status & 1) == 0) { if (DEBUG) swrite(sock,"approx match returns %d\r\n", status); return status; } /* this sould always find something, is it the right record? */ status = (strncmp(idx_record, query, strlen(query)) == 0); if (DEBUG) swrite(sock, "approx match finds %s and returns %d\r\n", idx_record, status); return status; } /* find soundex match */ int find_soundex(int sock, char *query, int *field) { int status; /* ensure only name field is implicitely searched for soundex */ if (*field != atoi(NAME_FIELD)) { if (DEBUG) swrite(sock, "soundex match rejects field %d\r\n", *field); return; } *field = atoi(SOUNDEX_FIELD); idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$l_rop = 0; /* set up exact match */ idxrab.rab$b_ksz = KEYWORD_SIZE + FIELD_SIZE; sprintf(idx_key, "%-*s%s", KEYWORD_SIZE, soundex(query, query, SOUNDEX_SIZE), SOUNDEX_FIELD); status = sys$get(&idxrab); if (DEBUG) swrite(sock, "soundex match returns %d\r\n", status); return (status & 1); } int find(int sock, Arg *arg, Result **result, int size) { int status, id, field; char *cp, query[DATA_SIZE + 1], wild_query[DATA_SIZE + 1]; char found_keyword[KEYWORD_SIZE + 1], found_id[ID_SIZE + 1]; char found_field[FIELD_SIZE + 1]; Result *rptr, *pptr; $DESCRIPTOR(wild_dsc, wild_query); $DESCRIPTOR(found_dsc, found_keyword); /* find() only works on indexed fields */ if ((fields[arg->field].attrib & ATTR_INDEXED) == 0) return size; if (DEBUG) swrite(sock, "Find %s in field %d\r\n", arg->value, arg->field); strcpy(query, arg->value); wild_query[0] = '\0'; field = arg->field; for (cp = query; *cp; cp++) /* convert all '?' to '%' */ if (*cp == '?') /* STR$WILDCARD uses '%' */ *cp = '%'; if ((cp = strchr(query, '*')) || (cp = strchr(query, '%'))) { /* wildcard? */ strcpy(wild_query, query); /* make a copy with the wildcard */ *cp = '\0'; /* truncate at the wildcard */ if (!find_approx(sock, query, field)) /* try to find the first part */ return size; /* no match */ } else if (!find_exact(sock, query, field)) /* no wildcard, find the item */ if (!EXACT && !find_approx(sock, query, field)) /* no exact match, try approx match */ if (!find_soundex(sock, query, &field)) /* no approx, try soundex */ return size; /* no match */ idxrab.rab$b_rac = RAB$C_SEQ; do { strncpy(found_keyword, idx_record, KEYWORD_SIZE); found_keyword[KEYWORD_SIZE] = '\0'; if (cp = strchr(found_keyword, ' ')) *cp = '\0'; if ((idxrab.rab$l_rop == 0) && /* if exact match, */ strcmp(found_keyword, query)) /* do exact compare */ break; /* no match */ if ((idxrab.rab$l_rop == RAB$M_KGE) && /* if approx match, */ strncmp(found_keyword, query, strlen(query))) /* approx compare */ break; /* no match */ if (strlen(wild_query)) { /* if wildcard match, */ found_dsc.dsc$w_length = (short) strlen(found_keyword); wild_dsc.dsc$w_length = (short) strlen(wild_query); if ((str$match_wild(&found_dsc, &wild_dsc) & 1) == 0) /* wild compare */ continue; /* no match, try again */ } sprintf(found_field, "%0*d", FIELD_SIZE, field); if (strncmp(found_field, idx_record + KEYWORD_SIZE, FIELD_SIZE) != 0) continue; /* this isn't the field we're looking for */ strncpy(found_id, idx_record + KEYWORD_SIZE + FIELD_SIZE, ID_SIZE); found_id[ID_SIZE] = '\0'; if (DEBUG) swrite(sock, "-100: Find >> %s\r\n", idx_record); *result = (Result *) my_realloc((Result *) *result, (size + 1) * sizeof(Result)); id = atoi(found_id); (*result)[size].id = id; (*result)[size].field = atoi(found_field); size++; } while (((status = sys$get(&idxrab)) & 1) == SS$_NORMAL); return size; } /* this routine handles the rare case where a name= query matches both name and nickname (inflating the match count) and another indexed query has no matches; thus a false matched == iq in resolve() */ int validate_match(Result *results, int end, int matched, Arg *list) { Arg *ptr; int ind; /* look for an indexed query without a corresponding result */ for (ptr = list; ptr && NotRet(ptr); ptr = ptr->next) { /* all queries */ if ((fields[ptr->field].attrib & ATTR_INDEXED) && /* this is indexed */ (ptr->field != atoi(NAME_FIELD)) && (ptr->field != atoi(NICKNAME_FIELD))) { /* that isn't name= */ for (ind = end - matched; ind < end; ind++) if (results[ind].field == ptr->field) break; /* a result matched this query */ if (ind == end) /* didn't find a match */ return False; /* for this query */ } } return True; } /* see if a query field matches the id */ int lookup(int sock, int id, Arg *arg) { int status; char *cp, value[DATA_SIZE + 1], data[DATA_SIZE + 1]; $DESCRIPTOR(value_dsc, value); $DESCRIPTOR(data_dsc, data); if (DEBUG) swrite(sock, "lookup %d\r\n", id); /* first, read the data record */ datrab.rab$b_rac = RAB$C_KEY; datrab.rab$b_ksz = ID_SIZE + FIELD_SIZE; /* partial key size */ datrab.rab$l_rop = RAB$M_KGE; /* find any sequence number */ sprintf(dat_key, "%0*d%0*d%*s", ID_SIZE, id, FIELD_SIZE, arg->field, SEQ_SIZE, ""); while ((status = sys$get(&datrab)) & 1) { datrab.rab$b_rac = RAB$C_SEQ; if (strncmp(dat_key, dat_record, ID_SIZE + FIELD_SIZE)) break; /* finished with this id/field */ dat_record[datrab.rab$w_rsz] = '\0'; /* terminate string */ strcpy(data, dat_record + ID_SIZE + FIELD_SIZE + SEQ_SIZE + ATTR_SIZE); data_dsc.dsc$w_length = (short) strlen(data); if (DEBUG) swrite(sock, "-100: Lookup >> %s\r\n", dat_record); /* force case for compare; there's no case-blind flag to match_wild */ for (cp = data; *cp; cp++) *cp = _tolower(*cp); sprintf(value, "*%s*", arg->value); value_dsc.dsc$w_length = (short) strlen(value); if (str$match_wild(&data_dsc, &value_dsc) & 1) return True; /* one match is good enough */ } return False; } /* find the non-indexed query fields in the data file and see if id will match them */ int find_non_indexed(int sock, int id, Arg *list) { Arg *ptr; for (ptr = list; ptr && NotRet(ptr); ptr = ptr->next) { if (fields[ptr->field].attrib & ATTR_INDEXED) continue; /* already did indexed fields */ if (!lookup(sock, id, ptr)) return False; /* no match, you loose */ } return True; /* all fields matched */ } int compar(Result *a, Result *b) { if (a->id < b->id) return (-1); if (a->id > b->id) return (1); if (a->field < b->field) return (-1); if (a->field > b->field) return (1); return (0); } /* display the requested fields associated with id */ void display_id(int sock, int id, Arg *list, int match) { Arg *ptr; int request[MAX_FIELD], use_defaults = True; int max, ind, status, num, seq, attrib; char data[DATA_SIZE + 1], field[20]; /* first we need to know whether there are any requested fields */ for (ind = 0; ind < MAX_FIELD; ind++) request[ind] = False; for (ptr = list; ptr; ptr = ptr->next) { if (ptr->type & TYPE_RETURN) use_defaults = False; else if (strcmp(ptr->value, "all") == 0) for (ind = 0; ind < MAX_FIELD; ind++) request[ind] = True; /* mark all as requested */ else if (!use_defaults) request[ptr->field] = True; } /* if use_defaults = true, the Default fields will be used otherwise, request[] contains the fields requested, */ /* find the max field name size */ for (ind = 0, max = 0; ind < MAX_FIELD; ind++) if (fields[ind].name && (strlen(fields[ind].name) > max)) max = strlen(fields[ind].name); datrab.rab$b_rac = RAB$C_KEY; datrab.rab$b_ksz = ID_SIZE; /* partial key size */ datrab.rab$l_rop = RAB$M_KGE; /* find all records */ sprintf(dat_key, "%0*d%0*d%*s", ID_SIZE, id, FIELD_SIZE, 0, SEQ_SIZE, ""); while ((status = sys$get(&datrab)) & 1) { datrab.rab$b_rac = RAB$C_SEQ; if (strncmp(dat_key, dat_record, ID_SIZE)) break; /* finished with this id */ dat_record[datrab.rab$w_rsz] = '\0'; /* terminate string */ strncpy(field, dat_record + ID_SIZE, FIELD_SIZE); field[FIELD_SIZE] = '\0'; num = atoi(field); strncpy(field, dat_record + ID_SIZE + FIELD_SIZE, SEQ_SIZE); field[SEQ_SIZE] = '\0'; seq = atoi(field); strncpy(field, dat_record + ID_SIZE + FIELD_SIZE + SEQ_SIZE, ATTR_SIZE); field[ATTR_SIZE] = '\0'; attrib = atoi(field); strcpy(data, dat_record + ID_SIZE + FIELD_SIZE + SEQ_SIZE + ATTR_SIZE); /* if a public field is requested OR no fields were requested and the field is a default AND the field isn't suppressed OR the field may not be suppressed (ForcePub) then print the record */ if (((request[num] && (fields[num].attrib & ATTR_PUBLIC)) || (use_defaults && (fields[num].attrib & ATTR_DEFAULT))) && /* Default implies Public */ (((attrib & ATTR_SUPPRESS) == 0) || (fields[num].attrib & ATTR_FORCEPUB))) { swrite(sock, "-200:%d:%*s: %s\r\n", match, max, seq ? "" : fields[num].name, data); } } } /* given an array of indexed fields that match indexed part of query, return the count of actual matches with the list in results[] */ int resolve(int sock, Result *results, int size, Arg *list) { Arg *ptr; int ind, iq, matches, count = 0; qsort((char *) results, size, sizeof(Result), compar); /* sort the hits */ for (ptr = list, iq = 0; ptr && NotRet(ptr); ptr = ptr->next) { if (fields[ptr->field].attrib & ATTR_INDEXED) iq++; /* Get count of indexed queries (iq) */ } if (DEBUG) for (ind = 0; ind < size; ind++) swrite(sock, "-100: Resolve >> field %d id %d\r\n", results[ind].field, results[ind].id); /* find sequences of 'iq' matches of one id */ for (ind = 1, matches = 1; ind < (size + 1); ind++) { if ((ind < size) && (results[ind-1].id == results[ind].id)) matches++; else if ( (matches >= iq) && /* if everything matches */ validate_match(results, ind, matches, list) && find_non_indexed(sock, results[ind-1].id, list) ) { results[count++] = results[ind-1]; /* save the id */ matches = 1; } } return count; } /* return number of indexed fields ensure that and all fields exist and have lookup attribute */ int validate_fields(int sock, Arg *list) { int highest = 0, indexed = 0; Arg *ptr, *new; char *cp; for (ptr = list; ptr && NotRet(ptr); ptr = ptr->next) { if (ptr->name && (ptr->field == -1)) { writestring(sock, "507:Field does not exist.\r\n"); return (0); /* force failure */ } if (ptr->field == -1) { /* null field name is 'name' field */ ptr->field = atoi(NAME_FIELD); ptr->name = new_string(fields[ptr->field].name); } #if NAME_HACK /* mirror the code in qi_build.c */ if (ptr->field == atoi(NAME_FIELD)) { cp = strchr(ptr->value, '\''); if (cp) strcpy(cp, cp+1); /* squeeze out apostrophe */ cp = strchr(ptr->value, '-'); if (cp) { *cp = '\0'; new = make_arg(ptr->name, ptr->field, cp + 1, ptr->type); /* copy second name */ new->next = ptr->next; ptr->next = new; } } #endif if (fields[ptr->field].attrib & ATTR_INDEXED) indexed++; if ((fields[ptr->field].attrib & ATTR_LOOKUP) == 0) { writestring(sock, "516:No authorization for request.\r\n"); return (0); /* force failure */ } } return indexed; } int query(char *cmd, int sock) { int status, ind, hits, size = 0; Arg *list, *listp, *nick; Result *results = NULL; list = parse_cmd(cmd, sock); /* build query struct from cmd */ if (validate_fields(sock, list) == 0) writestring(sock, "515:No indexed field in query.\r\n"); else { for (listp = list; listp && NotRet(listp); listp = listp->next) { size = find(sock, listp, &results, size); /* record ids that match query */ /* if this is a name field, lookup nickname. Problems with resolver? */ if (listp->field == atoi(NAME_FIELD)) { nick = make_arg(listp->name, atoi(NICKNAME_FIELD), listp->value, listp->type), size = find(sock, nick, &results, size); free_args(nick); } } } if (size == 0) /* no index matches */ writestring(sock, "501:No matches to query.\r\n"); else { hits = resolve(sock, results, size, list); if (hits == 0) writestring(sock, "501:No matches to query.\r\n"); else if (hits > MAX_RECORDS) { writestring(sock, "502:Too many matches to query.\r\n"); qilog(sock, "Too many matches (%d)", hits); } else { for (ind = 0; ind < hits; ind++) display_id(sock, results[ind].id, list, ind+1); qilog(sock, "Returned %d out of %d", hits, size); writestring(sock, "200:Ok.\r\n"); } } free_args(list); free(results); return True; } void db_open() { idxfab = cc$rms_fab; idxfab.fab$b_fac = FAB$M_GET; idxfab.fab$l_fna = INDEX_NAME; idxfab.fab$b_fns = strlen(INDEX_NAME); idxfab.fab$b_shr = FAB$M_SHRGET; idxfab.fab$v_lnm_mode = PSL$C_EXEC; idxfab.fab$l_xab = &idxxab; idxrab = cc$rms_rab; idxrab.rab$l_fab = &idxfab; idxrab.rab$l_kbf = idx_key; idxrab.rab$b_ksz = IDX_KEY_SIZE; idxrab.rab$b_rac = RAB$C_KEY; idxrab.rab$w_usz = IDX_RECORD_SIZE; idxrab.rab$l_ubf = idx_record; idxxab = cc$rms_xabkey; idxxab.xab$w_pos0 = 0; idxxab.xab$b_siz0 = IDX_KEY_SIZE; datfab = cc$rms_fab; datfab.fab$b_fac = FAB$M_GET; datfab.fab$l_fna = DATA_NAME; datfab.fab$b_fns = strlen(DATA_NAME); datfab.fab$b_shr = FAB$M_SHRGET; datfab.fab$v_lnm_mode = PSL$C_EXEC; datfab.fab$l_xab = &datxab; datrab = cc$rms_rab; datrab.rab$l_fab = &datfab; datrab.rab$l_kbf = dat_key; datrab.rab$b_ksz = DAT_KEY_SIZE; datrab.rab$b_rac = RAB$C_KEY; datrab.rab$w_usz = DAT_RECORD_SIZE; datrab.rab$l_ubf = dat_record; datxab = cc$rms_xabkey; datxab.xab$w_pos0 = 0; datxab.xab$b_siz0 = DAT_KEY_SIZE; if (((db_status = sys$open(&idxfab)) & 1) != SS$_NORMAL) return; if (((db_status = sys$connect(&idxrab)) & 1) != SS$_NORMAL) return; if (idxfab.fab$b_org != FAB$C_IDX) { db_status = 0; return; } if (((db_status = sys$open(&datfab)) & 1) != SS$_NORMAL) return; if (((db_status = sys$connect(&datrab)) & 1) != SS$_NORMAL) return; if (datfab.fab$b_org != FAB$C_IDX) { db_status = 0; return; } } void db_close() { sys$close(&idxfab); sys$close(&datfab); } .