Index: src/backend/executor/nodeFunctionscan.c =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/backend/executor/nodeFunctionscan.c,v retrieving revision 1.5 diff -c -r1.5 nodeFunctionscan.c *** src/backend/executor/nodeFunctionscan.c 5 Aug 2002 02:30:50 -0000 1.5 --- src/backend/executor/nodeFunctionscan.c 19 Aug 2002 19:50:50 -0000 *************** *** 24,29 **** --- 24,31 ---- #include "miscadmin.h" #include "access/heapam.h" + #include "catalog/pg_language.h" + #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/execdebug.h" #include "executor/execdefs.h" *************** *** 34,39 **** --- 36,42 ---- #include "parser/parse_type.h" #include "storage/lmgr.h" #include "tcop/pquery.h" + #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tuplestore.h" *************** *** 42,47 **** --- 45,53 ---- static TupleTableSlot *function_getonetuple(FunctionScanState *scanstate, bool *isNull, ExprDoneCond *isDone); + static Tuplestorestate *function_getalltuples(FunctionScanState *scanstate); + static bool is_plpgsql_srf(FunctionScanState *scanstate); + static Tuplestorestate *function_call_plpgsql_srf(FunctionScanState *scanstate); static FunctionMode get_functionmode(Node *expr); static bool tupledesc_mismatch(TupleDesc tupdesc1, TupleDesc tupdesc2); *************** *** 59,65 **** FunctionNext(FunctionScan *node) { TupleTableSlot *slot; - EState *estate; ScanDirection direction; Tuplestorestate *tuplestorestate; FunctionScanState *scanstate; --- 65,70 ---- *************** *** 70,77 **** * get information from the estate and scan state */ scanstate = (FunctionScanState *) node->scan.scanstate; ! estate = node->scan.plan.state; ! direction = estate->es_direction; tuplestorestate = scanstate->tuplestorestate; --- 75,81 ---- * get information from the estate and scan state */ scanstate = (FunctionScanState *) node->scan.scanstate; ! direction = node->scan.plan.state->es_direction; tuplestorestate = scanstate->tuplestorestate; *************** *** 82,119 **** if (tuplestorestate == NULL) { /* ! * Initialize tuplestore module. */ ! tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */ ! SortMem); scanstate->tuplestorestate = (void *) tuplestorestate; - - /* - * Compute all the function tuples and pass to tuplestore. - */ - for (;;) - { - bool isNull; - ExprDoneCond isDone; - - isNull = false; - isDone = ExprSingleResult; - slot = function_getonetuple(scanstate, &isNull, &isDone); - if (TupIsNull(slot)) - break; - - tuplestore_puttuple(tuplestorestate, (void *) slot->val); - ExecClearTuple(slot); - - if (isDone != ExprMultipleResult) - break; - } - - /* - * Complete the store. - */ - tuplestore_donestoring(tuplestorestate); } /* --- 86,99 ---- if (tuplestorestate == NULL) { /* ! * PL/PgSQL set-returning functions need to be handled specially */ ! if (is_plpgsql_srf(scanstate)) ! tuplestorestate = function_call_plpgsql_srf(scanstate); ! else ! tuplestorestate = function_getalltuples(scanstate); scanstate->tuplestorestate = (void *) tuplestorestate; } /* *************** *** 127,132 **** --- 107,256 ---- return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free); } + static Tuplestorestate * + function_getalltuples(FunctionScanState *scanstate) + { + Tuplestorestate *tuplestorestate; + TupleTableSlot *slot; + + /* + * Initialize tuplestore module. + */ + tuplestorestate = tuplestore_begin_heap(true, /* randomAccess */ + SortMem); + + /* + * Compute all the function tuples and pass to tuplestore. + */ + for (;;) + { + bool isNull; + ExprDoneCond isDone; + + isNull = false; + isDone = ExprSingleResult; + slot = function_getonetuple(scanstate, &isNull, &isDone); + if (TupIsNull(slot)) + break; + + tuplestore_puttuple(tuplestorestate, (void *) slot->val); + ExecClearTuple(slot); + + if (isDone != ExprMultipleResult) + break; + } + + /* + * Complete the store. + */ + tuplestore_donestoring(tuplestorestate); + + return tuplestorestate; + } + + /* + * is_plpgsql_srf(scanstate) + * + * If the function is a set-returning function defined in PL/PgSQL, + * return true. Note that this includes both scalar and composite (a.k.a + * tuple-returning) SRFs. + */ + static bool + is_plpgsql_srf(FunctionScanState *scanstate) + { + Node *node = scanstate->funcexpr; + Expr *expr; + Func *func; + HeapTuple tuple; + Oid lanoid; + NameData lanname; + bool retset; + bool matched; + + if (nodeTag(node) != T_Expr) + elog(ERROR, "Unexpected node type in function scan: %d", + nodeTag(node)); + + expr = (Expr *) node; + + if (expr->opType != FUNC_EXPR) + elog(ERROR, "Unexpected expr type in function scan: %d", + expr->opType); + + func = (Func *) expr->oper; + + tuple = SearchSysCache(PROCOID, + ObjectIdGetDatum(func->funcid), + 0, 0, 0); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "Cache lookup for procedure %u failed", + func->funcid); + + lanoid = ((Form_pg_proc) GETSTRUCT(tuple))->prolang; + retset = ((Form_pg_proc) GETSTRUCT(tuple))->proretset; + ReleaseSysCache(tuple); + + /* Not a set-returning function, we're done */ + if (!retset) + return false; + + /* Short-cut: if the OID matches a well-known language, we're done */ + if (lanoid == INTERNALlanguageId || lanoid == ClanguageId || + lanoid == SQLlanguageId) + return false; + + tuple = SearchSysCache(LANGOID, + ObjectIdGetDatum(lanoid), + 0, 0, 0); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "Cache lookup for language %u failed", + lanoid); + + lanname = ((Form_pg_language) GETSTRUCT(tuple))->lanname; + + matched = (namestrcmp(&lanname, "plpgsql") == 0); + + ReleaseSysCache(tuple); + + return matched; + } + + /* + * function_call_plpgsql_srf(scanstate) + * + * The execution of PL/PgSQL set-returning functions is similar to + * the execution of other set-returning functions: the first call + * to FunctionNext() creates a tuplestore of results, subsequent calls + * to FunctionNext() fetch data from the store. The difference with SRFs + * in PL/PgSQL is that the tuplestore is created within the PL/PgSQL + * code itself: inside the PL/PgSQL function, the application developer + * can store multiple tuples using RETURN NEXT. Thus, we simple execute + * the function once, and assume it returns a pointer to the tuplestore + * containing the results. + */ + + static Tuplestorestate * + function_call_plpgsql_srf(FunctionScanState *scanstate) + { + ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext; + bool isNull; + ExprDoneCond isDone; + Datum retval; + + /* + * Execute the PL/PgSQL function, returns a pointer to + * tuplestore allocated in the TransactionCommandContext. + */ + retval = ExecEvalExprSwitchContext(scanstate->funcexpr, + econtext, + &isNull, + &isDone); + + return (Tuplestorestate *) DatumGetPointer(retval); + } + /* ---------------------------------------------------------------- * ExecFunctionScan(node) * *************** *** 491,499 **** get_functionmode(Node *expr) { /* ! * for the moment, hardwire this */ ! return PM_REPEATEDCALL; } static bool --- 615,623 ---- get_functionmode(Node *expr) { /* ! * for the moment, hardwire this: only one FM at present */ ! return FM_MATERIALIZE; } static bool Index: src/backend/executor/spi.c =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/backend/executor/spi.c,v retrieving revision 1.72 diff -c -r1.72 spi.c *** src/backend/executor/spi.c 20 Jul 2002 05:16:58 -0000 1.72 --- src/backend/executor/spi.c 19 Aug 2002 19:50:50 -0000 *************** *** 663,689 **** return pointer; } - void * - SPI_repalloc(void *pointer, Size size) - { - /* No longer need to worry which context chunk was in... */ - return repalloc(pointer, size); - } - - void - SPI_pfree(void *pointer) - { - /* No longer need to worry which context chunk was in... */ - pfree(pointer); - } - - void - SPI_freetuple(HeapTuple tuple) - { - /* No longer need to worry which context tuple was in... */ - heap_freetuple(tuple); - } - void SPI_freetuptable(SPITupleTable *tuptable) { --- 663,668 ---- Index: src/include/executor/spi.h =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/include/executor/spi.h,v retrieving revision 1.33 diff -c -r1.33 spi.h *** src/include/executor/spi.h 8 Nov 2001 20:37:52 -0000 1.33 --- src/include/executor/spi.h 19 Aug 2002 19:50:51 -0000 *************** *** 101,110 **** extern Oid SPI_gettypeid(TupleDesc tupdesc, int fnumber); extern char *SPI_getrelname(Relation rel); extern void *SPI_palloc(Size size); - extern void *SPI_repalloc(void *pointer, Size size); - extern void SPI_pfree(void *pointer); - extern void SPI_freetuple(HeapTuple pointer); extern void SPI_freetuptable(SPITupleTable *tuptable); extern Portal SPI_cursor_open(char *name, void *plan, Datum *Values, char *Nulls); --- 101,112 ---- extern Oid SPI_gettypeid(TupleDesc tupdesc, int fnumber); extern char *SPI_getrelname(Relation rel); extern void *SPI_palloc(Size size); extern void SPI_freetuptable(SPITupleTable *tuptable); + + /* No longer need to worry which context chunk was in... */ + #define SPI_pfree(pointer) pfree((pointer)) + #define SPI_repalloc(pointer, size) repalloc((pointer), (size)) + #define SPI_freetuple(pointer) heap_freetuple((pointer)) extern Portal SPI_cursor_open(char *name, void *plan, Datum *Values, char *Nulls); Index: src/include/nodes/execnodes.h =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/include/nodes/execnodes.h,v retrieving revision 1.71 diff -c -r1.71 execnodes.h *** src/include/nodes/execnodes.h 4 Aug 2002 19:48:10 -0000 1.71 --- src/include/nodes/execnodes.h 19 Aug 2002 19:50:51 -0000 *************** *** 509,518 **** * Function nodes are used to scan the results of a * function appearing in FROM (typically a function returning set). * ! * functionmode function operating mode: ! * - repeated call ! * - materialize ! * - return query * tupdesc function's return tuple description * tuplestorestate private state of tuplestore.c * funcexpr function expression being evaluated --- 509,519 ---- * Function nodes are used to scan the results of a * function appearing in FROM (typically a function returning set). * ! * functionmode function operating mode. At the moment, the ! * only mode is 'materialize', in which the ! * executor repeatedly calls the SRF, building ! * up a tuplestore of results. More function ! * modes will be added in the future. * tupdesc function's return tuple description * tuplestorestate private state of tuplestore.c * funcexpr function expression being evaluated *************** *** 524,532 **** */ typedef enum FunctionMode { ! PM_REPEATEDCALL, ! PM_MATERIALIZE, ! PM_QUERY } FunctionMode; typedef struct FunctionScanState --- 525,531 ---- */ typedef enum FunctionMode { ! FM_MATERIALIZE } FunctionMode; typedef struct FunctionScanState Index: src/interfaces/libpq/libpq-int.h =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/interfaces/libpq/libpq-int.h,v retrieving revision 1.55 diff -c -r1.55 libpq-int.h *** src/interfaces/libpq/libpq-int.h 18 Aug 2002 03:47:08 -0000 1.55 --- src/interfaces/libpq/libpq-int.h 19 Aug 2002 19:50:51 -0000 *************** *** 363,369 **** #define DefaultPassword "" /* ! * this is so that we can check is a connection is non-blocking internally * without the overhead of a function call */ #define pqIsnonblocking(conn) ((conn)->nonblocking) --- 363,369 ---- #define DefaultPassword "" /* ! * This is so that we can check if a connection is non-blocking internally * without the overhead of a function call */ #define pqIsnonblocking(conn) ((conn)->nonblocking) Index: src/pl/plpgsql/src/Makefile =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/Makefile,v retrieving revision 1.20 diff -c -r1.20 Makefile *** src/pl/plpgsql/src/Makefile 16 Nov 2001 16:32:33 -0000 1.20 --- src/pl/plpgsql/src/Makefile 19 Aug 2002 19:50:51 -0000 *************** *** 78,84 **** $(srcdir)/pl_scan.c: scan.l ifdef FLEX ! $(FLEX) -i $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $< else @$(missing) flex $< $@ endif --- 78,84 ---- $(srcdir)/pl_scan.c: scan.l ifdef FLEX ! $(FLEX) $(FLEXFLAGS) -Pplpgsql_base_yy -o'$@' $< else @$(missing) flex $< $@ endif Index: src/pl/plpgsql/src/gram.y =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/gram.y,v retrieving revision 1.34 diff -c -r1.34 gram.y *** src/pl/plpgsql/src/gram.y 8 Aug 2002 01:36:04 -0000 1.34 --- src/pl/plpgsql/src/gram.y 19 Aug 2002 19:50:51 -0000 *************** *** 121,128 **** %type proc_sect, proc_stmts, stmt_else, loop_body %type proc_stmt, pl_block %type stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit ! %type stmt_return, stmt_raise, stmt_execsql, stmt_fori ! %type stmt_fors, stmt_select, stmt_perform %type stmt_dynexecute, stmt_dynfors, stmt_getdiag %type stmt_open, stmt_fetch, stmt_close --- 121,128 ---- %type proc_sect, proc_stmts, stmt_else, loop_body %type proc_stmt, pl_block %type stmt_assign, stmt_if, stmt_loop, stmt_while, stmt_exit ! %type stmt_return, stmt_return_next, stmt_raise, stmt_execsql ! %type stmt_fori, stmt_fors, stmt_select, stmt_perform %type stmt_dynexecute, stmt_dynfors, stmt_getdiag %type stmt_open, stmt_fetch, stmt_close *************** *** 166,171 **** --- 166,172 ---- %token K_IS %token K_LOG %token K_LOOP + %token K_NEXT %token K_NOT %token K_NOTICE %token K_NULL *************** *** 708,713 **** --- 709,716 ---- { $$ = $1; } | stmt_return { $$ = $1; } + | stmt_return_next + { $$ = $1; } | stmt_raise { $$ = $1; } | stmt_execsql *************** *** 1127,1133 **** new = malloc(sizeof(PLpgSQL_stmt_return)); memset(new, 0, sizeof(PLpgSQL_stmt_return)); ! if (plpgsql_curr_compile->fn_retistuple) { new->retistuple = true; new->retrecno = -1; --- 1130,1137 ---- new = malloc(sizeof(PLpgSQL_stmt_return)); memset(new, 0, sizeof(PLpgSQL_stmt_return)); ! if (plpgsql_curr_compile->fn_retistuple && ! !plpgsql_curr_compile->fn_retset) { new->retistuple = true; new->retrecno = -1; *************** *** 1147,1153 **** break; default: ! yyerror("return type mismatch in function returning table row"); break; } if (yylex() != ';') --- 1151,1157 ---- break; default: ! yyerror("return type mismatch in function returning tuple"); break; } if (yylex() != ';') *************** *** 1160,1165 **** --- 1164,1193 ---- new->cmd_type = PLPGSQL_STMT_RETURN; new->lineno = $2; new->expr = expr; + + $$ = (PLpgSQL_stmt *)new; + } + ; + + /* FIXME: this syntax needs work, RETURN NEXT breaks stmt_return */ + stmt_return_next: K_NEXT lno fors_target ';' + { + PLpgSQL_stmt_return_next *new; + + new = malloc(sizeof(PLpgSQL_stmt_return_next)); + memset(new, 0, sizeof(PLpgSQL_stmt_return_next)); + + new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; + new->lineno = $2; + switch ($3->dtype) + { + case PLPGSQL_DTYPE_REC: + new->rec = $3; + break; + case PLPGSQL_DTYPE_ROW: + new->row = (PLpgSQL_row *) $3; + break; + } $$ = (PLpgSQL_stmt *)new; } Index: src/pl/plpgsql/src/pl_exec.c =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/pl_exec.c,v retrieving revision 1.56 diff -c -r1.56 pl_exec.c *** src/pl/plpgsql/src/pl_exec.c 24 Jun 2002 23:12:06 -0000 1.56 --- src/pl/plpgsql/src/pl_exec.c 19 Aug 2002 19:50:51 -0000 *************** *** 105,110 **** --- 105,112 ---- PLpgSQL_stmt_exit * stmt); static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt); + static int exec_stmt_return_next(PLpgSQL_execstate * estate, + PLpgSQL_stmt_return_next * stmt); static int exec_stmt_raise(PLpgSQL_execstate * estate, PLpgSQL_stmt_raise * stmt); static int exec_stmt_execsql(PLpgSQL_execstate * estate, *************** *** 150,155 **** --- 152,160 ---- int32 reqtypmod, bool *isnull); static void exec_set_found(PLpgSQL_execstate * estate, bool state); + static void exec_init_tuple_store(PLpgSQL_execstate *estate); + static int exec_finish_set_func(PLpgSQL_execstate *estate, + PLpgSQL_stmt_return *stmt); /* ---------- *************** *** 315,321 **** * Set the magic variable FOUND to false */ exec_set_found(&estate, false); ! /* * Now call the toplevel block of statements */ --- 320,326 ---- * Set the magic variable FOUND to false */ exec_set_found(&estate, false); ! /* * Now call the toplevel block of statements */ *************** *** 332,338 **** * We got a return value - process it */ error_info_stmt = NULL; ! error_info_text = "while casting return value to functions return type"; fcinfo->isnull = estate.retisnull; --- 337,343 ---- * We got a return value - process it */ error_info_stmt = NULL; ! error_info_text = "while casting return value to function's return type"; fcinfo->isnull = estate.retisnull; *************** *** 912,917 **** --- 917,926 ---- rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt); break; + case PLPGSQL_STMT_RETURN_NEXT: + rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt); + break; + case PLPGSQL_STMT_RAISE: rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt); break; *************** *** 1500,1505 **** --- 1509,1521 ---- static int exec_stmt_return(PLpgSQL_execstate * estate, PLpgSQL_stmt_return * stmt) { + /* + * If processing a set-returning PL/PgSQL function, the final RETURN + * indicates that the function is finished producing tuples. + */ + if (estate->retisset) + return exec_finish_set_func(estate, stmt); + if (estate->retistuple) { /* initialize for null result tuple */ *************** *** 1540,1545 **** --- 1556,1632 ---- return PLPGSQL_RC_RETURN; } + static int + exec_finish_set_func(PLpgSQL_execstate *estate, + PLpgSQL_stmt_return *stmt) + { + /* We should probably allow this */ + if (estate->tuple_store == NULL) + elog(ERROR, "Table functions must return at least 1 tuple"); + + tuplestore_donestoring(estate->tuple_store); + + return PLPGSQL_RC_RETURN; + } + + /* + * TODO: + * - if the function returns a set of scalars, form a tuple and insert + * it into the tuplestore (using heap_formtuple() ) + * - what other return types should we allow? i.e. record, row, etc. + */ + /* + * Notes: + * - the tuple store must be created in a sufficiently long-lived + * memory context, as the same store must be used within the executor + * after the PL/PgSQL call returns. At present, the code uses + * TopTransactionContext. + */ + static int + exec_stmt_return_next(PLpgSQL_execstate *estate, + PLpgSQL_stmt_return_next *stmt) + { + HeapTuple tuple; + + if (!estate->retisset) + elog(ERROR, "Cannot use RETURN NEXT in a non-table function"); + + if (estate->tuple_store == NULL) + exec_init_tuple_store(estate); + + if (stmt->rec) + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + tuple = rec->tup; + estate->rettupdesc = rec->tupdesc; + } + else + elog(ERROR, "Blank RETURN NEXT not allowed"); + + if (HeapTupleIsValid(tuple)) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(TopTransactionContext); + tuplestore_puttuple(estate->tuple_store, tuple); + MemoryContextSwitchTo(oldcxt); + } + + return PLPGSQL_RC_OK; + } + + static void + exec_init_tuple_store(PLpgSQL_execstate *estate) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(TopTransactionContext); + estate->tuple_store = tuplestore_begin_heap(true, SortMem); + MemoryContextSwitchTo(oldcxt); + + estate->retval = PointerGetDatum(estate->tuple_store); + } + /* ---------- * exec_stmt_raise Build a message and throw it with *************** *** 1716,1721 **** --- 1803,1810 ---- estate->retisset = func->fn_retset; estate->exitlabel = NULL; + estate->tuple_store = NULL; + estate->trig_nargs = 0; estate->trig_argv = NULL; *************** *** 2102,2108 **** if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else ! elog(ERROR, "unsupported target in exec_stmt_fors()"); } /* --- 2191,2197 ---- if (stmt->row != NULL) row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]); else ! elog(ERROR, "unsupported target in exec_stmt_dynfors()"); } /* Index: src/pl/plpgsql/src/plpgsql.h =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/plpgsql.h,v retrieving revision 1.25 diff -c -r1.25 plpgsql.h *** src/pl/plpgsql/src/plpgsql.h 8 Aug 2002 01:36:05 -0000 1.25 --- src/pl/plpgsql/src/plpgsql.h 19 Aug 2002 19:50:51 -0000 *************** *** 40,47 **** --- 40,49 ---- #include "postgres.h" #include "fmgr.h" + #include "miscadmin.h" #include "executor/spi.h" #include "commands/trigger.h" + #include "utils/tuplestore.h" /********************************************************************** * Definitions *************** *** 90,95 **** --- 92,98 ---- PLPGSQL_STMT_SELECT, PLPGSQL_STMT_EXIT, PLPGSQL_STMT_RETURN, + PLPGSQL_STMT_RETURN_NEXT, PLPGSQL_STMT_RAISE, PLPGSQL_STMT_EXECSQL, PLPGSQL_STMT_DYNEXECUTE, *************** *** 425,430 **** --- 428,440 ---- int retrecno; } PLpgSQL_stmt_return; + typedef struct + { /* RETURN NEXT statement */ + int cmd_type; + int lineno; + PLpgSQL_rec *rec; + PLpgSQL_row *row; + } PLpgSQL_stmt_return_next; typedef struct { /* RAISE statement */ *************** *** 499,504 **** --- 509,516 ---- TupleDesc rettupdesc; bool retisset; char *exitlabel; + + Tuplestorestate *tuple_store; /* SRFs accumulate results here */ int trig_nargs; Datum *trig_argv; Index: src/pl/plpgsql/src/scan.l =================================================================== RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/scan.l,v retrieving revision 1.21 diff -c -r1.21 scan.l *** src/pl/plpgsql/src/scan.l 8 Aug 2002 01:36:05 -0000 1.21 --- src/pl/plpgsql/src/scan.l 19 Aug 2002 19:50:51 -0000 *************** *** 134,139 **** --- 134,140 ---- is { return K_IS; } log { return K_LOG; } loop { return K_LOOP; } + next { return K_NEXT; } not { return K_NOT; } notice { return K_NOTICE; } null { return K_NULL; }