From c889f65c25fdce0e77a9e5bfb388305c0e0146eb Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Mon, 10 Mar 2025 13:19:07 -0400 Subject: [PATCH] Check public schema path too in withJarInPath Have withJarInPath impose a temporary class path on a schema other than 'public' only after also checking the 'public' class path and not finding the jar there either. Addresses #516. Also recast an ok but odd-looking catch block as a less-puzzling finally. --- .../pljava/management/Commands.java | 84 ++++++++++++------- .../org/postgresql/pljava/sqlj/Loader.java | 4 +- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/pljava/src/main/java/org/postgresql/pljava/management/Commands.java b/pljava/src/main/java/org/postgresql/pljava/management/Commands.java index df3884388..60c4dc39a 100644 --- a/pljava/src/main/java/org/postgresql/pljava/management/Commands.java +++ b/pljava/src/main/java/org/postgresql/pljava/management/Commands.java @@ -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 @@ -43,6 +43,7 @@ import java.text.ParseException; import java.util.ArrayList; import static java.util.Arrays.fill; +import static java.util.Objects.requireNonNullElse; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; @@ -68,6 +69,7 @@ import static org.postgresql.pljava.internal.Privilege.doPrivileged; import static org.postgresql.pljava.jdbc.SQLUtils.getDefaultConnection; import org.postgresql.pljava.sqlj.Loader; +import static org.postgresql.pljava.sqlj.Loader.PUBLIC_SCHEMA; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; @@ -934,7 +936,8 @@ public static void replaceJar(String urlString, String jarName, * * @param schemaName Name of the schema for which this path is valid. * @param path Colon separated list of names. Each name must denote the name - * of a jar that is present in the jar repository. + * of a jar that is present in the jar repository. An empty + * string or null equivalently set no class path for the schema. * @throws SQLException If no schema can be found with the givene name, or * if one or several names of the path denotes a nonexistant jar * file. @@ -1022,7 +1025,6 @@ public static void setClassPath(Identifier.Simple schema, String path) { // Insert the new path. // - ; try(PreparedStatement stmt = getDefaultConnection() .prepareStatement( "INSERT INTO sqlj.classpath_entry("+ @@ -1042,41 +1044,60 @@ public static void setClassPath(Identifier.Simple schema, String path) Loader.clearSchemaLoaders(); } + /** + * Run runnable while a temporary class path including + * jarName, if needed, is imposed on the current + * (head-of-{@code search_path}) schema. + *

+ * The temporary class path is imposed if jarName is not already + * included in the current schema's class path, and also not in the public + * schema's class path if the current schema is not the public one. + * + * @param jarName Caller must have checked (as with {@code assertJarName}) + * that this is a sensible jar name, in particular without the colons that + * separate a PL/Java class path. + * @param schemaMayVanish Caller passes true if this is a {@code remove_jar} + * action, when it should not be surprising if undoing the temporary class + * path fails because the schema is gone after the undeploy steps. + * @param runnable The deploy/undeploy actions to take while the temporary + * class path is possibly imposed. + */ private static void withJarInPath(String jarName, boolean schemaMayVanish, Checked.Runnable runnable) throws SQLException { + String jarNameX = ':' + jarName + ':'; Identifier.Simple originalSchema = getCurrentSchema(); - String originalClasspath = getClassPath(originalSchema); - boolean changed; - if(originalClasspath == null) + String originalClasspath = + requireNonNullElse(getClassPath(originalSchema), ""); + + boolean found = false; + + if ( ! originalClasspath.isEmpty() ) + found = (':'+originalClasspath+':').contains(jarNameX); + else if ( ! PUBLIC_SCHEMA.equals(originalSchema) ) { - setClassPath(originalSchema, jarName); - changed = true; + String fallbackClasspath = + requireNonNullElse(getClassPath(PUBLIC_SCHEMA), ""); + found = (':'+fallbackClasspath+':').contains(jarNameX); } - else - { - String[] elems = originalClasspath.split(":"); - int idx = elems.length; - boolean found = false; - while(--idx >= 0) - if(elems[idx].equals(jarName)) - { - found = true; - break; - } - if(found) - changed = false; - else - { - setClassPath(originalSchema, jarName + ':' + originalClasspath); - changed = true; - } + if ( ! found ) + { + String newPath = jarName; + if ( ! originalClasspath.isEmpty() ) + newPath += ':' + originalClasspath; + setClassPath(originalSchema, newPath); } runnable.run(); - if ( changed ) + /* + * This is not a finally, because if something went wrong PostgreSQL + * won't allow the SPI operations in setClassPath anyway, and that's + * also ok, because if something went wrong PostgreSQL will roll back + * the transaction. + */ + if ( ! found ) { try { @@ -1453,17 +1474,20 @@ private static void installJar(String urlString, String jarName, try { deployInstall(jarId, jarName); + deploy = false; // flag that deployInstall completed } - catch ( Error | RuntimeException | SQLException e ) + finally { - Loader.clearSchemaLoaders(); - throw e; + if ( deploy ) // or in case it didn't complete ... + Loader.clearSchemaLoaders(); } } private static void replaceJar(String urlString, String jarName, boolean redeploy, byte[] image) throws SQLException { + assertJarName(jarName); + AclId[] ownerRet = new AclId[1]; int jarId = getJarId(jarName, ownerRet); if(jarId < 0) diff --git a/pljava/src/main/java/org/postgresql/pljava/sqlj/Loader.java b/pljava/src/main/java/org/postgresql/pljava/sqlj/Loader.java index 0301bd970..539dd527d 100644 --- a/pljava/src/main/java/org/postgresql/pljava/sqlj/Loader.java +++ b/pljava/src/main/java/org/postgresql/pljava/sqlj/Loader.java @@ -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 @@ -130,7 +130,7 @@ public URL nextElement() return entryURL(m_entryIds[m_top++]); } } - private static final Identifier.Simple PUBLIC_SCHEMA = + public static final Identifier.Simple PUBLIC_SCHEMA = Identifier.Simple.fromCatalog("public"); private static final Map