/* * Copyright (c) 2015-2016 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.management; import java.sql.Connection; import java.sql.SQLException; import org.postgresql.pljava.Session; import org.postgresql.pljava.SessionManager; import org.postgresql.pljava.internal.Backend; import org.postgresql.pljava.sqlgen.Lexicals.Identifier; /** * Abstract class for executing one deployment descriptor {@code <command>} * on a connection. * <p> * The {@link #forImplementor forImplementor} method returns an executor * according to the * {@code <implementor name>} associated with the command. The two possibilities * that support standard behavior are a "plain" executor, which simply * executes the SQL text (with any {@code SECURITY DEFINER} identity dropped), * and a "no-op" executor, which (you'll be shocked) does nothing. * Normally, a plain executor is returned if the implementor name is * null (command is for all implementations) or in the list recognized as being * for this implementation (normally just PostgreSQL, case-insensitively, but * adjustable as described below). A no-op executor is returned if there * is an implementor name and it is anything not on the recognized list. * <p><strong>Adjusting the recognized implementor names:</strong> * <p> * The recognized implementor names are taken from the (comma-separated) string * configuration option {@code pljava.implementors}, which can be set from * ordinary SQL using * {@code SET LOCAL pljava.implementors TO thing, thing, thing}. * It is re-parsed each time {@code forImplementor} is called, which happens * for every {@code <command>} in a deployment descriptor, so that SQL code * early in a deployment descriptor <em>can influence which code blocks later * are executed</em>. * <p> * The {@code SET LOCAL} command shown above only accepts a literal list of * elements. It will ordinarily be better for SQL code to <em>add</em> another * element to whatever is currently in the list, which is fussier in SQL: * <pre> * SELECT set_config('pljava.implementors', 'NewThing,' || * current_setting('pljava.implementors'), true) * </pre> * where the final {@code true} gives the setting the same lifetime as * {@code SET LOCAL}, that is, it reverts when the transaction is over. * <p> * The possibility that, for certain implementor names, {@code forImplementor} * could return other subclasses of {@code DDRExecutor} with specialized * behavior, is definitely contemplated. */ public abstract class DDRExecutor { protected DDRExecutor() { } private static final DDRExecutor PLAIN = new Plain(); private static final DDRExecutor NOOP = new Noop(); /** * Execute the command {@code sql} using the connection {@code conn}, * according to whatever meaning of "execute" the target {@code DDRExecutor} * subclass implements. * * @param sql The command to execute * @param conn The connection to use * @throws SQLException Anything thrown in the course of executing * {@code sql} */ public abstract void execute( String sql, Connection conn) throws SQLException; /** * Return a {@code DDRExecutor} instance chosen according to the supplied * implementor name and current {@code pljava.implementors} value. * See the class description for more. * * @param name The {@code <implementor name>} associated with the deployment * descriptor {@code <command>}, or {@code null} if the {@code <command>} is * an unadorned {@code <SQL statement>} instead of an * {@code <implementor block>}. */ public static DDRExecutor forImplementor( Identifier name) throws SQLException { if ( null == name ) return PLAIN; Iterable<Identifier> imps = Backend.getListConfigOption( "pljava.implementors"); for ( Identifier i : imps ) if ( name.equals( i) ) return PLAIN; return NOOP; } static class Noop extends DDRExecutor { public void execute( String sql, Connection conn) throws SQLException { } } static class Plain extends DDRExecutor { public void execute( String sql, Connection conn) throws SQLException { Session session = SessionManager.current(); session.executeAsOuterUser( conn, sql); } } }