See More

/* * Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden * Distributed under the terms shown in the file COPYRIGHT * found in the root folder of this project or at * http://eng.tada.se/osprojects/COPYRIGHT.html * * @author Thomas Hallgren */ #include "pljava/PgObject_priv.h" #include "pljava/backports.h" #include "pljava/Exception.h" #include "pljava/Invocation.h" #include "pljava/Function.h" #include "pljava/HashMap.h" #include "pljava/Iterator.h" #include "pljava/type/Oid.h" #include "pljava/type/String.h" #include "pljava/type/TriggerData.h" #include "pljava/type/UDT.h" #include #include #include #include #include #include #if (PGSQL_MAJOR_VER == 8 && PGSQL_MINOR_VER == 0) # define PARAM_OIDS(procStruct) (procStruct)->proargtypes #else # define PARAM_OIDS(procStruct) (procStruct)->proargtypes.values #endif static jclass s_Loader_class; static jclass s_ClassLoader_class; static jmethodID s_Loader_getSchemaLoader; static jmethodID s_Loader_getTypeMap; static jmethodID s_ClassLoader_loadClass; static PgObjectClass s_FunctionClass; struct Function_ { struct PgObject_ PgObject_extension; /** * True if the function is not a volatile function (i.e. STABLE or * IMMUTABLE). This means that the function is not allowed to have * side effects. */ bool readOnly; /** * True if this is a UDT function (input/output/receive/send) */ bool isUDT; /** * Java class, i.e. the UDT class or the class where the static method * is defined. */ jclass clazz; union { struct { /* * True if the function is a multi-call function and hence, will * allocate a memory context of its own. */ bool isMultiCall; /* * The number of parameters */ int32 numParams; /* * Array containing one type for eeach parameter. */ Type* paramTypes; /* * The return type. */ Type returnType; /* * The type map used when mapping parameter and return types. We * need to store it here in order to cope with dynamic types (any * and anyarray) */ jobject typeMap; /* * The static method that should be called. */ jmethodID method; } nonudt; struct { /** * The UDT that this function is associated with */ UDT udt; /** * The UDT function to call */ UDTFunction udtFunction; } udt; } func; }; typedef struct ParseResultData { char* buffer; /* The buffer to pfree once we are done */ const char* returnType; const char* className; const char* methodName; const char* parameters; bool isUDT; } ParseResultData; typedef ParseResultData *ParseResult; static HashMap s_funcMap = 0; static jclass s_Loader_class; static jmethodID s_Loader_getSchemaLoader; static void _Function_finalize(PgObject func) { Function self = (Function)func; JNI_deleteGlobalRef(self->clazz); if(!self->isUDT) { if(self->func.nonudt.typeMap != 0) JNI_deleteGlobalRef(self->func.nonudt.typeMap); if(self->func.nonudt.paramTypes != 0) pfree(self->func.nonudt.paramTypes); } } extern void Function_initialize(void); void Function_initialize(void) { s_funcMap = HashMap_create(59, TopMemoryContext); s_Loader_class = JNI_newGlobalRef(PgObject_getJavaClass("org/postgresql/pljava/sqlj/Loader")); s_Loader_getSchemaLoader = PgObject_getStaticJavaMethod(s_Loader_class, "getSchemaLoader", "(Ljava/lang/String;)Ljava/lang/ClassLoader;"); s_Loader_getTypeMap = PgObject_getStaticJavaMethod(s_Loader_class, "getTypeMap", "(Ljava/lang/String;)Ljava/util/Map;"); s_ClassLoader_class = JNI_newGlobalRef(PgObject_getJavaClass("java/lang/ClassLoader")); s_ClassLoader_loadClass = PgObject_getJavaMethod(s_ClassLoader_class, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); s_FunctionClass = PgObjectClass_create("Function", sizeof(struct Function_), _Function_finalize); } static void buildSignature(Function self, StringInfo sign, Type retType, bool alt) { Type* tp = self->func.nonudt.paramTypes; Type* ep = tp + self->func.nonudt.numParams; appendStringInfoChar(sign, '('); while(tp < ep) appendStringInfoString(sign, Type_getJNISignature(*tp++)); if(!self->func.nonudt.isMultiCall && Type_isOutParameter(retType)) appendStringInfoString(sign, Type_getJNISignature(retType)); appendStringInfoChar(sign, ')'); appendStringInfoString(sign, Type_getJNIReturnSignature(retType, self->func.nonudt.isMultiCall, alt)); } static void parseParameters(Function self, Oid* dfltIds, const char* paramDecl) { char c; int idx = 0; int top = self->func.nonudt.numParams; bool lastIsOut = !self->func.nonudt.isMultiCall && Type_isOutParameter(self->func.nonudt.returnType); StringInfoData sign; initStringInfo(&sign); for(;;) { if(idx >= top) { if(!(lastIsOut && idx == top)) ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("To many parameters - expected %d ", top))); } c = *paramDecl++; if(c == 0 || c == ',') { Type deflt = (idx == top) ? self->func.nonudt.returnType : self->func.nonudt.paramTypes[idx]; const char* jtName = Type_getJavaTypeName(deflt); if(strcmp(jtName, sign.data) != 0) { Oid did; Type repl; if(idx == top) /* * Last parameter is the OUT parameter. It has no corresponding * entry in the dfltIds array. */ did = InvalidOid; else did = dfltIds[idx]; repl = Type_fromJavaType(did, sign.data); if(!Type_canReplaceType(repl, deflt)) repl = Type_getCoerceIn(repl, deflt); if(idx == top) self->func.nonudt.returnType = repl; else self->func.nonudt.paramTypes[idx] = repl; } pfree(sign.data); ++idx; if(c == 0) { /* * We are done. */ if(lastIsOut) ++top; if(idx != top) ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("To few parameters - expected %d ", top))); break; } /* * Initialize next parameter. */ initStringInfo(&sign); } else appendStringInfoChar(&sign, c); } } static char* getAS(HeapTuple procTup, char** epHolder) { char c; char* cp1; char* cp2; char* bp; bool atStart = true; bool passedFirst = false; bool isNull = false; Datum tmp = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_prosrc, &isNull); if(isNull) { ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("'AS' clause of Java function cannot be NULL"))); } bp = pstrdup(DatumGetCString(DirectFunctionCall1(textout, tmp))); /* Strip all whitespace except the first one if it occures after * some alpha numeric characers and before some other alpha numeric * characters. We insert a '=' when that happens since it delimits * the return value from the method name. */ cp1 = cp2 = bp; while((c = *cp1++) != 0) { if(isspace(c)) { if(atStart || passedFirst) continue; while((c = *cp1++) != 0) if(!isspace(c)) break; if(c == 0) break; if(isalpha(c)) *cp2++ = '='; passedFirst = true; } atStart = false; if(!isalnum(c)) passedFirst = true; *cp2++ = c; } *cp2 = 0; *epHolder = cp2; return bp; } static void parseUDT(ParseResult info, char* bp, char* ep) { char* ip = ep - 1; while(ip > bp && *ip != ']') --ip; if(ip == bp) { ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("Missing ending ']' in UDT declaration"))); } *ip = 0; /* Terminate class name */ info->className = bp; info->methodName = ip + 1; info->isUDT = true; } static void parseFunction(ParseResult info, HeapTuple procTup) { /* The user's function definition must be the fully * qualified name of a java method short of parameter * signature. */ char* ip; char* ep; char* bp = getAS(procTup, &ep); info->buffer = bp; /* The AS clause can have two formats * * "." [ "(" ["," ... ] ")" ] * or * "UDT" "[" "]" * where is one of "input", "output", "receive" or "send" */ if(ep - bp >= 4 && strncasecmp(bp, "udt[", 4) == 0) { parseUDT(info, bp + 4, ep); return; } info->isUDT = false; /* Scan backwards from ep. */ ip = ep - 1; if(*ip == ')') { /* We have an explicit parameter type declaration */ *ip-- = 0; while(ip > bp && *ip != '(') --ip; if(ip == bp) { ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unbalanced parenthesis"))); } info->parameters = ip + 1; *ip-- = 0; } /* Find last '.' occurrence. */ while(ip > bp && *ip != '.') --ip; if(ip == bp) { ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("Did not find ."))); } info->methodName = ip + 1; *ip = 0; /* Check if we have a return type declaration */ while(--ip > bp) { if(*ip == '=') { info->className = ip + 1; *ip = 0; break; } } if(info->className != 0) info->returnType = bp; else info->className = bp; elog(DEBUG3, "className = '%s', methodName = '%s', parameters = '%s', returnType = '%s'", info->className == 0 ? "null" : info->className, info->methodName == 0 ? "null" : info->methodName, info->parameters == 0 ? "null" : info->parameters, info->returnType == 0 ? "null" : info->returnType); } static jstring getSchemaName(int namespaceOid) { HeapTuple nspTup = PgObject_getValidTuple(NAMESPACEOID, namespaceOid, "namespace"); Form_pg_namespace nspStruct = (Form_pg_namespace)GETSTRUCT(nspTup); jstring schemaName = String_createJavaStringFromNTS(NameStr(nspStruct->nspname)); ReleaseSysCache(nspTup); return schemaName; } static void setupTriggerParams(Function self, ParseResult info) { if(info->parameters != 0) ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("Triggers can not have a java parameter declaration"))); self->func.nonudt.returnType = Type_fromJavaType(InvalidOid, "void"); /* Parameters are not used when calling triggers. */ self->func.nonudt.numParams = 1; self->func.nonudt.paramTypes = (Type*)MemoryContextAlloc(GetMemoryChunkContext(self), sizeof(Type)); self->func.nonudt.paramTypes[0] = Type_fromJavaType(InvalidOid, "org.postgresql.pljava.TriggerData"); } static void setupUDT(Function self, ParseResult info, Form_pg_proc procStruct) { Oid udtId = 0; HeapTuple typeTup; Form_pg_type pgType; if(strcasecmp("input", info->methodName) == 0) { self->func.udt.udtFunction = UDT_input; udtId = procStruct->prorettype; } else if(strcasecmp("output", info->methodName) == 0) { self->func.udt.udtFunction = UDT_output; udtId = PARAM_OIDS(procStruct)[0]; } else if(strcasecmp("receive", info->methodName) == 0) { self->func.udt.udtFunction = UDT_receive; udtId = procStruct->prorettype; } else if(strcasecmp("send", info->methodName) == 0) { self->func.udt.udtFunction = UDT_send; udtId = PARAM_OIDS(procStruct)[0]; } else { ereport(ERROR, ( errcode(ERRCODE_SYNTAX_ERROR), errmsg("Unknown UDT function %s", info->methodName))); } typeTup = PgObject_getValidTuple(TYPEOID, udtId, "type"); pgType = (Form_pg_type)GETSTRUCT(typeTup); self->func.udt.udt = UDT_registerUDT(self->clazz, udtId, pgType, 0, true); ReleaseSysCache(typeTup); } static void setupFunctionParams(Function self, ParseResult info, Form_pg_proc procStruct, PG_FUNCTION_ARGS) { Oid* paramOids; MemoryContext ctx = GetMemoryChunkContext(self); int32 top = (int32)procStruct->pronargs;; self->func.nonudt.numParams = top; self->func.nonudt.isMultiCall = procStruct->proretset; self->func.nonudt.returnType = Type_fromOid(procStruct->prorettype, self->func.nonudt.typeMap); if(top > 0) { int idx; paramOids = PARAM_OIDS(procStruct); self->func.nonudt.paramTypes = (Type*)MemoryContextAlloc(ctx, top * sizeof(Type)); for(idx = 0; idx < top; ++idx) self->func.nonudt.paramTypes[idx] = Type_fromOid(paramOids[idx], self->func.nonudt.typeMap); } else { self->func.nonudt.paramTypes = 0; paramOids = 0; } if(info->parameters != 0) parseParameters(self, paramOids, info->parameters); if(info->returnType != 0) { const char* jtName = Type_getJavaTypeName(self->func.nonudt.returnType); if(strcmp(jtName, info->returnType) != 0) { Type repl = Type_fromJavaType(Type_getOid(self->func.nonudt.returnType), info->returnType); if(!Type_canReplaceType(repl, self->func.nonudt.returnType)) repl = Type_getCoerceOut(repl, self->func.nonudt.returnType); self->func.nonudt.returnType = repl; } } } static void Function_init(Function self, ParseResult info, Form_pg_proc procStruct, PG_FUNCTION_ARGS) { StringInfoData sign; jobject loader; jstring className; /* Get the ClassLoader for the schema that this function belongs to */ jstring schemaName = getSchemaName(procStruct->pronamespace); /* Install the type map for the current schema. This must be done ASAP since * many other functions (including obtaining the loader) depends on it. */ jobject tmp = JNI_callStaticObjectMethod(s_Loader_class, s_Loader_getTypeMap, schemaName); self->func.nonudt.typeMap = JNI_newGlobalRef(tmp); JNI_deleteLocalRef(tmp); self->readOnly = (procStruct->provolatile != PROVOLATILE_VOLATILE); self->isUDT = info->isUDT; currentInvocation->function = self; /* Get the ClassLoader for the schema that this function belongs to */ loader = JNI_callStaticObjectMethod(s_Loader_class, s_Loader_getSchemaLoader, schemaName); JNI_deleteLocalRef(schemaName); elog(DEBUG1, "Loading class %s", info->className); className = String_createJavaStringFromNTS(info->className); tmp = JNI_callObjectMethod(loader, s_ClassLoader_loadClass, className); JNI_deleteLocalRef(loader); JNI_deleteLocalRef(className); self->clazz = (jclass)JNI_newGlobalRef(tmp); JNI_deleteLocalRef(tmp); if(self->isUDT) { setupUDT(self, info, procStruct); return; } if(CALLED_AS_TRIGGER(fcinfo)) { self->func.nonudt.typeMap = 0; setupTriggerParams(self, info); } else { setupFunctionParams(self, info, procStruct, fcinfo); } initStringInfo(&sign); buildSignature(self, &sign, self->func.nonudt.returnType, false); elog(DEBUG1, "Obtaining method %s.%s %s", info->className, info->methodName, sign.data); self->func.nonudt.method = JNI_getStaticMethodIDOrNull(self->clazz, info->methodName, sign.data); if(self->func.nonudt.method == 0) { char* origSign = sign.data; Type altType = 0; Type realRetType = self->func.nonudt.returnType; elog(DEBUG1, "Method %s.%s %s not found", info->className, info->methodName, origSign); if(Type_isPrimitive(self->func.nonudt.returnType)) { /* * One valid reason for not finding the method is when * the return type used in the signature is a primitive and * the true return type of the method is the object class that * corresponds to that primitive. */ altType = Type_getObjectType(self->func.nonudt.returnType); realRetType = altType; } else if(strcmp(Type_getJavaTypeName(self->func.nonudt.returnType), "java.sql.ResultSet") == 0) { /* * Another reason might be that we expected a ResultSetProvider * but the implementation returns a ResultSetHandle that needs to be * wrapped. The wrapping is internal so we retain the original * return type anyway. */ altType = realRetType; } if(altType != 0) { JNI_exceptionClear(); initStringInfo(&sign); buildSignature(self, &sign, altType, true); elog(DEBUG1, "Obtaining method %s.%s %s", info->className, info->methodName, sign.data); self->func.nonudt.method = JNI_getStaticMethodIDOrNull(self->clazz, info->methodName, sign.data); if(self->func.nonudt.method != 0) self->func.nonudt.returnType = realRetType; } if(self->func.nonudt.method == 0) PgObject_throwMemberError(self->clazz, info->methodName, origSign, true, true); if(sign.data != origSign) pfree(origSign); } pfree(sign.data); } static Function Function_create(PG_FUNCTION_ARGS) { ParseResultData info; Function self = (Function)PgObjectClass_allocInstance(s_FunctionClass, TopMemoryContext); HeapTuple procTup = PgObject_getValidTuple(PROCOID, fcinfo->flinfo->fn_oid, "function"); memset(&info, 0, sizeof(ParseResultData)); parseFunction(&info, procTup); Function_init(self, &info, (Form_pg_proc)GETSTRUCT(procTup), fcinfo); pfree(info.buffer); ReleaseSysCache(procTup); return self; } Function Function_getFunction(PG_FUNCTION_ARGS) { Oid funcOid = fcinfo->flinfo->fn_oid; Function func = (Function)HashMap_getByOid(s_funcMap, funcOid); if(func == 0) { func = Function_create(fcinfo); HashMap_putByOid(s_funcMap, funcOid, func); } return func; } jobject Function_getTypeMap(Function self) { return self->func.nonudt.typeMap; } static bool Function_inUse(Function func) { Invocation* ic = currentInvocation; while(ic != 0) { if(ic->function == func) return true; ic = ic->previous; } return false; } void Function_clearFunctionCache(void) { Entry entry; HashMap oldMap = s_funcMap; Iterator itor = Iterator_create(oldMap); s_funcMap = HashMap_create(59, TopMemoryContext); while((entry = Iterator_next(itor)) != 0) { Function func = (Function)Entry_getValue(entry); if(func != 0) { if(Function_inUse(func)) { /* This is the replace_jar function or similar. Just * move it to the new map. */ HashMap_put(s_funcMap, Entry_getKey(entry), func); } else { Entry_setValue(entry, 0); PgObject_free((PgObject)func); } } } PgObject_free((PgObject)itor); PgObject_free((PgObject)oldMap); } Datum Function_invoke(Function self, PG_FUNCTION_ARGS) { Datum retVal; int32 top; jvalue* args; Type invokerType; fcinfo->isnull = false; currentInvocation->function = self; if(self->isUDT) return self->func.udt.udtFunction(self->func.udt.udt, fcinfo); if(self->func.nonudt.isMultiCall && SRF_IS_FIRSTCALL()) Invocation_assertDisconnect(); top = self->func.nonudt.numParams; /* Leave room for one extra parameter. Functions that returns unmapped * composite types must have a single row ResultSet as an OUT parameter. */ args = (jvalue*)palloc((top + 1) * sizeof(jvalue)); invokerType = self->func.nonudt.returnType; if(top > 0) { int32 idx; Type* types = self->func.nonudt.paramTypes; /* a class loader or other mechanism might have connected already. This * connection must be dropped since its parent context is wrong. */ if(Type_isDynamic(invokerType)) invokerType = Type_getRealType(invokerType, get_fn_expr_rettype(fcinfo->flinfo), self->func.nonudt.typeMap); for(idx = 0; idx < top; ++idx) { if(PG_ARGISNULL(idx)) /* * Set this argument to zero (or null in case of object) */ args[idx].j = 0L; else { Type paramType = types[idx]; if(Type_isDynamic(paramType)) paramType = Type_getRealType(paramType, get_fn_expr_argtype(fcinfo->flinfo, idx), self->func.nonudt.typeMap); args[idx] = Type_coerceDatum(paramType, PG_GETARG_DATUM(idx)); } } } retVal = self->func.nonudt.isMultiCall ? Type_invokeSRF(invokerType, self->clazz, self->func.nonudt.method, args, fcinfo) : Type_invoke(invokerType, self->clazz, self->func.nonudt.method, args, fcinfo); pfree(args); return retVal; } Datum Function_invokeTrigger(Function self, PG_FUNCTION_ARGS) { jvalue arg; Datum ret; arg.l = TriggerData_create((TriggerData*)fcinfo->context); if(arg.l == 0) return 0; currentInvocation->function = self; Type_invoke(self->func.nonudt.returnType, self->clazz, self->func.nonudt.method, &arg, fcinfo); fcinfo->isnull = false; if(JNI_exceptionCheck()) ret = 0; else { /* A new Tuple may or may not be created here. If it is, ensure that * it is created in the upper SPI context. */ MemoryContext currCtx = Invocation_switchToUpperContext(); ret = PointerGetDatum(TriggerData_getTriggerReturnTuple(arg.l, &fcinfo->isnull)); /* Triggers are not allowed to set the fcinfo->isnull, even when * they return null. */ fcinfo->isnull = false; MemoryContextSwitchTo(currCtx); } JNI_deleteLocalRef(arg.l); return ret; } bool Function_isCurrentReadOnly(void) { /* function will be 0 during resolve of class and java function. At * that time, no updates are allowed (or needed). */ return (currentInvocation->function == 0) ? true : currentInvocation->function->readOnly; }