Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2025
Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Chapman Flack
*/
package org.postgresql.pljava.example.annotation;

import java.sql.Connection;
import static java.sql.DriverManager.getConnection;
import java.sql.SQLException;
import java.sql.Statement;

import org.postgresql.pljava.annotation.Function;
import org.postgresql.pljava.annotation.SQLType;

/**
* Illustrates how not to handle an exception thrown by a call into PostgreSQL.
*<p>
* Such an exception must either be rethrown (or result in some higher-level
* exception being rethrown) or cleared by rolling back the transaction or
* a previously-established savepoint. If it is simply caught and not propagated
* and the error condition is not cleared, no further calls into PostgreSQL
* functionality can be made within the containing transaction.
*
* @see <a href="../../RELDOTS/use/catch.html">Catching PostgreSQL exceptions
* in Java</a>
*/
public interface MishandledExceptions
{
/**
* Executes an SQL statement that produces an error (twice, if requested),
* catching the resulting exception but not propagating it or rolling back
* a savepoint; then throws an unrelated exception if succeed is false.
*/
@Function(schema = "javatest")
static String mishandle(
boolean twice, @SQLType(defaultValue="true")boolean succeed)
throws SQLException
{
String rslt = null;
do
{
try
(
Connection c = getConnection("jdbc:default:connection");
Statement s = c.createStatement();
)
{
s.execute("DO LANGUAGE \"no such language\" 'no such thing'");
}
catch ( SQLException e )
{
rslt = e.toString();
/* nothing rethrown, nothing rolled back <- BAD PRACTICE */
}
}
while ( ! (twice ^= true) );

if ( succeed )
return rslt;

throw new SQLException("unrelated");
}
}
43 changes: 39 additions & 4 deletions pljava-so/src/main/c/Exception.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2023 Tada AB and other contributors, as listed below.
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
Expand Down Expand Up @@ -27,12 +27,15 @@ jmethodID Class_getCanonicalName;

jclass ServerException_class;
jmethodID ServerException_getErrorData;
jmethodID ServerException_init;
jmethodID ServerException_obtain;

jclass Throwable_class;
jmethodID Throwable_getMessage;
jmethodID Throwable_printStackTrace;

static jclass UnhandledPGException_class;
static jmethodID UnhandledPGException_obtain;

jclass IllegalArgumentException_class;
jmethodID IllegalArgumentException_init;

Expand All @@ -46,6 +49,11 @@ jmethodID UnsupportedOperationException_init;
jclass NoSuchFieldError_class;
jclass NoSuchMethodError_class;

bool Exception_isPGUnhandled(jthrowable ex)
{
return JNI_isInstanceOf(ex, UnhandledPGException_class);
}

void
Exception_featureNotSupported(const char* requestedFeature, const char* introVersion)
{
Expand Down Expand Up @@ -161,6 +169,22 @@ void Exception_throwSPI(const char* function, int errCode)
SPI_result_code_string(errCode));
}

void Exception_throw_unhandled()
{
jobject ex;
PG_TRY();
{
ex = JNI_callStaticObjectMethodLocked(
UnhandledPGException_class, UnhandledPGException_obtain);
JNI_throw(ex);
}
PG_CATCH();
{
elog(WARNING, "Exception while generating exception");
}
PG_END_TRY();
}

void Exception_throw_ERROR(const char* funcName)
{
jobject ex;
Expand All @@ -170,7 +194,8 @@ void Exception_throw_ERROR(const char* funcName)

FlushErrorState();

ex = JNI_newObject(ServerException_class, ServerException_init, ed);
ex = JNI_callStaticObjectMethodLocked(
ServerException_class, ServerException_obtain, ed);
currentInvocation->errorOccurred = true;

elog(DEBUG2, "Exception in function %s", funcName);
Expand Down Expand Up @@ -216,7 +241,17 @@ extern void Exception_initialize2(void);
void Exception_initialize2(void)
{
ServerException_class = (jclass)JNI_newGlobalRef(PgObject_getJavaClass("org/postgresql/pljava/internal/ServerException"));
ServerException_init = PgObject_getJavaMethod(ServerException_class, "<init>", "(Lorg/postgresql/pljava/internal/ErrorData;)V");
ServerException_obtain = PgObject_getStaticJavaMethod(
ServerException_class, "obtain",
"(Lorg/postgresql/pljava/internal/ErrorData;)"
"Lorg/postgresql/pljava/internal/ServerException;");

ServerException_getErrorData = PgObject_getJavaMethod(ServerException_class, "getErrorData", "()Lorg/postgresql/pljava/internal/ErrorData;");

UnhandledPGException_class = (jclass)JNI_newGlobalRef(
PgObject_getJavaClass(
"org/postgresql/pljava/internal/UnhandledPGException"));
UnhandledPGException_obtain = PgObject_getStaticJavaMethod(
UnhandledPGException_class, "obtain",
"()Lorg/postgresql/pljava/internal/UnhandledPGException;");
}
22 changes: 20 additions & 2 deletions pljava-so/src/main/c/Invocation.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2021 Tada AB and other contributors, as listed below.
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
Expand All @@ -24,7 +24,9 @@

#define LOCAL_FRAME_SIZE 128

static jclass s_Invocation_class;
static jmethodID s_Invocation_onExit;
static jfieldID s_Invocation_s_unhandled;
static unsigned int s_callLevel = 0;

Invocation* currentInvocation;
Expand Down Expand Up @@ -85,8 +87,11 @@ void Invocation_initialize(void)
};

cls = PgObject_getJavaClass("org/postgresql/pljava/jdbc/Invocation");
s_Invocation_class = JNI_newGlobalRef(cls);
PgObject_registerNatives2(cls, invocationMethods);
s_Invocation_onExit = PgObject_getJavaMethod(cls, "onExit", "(Z)V");
s_Invocation_s_unhandled = PgObject_getStaticJavaField(
cls, "s_unhandled", "Ljava/sql/SQLException;");
JNI_deleteLocalRef(cls);
}

Expand Down Expand Up @@ -191,6 +196,7 @@ void Invocation_popInvocation(bool wasException)
{
Invocation* ctx = currentInvocation->previous;
bool heavy = FRAME_LIMITS_PUSHED == currentInvocation->frameLimits;
bool unhandled = currentInvocation->errorOccurred;

/*
* If the more heavyweight parameter-frame push wasn't done, do
Expand All @@ -215,11 +221,23 @@ void Invocation_popInvocation(bool wasException)
{
JNI_callVoidMethodLocked(
currentInvocation->invocation, s_Invocation_onExit,
(wasException || currentInvocation->errorOccurred)
(wasException || unhandled)
? JNI_TRUE : JNI_FALSE);
JNI_deleteGlobalRef(currentInvocation->invocation);
}

if ( unhandled )
{
jthrowable ex = (jthrowable)JNI_getStaticObjectField(
s_Invocation_class, s_Invocation_s_unhandled);
bool already_hit = Exception_isPGUnhandled(ex);
JNI_setStaticObjectField(
s_Invocation_class, s_Invocation_s_unhandled, NULL);

JNI_exceptionStacktraceAtLevel(ex,
wasException ? DEBUG2 : already_hit ? WARNING : DEBUG1);
}

/*
* Do nativeRelease for any DualState instances scoped to this invocation.
*/
Expand Down
35 changes: 26 additions & 9 deletions pljava-so/src/main/c/JNICalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,13 @@ static void elogExceptionMessage(JNIEnv* env, jthrowable exh, int logLevel)
ereport(logLevel, (errcode(sqlState), errmsg("%s", buf.data)));
}

static void printStacktrace(JNIEnv* env, jobject exh)
static void printStacktrace(JNIEnv* env, jobject exh, int elevel)
{
#ifndef _MSC_VER
if(DEBUG1 >= log_min_messages || DEBUG1 >= client_min_messages)
#if 100002<=PG_VERSION_NUM || \
90607<=PG_VERSION_NUM && PG_VERSION_NUM<100000 || \
90511<=PG_VERSION_NUM && PG_VERSION_NUM< 90600 || \
! defined(_MSC_VER)
if(elevel >= log_min_messages || elevel >= client_min_messages)
#else
/* This is gross, but only happens as often as an exception escapes Java
* code to be rethrown. There is some renewed interest on pgsql-hackers to
Expand All @@ -217,7 +220,7 @@ static void printStacktrace(JNIEnv* env, jobject exh)
|| 0 == strncmp("debug", PG_GETCONFIGOPTION("client_min_messages"), 5) )
#endif
{
int currLevel = Backend_setJavaLogLevel(DEBUG1);
int currLevel = Backend_setJavaLogLevel(elevel);
(*env)->CallVoidMethod(env, exh, Throwable_printStackTrace);
(*env)->ExceptionOccurred(env); /* sop for JNI exception-check check */
Backend_setJavaLogLevel(currLevel);
Expand All @@ -236,7 +239,7 @@ static void endCall(JNIEnv* env)
jniEnv = env;
if(exh != 0)
{
printStacktrace(env, exh);
printStacktrace(env, exh, DEBUG1);
if((*env)->IsInstanceOf(env, exh, ServerException_class))
{
/* Rethrow the server error.
Expand Down Expand Up @@ -266,7 +269,7 @@ static void endCallMonitorHeld(JNIEnv* env)
jniEnv = env;
if(exh != 0)
{
printStacktrace(env, exh);
printStacktrace(env, exh, DEBUG1);
if((*env)->IsInstanceOf(env, exh, ServerException_class))
{
/* Rethrow the server error.
Expand Down Expand Up @@ -329,8 +332,7 @@ bool beginNative(JNIEnv* env)
* backend at this point.
*/
env = JNI_setEnv(env);
Exception_throw(ERRCODE_INTERNAL_ERROR,
"An attempt was made to call a PostgreSQL backend function after an elog(ERROR) had been issued");
Exception_throw_unhandled();
JNI_setEnv(env);
return false;
}
Expand Down Expand Up @@ -950,12 +952,20 @@ void JNI_exceptionDescribe(void)
if(exh != 0)
{
(*env)->ExceptionClear(env);
printStacktrace(env, exh);
printStacktrace(env, exh, DEBUG1);
elogExceptionMessage(env, exh, WARNING);
}
END_JAVA
}

void JNI_exceptionStacktraceAtLevel(jthrowable exh, int elevel)
{
BEGIN_JAVA
elogExceptionMessage(env, exh, elevel);
printStacktrace(env, exh, elevel);
END_JAVA
}

jthrowable JNI_exceptionOccurred(void)
{
jthrowable result;
Expand Down Expand Up @@ -1612,6 +1622,13 @@ void JNI_setShortArrayRegion(jshortArray array, jsize start, jsize len, jshort*
END_JAVA
}

void JNI_setStaticObjectField(jclass clazz, jfieldID field, jobject value)
{
BEGIN_JAVA
(*env)->SetStaticObjectField(env, clazz, field, value);
END_JAVA
}

void JNI_setThreadLock(jobject lockObject)
{
BEGIN_JAVA
Expand Down
22 changes: 19 additions & 3 deletions pljava-so/src/main/include/pljava/Exception.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2023 Tada AB and other contributors, as listed below.
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
Expand Down Expand Up @@ -29,7 +29,15 @@ extern "C" {
*******************************************************************/

/*
* Trows an UnsupportedOperationException informing the caller that the
* Tests whether ex is an instance of UnhandledPGException, an SQLException
* subclass that is created when an attempted call into PostgreSQL internals
* cannot be made because of an earlier unhandled ServerException.
* An UnhandledPGException will have, as its cause, the earlier ServerException.
*/
extern bool Exception_isPGUnhandled(jthrowable ex);

/*
* Throws an UnsupportedOperationException informing the caller that the
* requested feature doesn't exist in the current version, it was introduced
* starting with the intro version.
*/
Expand Down Expand Up @@ -65,11 +73,19 @@ extern void Exception_throwSPI(const char* function, int errCode);

/*
* This method will raise a Java ServerException based on an ErrorData obtained
* by a call to CopyErrorData. It will NOT do a longjmp. It's intended use is
* by a call to CopyErrorData. It will NOT do a longjmp. Its intended use is
* in PG_CATCH clauses.
*/
extern void Exception_throw_ERROR(const char* function);

/*
* This method will raise a Java UnhandledPGException based on a ServerException
* that has been stored at some earlier time and not yet resolved (as by
* a rollback). Its intended use is from beginNative in JNICalls when
* errorOccurred is found to be true.
*/
extern void Exception_throw_unhandled(void);

/*
* Throw an exception indicating that wanted member could not be
* found. This is an ereport(ERROR...) so theres' no return from
Expand Down
4 changes: 3 additions & 1 deletion pljava-so/src/main/include/pljava/JNICalls.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2004-2021 Tada AB and other contributors, as listed below.
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
Expand Down Expand Up @@ -181,6 +181,7 @@ extern jint JNI_destroyVM(JavaVM *vm);
extern jboolean JNI_exceptionCheck(void);
extern void JNI_exceptionClear(void);
extern void JNI_exceptionDescribe(void);
extern void JNI_exceptionStacktraceAtLevel(jthrowable exh, int elevel);
extern jthrowable JNI_exceptionOccurred(void);
extern jclass JNI_findClass(const char* className);
extern jsize JNI_getArrayLength(jarray array);
Expand Down Expand Up @@ -254,6 +255,7 @@ extern void JNI_setIntField(jobject object, jfieldID field, jint value);
extern void JNI_setLongField(jobject object, jfieldID field, jlong value);
extern void JNI_setObjectArrayElement(jobjectArray array, jsize index, jobject value);
extern void JNI_setThreadLock(jobject lockObject);
extern void JNI_setStaticObjectField(jclass clazz, jfieldID field, jobject value);
extern jint JNI_throw(jthrowable obj);

#ifdef __cplusplus
Expand Down
Loading
Loading