/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Rodrigo Westrupp */ package com.caucho.quercus.lib.db; import com.caucho.quercus.annotation.Optional; import com.caucho.quercus.annotation.ResourceType; import com.caucho.quercus.env.ConnectionEntry; import com.caucho.quercus.env.Env; import com.caucho.quercus.env.LongValue; import com.caucho.quercus.env.StringValue; import com.caucho.quercus.env.Value; import com.caucho.util.L10N; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * postgres connection class (postgres has NO object oriented API) */ @ResourceType("pgsql link") public class Postgres extends JdbcConnectionResource { private static final Logger log = Logger.getLogger(Postgres.class.getName()); private static final L10N L = new L10N(Postgres.class); PostgresResult _asyncResult; PostgresStatement _asyncStmt; // named prepared statements for postgres private HashMap<String,PostgresStatement> _stmtTable = new HashMap<String,PostgresStatement>(); // Postgres specific server error message // org.postgresql.util.ServerErrorMessage Object _serverErrorMessage; public Postgres(Env env, @Optional("localhost") String host, @Optional String user, @Optional String password, @Optional String db, @Optional("5432") int port, @Optional String driver, @Optional String url) { super(env); connectInternal(env, host, user, password, db, port, "", 0, driver, url, false); } /** * Connects to the underlying database. */ @Override protected ConnectionEntry connectImpl(Env env, String host, String userName, String password, String dbname, int port, String socket, int flags, String driver, String url, boolean isNewLink) { if (isConnected()) { env.warning(L.l("Connection is already opened to '{0}'", this)); return null; } try { if (host == null || host.equals("")) { host = "localhost"; } if (driver == null || driver.equals("")) { driver = "org.postgresql.Driver"; } if (url == null || url.equals("")) { url = "jdbc:postgresql://" + host + ":" + port + "/" + dbname; } ConnectionEntry jConn; jConn = env.getConnection(driver, url, userName, password, ! isNewLink); return jConn; } catch (SQLException e) { env.warning( "A link to the server could not be established. " + e.toString()); env.setSpecialValue( "postgres.connectErrno", LongValue.create(e.getErrorCode())); env.setSpecialValue( "postgres.connectError", env.createString(e.getMessage())); log.log(Level.FINE, e.toString(), e); return null; } catch (Exception e) { env.warning( "A link to the server could not be established. " + e.toString()); env.setSpecialValue( "postgres.connectError", env.createString(e.getMessage())); log.log(Level.FINE, e.toString(), e); return null; } } /** * returns a prepared statement */ public PostgresStatement prepare(Env env, StringValue query) { PostgresStatement stmt = new PostgresStatement( (Postgres)validateConnection()); stmt.prepare(env, query); return stmt; } /** * Executes a query. * * @param sql the escaped query string * (can contain escape sequences like `\n' and `\Z') * * @return a {@link JdbcResultResource}, or null for failure */ public PostgresResult query(Env env, String sql) { SqlParseToken tok = parseSqlToken(sql, null); if (tok != null && tok.matchesFirstChar('S', 's') && tok.matchesToken("SET")) { // Check for "SET CLIENT_ENCODING TO ..." tok = parseSqlToken(sql, tok); if (tok != null && tok.matchesToken("CLIENT_ENCODING")) { tok = parseSqlToken(sql, tok); if (tok != null && tok.matchesToken("TO")) { // Ignore any attempt to change the CLIENT_ENCODING since // the JDBC driver for Postgres only supports UNICODE. // Execute no-op SQL statement since we need to return // a valid SQL result to the caller. sql = "SET CLIENT_ENCODING TO 'UNICODE'"; } } } Object result = realQuery(env, sql).toJavaObject(); if (! (result instanceof PostgresResult)) return null; return (PostgresResult) result; } /** * Creates a database-specific result. */ protected JdbcResultResource createResult(Env env, Statement stmt, ResultSet rs) { return new PostgresResult(env, stmt, rs, this); } public void setAsynchronousResult(PostgresResult asyncResult) { _asyncResult = asyncResult; } public PostgresResult getAsynchronousResult() { return _asyncResult; } public PostgresStatement getAsynchronousStatement() { return _asyncStmt; } public void setAsynchronousStatement(PostgresStatement asyncStmt) { _asyncStmt = asyncStmt; } public void putStatement(String name, PostgresStatement stmt) { _stmtTable.put(name, stmt); } public PostgresStatement getStatement(String name) { return _stmtTable.get(name); } public PostgresStatement removeStatement(String name) { return _stmtTable.remove(name); } /** * This function is overriden in Postgres to keep * result set references for php/430a (see also php/1f33) */ protected void keepResourceValues(Statement stmt) { setResultResource(createResult(getEnv(), stmt, null)); } /** * This function is overriden in Postgres to keep * statement references for php/430a */ protected boolean keepStatementOpen() { return true; } static public StringValue pgRealEscapeString(StringValue str) { StringValue buf = str.createStringBuilder(str.length()); final int strLength = str.length(); for (int i = 0; i < strLength; i++) { char c = str.charAt(i); switch (c) { case '\u0000': buf.append('\\'); buf.append('\u0000'); break; case '\n': buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '\'': buf.append('\''); buf.append('\''); break; case '"': // pg_escape_string does nothing about it. // buf.append('\\'); buf.append('\"'); break; case '\032': buf.append('\\'); buf.append('Z'); break; default: buf.append(c); break; } } return buf; } /** * Escape the given string for SQL statements. * * @param str a string * @return the string escaped for SQL statements */ protected StringValue realEscapeString(StringValue str) { return pgRealEscapeString(str); } /** * This function is overriden in Postgres to clear * any postgres specific server error message */ protected void clearErrors() { super.clearErrors(); _serverErrorMessage = null; } /** * This function is overriden in Postgres to save * the postgres specific server error message */ protected void saveErrors(SQLException e) { try { super.saveErrors(e); // Get the postgres specific server error message Class cl = Class.forName("org.postgresql.util.PSQLException"); Method method = cl.getDeclaredMethod("getServerErrorMessage", null); _serverErrorMessage = method.invoke(e, new Object[] {}); } catch (Exception ex) { log.log(Level.FINE, ex.toString(), ex); } } /** * Return the postgres server specific error message */ protected Object getServerErrorMessage() { return _serverErrorMessage; } public String toString() { if (isConnected()) return "Postgres[" + getHost() + "]"; else return "Postgres[]"; } /** * Return the "client_encoding" property. This is the * encoding the JDBC driver uses to read character * data from the server. The JDBC driver used to let * the user change the encoding, but it now fails on * any attempt to set the encoding to anything other * than UNICODE. */ public String getClientEncoding() { return "UNICODE"; } /** * Set the "client_encoding" property. This is * a no-op for the JDBC driver because it only * supports UNICODE as the client encoding. * Return true to indicate success in all cases. */ public boolean setClientEncoding(String encoding) { return true; } }