#include #include #include #include #include #include static char* ac_sys (AC_DB db) ; BITSET_MAKE_BITFIELD /* define bitField for bitset.h ops */ static void ac_fillobj (AC_OBJ obj) ; static void cache_detach (struct ac_real_object *obj) ; static AC_OBJ cache_find ( AC_DB db, int class_number , char *name, AC_HANDLE handle) ; static AC_OBJ cache_create (AC_DB db, int class_number, char *name , AC_HANDLE handle) ; static int active_objects_count (unsigned char *buff) ; static void ac_dump_object (char *s, AC_OBJ obj, struct ac_real_object *robj) { if (robj && strcmp (robj->name, "a") != 0) return ; if (0) ac_dump_object (0, 0, 0) ; /* for compiler happiness */ printf ("%-20s ", s) ; if (obj) printf ("obj %08x ", (int)obj) ; else printf (" ") ; if (robj) { printf ("x %08x ref %d ", (int)robj, robj->refcount) ; printf ("t %08x class %02x name %s", (int)robj->table, robj->class_number, robj->name) ; } printf ("\n") ; } /********************************************************************/ /* * Here we see some interesting implications of the handle library. * You call ac_free () to free the object, but it doesn't appear * to do much of anything. But when ac_free () calls messfree (), * messfree () calls the finalization function for that data item. * The finalization function was registered when you created the * data element with halloc (). */ static void ac_free_real_object (struct ac_real_object *robj ) { cache_detach (robj) ; ac_free (robj->table) ; messfree (robj->a_data) ; bitSetDestroy (robj->hasTag) ; arrayDestroy (robj->tagLine) ; /* * the tag_name fields in tagLine are all pointers * into AC_DB object data and do not need to be freed * by us. */ /* robj->class is pointer into AC_DB object data */ /* robj->name is pointer into AC_DB */ robj->db = 0 ; messfree (robj) ; } static void ac_finalize_object (void *v) { AC_OBJ obj = (AC_OBJ)v ; struct ac_real_object *x ; BOOL test = FALSE ; x = obj->x ; if (x == 0) return ; /* mieg */ if (x == 0) messcrash ("finalizing AC_OBJ with NULL x pointer") ; test = !strcmp (ac_name(obj), "self_ref") ; obj->x = 0 ; if (x->refcount > 0) x->refcount-- ; if (test) printf ("ac_finalize self_ref recfount now =%d\n",x->refcount ) ; if (x->refcount > 0) return ; if (test) printf ("ac_finalize freeing self_ref\n") ; ac_free_real_object (x) ; } static void ac_finalize_keyset (void *v) { AC_KEYSET ks = (AC_KEYSET)v ; char buf[100] ; ks->magic = 0 ; ks->x->refcount-- ; if (ks->x->refcount > 0) return ; sprintf (buf, "kclear _aks%d", ks->x->id) ; ks->x->db->lazy_command (ks->x->db, buf) ; messfree (ks->x) ; } static void ac_finalize_iterator (void *v) { struct ac_iter * iter = (struct ac_iter*)v ; int x ; iter->magic = 0 ; iter->real_keyset->refcount-- ; if (iter->real_keyset->refcount <= 0) messfree (iter->real_keyset) ; /* * must free any un-returned objects we have in the iterator */ for (x = iter->ra_next ; x < iter->ra_max ; x++) ac_free (iter->obj[x]) ; } static void ac_finalize_db (void *v) { int x, y, num ; struct ac_db *adb = (struct ac_db *) v ; Array real_objects[ 256] ; memset (real_objects, 0, 256 * sizeof (Array)) ; adb->magic = 0 ; /* * close the transport that talks to the database */ if (adb->close_transport) (adb->close_transport) (adb) ; num = 0 ; /* * Empty the object cache. For each class, we want to find * all the objects in the cache and destroy them. */ for (x=0 ; x<256 ; x++) { void *key, *value ; if (! adb->cached_objects[x]) continue ; /* * First: walk through the cache and make a list of * all the ac_real_object structs. You can't use * the associator iterator because we will be changing * the associator at various points in the loop. * * We increase the refcount on every object that we * are about to free, so we can be sure that it does * not get freed too early. */ real_objects[x]=arrayCreate (100, struct ac_real_object * ) ; num = 0 ; key = 0 ; value = 0 ; while (uAssNext (adb->cached_objects[x], &key, &value)) { struct ac_real_object *t ; t = (struct ac_real_object *)value ; if (! t) continue ; t->refcount++ ; if (t->name && !strcmp (t->name, "self_ref")) printf ("db_finalize increased refcount of self_ref, now %d\n", t->refcount) ; array (real_objects[x], num++, struct ac_real_object *) = t ; } } for (x=0 ; x<256 ; x++) { if (!real_objects[x]) continue ; /* * Next: free all the data tables from the cached objects. This * may try to deallocate some AC_OBJ objects, but we incremented * all the ref counts above so nothing is really deallocated. */ for (y=0 ; y < arrayMax (real_objects[x]) ; y++) { struct ac_real_object *t ; AC_TABLE tb ; t = arr (real_objects[x], y, struct ac_real_object *) ; if (t && t->table) { /* * tables point back into objects, so when you free the * table, you need to make sure you can't find your way * back to this object and try to free the table again. */ if (t->name && !strcmp (t->name, "self_ref")) printf ("db_finalize fres the table of self_ref, refcount=%d\n", t->refcount) ; tb = t->table ; t->table = 0 ; ac_free (tb) ; } } } for (x=0 ; x<256 ; x++) { if (!real_objects[x]) continue ; /* * Now we believe there are no more AC_OBJ that refer to any of * our struct ac_real_object -- at least none that are under our * control. We can free our ac_real_object now. */ for (y = 0 ; y < arrayMax (real_objects[x]) ; y++) { struct ac_real_object *t ; t = arr (real_objects[x], y, struct ac_real_object *) ; if (t) { t->refcount-- ; /* undo the increment we made above */ if (t->name && !strcmp (t->name, "self_ref")) printf ("db_finalize decreased refcount of self_ref, now %d\n", t->refcount) ; ac_free_real_object (t) ; } } arrayDestroy (real_objects[x]) ; } /* * now destroy the object cache itself - the symbol table is made * of a dict and an associator for each class. */ for (x=0 ; x<256 ; x++) { if (adb->cached_names[x]) uDictDestroy (adb->cached_names[x]) ; if (adb->cached_objects[x]) uAssDestroy (adb->cached_objects[x]) ; if (adb->flags[x]) arrayDestroy (adb->flags[x]) ; } dictDestroy (adb->classDict) ; dictDestroy (adb->tag_dict) ; } /********************************************************************/ /* * Contact with the server is through * void ac_partial_command_XXX ( * * AC_DB db, db to contact * * char *command, command to send or "encore" * * unsigned char **response, filled in with ptr to * response data * * int *response_length, filled in with len of output * data * * unsigned char **response_free, free this address when done * with the response - may not * be the same as the response * * int *encore filled in with 0 if you got * the entire response or 1 if * there is more * ) * * The server may choose to send a partial response. If it does, *encore * is set and you should call ac_partial_command again with the command * "encore" to get the rest of the response. * * When you are done with the response, use free () to deallocate * the value returned in the response_free parameter. With some * transports, the response is a pointer into the middle of the data * block that was allocated for the response. * * The correct partial_command function is available in * AC_DB->ac_partial_command * * The following includes bring in implementations of various * transports. To create a new transport, include the implementation * code here and modify ac_open_connection () - no other changes are * needed. */ #include "../wac/acclient_rpc.c" #include "../wac/acclient_socket.c" #include "../wac/acclient_acetcp.c" #define protocol_match(p,s) (strncmp (p, s, sizeof (s)-1) == 0 && ( p[sizeof (s)-1] == 0 || isdigit ((int)p[sizeof (s)-1]) ) ) /* * match protocol p to constant string s - the name of the protocol matches up to the end * of the string or the first digit */ char * ac_open_connection (AC_DB db, char *protocol, char *host, int port, char *extras ) { if ( protocol_match (protocol, "rpc") || protocol_match (protocol, "r")) return ac_open_rpc (db, protocol, host, port) ; if ( protocol_match (protocol, "acetcp") || protocol_match (protocol, "a")) return ac_open_acetcp (db, protocol, host, port) ; if ( protocol_match (protocol, "socket") || protocol_match (protocol, "s")) return ac_open_socket (db, protocol, host, port, extras) ; return "000 unrecognized database specifier" ; } /* in case of success, use the returned value as a handle for all transcations, in case of failure, we return 0 plus an error mesage this error message is a fixed string, so the client does not have to free it and the message is not volatile */ AC_DB ac_open_db (char *database, char **error) { char *buf, *s = 0 ; int port_number ; struct ac_db *db = 0 ; char *protocol, *host, *port, *extras ; if (error) *error = 0 ; if (!database || !*database) { if (error) *error = "001 bad call to ac_open, no database name provided" ; return 0 ; } protocol = 0 ; host = 0 ; port = 0 ; extras = 0 ; buf = strdup (database) ; protocol = buf ; /* which transport we will use */ s = strchr (protocol, ':') ; if (s) { *s++ = 0 ; host = s ; /* which host to contact */ s = strchr (host, ':') ; } if (s) { *s++ = 0 ; port = s ; /* numeric part of address */ s = strchr (port, ':') ; } if (s) { *s++ = 0 ; extras = s ; /* further parameters, like user/pass */ } if (! port ) { /* * This used to be for compatibility with the old RPC specifier. * Now it chooses a default transport. */ if (host && atoi (host)) { port = host ; host = protocol ; protocol = "a" ; } else { if (error) *error = "003 badly formatted database name" ; return 0 ; } } port_number = atoi (port) ; /* we allocate the db struct and initialise it */ /* NB: halloc sets memory to zero and never fails */ db = (AC_DB) halloc (sizeof (struct ac_db), 0) ; blockSetFinalise (db, ac_finalize_db) ; db->magic = MAGIC_BASE + MAGIC_AC_DB ; s = ac_open_connection (db, protocol, host, port_number, extras ) ; free (buf) ; if (s) { messfree (db) ; if (error) *error = s ; return NULL ; } s = ac_sys (db) ; if (s) /* error */ { messfree (db) ; if (error) *error = s ; return NULL ; } return db ; } /********************************************************************/ /* general acedb command, * return zero if null parameters are provided by the user. * or the database reply * * the result is allocated on the user provided handle * so the user should either messfree his handle or * explicitely messfree the returned buffer * * We have to copy the result because askServerBinary uses * malloc () but we want halloc () so we can have a handle. * We should fix this so we can eliminate the copy operation. * * If the server says "encore", we collect up a list of all the responses * and then concatenate them at the end. This is faster that continually * using realloc () to make the buffer bigger, because each realloc () is * likely to require a copy of all the partial response data received * so far. * */ unsigned char *ac_command (AC_DB db, char *command, int *result_lengthp, AC_HANDLE handle) { unsigned char *response, *response_free ; int response_len ; int encore ; struct response_record { unsigned char *data ; unsigned char *free ; int len ; } *t ; Array responses ; int x, y ; /* * ac_partial_command sends a command to the server and gets the * first block of the server's reply. */ (db->ac_partial_command) (db, command, &response, &response_len, &response_free, &encore) ; if (! encore) { /* * The reply contains all the reply data. Most responses come here. */ unsigned char *s ; s = halloc (response_len + 1, handle) ; memcpy (s, response, response_len) ; s[response_len] = 0 ; free (response_free) ; if (result_lengthp) *result_lengthp = response_len ; return s ; } /* * The server told us that we will get more data if we ask for "encore". * Keep doing that until we get them all, then concatenate them into a * single buffer. */ responses = arrayCreate (10, struct response_record) ; t = & array (responses, 0, struct response_record) ; t->data = response ; t->free = response_free ; t->len = response_len ; x = 1 ; while (encore) { /* * getting the next section of the response */ (db->ac_partial_command) (db, "encore", &response, &response_len, &response_free, &encore) ; t = & array (responses, x, struct response_record) ; t->data = response ; t->free = response_free ; t->len = response_len ; x++ ; } /* * Count how big the returned buffer must be. */ response_len = 0 ; for (x=0 ; xdata, t->len) ; free (t->free) ; y = y + t->len ; } /* * clean up our overhead */ arrayDestroy (responses) ; /* * done */ if (result_lengthp) *result_lengthp = response_len ; return response ; } /********************************************************************* ********************************************************************* ********************************************************************* ** ** Here is a section of code that deals with parsing the -C mode ** server responses. All functions and data structures are private. ** ** (struct parsing *) is a cursor that the parsing functions use for ** their input. ** ** parse_init ( char *buffer, int len, AC_DB db) ** create a cursor. buffer is the text you want to parse, and ** len is how long it is. db is the database we are talking ** to, and is required because we have to recognize names that ** -C encodes as numbers and because we may need to create objects ** ** These functions assume that any input you give is coming from ** ac_command or ac_partial_command, and therefore it is known ** that strlen () used in the buffer will always find a '\0' even ** if the buffer is not well formed. ** ** parse_getc ( struct parsing *p ) ** returns the next byte from the input stream. returns -1 on EOF. ** ** parse_getstring ( struct parsing *p ) ** returns a null terminated string read from the input. The ** returned pointer points into the buffer that was passed to ** parse_init () ** ** parse_object_header (struct parsing *in, int *class_out, char **name_out) ** gets the class number and object name from the beginning of an ** object record. messfree () the name. ** ** parse_u (struct parsing *p, void *u) ** gets a 4 byte quantity, performing byte swap if necessary. the ** name "u" comes from the fact that there is typically a union of ** int, float, etc that holds the data. the "u" parameters is the ** address of where to store the data. ** ** parse_table (struct parsing *p, AC_TABLE table, AC_OBJ obj) ** parse data into an AC_TABLE. obj is the object that the data is ** going into (though you must still pass the correct table) or NULL if ** the table is not part of an object (i.e. tablemaker or aql results). ** row and col are the current row/col at the start of the parsing. ** ** ** *********************************************************************/ /* * this is the input descriptor for the parsing functions */ struct parsing { unsigned char * buffer ; int len ; int cursor ; int swap ; AC_DB db ; } ; /********************************************************************/ static struct parsing * parse_init (unsigned char *buffer, int len, AC_DB db ) { struct parsing *p ; p = halloc (len, 0) ; p->buffer = buffer ; p->len = len ; p->cursor = 0 ; p->swap = db->swap_needed ; p->db = db ; return p ; } /********************************************************************/ /* * bug: inline this, but the alpha compiler doesn't know inline * static int parse_getc (struct parsing *p) { int c ; if (p->cursor >= p->len) return -1 ; c=p->buffer[p->cursor] ; p->cursor++ ; return c ; } */ /* here is an equivalent macro */ #define parse_getc(_pp) ((_pp)->cursor >= (_pp)->len ? -1 : ((_pp)->buffer[((_pp)->cursor)++])) /********************************************************************/ static char * parse_getstring (struct parsing *p) { int len ; unsigned char *text ; len = strlen ((char *) (p->buffer+p->cursor)) ; text = p->buffer+p->cursor ; if (p->cursor+len > p->len) return NULL ; p->cursor += len+1 ; return (char *)text ; } /********************************************************************/ static int parse_object_header (struct parsing *in, int *class_out, char **name_out) { int c ; for ( ; ;) { c = parse_getc (in) ; if (c == -1) return -1 ; else if (c == 'N') { *class_out = parse_getc (in) ; *name_out = parse_getstring (in) ; return 0 ; } else if (c == '/') { while ((c != -1) && (c != '\n') ) c = parse_getc (in) ; } else if (c == '\n') continue ; else if (c == 0) continue ; else return -2 ; } } /********************************************************************/ static void parse_u (struct parsing *p, void *u) { unsigned char *uu = (unsigned char *)u ; if (p->swap) { uu[3] = parse_getc (p) ; uu[2] = parse_getc (p) ; uu[1] = parse_getc (p) ; uu[0] = parse_getc (p) ; } else { uu[0] = parse_getc (p) ; uu[1] = parse_getc (p) ; uu[2] = parse_getc (p) ; uu[3] = parse_getc (p) ; } } /********************************************************************/ static void parse_table (struct parsing *p, AC_TABLE table, AC_OBJ obj) { int kType, n ; char *s ; union { int i ; float f ; KEY k ; mytime_t time ; } u ; int class_number ; char *name ; int row, col ; int a_data_len = -1 ; int in_hash ; /* * -C begins it's output assuming that your cursor is at * the correct row, but column == -1. The first time you * call parse_table, there are no rows or columns, so you start * at (0, -1). On successive calls (like you might have with * tablemaker encores), you begin at the max row of the table, * but still at column -1. */ row = table->rows ; col = -1 ; in_hash = 0 ; for ( ; ;) { kType = parse_getc (p) ; switch (kType) { case -1: /* EOF on input string */ case '#': /* end of data */ return ; /* * entering or leaving a #xxx construct */ case '!': in_hash++ ; break ; case '@': in_hash-- ; break ; /* simple data */ case 'g': /* 4 bytes deposit tag in current cell */ { parse_u (p, &u) ; s = dictName (p->db->tag_dict, u.k ) ; ac_table_insert_type (table, row, col, &s, ac_type_tag) ; if ( obj && ( in_hash == 0) ) { /* * if we are loading an object, we need to remember where * the tag was. Note that we do NOT just remember the * first place we see the tag! If you are in a #xxx * construct, you can see duplicate tags. * */ if ( ! bit (obj->x->hasTag, u.k) ) { /* * We have not seen it before. add it, remember * where it is. */ struct tagLine nt, *t ; nt.row = row ; nt.col = col ; nt.tag_number = u.k ; nt.tag_name = s ; t = & array (obj->x->tagLine, arrayMax (obj->x->tagLine), struct tagLine) ; *t = nt ; bitSet (obj->x->hasTag, u.k) ; } } } break ; case 'K': case 'k': { AC_OBJ new_obj ; class_number = parse_getc (p) ; name = parse_getstring (p) ; new_obj = cache_find ( p->db, class_number , name, NULL ) ; if (! new_obj) new_obj = cache_create (p->db, class_number, name, NULL) ; if (kType == 'k' && ! new_obj->x->table) { /* * we know the object to be empty, so we fill it as empty */ new_obj->x->table = ac_empty_table (0, 0, 0) ; new_obj->x->filled = TRUE ; new_obj->x->get_date = ++(p->db->date) ; } ac_table_insert_type (table, row, col, &new_obj, ac_type_obj ) ; messfree (new_obj) ; } break ; case 'i': /* 4 bytes deposit integer in current cell */ parse_u (p, &u) ; ac_table_insert_type (table, row, col, &u, ac_type_int) ; break ; case 'f': /* 4 bytes deposit float in current cell */ parse_u (p, &u) ; ac_table_insert_type (table, row, col, &u, ac_type_float) ; break ; case 'd': /* 4 bytes deposit date in current cell */ parse_u (p, &u) ; ac_table_insert_type (table, row, col, &u, ac_type_date) ; break ; case 'v': /* void : vNULL*/ /* * We must insert a cell that is ac_type_empty even though * that is the value already there. By performing the * insert, we also increase the size of the table. */ ac_table_insert_type (table, row, col, &u, ac_type_empty) ; break ; case 't': /* string "\0" deposit text in current cell */ s = parse_getstring (p) ; ac_table_insert_text (table, row, col, s) ; break ; /* geometry of the table */ case '>': col++ ; break ; case '.': /* increment row */ row++ ; ac_table_copydown (table, row, col) ; break ; case '<': /* increment row, decrement col */ row++ ; col-- ; ac_table_copydown (table, row, col) ; break ; case 'l': /* left: subtract %c from column. Do not change row * this is always followed by a "." that increments the row */ n = parse_getc (p) ; col -= n ; break ; /* null operations */ case '\n': /* no operation */ break ; case '/': /* acedb comment */ while (kType != '\n') kType = parse_getc (p) ; break ; case 'c': /* * c integer * tells the length of the following A data */ parse_u (p, &u) ; a_data_len = u.i ; break ; case 'D': /* dna */ case 'p': /* peptide */ { /* * dna and peptide have the same format - a_data_len bytes of * unformatted data. */ if (! obj) messcrash ("server broken: trying to load type A data into non-object table") ; if (a_data_len == -1) messcrash ("server broken: sent type A data without length") ; /* * get some memory, copy the data in. */ if (obj->x->a_data) messfree (obj->x->a_data) ; obj->x->a_data_len = a_data_len ; obj->x->a_data = halloc (a_data_len+1, 0) ; s = (char *) obj->x->a_data ; while (a_data_len > 0) { *s++ = parse_getc (p) ; a_data_len -- ; } *s++ = 0 ; /* * always have an empty table - it makes the code easier to * read if we don't have to litter it with " if (x->table) ... " */ if (! obj->x->table) obj->x->table = ac_empty_table (0, 0, 0) ; /* * object is now filled. also, clear a_data_len in case the * server sends another type A data block. */ obj->x->filled = TRUE ; obj->x->get_date = ++(p->db->date) ; a_data_len = -1 ; } break ; case 0: /* * buggy servers sometimes insert a 00 byte */ break ; default: printf ("in parse table: %d of %d bytes\n", p->cursor, p->len) ; printf ("non implemented type in wac parse_table: %d %c", kType, kType) ; messcrash ("non implemented type in wac parse_table: %d %c", kType, kType) ; break ; } } } /******************************************************************** ********************************************************************* ********************************************************************* ** ** Here is a section of code that deals with the object cache. It ** is not very elaborate, but it gets us most of the speedup of a ** cache by eliminating a server transaction each time a cached ** object is found. ** ** The cache uses a dict and an associator to make a symbol table. ** To find an object, you look it up in the dict, then use the ** index as the key to the associator. The resulting pointer is ** the AC_OBJ. ** ** The cache currently has no provision for keeping objects that ** have been freed recently. As long as we continue to use this ** library primarily in CGI programs, that is probably not going ** to be a problem. ** *********************************************************************/ void ac_db_refresh (AC_DB db) { db->refresh_date = ++db->date ; /* all previously cached object are now too old */ } /* * cache_find () locates the object in the cache, increments the reference * count, and returns it. You must ac_free () the object after you get * it from cache_find (). */ static AC_OBJ cache_find ( AC_DB db, int class_number, char *name, AC_HANDLE handle) { struct ac_real_object *obj ; AC_OBJ ersatz ; int index ; void *tmp ; /* * no cached names in this class */ if (! db->cached_names[class_number]) return NULL ; if (! db->cached_objects[class_number] ) return NULL ; /* * there are cached names, but not this one */ if ( ! dictFind (db->cached_names[class_number], name, &index) ) return NULL ; if ( ! assFind ( db->cached_objects[class_number], assVoid (index), &tmp) ) return NULL ; /* * found it */ obj = (struct ac_real_object *) tmp ; if (obj->get_date < db->refresh_date) /* cached object is too old */ { cache_detach (obj) ; return NULL ; } obj->refcount++ ; ersatz = halloc (sizeof (struct ac_object), handle) ; blockSetFinalise ( ersatz, ac_finalize_object ) ; ersatz->magic = MAGIC_BASE + MAGIC_AC_OBJECT ; ersatz->x = obj ; if (!strcmp (ac_name(ersatz), "self_ref")) printf ("cache_find self_ref, refcount=%d\n", obj->refcount) ; return ersatz ; } /* * cache_create () makes an empty object in the cache. You must ac_free () * the object when you are done with it. * * The name parameter is not needed after cache_create returns. */ static AC_OBJ cache_create (AC_DB db, int class_number, char *name, AC_HANDLE handle ) { struct ac_real_object *x ; AC_OBJ ersatz ; int index ; /** ** create the object **/ x = (struct ac_real_object *) halloc (sizeof (struct ac_real_object), 0) ; x->refcount = 1 ; x->db = db ; x->table = NULL ; /* * An empty object would have a table with 0 rows */ x->a_data = NULL ; x->filled = FALSE ; x->hasTag = bitSetCreate (db->maxTag, 0) ; if (db->maxTag > 3000) abort () ; x->tagLine = arrayHandleCreate (8, struct tagLine, 0) ; x->class = dictName (db->classDict, class_number) ; x->class_number = class_number ; /** ** add the object to the cache ** **/ if (! db->cached_names[class_number]) { /* * No dict present - create it. Index 0 is not allowed in * associators, so we have to use it up by inserting an invalid * object name. * Do not allocate it on the db->handle */ db->cached_names[class_number] = dictHandleCreate (100, 0) ; dictAdd ( db->cached_names[class_number], "_____", &index ) ; } dictAdd ( db->cached_names[class_number], name, &index) ; x->name = dictName ( db->cached_names[class_number], index) ; /* * The associator part of the symbol table. * Do not allocate it on the db->handle */ if (! db->cached_objects[class_number] ) db->cached_objects[class_number] = assHandleCreate (0) ; assInsert ( db->cached_objects[class_number], assVoid (index), (void *)x) ; ersatz = halloc (sizeof (struct ac_object), handle) ; blockSetFinalise ( ersatz, ac_finalize_object) ; ersatz->magic = MAGIC_BASE + MAGIC_AC_OBJECT ; ersatz->x = x ; if (!strcmp (ac_name(ersatz), "self_ref")) printf ("cache_create self_ref, refcount=%d\n", ersatz->x->refcount) ; return ersatz ; } /* cache_create */ /* * cache_detach () just takes it out of the cache. It does not destroy * the object. */ static void cache_detach (struct ac_real_object *obj) { AC_DB db ; int index ; int class_number ; db = obj->db ; class_number = obj->class_number ; if ( ! dictFind (db->cached_names[class_number], obj->name, &index) ) abort () ; /* never happens */ assRemove (db->cached_objects[class_number], assVoid (index)) ; } /********************************************************************/ /********************************************************************/ /********************************************************************/ /********************************************************************/ /********************************************************************/ /********************************************************************* ********************************************************************* ** ** assorted private functions ** ********************************************************************* ********************************************************************/ /* * find_one () - find one object in the database and leave it in the * active keyset. This is used by some of our internal functions, but * can never be public because the AC library does not have a concept * of the active keyset. */ static int find_one (AC_DB db, char *class, char *name) { char *new_class ; char *new_name ; char *command ; unsigned char *response ; int nn ; new_class = freeprotect (class) ; new_class = strnew (new_class, 0) ; new_name = freeprotect (name) ; new_name = strnew (new_name, 0) ; command = messalloc (30 + strlen (new_class) + strlen (new_name)) ; if (0) sprintf (command, "query find %s IS %s\nlist -C", new_class, new_name ) ; else sprintf (command, "query find %s IS %s", new_class, new_name ) ; response = ac_command (db, command, 0, 0) ; nn = active_objects_count (response) ; messfree (response) ; messfree (command) ; messfree (new_name) ; messfree (new_class) ; return nn ; } /* * ac_fillobj () - fetch the data for an unfilled object * * ac_fillobj_part2 () - entry point for filling object when it is the * only one in the active keyset */ static void ac_fillobj_part2 (AC_DB db, AC_OBJ *objp, BOOL create, int class_number, char *name, AC_HANDLE handle) ; static void ac_fillobj (AC_OBJ obj) { int class_number ; if (obj->x->filled) return ; /* * find the object in the database server */ if (find_one (obj->x->db, obj->x->class, obj->x->name) == 0) return ; /* * Turn the class name into a class number. If we do not know * the class name, obviously we will not know the object. */ if (!dictFind (obj->x->db->classDict, obj->x->class, &class_number)) return ; ac_fillobj_part2 (obj->x->db, &obj, FALSE, class_number, obj->x->name, 0) ; } /* * ac_fillobj_part2 () assumes that the object is currently the only object * in the active keyset. That is OK because either ac_fillobj () or ac_get () * caused that to be true before calling here. * * This entry point only exists so we can eliminate the server transaction * in ac_fillobj () when you ac_get () an object. */ static void ac_fillobj_part2 (AC_DB db, AC_OBJ *objp, BOOL create, int class_number, char *name, AC_HANDLE handle) { unsigned char *response ; int new_class ; char *new_name ; int response_len ; struct parsing *p = 0 ; AC_TABLE table ; /* * Make a table for the object data. The object is now empty instead * of unfilled. We just need to fill in the data. If the server * response is that there is no data, we do not need to do anything * further. */ table = ac_empty_table (10, 0, 0) ; /* * Show the object. If the response does not contain any data, then * the object is empty. We already have an empty table, so the object * is already empty (as opposed to un-filled) . */ response = ac_command (db, "show -C", &response_len, 0) ; if (response) { p = parse_init (response, response_len, db) ; /* * must consume the object header even though it contains nothing interesting */ if (parse_object_header (p, &new_class, &new_name ) == 0) { if (class_number != new_class) messcrash ("ac_fillobj - server responded with wrong class") ; if (strcasecmp (name, new_name) != 0) messcrash ("ac_fillobj - server responded with wrong object") ; if (create) *objp = cache_create (db, class_number, new_name, handle) ; (*objp)->x->table = table ; (*objp)->x->filled = TRUE ; (*objp)->x->get_date = ++(db->date) ; /* * parse the object data: */ parse_table (p, table, *objp) ; } } else { if (create) { *objp = cache_create (db, class_number, name, handle) ; } (*objp)->x->table = table ; (*objp)->x->filled = TRUE ; (*objp)->x->get_date = ++(db->date) ; } messfree (response) ; messfree (p) ; return ; } /* ac_fillobj_part2 */ /********************************************************************/ /* Private: called by ac_tag_type * Locate the tag in the object table */ static BOOL ac_findTag (AC_OBJ obj, char *tag, int *rowp, int *colp) { int tag_number ; int i ; Array lines ; struct tagLine *t ; if (! obj->x->filled) ac_fillobj (obj) ; if (! dictFind (obj->x->db->tag_dict, tag, &tag_number)) return FALSE ; if (! bit (obj->x->hasTag, tag_number)) return FALSE ; lines = obj->x->tagLine ; for (i=0 ; i< arrayMax (lines) ; i++) { t = & array (lines, i, struct tagLine) ; if (t->tag_number == tag_number) { *rowp = t->row ; *colp = t->col ; return TRUE ; } } return FALSE ; } /* ac_findTag */ /****************************************************************/ /* * Return the number of objects stated in the response to a command * that alters the active keyset. */ static int active_objects_count (unsigned char *buff) { char cc, *cp = (char *) buff, *cq, *cp0 ; int nn ; cp0 = cp ; while ((cq = strstr (cp, "Active Objects"))) { cp0 = cp ; cp = cq+1 ;} /* find last occurence */ cp0 = cp ; while (cp0 && cp0 > (char *)buff && *cp0 != '/') cp0-- ; cp0++ ; while (cp0 && *cp0 == ' ') cp0++ ; if (sscanf (cp0, "%d%c", &nn, &cc) == 2 && cc == ' ') return nn ; return -1 ; } /****************************************************************/ /* * load a named keyset into the active keyset */ void ac_kget (struct ac_real_keyset *k) { char buff[30] ; sprintf (buff, "kget _aks%d", k->id) ; k->db->lazy_command (k->db, buff) ; } /****************************************************************/ static char* ac_parse_sys (AC_DB db, unsigned char *binaryBuffer, int nn) { int key = 0 ; unsigned char kType, *cp = binaryBuffer ; int n, ncl = 0, ntags = 0 ; unsigned int swap_magic ; char *s, buff[50] ; /* * the first 4 bytes are a magic cookie so we can identify what * sort of byte swapping is needed */ memcpy (&swap_magic, cp, 4) ; cp+= 4 ; nn -= 4 ; switch (swap_magic) { case 0x12345678: /* server/client use save byte rules */ db->swap_needed = FALSE ; break ; case 0x78563412: /* server/client disagree */ db->swap_needed = TRUE ; break ; default: /* * No machine we support uses any other byte order than strict big endian * or strict little endian. If we come here, it means there was a bug. */ return "005 bad call to ac_open, server return a bad swap_magic confirmation" ; } /* * skip over the number of tags */ cp+= 3 ; nn -= 3 ; while (cp++, nn--) switch (*cp) { case '#': /* end of object */ db->maxTag = ntags ; /* remember the number of tags we actually saw */ /* if (ncl != 256) abort () ; */ return 0 ; case '\n': /* no operation */ case '>': case '.': /* increment row */ cp++ ; nn-- ; kType = *cp++ ; nn-- ; switch (kType) { case 'k': ncl++ ; s = (char *)cp ; if (! *s) { sprintf (buff, "_sys_%d", ncl) ; s = buff ; } dictAdd (db->classDict, s, &key) ; break ; case 'g': ntags++ ; dictAdd (db->tag_dict, (char *)cp, &key) ; break ; } /* jump the name itself, zero terminated, followed by \n */ n = strlen ((char *)cp) ; nn -= n + 1 ; cp += n + 1 ; break ; default: break ; } messcrash ("missing # terminator in ac_parse_sys") ; /* extract the table definition */ return 0 ; } /* ac_parse_sys */ /****************************************************************/ /* obtain the class and tag names from the database */ static char* ac_sys (AC_DB db) { int nn ; unsigned char *binaryBuffer = 0 ; char *error = "004 bad call to ac_open, server did not return a system_list" ; db->tag_dict = dictHandleCreate (1000, 0) ; db->classDict = dictHandleCreate (256, 0) ; binaryBuffer = ac_command (db, "system_list", &nn, 0) ; if (binaryBuffer) { error = ac_parse_sys (db, binaryBuffer, nn) ; messfree (binaryBuffer) ; } return error ; } /* ac_sys */ /****************************************************************/ /********************************************************************* ********************************************************************** ********************************************************************** ** ** More published library entry points ** *********************************************************************/ /********************************************************************/ char *ac_name (AC_OBJ obj) { if (obj && obj->x && obj->x->name) return obj->x->name ; else return "(NULL)" ; } /********************************************************************/ char * ac_class (AC_OBJ obj) { if (obj) return obj->x->class ; else return " (NULL)" ; } /* ac_class */ /********************************************************************/ /* PUBLIC */ BOOL ac_has_tag (AC_OBJ obj, char *tagName) { int row, col ; return ac_findTag (obj, tagName, &row, &col) ; } /* ac_has_tag */ /********************************************************************/ /* PUBLIC * returns the type of the next cell right of the tag */ ac_type ac_tag_type (AC_OBJ obj, char *tagName) { int row = 0, col = 0 ; if (ac_findTag (obj, tagName, &row, &col)) return ac_table_type (obj->x->table, row, col + 1) ; return ac_type_empty ; } /* ac_tag_type */ int ac_tag_int ( AC_OBJ obj, char *tag, int deflt) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_int ( obj->x->table, row, col+1, deflt ) ; else return deflt ; } float ac_tag_float ( AC_OBJ obj, char *tag, float deflt) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_float ( obj->x->table, row, col+1, deflt ) ; else return deflt ; } char * ac_tag_text ( AC_OBJ obj, char *tag, char * deflt) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_text ( obj->x->table, row, col+1, deflt ) ; else return deflt ; } mytime_t ac_tag_date ( AC_OBJ obj, char *tag, mytime_t deflt) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_date ( obj->x->table, row, col+1, deflt ) ; else return deflt ; } AC_OBJ ac_tag_obj ( AC_OBJ obj, char *tag, AC_HANDLE handle ) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_obj ( obj->x->table, row, col+1, handle ) ; else return NULL ; } char * ac_tag_printable ( AC_OBJ obj, char *tag, char *dflt ) { int row, col ; if (ac_findTag (obj, tag, &row, &col)) return ac_table_printable ( obj->x->table, row, col+1, dflt ) ; else return dflt ; } /********************************************************************/ /* PUBLIC * fetch a table from a tag in an object * returns a non-writable subtable pointing into the obj->table */ AC_TABLE ac_tag_table (AC_OBJ obj, char *tagName, AC_HANDLE handle) { int row, col ; if (tagName == NULL) { if (! obj->x->filled) ac_fillobj (obj) ; return ac_copy_table (obj->x->table, 0, 0, obj->x->table->rows, obj->x->table->cols, handle) ; } if (ac_findTag (obj, tagName, &row, &col)) return ac_subtable (obj->x->table, row, col, handle) ; return 0 ; } /* ac_tag_table */ /****************************************************************/ /* perform AQL query*/ AC_TABLE ac_aql_table ( AC_DB db, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle) { AC_TABLE tt = 0 ; unsigned char *binaryBuffer = 0 ; int nn = 0 ; if (initial_keyset) ac_kget (initial_keyset->x) ; if (query && *query) { char * command = halloc (strlen (query) + 100, 0) ; sprintf (command, "aql -C %s %s", initial_keyset ? "-active" : "" , query) ; binaryBuffer = ac_command (db, command, &nn, handle) ; messfree (command) ; } if (binaryBuffer) { struct parsing *p ; tt = ac_empty_table (100, 0, handle) ; p = parse_init ( binaryBuffer, nn, db) ; parse_table (p, tt, 0) ; messfree (binaryBuffer) ; messfree (p) ; } return tt ; } /* ac_aql */ /****************************************************************/ /* * perform tablemaker query: * db is the database * query is the query * where describes how to interpret the value of query * ac_tablemaker_db => query is name of tablemaker query in database * ac_tablemaker_file => query is a file in the client disk space * ac_tablemaker_server_file => query is file name known by server * Not a particularly secure feature * ac_tablemaker_text => query is the actual text of a tablemaker * query that you read into your source code. * parameters are parameters to substitute in the query */ AC_TABLE ac_tablemaker_table ( AC_DB db, char *query, AC_KEYSET initial_keyset, enum ac_tablemaker_where where, char *params, AC_HANDLE handle) { AC_TABLE tt = 0 ; unsigned char *binaryBuffer = 0 ; int nn = 0 ; char *fa = "" ; if (initial_keyset) { if (initial_keyset->x->db != db) messcrash ("initial keyset does not belong to database being queried") ; ac_kget (initial_keyset->x) ; fa = "-active" ; } if (!params) params = "" ; if (query && *query) { char * command = halloc (strlen (query) + strlen (params) + 100, 0) ; switch (where) { case ac_tablemaker_db: sprintf (command, "table %s -C -n %s %s", fa, query, params) ; break ; case ac_tablemaker_server_file: sprintf (command, "table %s -C -f %s %s", fa, query, params) ; break ; case ac_tablemaker_file: messcrash ("ac_tablemaker_file mode not supported") ; break ; case ac_tablemaker_text: sprintf (command, "table %s -C =%s %s", fa, query, params) ; break ; } /* * it would be more efficient if we could collect the various * encore responses and parse each of them into the table. */ if (*command) binaryBuffer = ac_command (db, command, &nn, handle) ; messfree (command) ; } if (binaryBuffer) { if (strncmp ((char *)binaryBuffer, "Sorry, ", 6) == 0) { tt = NULL ; } else { struct parsing *p ; p = parse_init (binaryBuffer, nn, db) ; tt = ac_empty_table (20, 0, 0) ; parse_table (p, tt, 0) ; messfree (p) ; } messfree (binaryBuffer) ; } return tt ; } /****************************************************************/ static AC_OBJ ac_doget_obj (AC_DB db, char *classe, char *name, int fillhint, AC_HANDLE handle) { AC_OBJ obj = 0 ; int class_number ; /* * Turn the class name into a class number. If we do not know * the class name, obviously we will not know the object. */ if (!dictFind (db->classDict, classe, &class_number)) return NULL ; /* * See if we already have it. */ obj = cache_find ( db, class_number, name, handle) ; if (obj) { if (fillhint && ! obj->x->filled) ac_fillobj (obj) ; return obj ; } /* * We do not have it. Get it from the server. */ if (find_one (db, classe, name) == 0) return NULL ; /* * we still need to create the object * * If we fill it, we garantee the case of the name * Do we need to fill it ? */ if (fillhint) ac_fillobj_part2 (db, &obj, TRUE, class_number, name, handle ) ; else obj = cache_create (db, class_number, name, handle ) ; return obj ; } /* ac_doget_obj */ /* * PUBLIC ac_get () * * find an object and get it from the server * always fill it: this insures that casing of name is correct */ AC_OBJ ac_get_obj (AC_DB db, char *classe, char *name, AC_HANDLE handle) { return ac_doget_obj (db, classe, name, TRUE, handle) ; } /****************************************************************/ /* * PUBLIC ac_copy_obj () * * make another copy of an object */ AC_OBJ ac_copy_obj (AC_OBJ obj, AC_HANDLE handle) { /* * a lazy implementation so we can have one when we need it. * It would be faster to just allocate the object directly. */ AC_OBJ new_obj = 0 ; if (obj) new_obj = ac_doget_obj ( obj->x->db, obj->x->class, obj->x->name, 0, handle ) ; return new_obj ; } /****************************************************************/ /****************************************************************/ /* * make an empty keyset structure * * This is not the same as an empty keyset. It just initializes the * data structure that we use on our end. */ static AC_KEYSET empty_keyset_struct (AC_DB db, AC_HANDLE handle) { struct ac_keyset *ersatz ; struct ac_real_keyset *ks ; ks = (struct ac_real_keyset *) halloc (sizeof (struct ac_real_keyset), 0) ; ks->db = db ; ks->id = ++ (db->keysetId) ; /* unique within the existence of the db handle */ ks->max = 0 ; ks->refcount = 1 ; ersatz = (struct ac_keyset *) halloc (sizeof (struct ac_keyset), handle) ; blockSetFinalise (ersatz, ac_finalize_keyset) ; ersatz->magic = MAGIC_BASE + MAGIC_AC_KEYSET ; ersatz->x = ks ; return ersatz ; } /* PRIVATE * implements the different ..query_keyset */ static AC_KEYSET ac_doquery_keyset (AC_DB db, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle) { unsigned char *r = 0 ; char *buff ; struct ac_keyset *ks = empty_keyset_struct (db, handle) ; /* * either load the keyset we want to start the query from, or load * an empty keyset so the query does not operate on whatever keyset is * left over from some unknown previous operation. */ if (initial_keyset == (void*)1) ; else if (initial_keyset) ac_kget (initial_keyset->x) ; else { db->lazy_command (db, "kget _aks_no_such_keyset") ; } /* * now make the query and save the results in the keyset */ buff = messalloc (20 + (query ? strlen (query) : 10)) ; if (initial_keyset == (void*)1) sprintf (buff, "%s" , query) ; else sprintf (buff, "query %s" , query) ; db->lazy_command (db, buff) ; sprintf (buff, "kstore _aks%d" , ks->x->id) ; r = ac_command (db, buff, 0, 0) ; ks->x->max = active_objects_count (r) ; messfree (r) ; messfree (buff) ; return (AC_KEYSET) ks ; } /* ac_query_keyset */ /* PUBLIC * performs a query, creates a keyset of the result */ AC_KEYSET ac_dbquery_keyset (AC_DB db, char *queryText, AC_HANDLE handle) { return ac_doquery_keyset (db, queryText, 0, handle) ; } AC_KEYSET ac_ksquery_keyset (AC_KEYSET initial_keyset, char *queryText, AC_HANDLE handle) { return ac_doquery_keyset (initial_keyset->x->db, queryText, initial_keyset, handle) ; } AC_KEYSET ac_objquery_keyset (AC_OBJ aobj, char *queryText, AC_HANDLE handle) { AC_KEYSET aks1 = ac_new_keyset (aobj->x->db, aobj, handle) , aks2 ; if (queryText) { aks2 = ac_doquery_keyset (aobj->x->db, queryText, aks1, handle) ; ac_free (aks1) ; return aks2 ; } else return aks1 ; } /****************************************************************/ /* PUBLIC * makes an empty keyset to use keyset operators on */ AC_KEYSET ac_new_keyset (AC_DB db, AC_OBJ obj, AC_HANDLE handle) { struct ac_keyset *aks ; char buff[50] ; aks = empty_keyset_struct (db, handle) ; ac_kget (aks->x) ; sprintf (buff, "kstore _aks%d" , aks->x->id) ; db->lazy_command (db, buff) ; aks->x->max = 0 ; if (obj) ac_keyset_add (aks, obj) ; return (AC_KEYSET) aks ; } /* ac_new_keyset */ /****************************************************************/ /* Private * implements the boolean operation */ static int ac_keyset_op (AC_KEYSET aks1, AC_KEYSET aks2, char *op) { unsigned char *r ; char buff[50] ; AC_HANDLE h = ac_new_handle () ; if (aks1->x->db != aks2->x->db) messcrash ("keyset operation on two keysets that are not in the same database") ; ac_kget (aks1->x) ; if (op) { aks1->x->db->lazy_command (aks1->x->db, "spush") ; ac_kget (aks2->x) ; aks1->x->db->lazy_command (aks1->x->db, op) ; aks1->x->db->lazy_command (aks1->x->db, "spop") ; } sprintf (buff, "kstore _aks%d" , aks1->x->id) ; r = ac_command (aks1->x->db, buff, 0, h) ; aks1->x->max = active_objects_count (r) ; handleDestroy (h) ; return aks1->x->max ; } /* ac_keyset_op */ /****************************************************************/ /* PUBLIC * perform boolean operation ks1 = ks1 OP ks2 * on a keyset. * returns the number of element in the result */ int ac_keyset_and (AC_KEYSET ks1, AC_KEYSET ks2) { return ac_keyset_op (ks1, ks2, "sand") ; } int ac_keyset_or (AC_KEYSET ks1, AC_KEYSET ks2) { return ac_keyset_op (ks1, ks2, "sor") ; } int ac_keyset_xor (AC_KEYSET ks1, AC_KEYSET ks2) { return ac_keyset_op (ks1, ks2, "sxor") ; } int ac_keyset_minus (AC_KEYSET ks1, AC_KEYSET ks2) { return ac_keyset_op (ks1, ks2, "sminus") ; } /****************************************************************/ /* PUBLIC * makes another keyset with identical contents. Because * you don't have the 3 operand instructions, you copy the * keyset and then use read-modify-write operations on it. */ AC_KEYSET ac_copy_keyset (AC_KEYSET aks, AC_HANDLE handle) { AC_KEYSET aks1 = ac_new_keyset (aks->x->db, NULL, handle) ; ac_keyset_or (aks1, aks) ; return aks1 ; } /* ac_keyset_copy */ /****************************************************************/ /* PUBLIC * * add/remove object in keyset * */ BOOL ac_keyset_add (AC_KEYSET aks, AC_OBJ obj) { unsigned char *response ; char buff[50] ; ac_kget (aks->x) ; aks->x->db->lazy_command (aks->x->db, "spush") ; if (find_one (aks->x->db, obj->x->class, obj->x->name) == 0) return FALSE ; aks->x->db->lazy_command (aks->x->db, "sor") ; aks->x->db->lazy_command (aks->x->db, "spop") ; sprintf (buff, "kstore _aks%d" , aks->x->id) ; response = ac_command (aks->x->db, buff, 0, 0) ; aks->x->max = active_objects_count (response) ; messfree (response) ; return TRUE ; } /* ac_keyset_add */ /****************************/ BOOL ac_keyset_remove (AC_KEYSET aks, AC_OBJ obj) { unsigned char *response ; char buff[50] ; ac_kget (aks->x) ; aks->x->db->lazy_command (aks->x->db, "spush") ; if (find_one (aks->x->db, obj->x->class, obj->x->name) == 0) return FALSE ; aks->x->db->lazy_command (aks->x->db, "sminus") ; aks->x->db->lazy_command (aks->x->db, "spop") ; sprintf (buff, "kstore _aks%d" , aks->x->id) ; response = ac_command (aks->x->db, buff, 0, 0) ; aks->x->max = active_objects_count (response) ; messfree (response) ; return TRUE ; } /* ac_keyset_remove */ /****************************************************************/ /* PUBLIC * returns a subset strating at x0 of length nx * index start at 1 and are ordered alphabetically * which is usefull to get slices for display */ AC_KEYSET ac_subset_keyset (AC_KEYSET aks, int x0, int nx, AC_HANDLE handle) { unsigned char *r = 0 ; AC_KEYSET aks1 = ac_new_keyset (aks->x->db, NULL, handle) ; char buff[256] ; if (x0<1) x0 = 1 ; if (nx > aks->x->max - x0 + 1) nx = aks->x->max - x0 + 1 ; sprintf (buff, "subset %d %d", x0, nx) ; ac_kget (aks->x) ; aks1->x->db->lazy_command (aks1->x->db, buff) ; sprintf (buff, "kstore _aks%d" , aks1->x->id) ; r = ac_command (aks1->x->db, buff, 0, 0) ; aks1->x->max = active_objects_count (r) ; messfree (r) ; return aks1 ; } /* ac_subset_keyset */ /****************************************************************/ /* PUBLIC * return the number of keys in a keyset */ int ac_keyset_count (AC_KEYSET aks) { return aks->x->max ; } /* ac_keyset_count */ /****************************************************************/ /**************************************************************** * * Keyset Iterator functions * ****************************************************************/ /* PUBLIC ac_query_iter () * make a query, dump resulting keyset directly into an iterator * db = database to query * fillhint = true means load object data during iteration, * false means use lazy data loading * initial_keyset = keyset to make active before issuing the query * query = query to use */ AC_ITER ac_query_iter (AC_DB db, int fillhint, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle) { AC_ITER ret ; AC_KEYSET ks ; ks = ac_doquery_keyset (db, query, initial_keyset, 0) ; ret = ac_keyset_iter ( ks, fillhint, handle ) ; ac_free (ks) ; return ret ; } /* ac_query_iter */ /****************************************************************/ /* PUBLIC ac_dbquery_iter () * make a query, dump resulting keyset directly into an iterator * Short hand 1 of previous function, fillhint = true by default * db = database to query * query = query to use */ AC_ITER ac_dbquery_iter (AC_DB db, char *query, AC_HANDLE handle) { return ac_query_iter (db, TRUE, query, 0, handle) ; } /* ac_dbquery_iter */ /****************************************************************/ /* PUBLIC ac_ksquery_iter () * make a query, dump resulting keyset directly into an iterator * Short hand 2 of previous function, fillhint = true by default * ks = keyset to query * query = query to use */ AC_ITER ac_ksquery_iter (AC_KEYSET ks, char *query, AC_HANDLE handle) { if (ks) return ac_query_iter (ks->x->db, TRUE, query, ks, handle) ; return 0 ; } /* ac_ksquery_iter */ /****************************************************************/ /* PUBLIC ac_objquery_iter () * make a query, dump resulting keyset directly into an iterator * Short hand 3 of previous function, fillhint = true by default * obj = single object on which we initialise the query * query = query to use */ AC_ITER ac_objquery_iter (AC_OBJ obj, char *query, AC_HANDLE handle) { AC_ITER iter = 0 ; if (obj) { AC_KEYSET ks = ac_objquery_keyset (obj, "IS *", 0) ; iter = ac_query_iter (obj->x->db, TRUE, query, ks, handle) ; ac_free (ks) ; } return iter ; } /* ac_objquery_iter */ /****************************************************************/ /* PUBLIC ac_keyset_iter () * create an iterator that loops over a keyset * ks = the keyset to loop over * fillhint = true means load object data during iteration, * false means use lazy data loading * * Set up an iterator to loop over a keyset. Each call to ac_next_obj * will return the next object from the keyset. * * This function just sets up an empty iterator. The real work happens * in ac_next () */ AC_ITER ac_keyset_iter (AC_KEYSET ks, int fillhint, AC_HANDLE handle) { struct ac_iter *iter = (struct ac_iter *) halloc (sizeof (struct ac_iter), handle) ; blockSetFinalise (iter, ac_finalize_iterator) ; /* * get a reference to the ac_real_keyset */ iter->real_keyset = ks->x ; ks->x->refcount++ ; /* * generally fill in the iterator */ iter->magic = MAGIC_BASE + MAGIC_AC_ITERATOR ; iter->next = 0 ; iter->fillhint = fillhint ; iter->ra_next = 0 ; iter->ra_max = 0 ; iter->user_handle = handle ; /* * these fields are copied from the keyset for convenience */ iter->db = ks->x->db ; iter->id = ks->x->id ; iter->max = ks->x->max ; return (AC_ITER) iter ; } /* ac_keyset_iter */ /****************************************************************/ /* * PRIVATE - ac_iter_show () * * re-fill the iterator, using show so we get all the object data */ static void ac_iter_show (AC_ITER it) { unsigned char *response, *response_free ; int response_len ; struct parsing *p ; int class ; char *name, buff[80] ; int status ; int c ; /* * show the next batch of objects. we use ac_partial_command instead * of ac_command. The server may choose to send back fewer objects than * we ask for. In that case, ac_command will initiate another transaction * to get the rest of the response. But we don't really need it. We * continue iterating with the response we have, and when we use up the * objects returned, _then_ we will make another transaction anyway. * This reduces overhead both in transaction time and memory use in * copying the multiple sections of an encore response into a single * buffer. * */ sprintf (buff, "show -C -b %d -c %d", it->next, AC_ITER_OBJECT_READAHEAD) ; (it->db->ac_partial_command) (it->db, buff, &response, &response_len, &response_free, &c) ; if (! response) messcrash ("null server response") ; /* * Now we have a response containing a bunch of objects. show sends * back a list of objects, which we parse as * list: element | element list ; * element: object_header table ; */ p = parse_init (response, response_len, it->db) ; while ((status = parse_object_header (p, &class, &name)) == 0) { AC_OBJ o ; /* * we may already know the object. If not, create it. * Either way, we have a reference to stick on our list. */ o = cache_find (it->db, class, name, it->user_handle) ; if (! o) o = cache_create (it->db, class, name, it->user_handle ) ; it->obj[ it->ra_max ] = o ; /* * If the object already has a table, that means we already * have filled the object. But we have to parse the object * data anyway, to skip over it in the buffer. Here, we * discard the data and parse it again. Unless the data has * been updated in the server, the content will be invariant. */ if (o->x->table) ac_free (o->x->table) ; o->x->table = ac_empty_table (10, 10, 0) ; o->x->filled = TRUE ; o->x->get_date = ++(p->db->date) ; parse_table (p, o->x->table, o) ; it->ra_max++ ; if (it->ra_max >= AC_ITER_OBJECT_READAHEAD) { /* * we can only come here if the server sent back more objects * than we asked for. That's ok - we can ignore them. */ break ; } } messfree (p) ; /* * remember that response came from ac_partial_command, which is * returning a buffer that came out of the RPC library */ free (response_free) ; } /******************************************************************/ /* * PRIVATE - ac_iter_list () * * re-fill the iterator, using list so we get unfilled objects */ static void ac_iter_list (AC_ITER it) { unsigned char *response, *response_free ; char buff[80] ; int response_len ; struct parsing *p ; int class ; char *name ; int c ; /* * fillhint is 0, so we want to just list the objects */ sprintf (buff, "list -C -b %d -c %d", it->next, AC_ITER_OBJECT_READAHEAD) ; (it->db->ac_partial_command) (it->db, buff, &response, &response_len, &response_free, &c) ; if (! response) messcrash ("null server response") ; /* * Now we have a response containing a list of objects. */ p = parse_init (response, response_len, it->db) ; /* * first 5 bytes are not interesting */ (void) parse_getc (p) ; (void) parse_getc (p) ; (void) parse_getc (p) ; (void) parse_getc (p) ; (void) parse_getc (p) ; /* * Now parse the list of object names that came back. * * These tokens are junk: * > * . * \n * /.*\n (i.e. '/' to '\n') * * These are what we are looking for: * K classnumber object_name_string * k classnumber object_name_string */ while ((c = parse_getc (p)) != -1) switch (c) { case '\n': case '.': case '>': break ; case '/': while ((c = parse_getc (p)) != '\n') ; break ; case 'k': case 'K': { AC_OBJ o ; class = parse_getc (p) ; name = parse_getstring (p) ; /* * we may already know the object. If not, create it. * Either way, we have a reference to stick on our list. */ o = cache_find (it->db, class, name, it->user_handle) ; if (! o) o = cache_create (it->db, class, name, it->user_handle ) ; if (c == 'k' && ! o->x->table ) { /* * we know the object to be empty, so we can fill the object * now and avoid a server transaction to fill it later. */ o->x->table = ac_empty_table (0, 0, 0) ; o->x->filled = TRUE ; o->x->get_date = ++(it->db->date) ; } it->obj[ it->ra_max ] = o ; it->ra_max++ ; if (it->ra_max >= AC_ITER_OBJECT_READAHEAD) { /* * we can only come here if the server sent back more objects * than we asked for. That's ok - we can ignore them. */ break ; } } break ; } messfree (p) ; /* * remember that response came from ac_partial_command, which is * returning a buffer that came out of the RPC library */ free (response_free) ; } /******************************************************************/ /* PUBLIC ac_next_obj () * * Return the next object in an iterator. * * The iterator contains a read-ahead buffer AC_ITER_OBJECT_READAHEAD * objects long. When we fill the read-ahead, we get references * to a bunch of objects. As we return each object, the * reference becomes the property of the application. We still * own the reference for objects that we have not returned, so if * we destroy the iterator early, we have to ac_free the un-used * objects. * */ AC_OBJ ac_next_obj (AC_ITER it) { /* * Return NULL if no more objects */ if (it->next > it->max) return NULL ; /* * if the read-ahead buffer is empty, fill it */ if (it->ra_next >= it->ra_max) { it->ra_next = 0 ; it->ra_max = 0 ; /* * load the keyset of the iterator. next we will either * "list" or "show" from the active keyset. */ ac_kget (it->real_keyset) ; if (it->fillhint) ac_iter_show (it) ; else ac_iter_list (it) ; } /* * return one object from the read-ahead if we have one */ if (it->ra_next < it->ra_max) { AC_OBJ o ; o = it->obj[it->ra_next] ; it->obj[it->ra_next] = 0 ; /* not really necessary */ it->ra_next++ ; it->next++ ; return o ; } /* * if we get here, we tried to fill the read-ahead and it was still * empty. We are at the end. */ return NULL ; } /* ac_next */ /****************************************************************/ /* PUBLIC ac_iter_rewind () * * Set the iterator back to the beginning. This includes freeing * any objects in the read-ahead. * */ BOOL ac_iter_rewind (AC_ITER it) { int x ; for (x = it->ra_next ; x < it->ra_max ; x++) ac_free (it->obj[it->ra_next]) ; it->ra_next = 0 ; it->ra_max = 0 ; it->next = 0 ; return TRUE ; } /* ac_rewind */ /****************************************************************/ /* * PUBLIC ac_read_keyset * * read/write a keyset in non-volatile storage. This is here so we * can preserve keysets from one invocation of a cgi to the next. * * In the current client implementation, name is a file name on the * server's disk. The cgi programs take advantage of that by also * having an external program clean up old keysets. */ AC_KEYSET ac_read_keyset (AC_DB db, char *name, AC_HANDLE handle) { AC_HANDLE h = ac_new_handle () ; unsigned char *r ; struct ac_keyset *aks ; int max ; char *command = messalloc (60 + strlen (name)) ; sprintf (command, "keyset-read %s" , name) ; r = ac_command (db, command, 0, NULL) ; max = active_objects_count (r) ; messfree (r) ; if (max == -1) return NULL ; aks = empty_keyset_struct (db, handle) ; aks->x->max = max ; sprintf (command, "kstore _aks%d" , aks->x->id) ; ac_command (db, command, 0, h) ; messfree (command) ; handleDestroy (h) ; return (AC_KEYSET) aks ; } /* ac_read_keyset */ /******************************************************************/ /* * PUBLIC ac_keyset_write */ BOOL ac_keyset_write (AC_KEYSET ks, char *name) { char *command = messalloc (strlen(name)+100) ; unsigned char *r ; if (!ks) return FALSE ; ac_kget (ks->x) ; /* * Strictly speaking, we could delay this command, but because it writes * non-volatile data to disk I want it to happen immediately. */ sprintf (command, "list -a -f %s", name) ; r = ac_command (ks->x->db, command, NULL, NULL) ; messfree (r) ; messfree (command) ; return TRUE ; } /* ac_write_keyset */ /****************************************************************/ /****************************************************************/ /****************************************************************/ /* * PUBLIC ac_keyset_table * * Create a 1 column table containing all objects in the keyset. */ AC_TABLE ac_keyset_table ( AC_KEYSET ks, int min, int max, int fillhint, AC_HANDLE handle ) { AC_TABLE tbl ; AC_ITER i ; int x ; if (max == -1) max = ks->x->max ; tbl = ac_empty_table (max, 1, handle) ; /* * create an iterator. we cheat by poking our min value * into the iterator at the start. We can do this because * the read-ahead buffer is still empty. */ i = ac_keyset_iter (ks, fillhint, 0) ; i->next = min ; /* * fetch objects from min to max and put them in the table. */ x = min ; while (x < max) { AC_OBJ o ; o = ac_next_obj (i) ; if (!o) break ; /* never happens */ ac_table_insert_type (tbl, x, 0, &o, ac_type_obj) ; ac_free (o) ; x++ ; } ac_free (i) ; return tbl ; } /******************************************************************/ /* PUBLIC * performs a command, creates a keyset of the result - this is to get * keysets from commands that are not part of the library */ AC_KEYSET ac_command_keyset (AC_DB db, char *command, AC_KEYSET iks, AC_HANDLE handle) { unsigned char *r = 0 ; char buff[100] ; struct ac_keyset *ks = empty_keyset_struct (db, handle) ; if (iks) ac_kget (iks->x) ; db->lazy_command (db, command) ; sprintf (buff, "kstore _aks%d" , ks->x->id) ; r = ac_command (db, buff, 0, 0) ; ks->x->max = active_objects_count (r) ; messfree (r) ; return (AC_KEYSET) ks ; } /* ac_query_keyset */ unsigned char *ac_a_data (AC_OBJ obj, int *lenp, AC_HANDLE handle) { unsigned char *buf ; int len ; if (! obj->x->filled ) ac_fillobj (obj) ; len = obj->x->a_data_len ; if (lenp) *lenp = len ; buf = (unsigned char *) halloc (len + 1 , handle) ; if (len) memcpy (buf, obj->x->a_data, len) ; return buf ; } /* PUBLIC * INCOMPLETE unless you add code in dump.c !! * nothing will get exported except for DNA Peptide * returns a DNA or peptide sequence (according to seq_type). * The returned string contains only the letters of the * sequence. If result_length is not NULL, it is filled in * with the length of the sequence. */ /******************************************************************/ enum ac_sequence_type { ac_sequence_dna, ac_sequence_peptide }; /* * returns a DNA or peptide sequence (according to seq_type). * The returned string contains only the letters of the * sequence. If result_length is not NULL, it is filled in * with the length of the sequence. * * messfree() the returned string when you are finished with it. */ static char *ac_sequence (AC_OBJ obj, int *result_length, enum ac_sequence_type seq_type, int x1, int x2, AC_HANDLE h) { unsigned char *seq, *in, *out ; int n ; char command [256] ; n = find_one ( obj->x->db, obj->x->class, obj->x->name ) ; if (!n) return NULL ; switch (seq_type) { case ac_sequence_dna: if (x1 || x2) sprintf (command, "dna -x1 %d -x2 %d", x1, x2) ; else sprintf (command, "dna") ; seq = ac_command (obj->x->db, command, NULL, h) ; break ; case ac_sequence_peptide: seq = ac_command (obj->x->db, "peptide", NULL, h) ; break ; default: return NULL ; } out = seq ; in = seq ; while (isspace (*in)) in++ ; if (*in != '>') goto bad_resp ; in = (unsigned char *)strchr ((char *)in, '\n') ; if (!in) goto bad_resp ; in++ ; while (*in) { if (*in == '/') break ; if (isspace (*in)) { in++ ; continue ; } if (isalpha (*in)) { *out++ = *in++ ; continue ; } goto bad_resp ; } *out = 0 ; if (result_length) * result_length = strlen ((char *)seq) ; return (char *)seq ; bad_resp: messerror ("No sequence available for %s\n", ac_name(obj)) ; return NULL ; } /******************************************************************/ /* PUBLIC */ char *ac_obj_peptide (AC_OBJ obj, AC_HANDLE h) { int result_length = 0 ; return ac_sequence (obj, &result_length, ac_sequence_peptide, 0, 0, h) ; } char *ac_obj_dna (AC_OBJ obj, AC_HANDLE h) { int result_length = 0 ; return ac_sequence (obj, &result_length, ac_sequence_dna, 0, 0, h) ; } char *ac_zone_dna (AC_OBJ obj, int x1, int x2, AC_HANDLE h) { int result_length = 0 ; return ac_sequence (obj, &result_length, ac_sequence_dna, x1, x2, h) ; } char *ac_longtext (AC_OBJ obj, AC_HANDLE h) { int n = find_one ( obj->x->db, obj->x->class, obj->x->name ) ; char* lt ; char *cp, *cq ; if (!n) return NULL ; lt = (char *) ac_command (obj->x->db, "show", NULL, h) ; if ((cp = strstr (lt, "***LongTextEnd***"))) *cp = 0 ; else return lt ; cp = strstr (lt, "LongText") ; while (*cp && *cp != '\n') cp++ ; /* jump tiltle line */ if (strlen (cp)) cq = strnew (cp, h) ; else cq = 0 ; messfree (lt) ; return cq ; } /******************************************************************/ /* PUBLIC */ BOOL ac_parse ( AC_DB db, char *text, char **error_messages, AC_KEYSET *nks, AC_HANDLE h ) { char *s, *s1 ; unsigned char *s2 ; int len ; AC_KEYSET ks = 0 ; char buff[100] ; BOOL success = TRUE ; len = strlen (text) + 100 ; s = halloc (len, NULL_HANDLE) ; strcpy (s, "serverparse\n") ; strcat (s, text) ; /* * create the new keyset first, because it destroys the * server's idea of the active keyset */ if (nks) ks = ac_new_keyset (db, NULL, h) ; s1 = (char *) ac_command (db, s, NULL, h) ; if (strstr (s1, "error")) success=FALSE ; if (error_messages) *error_messages = s1 ; else messfree (s1) ; if (nks) { sprintf (buff, "kstore _aks%d" , ks->x->id) ; s2 = ac_command (db, buff, 0, 0) ; ks->x->max = active_objects_count (s2) ; messfree (s2) ; *nks = ks ; } messfree (s) ; return success ; } /******************************************************************/ /******************************************************************/