/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.odbc;
import java.io.IOException;
import java.io.StringReader;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.teiid.client.util.ResultsFuture;
import org.teiid.core.util.ApplicationInfo;
import org.teiid.core.util.StringUtil;
import org.teiid.jdbc.ConnectionImpl;
import org.teiid.jdbc.PreparedStatementImpl;
import org.teiid.jdbc.StatementImpl;
import org.teiid.jdbc.TeiidDriver;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.runtime.RuntimePlugin;
import org.teiid.transport.ODBCClientInstance;
/**
* While executing the multiple prepared statements I see this bug currently
* http://pgfoundry.org/tracker/?func=detail&atid=538&aid=1007690&group_id=1000125
*/
public class ODBCServerRemoteImpl implements ODBCServerRemote {
private static final String UNNAMED = "UNNAMED"; //$NON-NLS-1$
private static Pattern setPattern = Pattern.compile("(SET|set)\\s+(\\w+)\\s+(TO|to)\\s+'(\\w+\\d*)'");//$NON-NLS-1$
private static Pattern pkPattern = Pattern.compile("select ta.attname, ia.attnum, ic.relname, n.nspname, tc.relname " +//$NON-NLS-1$
"from pg_catalog.pg_attribute ta, pg_catalog.pg_attribute ia, pg_catalog.pg_class tc, pg_catalog.pg_index i, " +//$NON-NLS-1$
"pg_catalog.pg_namespace n, pg_catalog.pg_class ic where tc.relname = (E?(?:'[^']*')+) AND n.nspname = (E?(?:'[^']*')+).*" );//$NON-NLS-1$
private static Pattern pkKeyPattern = Pattern.compile("select ta.attname, ia.attnum, ic.relname, n.nspname, NULL from " + //$NON-NLS-1$
"pg_catalog.pg_attribute ta, pg_catalog.pg_attribute ia, pg_catalog.pg_class ic, pg_catalog.pg_index i, " + //$NON-NLS-1$
"pg_catalog.pg_namespace n where ic.relname = (E?(?:'[^']*')+) AND n.nspname = (E?(?:'[^']*')+) .*"); //$NON-NLS-1$
private Pattern fkPattern = Pattern.compile("select\\s+((?:'[^']*')+)::name as PKTABLE_CAT," + //$NON-NLS-1$
"\\s+n2.nspname as PKTABLE_SCHEM," + //$NON-NLS-1$
"\\s+c2.relname as PKTABLE_NAME," + //$NON-NLS-1$
"\\s+a2.attname as PKCOLUMN_NAME," + //$NON-NLS-1$
"\\s+((?:'[^']*')+)::name as FKTABLE_CAT," + //$NON-NLS-1$
"\\s+n1.nspname as FKTABLE_SCHEM," + //$NON-NLS-1$
"\\s+c1.relname as FKTABLE_NAME," + //$NON-NLS-1$
"\\s+a1.attname as FKCOLUMN_NAME," + //$NON-NLS-1$
"\\s+i::int2 as KEY_SEQ," + //$NON-NLS-1$
"\\s+case ref.confupdtype" + //$NON-NLS-1$
"\\s+when 'c' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'n' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'd' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'r' then (\\d)::int2" + //$NON-NLS-1$
"\\s+else 3::int2" + //$NON-NLS-1$
"\\s+end as UPDATE_RULE," + //$NON-NLS-1$
"\\s+case ref.confdeltype" + //$NON-NLS-1$
"\\s+when 'c' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'n' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'd' then (\\d)::int2" + //$NON-NLS-1$
"\\s+when 'r' then (\\d)::int2" + //$NON-NLS-1$
"\\s+else 3::int2" + //$NON-NLS-1$
"\\s+end as DELETE_RULE," + //$NON-NLS-1$
"\\s+ref.conname as FK_NAME," + //$NON-NLS-1$
"\\s+cn.conname as PK_NAME," + //$NON-NLS-1$
"\\s+case" + //$NON-NLS-1$
"\\s+when ref.condeferrable then" + //$NON-NLS-1$
"\\s+case" + //$NON-NLS-1$
"\\s+when ref.condeferred then (\\d)::int2" + //$NON-NLS-1$
"\\s+else (\\d)::int2" + //$NON-NLS-1$
"\\s+end" + //$NON-NLS-1$
"\\s+else (\\d)::int2" + //$NON-NLS-1$
"\\s+end as DEFERRABLITY" + //$NON-NLS-1$
"\\s+from" + //$NON-NLS-1$
"\\s+\\(\\(\\(\\(\\(\\(\\( \\(select cn.oid, conrelid, conkey, confrelid, confkey," + //$NON-NLS-1$
"\\s+generate_series\\(array_lower\\(conkey, 1\\), array_upper\\(conkey, 1\\)\\) as i," + //$NON-NLS-1$
"\\s+confupdtype, confdeltype, conname," + //$NON-NLS-1$
"\\s+condeferrable, condeferred" + //$NON-NLS-1$
"\\s+from pg_catalog.pg_constraint cn," + //$NON-NLS-1$
"\\s+pg_catalog.pg_class c," + //$NON-NLS-1$
"\\s+pg_catalog.pg_namespace n" + //$NON-NLS-1$
"\\s+where contype = 'f' " + //$NON-NLS-1$
"\\s+and conrelid = c.oid" + //$NON-NLS-1$
"\\s+and relname = (E?(?:'[^']*')+)" + //$NON-NLS-1$
"\\s+and n.oid = c.relnamespace" + //$NON-NLS-1$
"\\s+and n.nspname = (E?(?:'[^']*')+)" + //$NON-NLS-1$
"\\s+\\) ref" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_class c1" + //$NON-NLS-1$
"\\s+on c1.oid = ref.conrelid\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_namespace n1" + //$NON-NLS-1$
"\\s+on n1.oid = c1.relnamespace\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_attribute a1" + //$NON-NLS-1$
"\\s+on a1.attrelid = c1.oid" + //$NON-NLS-1$
"\\s+and a1.attnum = conkey\\[i\\]\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_class c2" + //$NON-NLS-1$
"\\s+on c2.oid = ref.confrelid\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_namespace n2" + //$NON-NLS-1$
"\\s+on n2.oid = c2.relnamespace\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_attribute a2" + //$NON-NLS-1$
"\\s+on a2.attrelid = c2.oid" + //$NON-NLS-1$
"\\s+and a2.attnum = confkey\\[i\\]\\)" + //$NON-NLS-1$
"\\s+left outer join pg_catalog.pg_constraint cn" + //$NON-NLS-1$
"\\s+on cn.conrelid = ref.confrelid" + //$NON-NLS-1$
"\\s+and cn.contype = 'p'\\)" + //$NON-NLS-1$
"\\s+order by ref.oid, ref.i"); //$NON-NLS-1$
private static Pattern preparedAutoIncrement = Pattern.compile("select 1 \\s*from pg_catalog.pg_attrdef \\s*where adrelid = \\$1 AND adnum = \\$2 " + //$NON-NLS-1$
"\\s*and pg_catalog.pg_get_expr\\(adbin, adrelid\\) \\s*like '%nextval\\(%'", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern deallocatePattern = Pattern.compile("DEALLOCATE \"(\\w+\\d+_*)\""); //$NON-NLS-1$
private static Pattern releasePattern = Pattern.compile("RELEASE (\\w+\\d+_*)"); //$NON-NLS-1$
private static Pattern savepointPattern = Pattern.compile("SAVEPOINT (\\w+\\d+_*)"); //$NON-NLS-1$
private static Pattern rollbackPattern = Pattern.compile("ROLLBACK\\s*(to)*\\s*(\\w+\\d+_*)*"); //$NON-NLS-1$
private TeiidDriver driver;
private ODBCClientRemote client;
private Properties props;
private AuthenticationType authType;
private ConnectionImpl connection;
private boolean executing;
private boolean errorOccurred;
private volatile ResultsFuture<Boolean> executionFuture;
// TODO: this is unbounded map; need to define some boundaries as to how many stmts each session can have
private Map<String, Prepared> preparedMap = Collections.synchronizedMap(new HashMap<String, Prepared>());
private Map<String, Portal> portalMap = Collections.synchronizedMap(new HashMap<String, Portal>());
public ODBCServerRemoteImpl(ODBCClientInstance client, AuthenticationType authType, TeiidDriver driver) {
this.driver = driver;
this.client = client.getClient();
this.authType = authType;
}
@Override
public void initialize(Properties props) {
this.props = props;
this.client.initialized(this.props);
if (this.authType.equals(AuthenticationType.CLEARTEXT)) {
this.client.useClearTextAuthentication();
}
else if (this.authType.equals(AuthenticationType.MD5)) {
// TODO: implement MD5 auth type
}
}
@Override
public void logon(String databaseName, String user, String password) {
try {
java.util.Properties info = new java.util.Properties();
String url = "jdbc:teiid:"+databaseName+";ApplicationName=ODBC"; //$NON-NLS-1$ //$NON-NLS-2$
info.put("user", user); //$NON-NLS-1$
info.put("password", password); //$NON-NLS-1$
this.connection = (ConnectionImpl)driver.connect(url, info);
int hash = this.connection.getConnectionId().hashCode();
Enumeration keys = this.props.propertyNames();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
Statement stmt = this.connection.createStatement();
stmt.execute("SET " + key + " '" + this.props.getProperty(key) + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
stmt.close();
}
this.client.authenticationSucess(hash, hash);
ready();
} catch (SQLException e) {
errorOccurred(e);
terminate();
}
}
@Override
public void prepare(String prepareName, String sql, int[] paramType) {
if (this.connection != null) {
if (prepareName == null || prepareName.length() == 0) {
prepareName = UNNAMED;
}
if (sql != null) {
String modfiedSQL = fixSQL(sql);
try {
// close if the name is already used or the unnamed prepare; otherwise
// stmt is alive until session ends.
Prepared previous = this.preparedMap.remove(prepareName);
if (previous != null) {
previous.stmt.close();
}
PreparedStatementImpl stmt = this.connection.prepareStatement(modfiedSQL);
this.preparedMap.put(prepareName, new Prepared(prepareName, sql, stmt, paramType));
this.client.prepareCompleted(prepareName);
} catch (SQLException e) {
errorOccurred(e);
}
}
}
else {
errorOccurred(RuntimePlugin.Util.getString("no_active_connection")); //$NON-NLS-1$
}
}
@Override
public void bindParameters(String bindName, String prepareName, int paramCount, Object[] params, int resultCodeCount, int[] resultColumnFormat) {
// An unnamed portal is destroyed at the end of the transaction, or as soon as
// the next Bind statement specifying the unnamed portal as destination is issued.
this.portalMap.remove(UNNAMED);
if (prepareName == null || prepareName.length() == 0) {
prepareName = UNNAMED;
}
Prepared previous = this.preparedMap.get(prepareName);
if (previous == null) {
errorOccurred(RuntimePlugin.Util.getString("bad_binding", prepareName)); //$NON-NLS-1$
return;
}
if (bindName == null || bindName.length() == 0) {
bindName = UNNAMED;
}
try {
for (int i = 0; i < paramCount; i++) {
previous.stmt.setObject(i+1, params[i]);
}
} catch (SQLException e) {
errorOccurred(e);
}
this.portalMap.put(bindName, new Portal(bindName, prepareName, previous.sql, previous.stmt, resultColumnFormat));
this.client.bindComplete();
}
@Override
public void unsupportedOperation(String msg) {
errorOccurred(msg);
}
@Override
public void execute(String bindName, int maxRows) {
if (beginExecution()) {
errorOccurred("Awaiting asynch result"); //$NON-NLS-1$
return;
}
if (bindName == null || bindName.length() == 0) {
bindName = UNNAMED;
}
final Portal query = this.portalMap.get(bindName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.getString("not_bound", bindName)); //$NON-NLS-1$
}
else {
if (query.sql.trim().isEmpty()) {
this.client.emptyQueryReceived();
return;
}
final PreparedStatementImpl stmt = query.stmt;
try {
// maxRows = 0, means unlimited.
if (maxRows != 0) {
stmt.setMaxRows(maxRows);
}
this.executionFuture = stmt.submitExecute();
executionFuture.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(ResultsFuture<Boolean> future) {
executionFuture = null;
try {
ResultsFuture<Void> result = null;
if (future.get()) {
result = new ResultsFuture<Void>();
client.sendResults(query.sql, stmt.getResultSet(), result, true);
} else {
result = ResultsFuture.NULL_FUTURE;
client.sendUpdateCount(query.sql, stmt.getUpdateCount());
setEncoding();
}
result.addCompletionListener(new ResultsFuture.CompletionListener<Void>() {
public void onCompletion(ResultsFuture<Void> future) {
try {
future.get();
doneExecuting();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
errorOccurred(e.getCause());
}
};
});
} catch (Throwable e) {
errorOccurred(e);
}
}
});
} catch (SQLException e) {
errorOccurred(e);
}
}
}
private String fixSQL(String sql) {
String modified = modifySQL(sql);
if (modified != null && !modified.equals(sql)) {
LogManager.logDetail(LogConstants.CTX_ODBC, "Modified Query:", modified); //$NON-NLS-1$
}
return modified;
}
private String modifySQL(String sql) {
String modified = sql;
// select current_schema()
// set client_encoding to 'WIN1252'
if (sql == null) {
return null;
}
// selects are coming with "select\t" so using a space after "select" does not always work
if (StringUtil.startsWithIgnoreCase(sql, "select")) { //$NON-NLS-1$
modified = sql.replace('\n', ' ');
Matcher m = null;
if ((m = pkPattern.matcher(modified)).matches()) {
return new StringBuffer("SELECT k.Name AS attname, convert(Position, short) AS attnum, TableName AS relname, SchemaName AS nspname, TableName AS relname") //$NON-NLS-1$
.append(" FROM SYS.KeyColumns k") //$NON-NLS-1$
.append(" WHERE ") //$NON-NLS-1$
.append(" UCASE(SchemaName)").append(" LIKE UCASE(").append(m.group(2)).append(")")//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
.append(" AND UCASE(TableName)") .append(" LIKE UCASE(").append(m.group(1)).append(")")//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
.append(" AND KeyType LIKE 'Primary'") //$NON-NLS-1$
.append(" ORDER BY attnum").toString(); //$NON-NLS-1$
}
else if ((m = pkKeyPattern.matcher(modified)).matches()) {
String tableName = m.group(1);
if (tableName.endsWith("_pkey'")) { //$NON-NLS-1$
tableName = tableName.substring(0, tableName.length()-6) + '\'';
return "select ia.attname, ia.attnum, ic.relname, n.nspname, NULL "+ //$NON-NLS-1$
"from pg_catalog.pg_attribute ia, pg_catalog.pg_class ic, pg_catalog.pg_namespace n, Sys.KeyColumns kc "+ //$NON-NLS-1$
"where ic.relname = "+tableName+" AND n.nspname = "+m.group(2)+" AND "+ //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
"n.oid = ic.relnamespace AND ia.attrelid = ic.oid AND kc.SchemaName = n.nspname " +//$NON-NLS-1$
"AND kc.TableName = ic.relname AND kc.KeyType = 'Primary' AND kc.Name = ia.attname order by ia.attnum";//$NON-NLS-1$
}
return "SELECT NULL, NULL, NULL, NULL, NULL FROM (SELECT 1) as X WHERE 0=1"; //$NON-NLS-1$
}
else if ((m = fkPattern.matcher(modified)).matches()){
return "SELECT PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, PKCOLUMN_NAME, FKTABLE_CAT, FKTABLE_SCHEM, "+//$NON-NLS-1$
"FKTABLE_NAME, FKCOLUMN_NAME, KEY_SEQ, UPDATE_RULE, DELETE_RULE, FK_NAME, PK_NAME, DEFERRABILITY "+//$NON-NLS-1$
"FROM SYS.ReferenceKeyColumns WHERE PKTABLE_NAME LIKE "+m.group(14)+" and PKTABLE_SCHEM LIKE "+m.group(15);//$NON-NLS-1$ //$NON-NLS-2$
}
else if (modified.equalsIgnoreCase("select version()")) { //$NON-NLS-1$
return "SELECT 'Teiid "+ApplicationInfo.getInstance().getReleaseNumber()+"'"; //$NON-NLS-1$ //$NON-NLS-2$
}
else if (modified.startsWith("SELECT name FROM master..sysdatabases")) { //$NON-NLS-1$
return "SELECT 'Teiid'"; //$NON-NLS-1$
}
else if (modified.equalsIgnoreCase("select db_name() dbname")) { //$NON-NLS-1$
return "SELECT current_database()"; //$NON-NLS-1$
}
else if (preparedAutoIncrement.matcher(modified).matches()) {
return "SELECT 1 from matpg_relatt where attrelid = ? and attnum = ? and autoinc = true"; //$NON-NLS-1$
}
else {
// since teiid can work with multiple schemas at a given time
// this call resolution is ambiguous
if (sql.equalsIgnoreCase("select current_schema()")) { //$NON-NLS-1$
return "SELECT ''"; //$NON-NLS-1$
}
}
}
else if (sql.equalsIgnoreCase("show max_identifier_length")){ //$NON-NLS-1$
return "select 63"; //$NON-NLS-1$
}
else {
Matcher m = setPattern.matcher(sql);
if (m.matches()) {
return "SET " + m.group(2) + " " + m.group(4); //$NON-NLS-1$ //$NON-NLS-2$
}
else if (modified.equalsIgnoreCase("BEGIN")) { //$NON-NLS-1$
return "START TRANSACTION"; //$NON-NLS-1$
}
else if ((m = rollbackPattern.matcher(modified)).matches()) {
return "ROLLBACK"; //$NON-NLS-1$
}
else if ((m = savepointPattern.matcher(modified)).matches()) {
return "SELECT 'SAVEPOINT'"; //$NON-NLS-1$
}
else if ((m = releasePattern.matcher(modified)).matches()) {
return "SELECT 'RELEASE'"; //$NON-NLS-1$
}
else if ((m = deallocatePattern.matcher(modified)).matches()) {
closePreparedStatement(m.group(1));
return "SELECT 'DEALLOCATE'"; //$NON-NLS-1$
}
}
modified = sql;
//these are somewhat dangerous
modified = modified.replaceAll("::[A-Za-z0-9]*", " "); //$NON-NLS-1$ //$NON-NLS-2$
modified = modified.replaceAll("'pg_toast'", "'SYS'"); //$NON-NLS-1$ //$NON-NLS-2$
return modified;
}
@Override
public void executeQuery(final String query) {
if (beginExecution()) {
this.client.errorOccurred("Awaiting asynch result"); //$NON-NLS-1$
ready();
return;
}
//46.2.3 Note that a simple Query message also destroys the unnamed portal.
this.portalMap.remove(UNNAMED);
this.preparedMap.remove(UNNAMED);
if (query.trim().length() == 0) {
this.client.emptyQueryReceived();
ready();
return;
}
QueryWorkItem r = new QueryWorkItem(query);
r.run();
}
private boolean beginExecution() {
if (this.executionFuture != null) {
return true;
}
synchronized (this) {
this.executing = true;
}
return false;
}
public boolean isExecuting() {
return executing;
}
public boolean isErrorOccurred() {
return errorOccurred;
}
@Override
public void getParameterDescription(String prepareName) {
if (prepareName == null || prepareName.length() == 0) {
prepareName = UNNAMED;
}
Prepared query = this.preparedMap.get(prepareName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.getString("no_stmt_found", prepareName)); //$NON-NLS-1$
}
else {
try {
this.client.sendParameterDescription(query.stmt.getParameterMetaData(), query.paramType);
} catch (SQLException e) {
errorOccurred(e);
}
}
}
private void errorOccurred(String error) {
this.client.errorOccurred(error);
synchronized (this) {
this.errorOccurred = true;
doneExecuting();
}
}
private void errorOccurred(Throwable error) {
this.client.errorOccurred(error);
synchronized (this) {
this.errorOccurred = true;
doneExecuting();
}
}
@Override
public void getResultSetMetaDataDescription(String bindName) {
if (bindName == null || bindName.length() == 0) {
bindName = UNNAMED;
}
Portal query = this.portalMap.get(bindName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.getString("not_bound", bindName)); //$NON-NLS-1$
}
else {
try {
this.client.sendResultSetDescription(query.stmt.getMetaData(), query.stmt);
} catch (SQLException e) {
errorOccurred(e);
}
}
}
@Override
public void sync() {
ready();
}
protected synchronized void doneExecuting() {
executing = false;
}
private void ready() {
boolean inTxn = false;
boolean failedTxn = false;
try {
if (!this.connection.getAutoCommit()) {
inTxn = true;
}
} catch (SQLException e) {
failedTxn = true;
}
synchronized (this) {
this.errorOccurred = false;
}
this.client.ready(inTxn, failedTxn);
}
@Override
public void cancel() {
// TODO Auto-generated method stub
}
@Override
public void closeBoundStatement(String bindName) {
if (bindName == null || bindName.length() == 0) {
bindName = UNNAMED;
}
Portal query = this.portalMap.remove(bindName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.getString("not_bound", bindName)); //$NON-NLS-1$
}
else {
try {
if (this.connection.getAutoCommit()) {
// After checking the pg's client code I do not see it send a
// close of the Prepare stmt as per the wire protocol, it only sends
// bound close. Since it also have issue with
// http://pgfoundry.org/tracker/?func=detail&atid=538&aid=1007690&group_id=1000125
// treating the prepare and bound as same for now.
closePreparedStatement(bindName);
}
} catch (SQLException e) {
closePreparedStatement(bindName);
}
}
}
@Override
public void closePreparedStatement(String preparedName) {
if (preparedName == null || preparedName.length() == 0) {
preparedName = UNNAMED;
}
Prepared query = this.preparedMap.remove(preparedName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.getString("no_stmt_found", preparedName)); //$NON-NLS-1$
}
else {
// Close all the bound messages off of this prepared
// TODO: can there be more than one?
this.portalMap.remove(preparedName);
try {
query.stmt.close();
this.client.statementClosed();
} catch (SQLException e) {
errorOccurred(RuntimePlugin.Util.getString("error_closing_stmt", preparedName)); //$NON-NLS-1$
}
}
}
@Override
public void terminate() {
for (Portal p: this.portalMap.values()) {
try {
p.stmt.close();
} catch (SQLException e) {
//ignore
}
}
for (Prepared p:this.preparedMap.values()) {
try {
p.stmt.close();
} catch (SQLException e) {
//ignore
}
}
try {
if (this.connection != null) {
this.connection.close();
}
} catch (SQLException e) {
//ignore
}
this.client.terminated();
}
@Override
public void flush() {
this.client.flush();
}
@Override
public void functionCall(int oid) {
errorOccurred(RuntimePlugin.Util.getString("lo_not_supported")); //$NON-NLS-1$
}
@Override
public void sslRequest() {
this.client.sslDenied();
}
private void setEncoding() {
try {
StatementImpl t = connection.createStatement();
ResultSet rs = t.executeQuery("show client_encoding"); //$NON-NLS-1$
if (rs.next()) {
String encoding = rs.getString(1);
if (encoding != null) {
//this may be unnecessary
this.client.setEncoding(encoding);
}
}
} catch (Exception e) {
//don't care
}
}
private final class QueryWorkItem implements Runnable {
private final ScriptReader reader;
String modfiedSQL;
String sql;
private QueryWorkItem(String query) {
this.reader = new ScriptReader(new StringReader(query));
}
@Override
public void run() {
try {
if (modfiedSQL == null) {
sql = reader.readStatement();
modfiedSQL = fixSQL(sql);
}
while (modfiedSQL != null) {
try {
final StatementImpl stmt = connection.createStatement();
executionFuture = stmt.submitExecute(modfiedSQL);
executionFuture.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(ResultsFuture<Boolean> future) {
executionFuture = null;
try {
ResultsFuture<Void> result = null;
if (future.get()) {
if (stmt.getResultSet() != null) {
result = new ResultsFuture<Void>();
client.sendResults(sql, stmt.getResultSet(), result, true);
}
else {
// handles the "SET" commands.
result = ResultsFuture.NULL_FUTURE;
client.sendUpdateCount(sql, 0);
}
} else {
result = ResultsFuture.NULL_FUTURE;
client.sendUpdateCount(sql, stmt.getUpdateCount());
setEncoding();
}
result.addCompletionListener(new ResultsFuture.CompletionListener<Void>() {
public void onCompletion(ResultsFuture<Void> future) {
try {
future.get();
sql = reader.readStatement();
modfiedSQL = fixSQL(sql);
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (IOException e) {
client.errorOccurred(e);
return;
} catch (ExecutionException e) {
client.errorOccurred(e.getCause());
return;
} finally {
try {
stmt.close();
} catch (SQLException e) {
LogManager.logDetail(LogConstants.CTX_ODBC, e, "Error closing statement"); //$NON-NLS-1$
}
}
QueryWorkItem.this.run(); //continue processing
};
});
} catch (Throwable e) {
client.errorOccurred(e);
return;
}
}
});
return; //wait for the execution to finish
} catch (SQLException e) {
client.errorOccurred(e);
break;
}
}
} catch(IOException e) {
client.errorOccurred(e);
}
doneExecuting();
ready();
}
}
/**
* Represents a PostgreSQL Prepared object.
*/
static class Prepared {
public Prepared (String name, String sql, PreparedStatementImpl stmt, int[] paramType) {
this.name = name;
this.sql = sql;
this.stmt = stmt;
this.paramType = paramType;
}
/**
* The object name.
*/
String name;
/**
* The SQL statement.
*/
String sql;
/**
* The prepared statement.
*/
PreparedStatementImpl stmt;
/**
* The list of parameter types (if set).
*/
int[] paramType;
}
/**
* Represents a PostgreSQL Portal object.
*/
static class Portal {
public Portal(String name, String preparedName, String sql, PreparedStatementImpl stmt, int[] resultColumnformat) {
this.name = name;
this.preparedName = preparedName;
this.sql = sql;
this.stmt = stmt;
this.resultColumnFormat = resultColumnformat;
}
/**
* The portal name.
*/
String name;
String preparedName;
/**
* The SQL statement.
*/
String sql;
/**
* The format used in the result set columns (if set).
*/
int[] resultColumnFormat;
/**
* The prepared statement.
*/
PreparedStatementImpl stmt;
}
}