src/java/deploy/org/postgresql/pljava/deploy/Deployer.java0100664000076400007640000002647310177643106023355 0ustar ownerowner/* * Copyright (c) 2004, 2005 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 */ package org.postgresql.pljava.deploy; import java.io.PrintStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.sql.ResultSet; import java.util.ArrayList; /** * When running the deployer, you must use a classpath that can see the * deploy.jar found in the Pl/Java distribution and the postgresql.jar from the * PostgreSQL distribution. The former contains the code for the deployer * command and the second includes the PostgreSQL JDBC driver. You then run the * deployer with the command: *

*
* java -cp <your classpath> org.postgresql.pljava.deploy.Deployer [ options ] *
*

* It's recommended that create a shell script or a .bat script that does this * for you so that you don't have to do this over and over again. *

*

Deployer options

*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
OptionDescription
-installInstalls the Java language along with the sqlj procedures. The deployer * will fail if the language is installed already.
-reinstallReinstalls the Java language and the sqlj procedures. This will * effectively drop all jar files that have been loaded.
-removeDrops the Java language and the sqjl procedures and loaded jars
-user <user name>Name of user that connects to the database. Default is current user
-password <password>Password of user that connects to the database. Default is no password *
-database <database>The name of the database to connect to. Default is current user
-host <hostname>Name of the host. Default is "localhost"
-windowsUse this option if the host runs on a windows platform. Affects the * name used for the Pl/Java dynamic library
* * @author Thomas Hallgren */ public class Deployer { private static final int CMD_AMBIGUOUS = -2; private static final int CMD_UNKNOWN = -1; private static final int CMD_UNINSTALL = 0; private static final int CMD_INSTALL = 1; private static final int CMD_REINSTALL = 2; private static final int CMD_USER = 3; private static final int CMD_PASSWORD = 4; private static final int CMD_DATABASE = 5; private static final int CMD_HOSTNAME = 6; private static final int CMD_PORT = 7; private static final int CMD_WINDOWS = 8; private final Connection m_connection; private static final ArrayList s_commands = new ArrayList(); static { s_commands.add(CMD_UNINSTALL, "uninstall"); s_commands.add(CMD_INSTALL, "install"); s_commands.add(CMD_REINSTALL, "reinstall"); s_commands.add(CMD_USER, "user"); s_commands.add(CMD_PASSWORD, "password"); s_commands.add(CMD_DATABASE, "database"); s_commands.add(CMD_HOSTNAME, "host"); s_commands.add(CMD_PORT, "port"); s_commands.add(CMD_WINDOWS, "windows"); } private static final int getCommand(String arg) { int top = s_commands.size(); int candidateCmd = CMD_UNKNOWN; for(int idx = 0; idx < top; ++idx) { if(((String)s_commands.get(idx)).startsWith(arg)) { if(candidateCmd != CMD_UNKNOWN) return CMD_AMBIGUOUS; candidateCmd = idx; } } return candidateCmd; } public static void printUsage() { PrintStream out = System.err; out.println("usage: java -jar deploy.jar"); out.println(" {-install | -uninstall | -reinstall}"); out.println(" [ -host ] # default is localhost"); out.println(" [ -port ] # default is blank"); out.println(" [ -database ] # default is name of current user"); out.println(" [ -user ] # default is name of current user"); out.println(" [ -password ] # default is no password"); out.println(" [ -windows ] # If the server is on a Windows machine"); } public static void main(String[] argv) { String driverClass = "org.postgresql.Driver"; String hostName = "localhost"; String userName = System.getProperty("user.name", "postgres"); String database = userName; String subsystem = "postgresql"; String password = null; String portNumber = null; boolean unix = true; int cmd = CMD_UNKNOWN; int top = argv.length; for(int idx = 0; idx < top; ++idx) { String arg = argv[idx]; if(arg.length() < 2) { printUsage(); return; } if(arg.charAt(0) == '-') { int optCmd = getCommand(arg.substring(1)); switch(optCmd) { case CMD_INSTALL: case CMD_UNINSTALL: case CMD_REINSTALL: if(cmd != CMD_UNKNOWN) { printUsage(); return; } cmd = optCmd; break; case CMD_USER: if(++idx < top) { userName = argv[idx]; if(userName.length() > 0 && userName.charAt(0) != '-') break; } printUsage(); return; case CMD_PASSWORD: if(++idx < top) { password = argv[idx]; if(password.length() > 0 && password.charAt(0) != '-') break; } printUsage(); return; case CMD_DATABASE: if(++idx < top) { database = argv[idx]; if(database.length() > 0 && database.charAt(0) != '-') break; } printUsage(); return; case CMD_HOSTNAME: if(++idx < top) { hostName = argv[idx]; if(hostName.length() > 0 && hostName.charAt(0) != '-') break; } printUsage(); return; case CMD_PORT: if(++idx < top) { portNumber = argv[idx]; if(portNumber.length() > 0 && portNumber.charAt(0) != '-') break; } printUsage(); return; case CMD_WINDOWS: unix = false; break; default: printUsage(); return; } } } if(cmd == CMD_UNKNOWN) { printUsage(); return; } try { Class.forName(driverClass); StringBuffer cc = new StringBuffer(); cc.append("jdbc:"); cc.append(subsystem); cc.append("://"); cc.append(hostName); if(portNumber != null) { cc.append(':'); cc.append(portNumber); } cc.append('/'); cc.append(database); Connection c = DriverManager.getConnection( cc.toString(), userName, password); checkIfConnectedAsSuperuser(c); Deployer deployer = new Deployer(c); if(cmd == CMD_UNINSTALL || cmd == CMD_REINSTALL) { deployer.dropSQLJSchema(); } if(cmd == CMD_INSTALL || cmd == CMD_REINSTALL) { deployer.createSQLJSchema(); deployer.initJavaHandler(unix); deployer.initializeSQLJSchema(); } c.close(); } catch(Exception e) { e.printStackTrace(); } } public Deployer(Connection c) { m_connection = c; } public static void checkIfConnectedAsSuperuser(Connection conn) throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SHOW IS_SUPERUSER"); try { if (rs.next() && rs.getString(1).equals("on")) { return; } } finally { rs.close(); stmt.close(); } throw new SQLException( "You must be a superuser to deploy/undeploy pl/Java."); } public void dropSQLJSchema() throws SQLException { Statement stmt = m_connection.createStatement(); stmt.execute("DROP SCHEMA sqlj CASCADE"); try { stmt.execute("DROP LANGUAGE java CASCADE"); } catch(SQLException e) { /* ignore */ } stmt.close(); } public void createSQLJSchema() throws SQLException { Statement stmt = m_connection.createStatement(); stmt.execute("CREATE SCHEMA sqlj AUTHORIZATION postgres"); stmt.execute("GRANT USAGE ON SCHEMA sqlj TO public"); stmt.close(); } public void initializeSQLJSchema() throws SQLException { Statement stmt = m_connection.createStatement(); stmt.execute( "CREATE TABLE sqlj.jar_repository(" + " jarId SERIAL PRIMARY KEY," + " jarName VARCHAR(100) UNIQUE NOT NULL," + " jarOrigin VARCHAR(500) NOT NULL," + " deploymentDesc INT" + ")"); stmt.execute("ALTER TABLE sqlj.jar_repository OWNER TO postgres"); stmt.execute("GRANT SELECT ON sqlj.jar_repository TO public"); stmt.execute( "CREATE TABLE sqlj.jar_entry(" + " entryId SERIAL PRIMARY KEY," + " entryName VARCHAR(200) NOT NULL," + " jarId INT NOT NULL REFERENCES sqlj.jar_repository ON DELETE CASCADE," + " entryImage BYTEA NOT NULL," + " UNIQUE(jarId, entryName)" + ")"); stmt.execute("ALTER TABLE sqlj.jar_entry OWNER TO postgres"); stmt.execute("GRANT SELECT ON sqlj.jar_entry TO public"); stmt.execute( "ALTER TABLE sqlj.jar_repository" + " ADD FOREIGN KEY (deploymentDesc) REFERENCES sqlj.jar_entry ON DELETE SET NULL"); // Create the table maintaining the class path. // stmt.execute( "CREATE TABLE sqlj.classpath_entry(" + " schemaName VARCHAR(30) NOT NULL," + " ordinal INT2 NOT NULL," + // Ordinal in class path " jarId INT NOT NULL REFERENCES sqlj.jar_repository ON DELETE CASCADE," + " PRIMARY KEY(schemaName, ordinal)" + ")"); stmt.execute("ALTER TABLE sqlj.classpath_entry OWNER TO postgres"); stmt.execute("GRANT SELECT ON sqlj.classpath_entry TO public"); // These are the proposed SQL standard methods. // stmt.execute( "CREATE FUNCTION sqlj.install_jar(VARCHAR, VARCHAR, BOOLEAN) RETURNS void" + " AS 'org.postgresql.pljava.management.Commands.installJar'" + " LANGUAGE java SECURITY DEFINER"); stmt.execute( "CREATE FUNCTION sqlj.replace_jar(VARCHAR, VARCHAR, BOOLEAN) RETURNS void" + " AS 'org.postgresql.pljava.management.Commands.replaceJar'" + " LANGUAGE java SECURITY DEFINER"); stmt.execute( "CREATE FUNCTION sqlj.remove_jar(VARCHAR, BOOLEAN) RETURNS void" + " AS 'org.postgresql.pljava.management.Commands.removeJar'" + " LANGUAGE java SECURITY DEFINER"); // This function is not as proposed. It's more Java'ish. The proposal // using sqlj.alter_jar_path is in my opinion bloated and will not be // well received in the Java community. Luckily, the support is suggested // to be optional. // stmt.execute( "CREATE FUNCTION sqlj.set_classpath(VARCHAR, VARCHAR) RETURNS void" + " AS 'org.postgresql.pljava.management.Commands.setClassPath'" + " LANGUAGE java SECURITY DEFINER"); stmt.execute( "CREATE FUNCTION sqlj.get_classpath(VARCHAR) RETURNS VARCHAR" + " AS 'org.postgresql.pljava.management.Commands.getClassPath'" + " LANGUAGE java STABLE SECURITY DEFINER"); stmt.close(); } public void initJavaHandler(boolean unix) throws SQLException { Statement stmt = m_connection.createStatement(); stmt.execute( "CREATE FUNCTION sqlj.java_call_handler()" + " RETURNS language_handler" + " AS '" + (unix ? "lib" : "") + "pljava'" + " LANGUAGE C"); stmt.execute("CREATE TRUSTED LANGUAGE java HANDLER sqlj.java_call_handler"); stmt.close(); } } src/java/pljava/org/postgresql/pljava/management/SQLDeploymentDescriptor.java0100664000076400007640000003332010177642452027122 0ustar ownerowner/* * Copyright (c) 2004, 2005 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 */ package org.postgresql.pljava.management; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.text.ParseException; import java.util.ArrayList; import java.util.logging.Logger; /** * This class deals with parsing and executing the deployment descriptor as * defined in ISO/IEC 9075-13:2003. It has the following format:

 * <descriptor file> ::=
 * SQLActions <left bracket> <right bracket> <equal sign>
 * { [ <double quote> <action group> <double quote>
 *   [ <comma> <double quote> <action group> <double quote> ] ] }
 *
 * <action group> ::=
 *     <install actions>
 *   | <remove actions>
 * 
 * <install actions> ::=
 *   BEGIN INSTALL [ <command> <semicolon> ]... END INSTALL
 *
 * <remove actions> ::=
 *   BEGIN REMOVE [ <command> <semicolon> ]... END REMOVE
 *
 * <command> ::=
 *     <SQL statement>
 *   | <implementor block>
 *
 * <SQL statement> ::= <SQL token>...
 * 
 * <implementor block> ::=
 *   BEGIN <implementor name> <SQL token>... END <implementor name>
 *
 * <implementor name> ::= <identifier>
 *
 * <SQL token> ::= an SQL lexical unit specified by the term "<token>" in
 * Subclause 5.2, "<token>" and "<separator>", in ISO/IEC 9075-2.
* * @author Thomas Hallgren */ public class SQLDeploymentDescriptor { private final ArrayList m_installCommands = new ArrayList(); private final ArrayList m_removeCommands = new ArrayList(); private final StringBuffer m_buffer = new StringBuffer(); private final char[] m_image; private final String m_implementorName; private final Logger m_logger; private int m_position = 0; private String sessionUser = null; private String currentUser = null; private boolean executeAs = false; private String executeAsFunctionName = null; /** * Parses the deployment descriptor descImage using * implementorName as discriminator for implementor specific * blocks. The install and remove blocks are remembered for later execution * with calls to {@link #install install()} and {@link #remove remove()}. * @param descImage The image to parse * @param implementorName The discriminator to use for implementor blocks * @throws ParseException If a parse error is encountered */ public SQLDeploymentDescriptor(String descImage, String implementorName) throws ParseException { m_image = descImage.toCharArray(); m_implementorName = implementorName; m_logger = Logger.getAnonymousLogger(); this.readDescriptor(); } /** * Executes the INSTALL actions. * @param conn The connection to use for the execution. * @throws SQLException */ public void install(Connection conn) throws SQLException { this.executeArray(m_installCommands, conn); } /** * Executes the REMOVE actions. * @param conn The connection to use for the execution. * @throws SQLException */ public void remove(Connection conn) throws SQLException { this.executeArray(m_removeCommands, conn); } /** * Returns the original image. */ public String toString() { return new String(m_image); } private void executeArray(ArrayList array, Connection conn) throws SQLException { m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "executeArray"); Statement stmt = conn.createStatement(); PreparedStatement pstmt = null; ResultSet rs = stmt.executeQuery( "SELECT session_user, current_user"); if (rs.next()) { sessionUser = rs.getString(1); currentUser = rs.getString(2); rs.close(); } else { rs.close(); throw new SQLException( "Error obtaining session/current user"); } executeAs = !currentUser.equals(sessionUser); if (executeAs) { createExecuteAsFunction(stmt); } int top = array.size(); for(int idx = 0; idx < top; ++idx) { String cmd = (String)array.get(idx); m_logger.finer(cmd); if (executeAs) { pstmt = conn.prepareStatement( callExecuteAsFunction()); pstmt.setString(1, cmd); //This strange looking construction is //here because otherwise nothing happend - //neither an error not the desired action pstmt.executeQuery().next(); //This did not work: //pstmt.execute(); pstmt.close(); } else { stmt.execute(cmd); } } if (executeAs) { dropExecuteAsFunction(stmt); } stmt.close(); m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "executeArray"); } private void createExecuteAsFunction(Statement stmt) throws SQLException { //Generate a temporary function name, which is 100% unique. //This is done by // backend pid => no confilct with other backends // current timestamp => no conflict in quick cycles // random number => no conflic in extra quick cycles //Thr problem is that if we used only the backend pid, //we would get "Cache lookup failed" error in some cases. //When using absolutely unique function IDs, this error //never occurs. String uniqueId = null; ResultSet rs = stmt.executeQuery( "select pg_backend_pid()||'_'||" + "trunc(extract(epoch from current_timestamp)*10000)||" + "'_'||" + "trunc(random()*100000)"); if (rs.next()) { uniqueId = rs.getString(1); executeAsFunctionName = "\"sqlj\".\"execute_as_" + sessionUser + "_pid_" + uniqueId + "\""; stmt.execute( "CREATE OR REPLACE FUNCTION " + executeAsFunctionName + "(varchar) RETURNS VOID AS " + "'org.postgresql.pljava.management.Commands.executeAs'" + "LANGUAGE 'java' VOLATILE SECURITY DEFINER"); stmt.execute( "ALTER FUNCTION " + executeAsFunctionName + "(varchar)" + " OWNER TO \"" + sessionUser + "\""); rs.close(); } else { rs.close(); throw new SQLException("Cannot get unique function ID"); } } private String callExecuteAsFunction() { return "SELECT " + executeAsFunctionName + "(?)"; } private void dropExecuteAsFunction(Statement stmt) throws SQLException { stmt.execute( "DROP FUNCTION " + executeAsFunctionName + "(varchar)"); } private ParseException parseError(String msg) { return new ParseException(msg, m_position); } private void readDescriptor() throws ParseException { m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readDescriptor"); if(!"SQLACTIONS".equals(this.readIdentifier())) throw this.parseError("Excpected keyword 'SQLActions'"); this.readToken('['); this.readToken(']'); this.readToken('='); this.readToken('{'); for(;;) { readActionGroup(); if(readToken("},") == '}') { // Only whitespace allowed now // int c = this.skipWhite(); if(c >= 0) throw this.parseError( "Extraneous characters at end of descriptor"); m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readDescriptor"); return; } } } private void readActionGroup() throws ParseException { m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readActionGroup"); this.readToken('"'); if(!"BEGIN".equals(this.readIdentifier())) throw this.parseError("Excpected keyword 'BEGIN'"); ArrayList commands; String actionType = this.readIdentifier(); if("INSTALL".equals(actionType)) commands = m_installCommands; else if("REMOVE".equals(actionType)) commands = m_removeCommands; else throw this.parseError("Excpected keyword 'INSTALL' or 'REMOVE'"); for(;;) { String cmd = this.readCommand(); // Check if the cmd is in the form: // // ::= // BEGIN ... END // // If it is, and if the implementor name corresponds to the one // defined for this deployment, then extract the SQL token stream. // int top = cmd.length(); if(top >= 15 && "BEGIN ".equalsIgnoreCase(cmd.substring(0, 6)) && Character.isJavaIdentifierStart(cmd.charAt(6))) { int pos; for(pos = 7; pos < top; ++pos) if(!Character.isJavaIdentifierPart(cmd.charAt(pos))) break; if(cmd.charAt(pos) != ' ') throw this.parseError( "Expected whitespace after "); String implementorName = cmd.substring(6, pos); int iLen = implementorName.length(); int endNamePos = top - iLen; int endPos = endNamePos - 4; if(!implementorName.equalsIgnoreCase(cmd.substring(endNamePos)) || !"END ".equalsIgnoreCase(cmd.substring(endPos, endNamePos))) throw this.parseError( "Implementor block must end with END "); if(implementorName.equalsIgnoreCase(m_implementorName)) cmd = cmd.substring(pos+1, endPos); else // Block is not intended for this implementor. // cmd = null; } if(cmd != null) commands.add(cmd.trim()); // Check if we have END INSTALL or END REMOVE // int savePos = m_position; try { String tmp = this.readIdentifier(); if("END".equals(tmp)) { tmp = this.readIdentifier(); if(actionType.equals(tmp)) break; } m_position = savePos; } catch(ParseException e) { m_position = savePos; } } this.readToken('"'); m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readActionGroup"); } private String readCommand() throws ParseException { m_logger.entering("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readCommand"); int startQuotePos = -1; int inQuote = 0; int c = this.skipWhite(); m_buffer.setLength(0); while(c != -1) { switch(c) { case '\\': m_buffer.append((char)c); c = this.read(); if(c != -1) { m_buffer.append((char)c); c = this.read(); } break; case '"': if(inQuote == 0) throw parseError("Unexpected double qoute in SQL command"); m_buffer.append((char)c); c = this.read(); break; case '\'': if(inQuote == 0) { startQuotePos = m_position; inQuote = c; } else if(inQuote == c) { startQuotePos = -1; inQuote = 0; } m_buffer.append((char)c); c = this.read(); break; case ';': if(inQuote == 0) { String cmd = m_buffer.toString(); m_logger.exiting("org.postgresql.pljava.management.SQLDeploymentDescriptor", "readCommand", cmd); return cmd; } m_buffer.append((char)c); c = this.read(); break; default: if(inQuote == 0 && Character.isWhitespace((char)c)) { // Change multiple whitespace into one singe space. // m_buffer.append(' '); c = this.skipWhite(); } else { m_buffer.append((char)c); c = this.read(); } } } if(inQuote != 0) throw this.parseError("Untermintated " + (char)inQuote + " starting at position " + startQuotePos); throw this.parseError("Unexpected EOF. Expecting ';' to end command"); } private int skipWhite() throws ParseException { int c; for(;;) { c = this.read(); if(c >= 0 && Character.isWhitespace((char)c)) continue; if(c == '/') { switch(this.peek()) { // "//" starts a line comment. Skip until end of line. // case '/': this.skip(); for(;;) { c = this.read(); switch(c) { case '\n': case '\r': case -1: break; default: continue; } break; } continue; // "/*" starts a line comment. Skip until "*/" // case '*': this.skip(); for(;;) { c = this.read(); switch(c) { case -1: throw this.parseError( "Unexpected EOF when expecting end of multi line comment"); case '*': if(this.peek() == '/') { this.skip(); break; } continue; default: continue; } break; } continue; } } break; } return c; } private String readIdentifier() throws ParseException { int c = this.skipWhite(); if(c < 0) throw this.parseError("Unexpected EOF when expecting start of identifier"); char ch = (char)c; if(!Character.isJavaIdentifierStart(ch)) throw this.parseError( "Syntax error at '" + ch + "', expected identifier"); m_buffer.setLength(0); m_buffer.append(ch); for(;;) { c = this.peek(); if(c < 0) break; ch = (char)c; if(Character.isJavaIdentifierPart(ch)) { m_buffer.append(ch); this.skip(); continue; } break; } return m_buffer.toString().toUpperCase(); } private char readToken(String tokens) throws ParseException { int c = this.skipWhite(); if(c < 0) throw this.parseError("Unexpected EOF when expecting one of \"" + tokens + '"'); char ch = (char)c; if(tokens.indexOf(ch) < 0) throw this.parseError( "Syntax error at '" + ch + "', expected one of '" + tokens + "'"); return ch; } private char readToken(char token) throws ParseException { int c = this.skipWhite(); if(c < 0) throw this.parseError("Unexpected EOF when expecting token '" + token + '\''); char ch = (char)c; if(ch != token) throw this.parseError( "Syntax error at '" + ch + "', expected '" + token + "'"); return ch; } private int peek() { return (m_position >= m_image.length) ? -1 : m_image[m_position]; } private void skip() { m_position++; } private int read() { int pos = m_position++; return (pos >= m_image.length) ? -1 : m_image[pos]; } } src/java/pljava/org/postgresql/pljava/management/Commands.java0100664000076400007640000005201310177442174024123 0ustar ownerowner/* * Copyright (c) 2004, 2005 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 */ package org.postgresql.pljava.management; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.ParseException; import java.util.ArrayList; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import org.postgresql.pljava.internal.Oid; /** * This methods of this class are implementations of SQLJ commands. *

SQJL functions

*

install_jar

* The install_jar command loads a jarfile from a location appointed by an URL * into the SQLJ jar repository. It is an error if a jar with the given name * already exists in the repository. *

Usage

*
SELECT sqlj.install_jar(<jar_url>, <jar_name>, ;<deploy>); *
*

Parameters

*
* * * * * * * * * * * * *
jar_urlThe URL that denotes the location of the jar that should be loaded *
jar_nameThis is the name by which this jar can be referenced once it has been * loaded
deployTrue if the jar should be deployed according to a {@link * org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptor}, * false otherwise
*

replace_jar

* The replace_jar will replace a loaded jar with another jar. Use this command * to update already loaded files. It's an error if the jar is not found. *

Usage

*
SELECT sqlj.replace_jar(<jar_url>, <jar_name>, ;<redeploy>); *
*

Parameters

*
* * * * * * * * * * * * *
jar_urlThe URL that denotes the location of the jar that should be loaded *
jar_nameThe name of the jar to be replaced
redeployTrue if the old and new jar should be undeployed and deployed according * to their respective {@link * org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptors}, * false otherwise
*

remove_jar

* The remove_jar will drop the jar from the jar repository. Any classpath that * references this jar will be updated accordingly. It's an error if the jar is * not found. *

Usage

*
SELECT sqlj.remove_jar(<jar_name>, <undeploy>); *
*

Parameters

*
* * * * * * * * *
jar_nameThe name of the jar to be removed
undeployTrue if the jar should be undeployed according to its {@link * org.postgresql.pljava.management.SQLDeploymentDescriptor deployment descriptor}, * false otherwise
*

get_classpath

* The get_classpath will return the classpath that has been defined for the * given schema or NULL if the schema has no classpath. It's an error if the * given schema does not exist. *

Usage

*
SELECT sqlj.get_classpath(<schema>); *
*

Parameters

*
* * * * *
schemaThe name of the schema
*

set_classpath

* The set_classpath will define a classpath for the given schema. A classpath * consists of a colon separated list of jar names. It's an error if the given * schema does not exist or if one or more jar names references non existent * jars. *

Usage

*
SELECT sqlj.set_classpath(<schema>, <classpath>); *
*

Parameters

*
* * * * * * * * *
schemaThe name of the schema
classpathThe colon separated list of jar names
* * @author Thomas Hallgren */ public class Commands { /** * Installs a new Jar in the database jar repository under name jarName. * Once installed classpaths can be defined that refrences this jar. This * method is exposed in SQL as * sqlj.install_jar(VARCHAR, VARCHAR, BOOLEAN). * * @param urlString * The location of the jar that will be installed. * @param jarName * The name by which the system will refer to this jar. * @param deploy * If set, execute install commands found in the deployment descriptor. * @throws SQLException * if the jarName contains characters that are * invalid or if the named jar already exists in the system. * @see #setClassPath */ public static void installJar(String urlString, String jarName, boolean deploy) throws SQLException { assertJarName(jarName); Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { if(getJarId(conn, jarName) >= 0) throw new SQLException("A jar named '" + jarName + "' already exists"); PreparedStatement stmt = conn.prepareStatement( "INSERT INTO sqlj.jar_repository(jarName, jarOrigin) VALUES(?, ?)"); try { stmt.setString(1, jarName); stmt.setString(2, urlString); if(stmt.executeUpdate() != 1) throw new SQLException("Jar repository insert did not insert 1 row"); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } int jarId = getJarId(conn, jarName); if(jarId < 0) throw new SQLException("Unable to obtain id of '" + jarName + "'"); addClassImages(conn, jarId, urlString); if(deploy) deployInstall(conn, jarId); } finally { try { conn.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** * Replaces the image of jar named jarName in the database jar * repository. This method is exposed in SQL as * sqlj.replace_jar(VARCHAR, VARCHAR, BOOLEAN). * @param urlString The location of the jar that will be installed. * @param jarName The name by which the system referes this jar. * @param redeploy If set, execute remove commands found in the deployment * descriptor of the old jar and install commands found in the deployment * descriptor of the new jar. * @throws SQLException if the named jar cannot be found in the repository. */ public static void replaceJar(String urlString, String jarName, boolean redeploy) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { int jarId = getJarId(conn, jarName); if(jarId < 0) throw new SQLException("No Jar named '" + jarName + "' is known to the system"); if(redeploy) deployRemove(conn, jarId); PreparedStatement stmt = conn.prepareStatement( "UPDATE sqlj.jar_repository SET jarOrigin = ?, deploymentDesc = NULL WHERE jarId = ?"); try { stmt.setString(1, urlString); stmt.setInt(2, jarId); if(stmt.executeUpdate() != 1) throw new SQLException("Jar repository update did not update 1 row"); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } stmt = conn.prepareStatement("DELETE FROM sqlj.jar_entry WHERE jarId = ?"); try { stmt.setInt(1, jarId); stmt.executeUpdate(); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } addClassImages(conn, jarId, urlString); if(redeploy) deployInstall(conn, jarId); } finally { try { conn.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** * Removes the jar named jarName from the database jar * repository. Class path entries that references this jar will also be * removed (just the entry, not the whole path). This method is exposed in * SQL as sqlj.remove_jar(VARCHAR, BOOLEAN). * @param jarName The name by which the system referes this jar. * @param undeploy If set, execute remove commands found in the deployment * descriptor of the jar. * @throws SQLException if the named jar cannot be found in the repository. */ public static void removeJar(String jarName, boolean undeploy) throws SQLException { assertJarName(jarName); Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { int jarId = getJarId(conn, jarName); if(jarId < 0) throw new SQLException("No Jar named '" + jarName + "' is known to the system"); if(undeploy) deployRemove(conn, jarId); PreparedStatement stmt = conn.prepareStatement( "DELETE FROM sqlj.jar_repository WHERE jarId = ?"); try { stmt.setInt(1, jarId); if(stmt.executeUpdate() != 1) throw new SQLException("Jar repository update did not update 1 row"); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } finally { try { conn.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** * Define the class path to use for Java functions, triggers, and procedures * that are created in the schema named schemaName * * This method is exposed in SQL as sqlj.set_classpath(VARCHAR, VARCHAR). * * @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. * @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. */ public static void setClassPath(String schemaName, String path) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { if(schemaName == null || schemaName.length() == 0) schemaName = "public"; else schemaName = schemaName.toLowerCase(); if(getSchemaId(conn, schemaName) == null) throw new SQLException("No such schema: " + schemaName); PreparedStatement stmt; ArrayList entries = null; if(path != null && path.length() > 0) { // Collect and verify that all entries in the path represents a // valid jar // entries = new ArrayList(); stmt = conn.prepareStatement( "SELECT jarId FROM sqlj.jar_repository WHERE jarName = ?"); try { for(;;) { int colon = path.indexOf(':'); String jarName; if(colon >= 0) { jarName = path.substring(0, colon); path = path.substring(colon + 1); } else jarName = path; int jarId = getJarId(stmt, jarName); if(jarId < 0) throw new SQLException("No such jar: " + jarName); entries.add(new Integer(jarId)); if(colon < 0) break; } } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } // Delete the old classpath // stmt = conn.prepareStatement( "DELETE FROM sqlj.classpath_entry WHERE schemaName = ?"); try { stmt.setString(1, schemaName); stmt.executeUpdate(); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } if(entries != null) { // Insert the new path. // stmt = conn.prepareStatement( "INSERT INTO sqlj.classpath_entry(schemaName, ordinal, jarId) VALUES(?, ?, ?)"); try { int top = entries.size(); for(int idx = 0; idx < top; ++idx) { int jarId = ((Integer)entries.get(idx)).intValue(); stmt.setString(1, schemaName); stmt.setInt(2, idx + 1); stmt.setInt(3, jarId); stmt.executeUpdate(); } } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } } finally { try { conn.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** * Return the classpath that has been defined for the schema named schemaName * * This method is exposed in SQL as sqlj.get_classpath(VARCHAR). * * @param schemaName * Name of the schema for which this path is valid. * @return The defined classpath or null if this schema has no classpath. * @throws SQLException */ public static String getClassPath(String schemaName) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); try { if(schemaName == null || schemaName.length() == 0) schemaName = "public"; else schemaName = schemaName.toLowerCase(); PreparedStatement stmt = conn.prepareStatement( "SELECT r.jarName" + " FROM sqlj.jar_repository r INNER JOIN sqlj.classpath_entry c ON r.jarId = c.jarId" + " WHERE c.schemaName = ? ORDER BY c.ordinal"); try { stmt.setString(1, schemaName); ResultSet rs = stmt.executeQuery(); try { StringBuffer buf = null; while(rs.next()) { if(buf == null) buf = new StringBuffer(); else buf.append(':'); buf.append(rs.getString(1)); } return (buf == null) ? null : buf.toString(); } finally { try { rs.close(); } catch(SQLException e) { /* ignore */ } } } finally { try { stmt.close(); } catch(SQLException e) { /* ignore */ } } } finally { try { conn.close(); } catch(SQLException e) { /* ignore */ } } } protected static void addClassImages(Connection conn, int jarId, String urlString) throws SQLException { InputStream urlStream = null; PreparedStatement stmt = null; PreparedStatement descIdStmt = null; ResultSet rs = null; try { int deployImageId = -1; URL url = new URL(urlString); urlStream = url.openStream(); byte[] buf = new byte[1024]; ByteArrayOutputStream img = new ByteArrayOutputStream(); stmt = conn.prepareStatement( "INSERT INTO sqlj.jar_entry(entryName, jarId, entryImage) VALUES(?, ?, ?)"); JarInputStream jis = new JarInputStream(urlStream); for(;;) { JarEntry je = jis.getNextJarEntry(); if(je == null) break; if(je.isDirectory()) continue; String entryName = je.getName(); Attributes attrs = je.getAttributes(); boolean isDepDescr = false; if(attrs != null) { isDepDescr = "true".equalsIgnoreCase( attrs.getValue("SQLJDeploymentDescriptor")); if(isDepDescr && deployImageId >= 0) throw new SQLException("Only one SQLJDeploymentDescriptor allowed"); } int nBytes; img.reset(); while((nBytes = jis.read(buf)) > 0) img.write(buf, 0, nBytes); jis.closeEntry(); stmt.setString(1, entryName); stmt.setInt(2, jarId); stmt.setBytes(3, img.toByteArray()); if(stmt.executeUpdate() != 1) throw new SQLException("Jar entry insert did not insert 1 row"); if(isDepDescr) { descIdStmt = conn.prepareStatement( "SELECT entryId FROM sqlj.jar_entry" + " WHERE jarId = ? AND entryName = ?"); descIdStmt.setInt(1, jarId); descIdStmt.setString(2, entryName); rs = descIdStmt.executeQuery(); if(!rs.next()) throw new SQLException("Failed to refecth row in sqlj.jar_entry"); deployImageId = rs.getInt(1); } } if(deployImageId >= 0) { stmt.close(); stmt = conn.prepareStatement( "UPDATE sqlj.jar_repository SET deploymentDesc = ? WHERE jarId = ?"); stmt.setInt(1, deployImageId); stmt.setInt(2, jarId); if(stmt.executeUpdate() != 1) throw new SQLException("Jar repository update did not insert 1 row"); } } catch(IOException e) { throw new SQLException("I/O exception reading jar file: " + e.getMessage()); } finally { if(urlStream != null) try { urlStream.close(); } catch(IOException e) { /* ignore */ } if(rs != null) try { rs.close(); } catch(SQLException e) { /* ignore */ } if(descIdStmt != null) try { descIdStmt.close(); } catch(SQLException e) { /* ignore */ } if(stmt != null) try { stmt.close(); } catch(SQLException e) { /* ignore */ } } } protected static void deployInstall(Connection conn, int jarId) throws SQLException { SQLDeploymentDescriptor depDesc = getDeploymentDescriptor(conn, jarId); if(depDesc != null) depDesc.install(conn); } protected static void deployRemove(Connection conn, int jarId) throws SQLException { SQLDeploymentDescriptor depDesc = getDeploymentDescriptor(conn, jarId); if(depDesc != null) depDesc.remove(conn); } /** * Throws an exception if the given name cannot be used as the name of * a jar. * @param jarName The naem to check. * @throws IOException */ protected static void assertJarName(String jarName) throws SQLException { if(jarName != null) { int len = jarName.length(); if(len > 0 && Character.isJavaIdentifierStart(jarName.charAt(0))) { int idx = 1; for(; idx < len; ++idx) if(!Character.isJavaIdentifierPart(jarName.charAt(idx))) break; if(idx == len) return; } } throw new SQLException("The jar name '" + jarName + "' is not a valid name"); } /** * Returns the primary key identifier for the given Jar. * @param conn The connection to use for the query. * @param jarName The name of the jar. * @return The primary key value of the given jar or -1 if no * such jar is found. * @throws SQLException */ protected static int getJarId(Connection conn, String jarName) throws SQLException { PreparedStatement stmt = conn.prepareStatement( "SELECT jarId FROM sqlj.jar_repository WHERE jarName = ?"); try { return getJarId(stmt, jarName); } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } protected static int getJarId(PreparedStatement stmt, String jarName) throws SQLException { stmt.setString(1, jarName); ResultSet rs = stmt.executeQuery(); try { if(!rs.next()) return -1; return rs.getInt(1); } finally { try { rs.close(); } catch(SQLException e) { /* ignore close errors */ } } } protected static SQLDeploymentDescriptor getDeploymentDescriptor(Connection conn, int jarId) throws SQLException { PreparedStatement stmt = conn.prepareStatement( "SELECT e.entryImage" + " FROM sqlj.jar_repository r INNER JOIN sqlj.jar_entry e" + " ON r.deploymentDesc = e.entryId" + " WHERE r.jarId = ?"); try { stmt.setInt(1, jarId); ResultSet rs = stmt.executeQuery(); try { if(!rs.next()) return null; byte[] bytes = rs.getBytes(1); if(bytes.length == 0) return null; // Accodring to the SQLJ standard, this entry must be // UTF8 encoded. // return new SQLDeploymentDescriptor(new String(bytes, "UTF8"), "postgresql"); } catch (UnsupportedEncodingException e) { // Excuse me? No UTF8 encoding? // throw new SQLException("JVM does not support UTF8!!"); } catch(ParseException e) { throw new SQLException(e.getMessage() + " at " + e.getErrorOffset()); } finally { try { rs.close(); } catch(SQLException e) { /* ignore close errors */ } } } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** * Returns the Oid for the given Schema. * @param conn The connection to use for the query. * @param schemaName The name of the schema. * @return The Oid of the given schema or null if no such * schema is found. * @throws SQLException */ protected static Oid getSchemaId(Connection conn, String schemaName) throws SQLException { PreparedStatement stmt = conn.prepareStatement( "SELECT oid FROM pg_namespace WHERE nspname = ?"); try { stmt.setString(1, schemaName); ResultSet rs = stmt.executeQuery(); try { if(!rs.next()) return null; return (Oid)rs.getObject(1); } finally { try { rs.close(); } catch(SQLException e) { /* ignore close errors */ } } } finally { try { stmt.close(); } catch(SQLException e) { /* ignore close errors */ } } } /** Executes an SQL command. The purpose of this method is to be executed as a "SECURITY DEFINER" function, so that the effective user is different from session user. */ public static void executeAs(String cmd) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection"); Statement stmt = conn.createStatement(); try { stmt.executeUpdate(cmd); } catch(SQLException e) { throw new SQLException( "Cannot execute: " + cmd + ", " + "reason: " + e.getMessage(), e.getSQLState(), e.getErrorCode()); } finally { stmt.close(); } } }