Skip to content

Commit ce467e4

Browse files
committed
Merge bug/REL1_6_STABLE/issue523 into REL1_6_STABLE
Merges PR #535.
2 parents e68610f + 69fc2a2 commit ce467e4

File tree

11 files changed

+465
-23
lines changed

11 files changed

+465
-23
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2025
3+
Tada AB and other contributors, as listed below.
4+
*
5+
* All rights reserved. This program and the accompanying materials
6+
* are made available under the terms of the The BSD 3-Clause License
7+
* which accompanies this distribution, and is available at
8+
* http://opensource.org/licenses/BSD-3-Clause
9+
*
10+
* Contributors:
11+
* Chapman Flack
12+
*/
13+
package org.postgresql.pljava.example.annotation;
14+
15+
import java.sql.Connection;
16+
import static java.sql.DriverManager.getConnection;
17+
import java.sql.SQLException;
18+
import java.sql.Statement;
19+
20+
import org.postgresql.pljava.annotation.Function;
21+
import org.postgresql.pljava.annotation.SQLType;
22+
23+
/**
24+
* Illustrates how not to handle an exception thrown by a call into PostgreSQL.
25+
*<p>
26+
* Such an exception must either be rethrown (or result in some higher-level
27+
* exception being rethrown) or cleared by rolling back the transaction or
28+
* a previously-established savepoint. If it is simply caught and not propagated
29+
* and the error condition is not cleared, no further calls into PostgreSQL
30+
* functionality can be made within the containing transaction.
31+
*
32+
* @see <a href="../../RELDOTS/use/catch.html">Catching PostgreSQL exceptions
33+
* in Java</a>
34+
*/
35+
public interface MishandledExceptions
36+
{
37+
/**
38+
* Executes an SQL statement that produces an error (twice, if requested),
39+
* catching the resulting exception but not propagating it or rolling back
40+
* a savepoint; then throws an unrelated exception if succeed is false.
41+
*/
42+
@Function(schema = "javatest")
43+
static String mishandle(
44+
boolean twice, @SQLType(defaultValue="true")boolean succeed)
45+
throws SQLException
46+
{
47+
String rslt = null;
48+
do
49+
{
50+
try
51+
(
52+
Connection c = getConnection("jdbc:default:connection");
53+
Statement s = c.createStatement();
54+
)
55+
{
56+
s.execute("DO LANGUAGE \"no such language\" 'no such thing'");
57+
}
58+
catch ( SQLException e )
59+
{
60+
rslt = e.toString();
61+
/* nothing rethrown, nothing rolled back <- BAD PRACTICE */
62+
}
63+
}
64+
while ( ! (twice ^= true) );
65+
66+
if ( succeed )
67+
return rslt;
68+
69+
throw new SQLException("unrelated");
70+
}
71+
}

pljava-so/src/main/c/Exception.c

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004-2023 Tada AB and other contributors, as listed below.
2+
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
33
*
44
* All rights reserved. This program and the accompanying materials
55
* are made available under the terms of the The BSD 3-Clause License
@@ -27,12 +27,15 @@ jmethodID Class_getCanonicalName;
2727

2828
jclass ServerException_class;
2929
jmethodID ServerException_getErrorData;
30-
jmethodID ServerException_init;
30+
jmethodID ServerException_obtain;
3131

3232
jclass Throwable_class;
3333
jmethodID Throwable_getMessage;
3434
jmethodID Throwable_printStackTrace;
3535

36+
static jclass UnhandledPGException_class;
37+
static jmethodID UnhandledPGException_obtain;
38+
3639
jclass IllegalArgumentException_class;
3740
jmethodID IllegalArgumentException_init;
3841

@@ -46,6 +49,11 @@ jmethodID UnsupportedOperationException_init;
4649
jclass NoSuchFieldError_class;
4750
jclass NoSuchMethodError_class;
4851

52+
bool Exception_isPGUnhandled(jthrowable ex)
53+
{
54+
return JNI_isInstanceOf(ex, UnhandledPGException_class);
55+
}
56+
4957
void
5058
Exception_featureNotSupported(const char* requestedFeature, const char* introVersion)
5159
{
@@ -161,6 +169,22 @@ void Exception_throwSPI(const char* function, int errCode)
161169
SPI_result_code_string(errCode));
162170
}
163171

172+
void Exception_throw_unhandled()
173+
{
174+
jobject ex;
175+
PG_TRY();
176+
{
177+
ex = JNI_callStaticObjectMethodLocked(
178+
UnhandledPGException_class, UnhandledPGException_obtain);
179+
JNI_throw(ex);
180+
}
181+
PG_CATCH();
182+
{
183+
elog(WARNING, "Exception while generating exception");
184+
}
185+
PG_END_TRY();
186+
}
187+
164188
void Exception_throw_ERROR(const char* funcName)
165189
{
166190
jobject ex;
@@ -170,7 +194,8 @@ void Exception_throw_ERROR(const char* funcName)
170194

171195
FlushErrorState();
172196

173-
ex = JNI_newObject(ServerException_class, ServerException_init, ed);
197+
ex = JNI_callStaticObjectMethodLocked(
198+
ServerException_class, ServerException_obtain, ed);
174199
currentInvocation->errorOccurred = true;
175200

176201
elog(DEBUG2, "Exception in function %s", funcName);
@@ -216,7 +241,17 @@ extern void Exception_initialize2(void);
216241
void Exception_initialize2(void)
217242
{
218243
ServerException_class = (jclass)JNI_newGlobalRef(PgObject_getJavaClass("org/postgresql/pljava/internal/ServerException"));
219-
ServerException_init = PgObject_getJavaMethod(ServerException_class, "<init>", "(Lorg/postgresql/pljava/internal/ErrorData;)V");
244+
ServerException_obtain = PgObject_getStaticJavaMethod(
245+
ServerException_class, "obtain",
246+
"(Lorg/postgresql/pljava/internal/ErrorData;)"
247+
"Lorg/postgresql/pljava/internal/ServerException;");
220248

221249
ServerException_getErrorData = PgObject_getJavaMethod(ServerException_class, "getErrorData", "()Lorg/postgresql/pljava/internal/ErrorData;");
250+
251+
UnhandledPGException_class = (jclass)JNI_newGlobalRef(
252+
PgObject_getJavaClass(
253+
"org/postgresql/pljava/internal/UnhandledPGException"));
254+
UnhandledPGException_obtain = PgObject_getStaticJavaMethod(
255+
UnhandledPGException_class, "obtain",
256+
"()Lorg/postgresql/pljava/internal/UnhandledPGException;");
222257
}

pljava-so/src/main/c/Invocation.c

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004-2021 Tada AB and other contributors, as listed below.
2+
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
33
*
44
* All rights reserved. This program and the accompanying materials
55
* are made available under the terms of the The BSD 3-Clause License
@@ -24,7 +24,9 @@
2424

2525
#define LOCAL_FRAME_SIZE 128
2626

27+
static jclass s_Invocation_class;
2728
static jmethodID s_Invocation_onExit;
29+
static jfieldID s_Invocation_s_unhandled;
2830
static unsigned int s_callLevel = 0;
2931

3032
Invocation* currentInvocation;
@@ -85,8 +87,11 @@ void Invocation_initialize(void)
8587
};
8688

8789
cls = PgObject_getJavaClass("org/postgresql/pljava/jdbc/Invocation");
90+
s_Invocation_class = JNI_newGlobalRef(cls);
8891
PgObject_registerNatives2(cls, invocationMethods);
8992
s_Invocation_onExit = PgObject_getJavaMethod(cls, "onExit", "(Z)V");
93+
s_Invocation_s_unhandled = PgObject_getStaticJavaField(
94+
cls, "s_unhandled", "Ljava/sql/SQLException;");
9095
JNI_deleteLocalRef(cls);
9196
}
9297

@@ -191,6 +196,7 @@ void Invocation_popInvocation(bool wasException)
191196
{
192197
Invocation* ctx = currentInvocation->previous;
193198
bool heavy = FRAME_LIMITS_PUSHED == currentInvocation->frameLimits;
199+
bool unhandled = currentInvocation->errorOccurred;
194200

195201
/*
196202
* If the more heavyweight parameter-frame push wasn't done, do
@@ -215,11 +221,23 @@ void Invocation_popInvocation(bool wasException)
215221
{
216222
JNI_callVoidMethodLocked(
217223
currentInvocation->invocation, s_Invocation_onExit,
218-
(wasException || currentInvocation->errorOccurred)
224+
(wasException || unhandled)
219225
? JNI_TRUE : JNI_FALSE);
220226
JNI_deleteGlobalRef(currentInvocation->invocation);
221227
}
222228

229+
if ( unhandled )
230+
{
231+
jthrowable ex = (jthrowable)JNI_getStaticObjectField(
232+
s_Invocation_class, s_Invocation_s_unhandled);
233+
bool already_hit = Exception_isPGUnhandled(ex);
234+
JNI_setStaticObjectField(
235+
s_Invocation_class, s_Invocation_s_unhandled, NULL);
236+
237+
JNI_exceptionStacktraceAtLevel(ex,
238+
wasException ? DEBUG2 : already_hit ? WARNING : DEBUG1);
239+
}
240+
223241
/*
224242
* Do nativeRelease for any DualState instances scoped to this invocation.
225243
*/

pljava-so/src/main/c/JNICalls.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,13 @@ static void elogExceptionMessage(JNIEnv* env, jthrowable exh, int logLevel)
203203
ereport(logLevel, (errcode(sqlState), errmsg("%s", buf.data)));
204204
}
205205

206-
static void printStacktrace(JNIEnv* env, jobject exh)
206+
static void printStacktrace(JNIEnv* env, jobject exh, int elevel)
207207
{
208-
#ifndef _MSC_VER
209-
if(DEBUG1 >= log_min_messages || DEBUG1 >= client_min_messages)
208+
#if 100002<=PG_VERSION_NUM || \
209+
90607<=PG_VERSION_NUM && PG_VERSION_NUM<100000 || \
210+
90511<=PG_VERSION_NUM && PG_VERSION_NUM< 90600 || \
211+
! defined(_MSC_VER)
212+
if(elevel >= log_min_messages || elevel >= client_min_messages)
210213
#else
211214
/* This is gross, but only happens as often as an exception escapes Java
212215
* code to be rethrown. There is some renewed interest on pgsql-hackers to
@@ -217,7 +220,7 @@ static void printStacktrace(JNIEnv* env, jobject exh)
217220
|| 0 == strncmp("debug", PG_GETCONFIGOPTION("client_min_messages"), 5) )
218221
#endif
219222
{
220-
int currLevel = Backend_setJavaLogLevel(DEBUG1);
223+
int currLevel = Backend_setJavaLogLevel(elevel);
221224
(*env)->CallVoidMethod(env, exh, Throwable_printStackTrace);
222225
(*env)->ExceptionOccurred(env); /* sop for JNI exception-check check */
223226
Backend_setJavaLogLevel(currLevel);
@@ -236,7 +239,7 @@ static void endCall(JNIEnv* env)
236239
jniEnv = env;
237240
if(exh != 0)
238241
{
239-
printStacktrace(env, exh);
242+
printStacktrace(env, exh, DEBUG1);
240243
if((*env)->IsInstanceOf(env, exh, ServerException_class))
241244
{
242245
/* Rethrow the server error.
@@ -266,7 +269,7 @@ static void endCallMonitorHeld(JNIEnv* env)
266269
jniEnv = env;
267270
if(exh != 0)
268271
{
269-
printStacktrace(env, exh);
272+
printStacktrace(env, exh, DEBUG1);
270273
if((*env)->IsInstanceOf(env, exh, ServerException_class))
271274
{
272275
/* Rethrow the server error.
@@ -329,8 +332,7 @@ bool beginNative(JNIEnv* env)
329332
* backend at this point.
330333
*/
331334
env = JNI_setEnv(env);
332-
Exception_throw(ERRCODE_INTERNAL_ERROR,
333-
"An attempt was made to call a PostgreSQL backend function after an elog(ERROR) had been issued");
335+
Exception_throw_unhandled();
334336
JNI_setEnv(env);
335337
return false;
336338
}
@@ -950,12 +952,20 @@ void JNI_exceptionDescribe(void)
950952
if(exh != 0)
951953
{
952954
(*env)->ExceptionClear(env);
953-
printStacktrace(env, exh);
955+
printStacktrace(env, exh, DEBUG1);
954956
elogExceptionMessage(env, exh, WARNING);
955957
}
956958
END_JAVA
957959
}
958960

961+
void JNI_exceptionStacktraceAtLevel(jthrowable exh, int elevel)
962+
{
963+
BEGIN_JAVA
964+
elogExceptionMessage(env, exh, elevel);
965+
printStacktrace(env, exh, elevel);
966+
END_JAVA
967+
}
968+
959969
jthrowable JNI_exceptionOccurred(void)
960970
{
961971
jthrowable result;
@@ -1612,6 +1622,13 @@ void JNI_setShortArrayRegion(jshortArray array, jsize start, jsize len, jshort*
16121622
END_JAVA
16131623
}
16141624

1625+
void JNI_setStaticObjectField(jclass clazz, jfieldID field, jobject value)
1626+
{
1627+
BEGIN_JAVA
1628+
(*env)->SetStaticObjectField(env, clazz, field, value);
1629+
END_JAVA
1630+
}
1631+
16151632
void JNI_setThreadLock(jobject lockObject)
16161633
{
16171634
BEGIN_JAVA

pljava-so/src/main/include/pljava/Exception.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004-2023 Tada AB and other contributors, as listed below.
2+
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
33
*
44
* All rights reserved. This program and the accompanying materials
55
* are made available under the terms of the The BSD 3-Clause License
@@ -29,7 +29,15 @@ extern "C" {
2929
*******************************************************************/
3030

3131
/*
32-
* Trows an UnsupportedOperationException informing the caller that the
32+
* Tests whether ex is an instance of UnhandledPGException, an SQLException
33+
* subclass that is created when an attempted call into PostgreSQL internals
34+
* cannot be made because of an earlier unhandled ServerException.
35+
* An UnhandledPGException will have, as its cause, the earlier ServerException.
36+
*/
37+
extern bool Exception_isPGUnhandled(jthrowable ex);
38+
39+
/*
40+
* Throws an UnsupportedOperationException informing the caller that the
3341
* requested feature doesn't exist in the current version, it was introduced
3442
* starting with the intro version.
3543
*/
@@ -65,11 +73,19 @@ extern void Exception_throwSPI(const char* function, int errCode);
6573

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

81+
/*
82+
* This method will raise a Java UnhandledPGException based on a ServerException
83+
* that has been stored at some earlier time and not yet resolved (as by
84+
* a rollback). Its intended use is from beginNative in JNICalls when
85+
* errorOccurred is found to be true.
86+
*/
87+
extern void Exception_throw_unhandled(void);
88+
7389
/*
7490
* Throw an exception indicating that wanted member could not be
7591
* found. This is an ereport(ERROR...) so theres' no return from

pljava-so/src/main/include/pljava/JNICalls.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004-2021 Tada AB and other contributors, as listed below.
2+
* Copyright (c) 2004-2025 Tada AB and other contributors, as listed below.
33
*
44
* All rights reserved. This program and the accompanying materials
55
* are made available under the terms of the The BSD 3-Clause License
@@ -181,6 +181,7 @@ extern jint JNI_destroyVM(JavaVM *vm);
181181
extern jboolean JNI_exceptionCheck(void);
182182
extern void JNI_exceptionClear(void);
183183
extern void JNI_exceptionDescribe(void);
184+
extern void JNI_exceptionStacktraceAtLevel(jthrowable exh, int elevel);
184185
extern jthrowable JNI_exceptionOccurred(void);
185186
extern jclass JNI_findClass(const char* className);
186187
extern jsize JNI_getArrayLength(jarray array);
@@ -254,6 +255,7 @@ extern void JNI_setIntField(jobject object, jfieldID field, jint value);
254255
extern void JNI_setLongField(jobject object, jfieldID field, jlong value);
255256
extern void JNI_setObjectArrayElement(jobjectArray array, jsize index, jobject value);
256257
extern void JNI_setThreadLock(jobject lockObject);
258+
extern void JNI_setStaticObjectField(jclass clazz, jfieldID field, jobject value);
257259
extern jint JNI_throw(jthrowable obj);
258260

259261
#ifdef __cplusplus

0 commit comments

Comments
 (0)