Index: doc/src/sgml/plpgsql.sgml =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/doc/src/sgml/plpgsql.sgml,v retrieving revision 1.75 diff -c -r1.75 plpgsql.sgml *** doc/src/sgml/plpgsql.sgml 2 Jul 2005 08:59:47 -0000 1.75 --- doc/src/sgml/plpgsql.sgml 6 Jul 2005 13:26:22 -0000 *************** *** 2117,2123 **** The condition names can be any of those shown in . A category name matches ! any error within its category. The special condition name OTHERS matches every error type except QUERY_CANCELED. (It is possible, but often unwise, to trap --- 2117,2125 ---- The condition names can be any of those shown in . A category name matches ! any error within its category. You can use exception variable as ! condition name. Exception variable is declared with type ! EXCEPTION The special condition name OTHERS matches every error type except QUERY_CANCELED. (It is possible, but often unwise, to trap *************** *** 2571,2577 **** raise errors. ! RAISE level 'format' , expression , ...; Possible levels are DEBUG, --- 2573,2580 ---- raise errors. ! RAISE level ! system exception|exception variable 'format' , expression , ...; Possible levels are DEBUG, *************** *** 2588,2593 **** --- 2591,2600 ---- variables. See for more information. + + + You can specify any system exception or any user exception. + Inside the format string, % is replaced by the Index: src/include/utils/elog.h =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/include/utils/elog.h,v retrieving revision 1.79 diff -c -r1.79 elog.h *** src/include/utils/elog.h 10 Jun 2005 16:23:10 -0000 1.79 --- src/include/utils/elog.h 6 Jul 2005 13:26:22 -0000 *************** *** 61,66 **** --- 61,72 ---- (PGSIXBIT(ch1) + (PGSIXBIT(ch2) << 6) + (PGSIXBIT(ch3) << 12) + \ (PGSIXBIT(ch4) << 18) + (PGSIXBIT(ch5) << 24)) + #define MAKE_SQLSTATE_STR(str) \ + ( \ + AssertMacro(strlen(str) == 5), \ + MAKE_SQLSTATE(str[0], str[1], str[2], str[3], str[4]) \ + ) + /* These macros depend on the fact that '0' becomes a zero in SIXBIT */ #define ERRCODE_TO_CATEGORY(ec) ((ec) & ((1 << 12) - 1)) #define ERRCODE_IS_CATEGORY(ec) (((ec) & ~((1 << 12) - 1)) == 0) Index: src/pl/plpgsql/src/gram.y =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/gram.y,v retrieving revision 1.80 diff -c -r1.80 gram.y *** src/pl/plpgsql/src/gram.y 2 Jul 2005 17:01:59 -0000 1.80 --- src/pl/plpgsql/src/gram.y 6 Jul 2005 13:38:35 -0000 *************** *** 103,108 **** --- 103,109 ---- PLpgSQL_exception_block *exception_block; PLpgSQL_nsitem *nsitem; PLpgSQL_diag_item *diagitem; + PLpgSQL_user_exc *user_exc; } %type decl_sect *************** *** 142,150 **** %type exception_sect %type proc_exception %type proc_conditions ! ! %type raise_level %type raise_msg %type getdiag_list --- 143,152 ---- %type exception_sect %type proc_exception %type proc_conditions + %type exception_name + %type decl_sqlstate sqlstate_defn ! %type raise_level opt_raise_exc %type raise_msg %type getdiag_list *************** *** 221,226 **** --- 223,229 ---- %token T_LABEL %token T_WORD %token T_ERROR + %token T_EXCEPTION %token O_OPTION %token O_DUMP *************** *** 348,354 **** (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row or record variable cannot be NOT NULL"))); } ! if ($5 != NULL) { if (var->dtype == PLPGSQL_DTYPE_VAR) ((PLpgSQL_var *) var)->default_val = $5; --- 351,357 ---- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row or record variable cannot be NOT NULL"))); } ! if ($5) { if (var->dtype == PLPGSQL_DTYPE_VAR) ((PLpgSQL_var *) var)->default_val = $5; *************** *** 358,363 **** --- 361,378 ---- errmsg("default value for row or record variable is not supported"))); } } + | decl_varname K_EXCEPTION decl_sqlstate + { + PLpgSQL_user_exc *exc_var; + PLpgSQL_type dtype; + + dtype.typname = "exception"; + dtype.ttype = PLPGSQL_TTYPE_EXCEPTION; + + exc_var = (PLpgSQL_user_exc *) plpgsql_build_variable($1.name, $1.lineno, + &dtype, true); + exc_var->sqlstate = $3; + } | decl_varname K_ALIAS K_FOR decl_aliasitem ';' { plpgsql_ns_additem($4->itemtype, *************** *** 563,572 **** | K_DEFAULT ; ! proc_sect : { ! $$ = NIL; } | proc_stmts { $$ = $1; } ; --- 578,615 ---- | K_DEFAULT ; ! decl_sqlstate : ';' ! { $$ = plpgsql_new_user_sqlstate(); } ! | decl_defkey sqlstate_defn ';' { ! $$ = $2; } + ; + + sqlstate_defn : T_STRING + { + char *state_str = plpgsql_get_string_value(); + + if (strlen(state_str) != 5) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid SQLSTATE value \"%s\" for " + "user-defined exception", state_str), + errdetail("SQLSTATE values must be 5 characters in length"))); + + if (strncmp(state_str, "U0", 2) == 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid SQLSTATE value \"%s\" for " + "user-defined exception", state_str), + errhint("Class \"U0\" is reserved for default values user's exceptions."))); + + $$ = MAKE_SQLSTATE_STR(state_str); + } + ; + + proc_sect : + { $$ = NIL; } | proc_stmts { $$ = $1; } ; *************** *** 1184,1190 **** } ; ! stmt_raise : K_RAISE lno raise_level raise_msg { PLpgSQL_stmt_raise *new; int tok; --- 1227,1233 ---- } ; ! stmt_raise : K_RAISE lno raise_level opt_raise_exc raise_msg { PLpgSQL_stmt_raise *new; int tok; *************** *** 1194,1202 **** new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; ! new->message = $4; new->params = NIL; tok = yylex(); /* --- 1237,1256 ---- new->cmd_type = PLPGSQL_STMT_RAISE; new->lineno = $2; new->elog_level = $3; ! new->message = $5; new->params = NIL; + /* No exception variable or SQLSTATE value specified? */ + if ($4 == -1) + { + if (new->elog_level >= ERROR) + new->sqlstate = ERRCODE_RAISE_EXCEPTION; + else + new->sqlstate = 0; + } + else + new->sqlstate = $4; + tok = yylex(); /* *************** *** 1266,1271 **** --- 1320,1344 ---- } ; + opt_raise_exc : T_EXCEPTION + { + $$ = yylval.user_exc->sqlstate; + } + | T_WORD + { + PLpgSQL_condition *c = plpgsql_parse_err_condition(yytext); + /* Don't allow "OTHERS" to be thrown via RAISE */ + if (c->sqlerrstate == 0) + yyerror("illegal SQLSTATE value"); + $$ = c->sqlerrstate; + pfree(c); + } + | /* EMPTY */ + { + $$ = -1; + } + ; + stmt_execsql : execsql_start lno { PLpgSQL_stmt_execsql *new; *************** *** 1285,1292 **** PLpgSQL_expr *expr; int endtoken; ! expr = read_sql_construct(K_INTO, ';', "INTO|;", "SELECT ", ! true, true, &endtoken); new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; --- 1358,1366 ---- PLpgSQL_expr *expr; int endtoken; ! expr = read_sql_construct(K_INTO, ';', "INTO or ;", ! "SELECT ", true, true, ! &endtoken); new = palloc(sizeof(PLpgSQL_stmt_dynexecute)); new->cmd_type = PLPGSQL_STMT_DYNEXECUTE; *************** *** 1581,1600 **** } ; ! proc_conditions : proc_conditions K_OR opt_lblname ! { ! PLpgSQL_condition *old; ! for (old = $1; old->next != NULL; old = old->next) ! /* skip */ ; ! old->next = plpgsql_parse_err_condition($3); ! $$ = $1; ! } ! | opt_lblname ! { ! $$ = plpgsql_parse_err_condition($1); ! } ; expr_until_semi : --- 1655,1686 ---- } ; ! proc_conditions : proc_conditions K_OR exception_name ! { ! PLpgSQL_condition *old; ! ! for (old = $1; old->next != NULL; old = old->next) ! /* skip */ ; ! old->next = plpgsql_parse_err_condition($3); ! $$ = $1; ! } ! | exception_name ! { ! $$ = plpgsql_parse_err_condition($1); ! } ! ; ! exception_name : T_WORD ! { ! char *name; ! plpgsql_convert_ident(yytext, &name, 1); ! $$ = name; ! } ! | T_EXCEPTION ! { ! $$ = yylval.user_exc->refname; ! } ; expr_until_semi : Index: src/pl/plpgsql/src/pl_comp.c =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/pl_comp.c,v retrieving revision 1.91 diff -c -r1.91 pl_comp.c *** src/pl/plpgsql/src/pl_comp.c 10 Jun 2005 16:23:11 -0000 1.91 --- src/pl/plpgsql/src/pl_comp.c 6 Jul 2005 13:26:22 -0000 *************** *** 80,85 **** --- 80,87 ---- bool plpgsql_DumpExecTree = false; bool plpgsql_check_syntax = false; + static int plpgsql_user_exc_counter; + PLpgSQL_function *plpgsql_curr_compile; /* A context appropriate for short-term allocs during compilation */ *************** *** 316,321 **** --- 318,325 ---- plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc); datums_last = 0; + plpgsql_user_exc_counter = 0; + /* * Do extra syntax checks when validating the function * definition. We skip this when actually compiling functions for *************** *** 905,910 **** --- 909,918 ---- plpgsql_yylval.row = (PLpgSQL_row *) (plpgsql_Datums[nse->itemno]); return T_ROW; + case PLPGSQL_NSTYPE_EXCEPTION: + plpgsql_yylval.user_exc = (PLpgSQL_user_exc *) (plpgsql_Datums[nse->itemno]); + return T_EXCEPTION; + default: return T_ERROR; } *************** *** 1626,1631 **** --- 1634,1658 ---- result = (PLpgSQL_variable *) rec; break; } + case PLPGSQL_TTYPE_EXCEPTION: + { + /* The exception type */ + PLpgSQL_user_exc *exception; + + exception = palloc0(sizeof(PLpgSQL_user_exc)); + exception->dtype = PLPGSQL_DTYPE_EXCEPTION; + exception->refname = pstrdup(refname); + exception->lineno = lineno; + /* caller should define sqlstate! */ + + plpgsql_adddatum((PLpgSQL_datum *) exception); + if (add2namespace) + plpgsql_ns_additem(PLPGSQL_NSTYPE_EXCEPTION, + exception->dno, + refname); + result = (PLpgSQL_variable *) exception; + break; + } case PLPGSQL_TTYPE_PSEUDO: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), *************** *** 1883,1890 **** * plpgsql_parse_err_condition * Generate PLpgSQL_condition entry(s) for an exception condition name * ! * This has to be able to return a list because there are some duplicate ! * names in the table of error code names. */ PLpgSQL_condition * plpgsql_parse_err_condition(char *condname) --- 1910,1919 ---- * plpgsql_parse_err_condition * Generate PLpgSQL_condition entry(s) for an exception condition name * ! * This has to be able to return a list because there are some ! * duplicate names in the table of error code names. Note that the ! * exception name should already be normalized (e.g. via ! * plpgsql_convert_ident). */ PLpgSQL_condition * plpgsql_parse_err_condition(char *condname) *************** *** 1892,1902 **** int i; PLpgSQL_condition *new; PLpgSQL_condition *prev; ! ! /* ! * XXX Eventually we will want to look for user-defined exception ! * names here. ! */ /* * OTHERS is represented as code 0 (which would map to '00000', but we --- 1921,1927 ---- int i; PLpgSQL_condition *new; PLpgSQL_condition *prev; ! PLpgSQL_nsitem *nse; /* * OTHERS is represented as code 0 (which would map to '00000', but we *************** *** 1924,1929 **** --- 1949,1981 ---- } } + /* + * If "condname" is not the name of any builtin exceptions, look + * for a user-defined exception variable with that name. + */ + if (!prev) + { + /* + * Do a lookup on the compiler's namestack + */ + nse = plpgsql_ns_lookup(condname, NULL); + + if (nse != NULL) + { + PLpgSQL_user_exc *exc_var; + + exc_var = (PLpgSQL_user_exc *) (plpgsql_Datums[nse->itemno]); + if (nse->itemtype == PLPGSQL_NSTYPE_EXCEPTION) + { + new = palloc(sizeof(PLpgSQL_condition)); + new->sqlerrstate = exc_var->sqlstate; + new->condname = condname; + new->next = prev; + prev = new; + } + } + } + if (!prev) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), *************** *** 2171,2179 **** plpgsql_HashEnt *hentry; hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable, ! (void *) function->fn_hashkey, HASH_REMOVE, NULL); if (hentry == NULL) elog(WARNING, "trying to delete function that does not exist"); } --- 2223,2246 ---- plpgsql_HashEnt *hentry; hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable, ! (void *) function->fn_hashkey, HASH_REMOVE, NULL); if (hentry == NULL) elog(WARNING, "trying to delete function that does not exist"); } + + #define MAX_USER_EXCPT 999 + + int + plpgsql_new_user_sqlstate(void) + { + char str[6]; + + if (plpgsql_user_exc_counter >= MAX_USER_EXCPT) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("Too many user-defined exceptions"))); + snprintf(str, sizeof(str), "U0%03d", ++plpgsql_user_exc_counter); + return MAKE_SQLSTATE_STR(str); + } Index: src/pl/plpgsql/src/pl_exec.c =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/pl_exec.c,v retrieving revision 1.149 diff -c -r1.149 pl_exec.c *** src/pl/plpgsql/src/pl_exec.c 26 Jun 2005 22:05:42 -0000 1.149 --- src/pl/plpgsql/src/pl_exec.c 6 Jul 2005 13:56:49 -0000 *************** *** 723,728 **** --- 723,733 ---- result = datum; break; + case PLPGSQL_DTYPE_EXCEPTION: + /* XXX: this is read-only at runtime -- just copy as well? */ + result = NULL; + break; + default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); result = NULL; /* keep compiler quiet */ *************** *** 825,830 **** --- 830,836 ---- case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_ARRAYELEM: + case PLPGSQL_DTYPE_EXCEPTION: break; default: *************** *** 2062,2069 **** estate->err_text = raise_skip_msg; /* suppress traceback of raise */ ereport(stmt->elog_level, ! ((stmt->elog_level >= ERROR) ? errcode(ERRCODE_RAISE_EXCEPTION) : 0, ! errmsg_internal("%s", plpgsql_dstring_get(&ds)))); estate->err_text = NULL; /* un-suppress... */ --- 2068,2075 ---- estate->err_text = raise_skip_msg; /* suppress traceback of raise */ ereport(stmt->elog_level, ! (errcode(stmt->sqlstate), ! errmsg_internal("%s", plpgsql_dstring_get(&ds)))); estate->err_text = NULL; /* un-suppress... */ Index: src/pl/plpgsql/src/plpgsql.h =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/pl/plpgsql/src/plpgsql.h,v retrieving revision 1.64 diff -c -r1.64 plpgsql.h *** src/pl/plpgsql/src/plpgsql.h 22 Jun 2005 01:35:02 -0000 1.64 --- src/pl/plpgsql/src/plpgsql.h 6 Jul 2005 13:26:22 -0000 *************** *** 58,64 **** PLPGSQL_NSTYPE_LABEL, PLPGSQL_NSTYPE_VAR, PLPGSQL_NSTYPE_ROW, ! PLPGSQL_NSTYPE_REC }; /* ---------- --- 58,65 ---- PLPGSQL_NSTYPE_LABEL, PLPGSQL_NSTYPE_VAR, PLPGSQL_NSTYPE_ROW, ! PLPGSQL_NSTYPE_REC, ! PLPGSQL_NSTYPE_EXCEPTION }; /* ---------- *************** *** 73,79 **** PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_ARRAYELEM, PLPGSQL_DTYPE_EXPR, ! PLPGSQL_DTYPE_TRIGARG }; /* ---------- --- 74,81 ---- PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_ARRAYELEM, PLPGSQL_DTYPE_EXPR, ! PLPGSQL_DTYPE_TRIGARG, ! PLPGSQL_DTYPE_EXCEPTION }; /* ---------- *************** *** 85,91 **** PLPGSQL_TTYPE_SCALAR, /* scalar types and domains */ PLPGSQL_TTYPE_ROW, /* composite types */ PLPGSQL_TTYPE_REC, /* RECORD pseudotype */ ! PLPGSQL_TTYPE_PSEUDO /* other pseudotypes */ }; /* ---------- --- 87,94 ---- PLPGSQL_TTYPE_SCALAR, /* scalar types and domains */ PLPGSQL_TTYPE_ROW, /* composite types */ PLPGSQL_TTYPE_REC, /* RECORD pseudotype */ ! PLPGSQL_TTYPE_PSEUDO, /* other pseudotypes */ ! PLPGSQL_TTYPE_EXCEPTION /* user-defined exception variables */ }; /* ---------- *************** *** 173,179 **** * PLpgSQL_trigarg */ typedef struct ! { /* Generic datum array item */ int dtype; int dno; } PLpgSQL_datum; --- 176,182 ---- * PLpgSQL_trigarg */ typedef struct ! { /* Generic datum array item */ int dtype; int dno; } PLpgSQL_datum; *************** *** 190,197 **** int lineno; } PLpgSQL_variable; typedef struct PLpgSQL_expr ! { /* SQL Query to plan and execute */ int dtype; int exprno; char *query; --- 193,209 ---- int lineno; } PLpgSQL_variable; + typedef struct + { /* User-defined exception variable */ + int dtype; + int dno; + char *refname; + int lineno; + int sqlstate; + } PLpgSQL_user_exc; + typedef struct PLpgSQL_expr ! { /* SQL Query to plan and execute */ int dtype; int exprno; char *query; *************** *** 292,298 **** typedef struct ! { /* Item in the compilers namestack */ int itemtype; int itemno; char name[1]; --- 304,310 ---- typedef struct ! { /* Item in the compiler's namestack */ int itemtype; int itemno; char name[1]; *************** *** 516,521 **** --- 528,534 ---- int cmd_type; int lineno; int elog_level; + int sqlstate; char *message; List *params; /* list of expressions */ } PLpgSQL_stmt_raise; *************** *** 688,693 **** --- 701,708 ---- extern int plpgsql_add_initdatums(int **varnos); extern void plpgsql_HashTableInit(void); extern void plpgsql_compile_error_callback(void *arg); + extern int plpgsql_new_user_sqlstate(void); + /* ---------- * Functions in pl_handler.c Index: src/test/regress/expected/plpgsql.out =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/expected/plpgsql.out,v retrieving revision 1.38 diff -c -r1.38 plpgsql.out *** src/test/regress/expected/plpgsql.out 2 Jul 2005 08:59:48 -0000 1.38 --- src/test/regress/expected/plpgsql.out 6 Jul 2005 15:03:56 -0000 *************** *** 2721,2723 **** --- 2721,2778 ---- $$ language plpgsql; ERROR: end label "outer_label" specified for unlabelled block CONTEXT: compile of PL/pgSQL function "end_label4" near line 5 + -- user-defined exceptions + -- should fail: illegal sqlstate for exception + create function innerfx() returns void as $$ + declare + my_exc exception = 'U0001'; + begin -- using msgtext as one param of exception + raise exception my_exc '%', CURRENT_TIMESTAMP; + end; + $$ language plpgsql; + ERROR: invalid SQLSTATE value "U0001" for user-defined exception + HINT: Class "U0" is reserved for default values user's exceptions. + CONTEXT: compile of PL/pgSQL function "innerfx" near line 2 + create function innerfx() returns integer as $$ + declare + my_exc1 exception = 'U1001'; + begin -- using msgtext as one param of exception + raise exception my_exc1 '%', 100.34::numeric; + return 1; + end $$ language plpgsql; + create function outerfx() returns integer as $$ + declare + my_excpt exception = 'U1001'; + alias_div_by_zero exception = '22012'; + def_sqlstate exception; + begin + begin + raise exception def_sqlstate 'foo'; + exception when def_sqlstate then + raise notice '01 catch: %, %', sqlstate, sqlerrm; + end; + begin + raise notice '%', innerfx(); + exception when my_excpt then + raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric; + end; + begin + raise exception alias_div_by_zero 'testing'; + exception when division_by_zero then + raise notice 'Divison by zero: %, %', sqlstate, sqlerrm; + end; + return 1; + end; $$ language plpgsql; + select innerfx(); + ERROR: 100.34 + select outerfx(); + NOTICE: 01 catch: U0001, foo + NOTICE: 02 catch: U1001, 100.34 + NOTICE: Divison by zero: 22012, testing + outerfx + --------- + 1 + (1 row) + + drop function outerfx(); + drop function innerfx(); Index: src/test/regress/sql/plpgsql.sql =================================================================== RCS file: /Users/neilc/local/cvs/pgsql/src/test/regress/sql/plpgsql.sql,v retrieving revision 1.33 diff -c -r1.33 plpgsql.sql *** src/test/regress/sql/plpgsql.sql 2 Jul 2005 08:59:48 -0000 1.33 --- src/test/regress/sql/plpgsql.sql 6 Jul 2005 13:57:44 -0000 *************** *** 2280,2282 **** --- 2280,2331 ---- end loop outer_label; end; $$ language plpgsql; + + -- user-defined exceptions + + -- should fail: illegal sqlstate for exception + create function innerfx() returns void as $$ + declare + my_exc exception = 'U0001'; + begin -- using msgtext as one param of exception + raise exception my_exc '%', CURRENT_TIMESTAMP; + end; + $$ language plpgsql; + + create function innerfx() returns integer as $$ + declare + my_exc1 exception = 'U1001'; + begin -- using msgtext as one param of exception + raise exception my_exc1 '%', 100.34::numeric; + return 1; + end $$ language plpgsql; + + create function outerfx() returns integer as $$ + declare + my_excpt exception = 'U1001'; + alias_div_by_zero exception = '22012'; + def_sqlstate exception; + begin + begin + raise exception def_sqlstate 'foo'; + exception when def_sqlstate then + raise notice '01 catch: %, %', sqlstate, sqlerrm; + end; + begin + raise notice '%', innerfx(); + exception when my_excpt then + raise notice '02 catch: %, %', sqlstate, sqlerrm::numeric; + end; + begin + raise exception alias_div_by_zero 'testing'; + exception when division_by_zero then + raise notice 'Divison by zero: %, %', sqlstate, sqlerrm; + end; + return 1; + end; $$ language plpgsql; + + select innerfx(); + select outerfx(); + + drop function outerfx(); + drop function innerfx();