/* qi_query - module for query command */
/* Bruce Tanner - Cerritos College */
/* 1993/09/14 -
[email protected] 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);
}