/*
* 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 static org.teiid.odbc.PGUtil.*;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
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.ietf.jgss.GSSCredential;
import org.teiid.adminapi.VDB;
import org.teiid.adminapi.impl.SessionMetadata;
import org.teiid.client.RequestMessage.ResultsMode;
import org.teiid.client.security.ILogon;
import org.teiid.client.security.LogonException;
import org.teiid.client.util.ResultsFuture;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.util.EquivalenceUtil;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.core.util.StringUtil;
import org.teiid.core.util.TimestampWithTimezone;
import org.teiid.deployers.PgCatalogMetadataStore;
import org.teiid.dqp.service.SessionService;
import org.teiid.jdbc.ConnectionImpl;
import org.teiid.jdbc.PreparedStatementImpl;
import org.teiid.jdbc.ResultSetImpl;
import org.teiid.jdbc.StatementImpl;
import org.teiid.jdbc.TeiidDriver;
import org.teiid.jdbc.TeiidSQLException;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.net.TeiidURL;
import org.teiid.net.socket.AuthenticationType;
import org.teiid.net.socket.SocketServerConnection;
import org.teiid.odbc.ODBCClientRemote.CursorDirection;
import org.teiid.odbc.PGUtil.PgColInfo;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.runtime.RuntimePlugin;
import org.teiid.security.GSSResult;
import org.teiid.transport.LocalServerConnection;
import org.teiid.transport.LogonImpl;
import org.teiid.transport.ODBCClientInstance;
import org.teiid.transport.PgBackendProtocol;
import org.teiid.transport.PgFrontendProtocol.NullTerminatedStringDataInputStream;
import org.teiid.transport.pg.TimestampUtils;
/**
* 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 boolean HONOR_DECLARE_FETCH_TXN = PropertiesUtils.getBooleanProperty(System.getProperties(), "org.teiid.honorDeclareFetchTxn", false); //$NON-NLS-1$
private static final String POSTGRESQL_VERSION = System.getProperties().getProperty("org.teiid.pgVersion", "PostgreSQL 8.2"); //$NON-NLS-1$ //$NON-NLS-2$
public static final String CONNECTION_PROPERTY_PREFIX = "connection."; //$NON-NLS-1$
private static final String UNNAMED = ""; //$NON-NLS-1$
private static Pattern setPattern = Pattern.compile("set\\s+(\\w+)\\s+to\\s+((?:'[^']*')+)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE);//$NON-NLS-1$
private static Pattern columnMetadataPattern = Pattern.compile("select n.nspname, c.relname, a.attname, a.atttypid, t.typname, a.attnum, a.attlen, a.atttypmod, a.attnotnull, " //$NON-NLS-1$
+ "c.relhasrules, c.relkind, c.oid, pg_get_expr\\(d.adbin, d.adrelid\\), case t.typtype when 'd' then t.typbasetype else 0 end, t.typtypmod, c.relhasoids " //$NON-NLS-1$
+ "from \\(\\(\\(pg_catalog.pg_class c inner join pg_catalog.pg_namespace n on n.oid = c.relnamespace and c.oid = (\\d+)\\) inner join pg_catalog.pg_attribute a " //$NON-NLS-1$
+ "on \\(not a.attisdropped\\) and a.attnum > 0 and a.attrelid = c.oid\\) inner join pg_catalog.pg_type t on t.oid = a.atttypid\\) left outer join pg_attrdef d " //$NON-NLS-1$
+ "on a.atthasdef and d.adrelid = a.attrelid and d.adnum = a.attnum order by n.nspname, c.relname, attnum"); //$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?(?:'[^']*')+).*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE);//$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?(?:'[^']*')+) .*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$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\\s+con(f?)relid = c.oid" + //$NON-NLS-1$
"\\s+and\\s+relname = (E?(?:'[^']*')+)" + //$NON-NLS-1$
"\\s+and\\s+n.oid = c.relnamespace" + //$NON-NLS-1$
"\\s+and\\s+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\\s+n1.oid = c1.relnamespace\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_attribute a1" + //$NON-NLS-1$
"\\s+on\\s+a1.attrelid = c1.oid" + //$NON-NLS-1$
"\\s+and\\s+a1.attnum = conkey\\[i\\]\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_class c2" + //$NON-NLS-1$
"\\s+on\\s+c2.oid = ref.confrelid\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_namespace n2" + //$NON-NLS-1$
"\\s+on\\s+n2.oid = c2.relnamespace\\)" + //$NON-NLS-1$
"\\s+inner join pg_catalog.pg_attribute a2" + //$NON-NLS-1$
"\\s+on\\s+a2.attrelid = c2.oid" + //$NON-NLS-1$
"\\s+and\\s+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", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern cursorSelectPattern = Pattern.compile("DECLARE\\s+(\\S+)(\\s+BINARY)?(?:\\s+INSENSITIVE)?(\\s+(NO\\s+)?SCROLL)?\\s+CURSOR\\s+(?:WITH(?:OUT)? HOLD\\s+)?FOR\\s+(.*)", Pattern.CASE_INSENSITIVE|Pattern.DOTALL); //$NON-NLS-1$
private static Pattern fetchPattern = Pattern.compile("FETCH(?:(?:\\s+(FORWARD|ABSOLUTE|RELATIVE))?\\s+(\\d+)\\s+(?:IN|FROM))?\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern fetchFirstLastPattern = Pattern.compile("FETCH\\s+(FIRST|LAST)\\s+(?:IN|FROM)\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern movePattern = Pattern.compile("MOVE(?:\\s+(FORWARD|BACKWARD))?\\s+(\\d+)\\s+(?:IN|FROM)\\s+(\\S+)\\s*", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern closePattern = Pattern.compile("CLOSE (\\S+)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern deallocatePattern = Pattern.compile("DEALLOCATE(?:\\s+PREPARE)?\\s+(.*)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern releasePattern = Pattern.compile("RELEASE\\s+(\\w+\\d?_*)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern savepointPattern = Pattern.compile("SAVEPOINT\\s+(\\w+\\d?_*)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern rollbackPattern = Pattern.compile("ROLLBACK(\\s+to)?\\s+(\\w+\\d+_*)", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private static Pattern txnPattern = Pattern.compile("(BEGIN(?:\\s+READ\\s+ONLY)?|COMMIT|ROLLBACK)(\\s+(WORK|TRANSACTION))?", Pattern.DOTALL|Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
private TeiidDriver driver;
private ODBCClientRemote client;
private Properties props;
private ConnectionImpl connection;
private volatile 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>());
private Map<String, Cursor> cursorMap = Collections.synchronizedMap(new HashMap<String, Cursor>());
private LogonImpl logon;
public ODBCServerRemoteImpl(ODBCClientInstance client, TeiidDriver driver, LogonImpl logon) {
this.driver = driver;
this.client = client.getClient();
this.logon = logon;
}
@Override
public void initialize(Properties props) {
this.props = props;
this.client.initialized(this.props);
String user = props.getProperty("user"); //$NON-NLS-1$
String database = props.getProperty("database"); //$NON-NLS-1$
AuthenticationType authType = null;
try {
authType = getAuthenticationType(user, database);
} catch (LogonException e) {
errorOccurred(e);
terminate();
return;
}
if (authType.equals(AuthenticationType.USERPASSWORD)) {
this.client.useClearTextAuthentication();
}
else if (authType.equals(AuthenticationType.GSS)) {
this.client.useAuthenticationGSS();
} else {
throw new AssertionError("Unsupported Authentication Type"); //$NON-NLS-1$
}
}
private AuthenticationType getAuthenticationType(String user,
String database) throws LogonException {
SessionService ss = this.logon.getSessionService();
if (ss == null) {
return AuthenticationType.USERPASSWORD;
}
return ss.getAuthenticationType(database, null, user);
}
@Override
public void logon(String databaseName, String user, NullTerminatedStringDataInputStream data, SocketAddress remoteAddress) {
try {
java.util.Properties info = new java.util.Properties();
info.put(TeiidURL.CONNECTION.USER_NAME, user);
AuthenticationType authType = getAuthenticationType(user, databaseName);
String password = null;
if (authType.equals(AuthenticationType.USERPASSWORD)) {
password = data.readString();
}
else if (authType.equals(AuthenticationType.GSS)) {
byte[] serviceToken = data.readServiceToken();
GSSResult result = this.logon.neogitiateGssLogin(serviceToken, databaseName, null, user);
serviceToken = result.getServiceToken();
if (result.isAuthenticated()) {
info.put(ILogon.KRB5TOKEN, serviceToken);
if (!result.isNullContinuationToken()) {
this.client.authenticationGSSContinue(serviceToken);
}
// if delegation is in progress, participate in it.
if (result.getDelegationCredential() != null) {
info.put(GSSCredential.class.getName(), result.getDelegationCredential());
}
}
else {
this.client.authenticationGSSContinue(serviceToken);
return;
}
} else {
throw new AssertionError("Unsupported Authentication Type"); //$NON-NLS-1$
}
// this is local connection
String url = "jdbc:teiid:"+databaseName; //$NON-NLS-1$
if (password != null) {
info.put(TeiidURL.CONNECTION.PASSWORD, password);
}
String applicationName = this.props.getProperty(PgBackendProtocol.APPLICATION_NAME);
if (applicationName == null) {
applicationName = PgBackendProtocol.DEFAULT_APPLICATION_NAME;
this.props.put(PgBackendProtocol.APPLICATION_NAME, applicationName);
}
info.put(TeiidURL.CONNECTION.APP_NAME, applicationName);
if (remoteAddress instanceof InetSocketAddress) {
SocketServerConnection.updateConnectionProperties(info, ((InetSocketAddress)remoteAddress).getAddress(), false);
}
this.connection = driver.connect(url, info);
//Propagate so that we can use in pg methods
SessionMetadata sm = ((LocalServerConnection)this.connection.getServerConnection()).getWorkContext().getSession();
sm.addAttchment(ODBCServerRemoteImpl.class, this);
setConnectionProperties(this.connection);
int hash = this.connection.getConnectionId().hashCode();
Enumeration<?> keys = this.props.propertyNames();
while (keys.hasMoreElements()) {
String key = (String)keys.nextElement();
this.connection.setExecutionProperty(key, this.props.getProperty(key));
}
StatementImpl s = this.connection.createStatement();
try {
s.execute("select teiid_session_set('resolve_groupby_positional', true)"); //$NON-NLS-1$
} finally {
s.close();
}
this.client.authenticationSucess(hash, hash);
ready();
} catch (SQLException e) {
errorOccurred(e);
terminate();
} catch(LogonException e) {
errorOccurred(e);
terminate();
} catch (IOException e) {
errorOccurred(e);
terminate();
}
}
public static void setConnectionProperties(ConnectionImpl conn)
throws SQLException {
SessionMetadata sm = ((LocalServerConnection)conn.getServerConnection()).getWorkContext().getSession();
VDB vdb = sm.getVdb();
Properties p = vdb.getProperties();
setConnectionProperties(conn, p);
}
public static void setConnectionProperties(ConnectionImpl conn,
Properties p) {
for (Map.Entry<Object, Object> entry : p.entrySet()) {
String key = (String)entry.getKey();
if (key.startsWith(CONNECTION_PROPERTY_PREFIX)) {
conn.setExecutionProperty(key.substring(CONNECTION_PROPERTY_PREFIX.length()), (String) entry.getValue());
}
}
}
private void cursorExecute(String cursorName, final String sql, final ResultsFuture<Integer> completion, boolean scroll, final boolean binary) {
try {
// close if the name is already used or the unnamed prepare; otherwise
// stmt is alive until session ends.
this.preparedMap.remove(UNNAMED);
Portal p = this.portalMap.remove(UNNAMED);
if (p != null) {
closePortal(p);
}
if (cursorName == null || cursorName.length() == 0) {
cursorName = UNNAMED;
}
Cursor cursor = cursorMap.get(cursorName);
if (cursor != null) {
errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40111, cursorName));
return;
}
final PreparedStatementImpl stmt = this.connection.prepareStatement(sql, scroll?ResultSet.TYPE_SCROLL_INSENSITIVE:ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
this.executionFuture = stmt.submitExecute(ResultsMode.RESULTSET, null);
final String name = cursorName;
this.executionFuture.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(ResultsFuture<Boolean> future) {
executionFuture = null;
try {
if (future.get()) {
List<PgColInfo> cols = getPgColInfo(stmt.getResultSet().getMetaData());
cursorMap.put(name, new Cursor(name, sql, stmt, stmt.getResultSet(), cols, binary));
client.sendCommandComplete("DECLARE CURSOR", null); //$NON-NLS-1$
completion.getResultsReceiver().receiveResults(0);
}
} catch (Throwable e) {
completion.getResultsReceiver().exceptionOccurred(e);
}
}
});
} catch (SQLException e) {
completion.getResultsReceiver().exceptionOccurred(e);
}
}
private void cursorFetch(String cursorName, CursorDirection direction, int rows, final ResultsFuture<Integer> completion) throws SQLException {
Cursor cursor = this.cursorMap.get(cursorName);
if (cursor == null) {
throw new SQLException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40078, cursorName));
}
if (rows < 1) {
throw new SQLException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40112, cursorName, rows));
}
this.client.sendResults("FETCH", cursor.rs, cursor.prepared.columnMetadata, completion, direction, rows, true, cursor.resultColumnFormat); //$NON-NLS-1$
}
private void cursorMove(String prepareName, String direction, final int rows, final ResultsFuture<Integer> completion) throws SQLException {
// win odbc driver sending a move after close; and error is ending up in failure; since the below
// is not harmful it is ok to send empty move.
if (rows == 0) {
client.sendCommandComplete("MOVE", 0); //$NON-NLS-1$
completion.getResultsReceiver().receiveResults(0);
return;
}
final Cursor cursor = this.cursorMap.get(prepareName);
if (cursor == null) {
throw new SQLException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40078, prepareName));
}
final boolean forward = direction == null || direction.equalsIgnoreCase("forward"); //$NON-NLS-1$
Runnable r = new Runnable() {
public void run() {
run(null, 0);
}
public void run(ResultsFuture<Boolean> next, int i) {
for (; i < rows; i++) {
try {
if (next == null) {
if (forward) {
next = cursor.rs.submitNext();
} else {
//TODO: we know that we are scrollable in this case, we should just
//use an absolute positioning
//as of now previous is non-blocking
next = StatementImpl.booleanFuture(cursor.rs.previous());
}
}
if (!next.isDone()) {
final int current = i;
next.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(
ResultsFuture<Boolean> future) {
run(future, current); //restart later
}
});
return;
}
if (!next.get()) {
break; //no next row
}
next = null;
} catch (Throwable e) {
completion.getResultsReceiver().exceptionOccurred(e);
return;
}
}
if (!completion.isDone()) {
client.sendCommandComplete("MOVE", i); //$NON-NLS-1$
completion.getResultsReceiver().receiveResults(i);
}
}
};
r.run();
}
private void cursorClose(String prepareName) throws SQLException {
Cursor cursor = this.cursorMap.remove(prepareName);
if (cursor != null) {
closePortal(cursor);
this.client.sendCommandComplete("CLOSE CURSOR", null); //$NON-NLS-1$
}
}
private void sqlExecute(final String sql, final ResultsFuture<Integer> completion) throws SQLException {
String modfiedSQL = fixSQL(sql);
final StatementImpl stmt = connection.createStatement();
executionFuture = stmt.submitExecute(modfiedSQL, null);
completion.addCompletionListener(new ResultsFuture.CompletionListener<Integer>() {
public void onCompletion(ResultsFuture<Integer> future) {
try {
stmt.close();
} catch (SQLException e) {
LogManager.logDetail(LogConstants.CTX_ODBC, e, "Error closing statement"); //$NON-NLS-1$
}
}
});
executionFuture.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(ResultsFuture<Boolean> future) {
executionFuture = null;
try {
if (future.get()) {
List<PgColInfo> cols = getPgColInfo(stmt.getResultSet().getMetaData());
String tag = PgBackendProtocol.getCompletionTag(sql, null);
client.sendResults(sql, stmt.getResultSet(), cols, completion, CursorDirection.FORWARD, -1, tag.equals("SELECT") || tag.equals("SHOW"), null); //$NON-NLS-1$ //$NON-NLS-2$
} else {
client.sendUpdateCount(sql, stmt.getUpdateCount());
updateSessionProperties();
completion.getResultsReceiver().receiveResults(1);
}
} catch (Throwable e) {
if (!completion.isDone()) {
completion.getResultsReceiver().exceptionOccurred(e);
}
}
}
});
}
@Override
public void prepare(String prepareName, String sql, int[] paramType) {
if (prepareName == null || prepareName.length() == 0) {
prepareName = UNNAMED;
}
if (sql != null) {
PreparedStatementImpl stmt = null;
try {
// close if the name is already used or the unnamed prepare; otherwise
// stmt is alive until session ends.
if (prepareName.equals(UNNAMED)) {
this.preparedMap.remove(prepareName);
} else {
Prepared previous = this.preparedMap.get(prepareName);
if (previous != null) {
errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40110, prepareName));
return;
}
}
//just pull the initial information - leave statement formation until binding
String modfiedSQL = fixSQL(sql);
stmt = this.connection.prepareStatement(modfiedSQL);
Prepared prepared = new Prepared(prepareName, sql, modfiedSQL, paramType, getPgColInfo(stmt.getMetaData()));
this.preparedMap.put(prepareName, prepared);
this.client.prepareCompleted(prepareName);
} catch (SQLException e) {
if (e.getCause() instanceof TeiidProcessingException) {
LogManager.logWarning(LogConstants.CTX_ODBC, e.getCause(), RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40020));
}
errorOccurred(e);
} finally {
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
}
}
}
}
private long readLong(byte[] bytes, int length) {
long val = 0;
for (int k = 0; k < length; k++) {
val += ((bytes[k] & 255) << ((length - k - 1)*8));
}
return val;
}
@Override
public void bindParameters(String bindName, String prepareName, Object[] params, int resultCodeCount, short[] resultColumnFormat, Charset encoding) {
// 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.
if (bindName == null || bindName.length() == 0) {
Portal p = this.portalMap.remove(UNNAMED);
if (p != null) {
closePortal(p);
}
bindName = UNNAMED;
} else if (this.portalMap.get(bindName) != null || this.cursorMap.get(bindName) != null) {
errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40111, bindName));
return;
}
if (prepareName == null || prepareName.length() == 0) {
prepareName = UNNAMED;
}
Prepared prepared = this.preparedMap.get(prepareName);
if (prepared == null) {
errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40077, prepareName));
return;
}
PreparedStatementImpl stmt = null;
try {
stmt = this.connection.prepareStatement(prepared.modifiedSql);
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if (param instanceof byte[] && prepared.paramType.length > i) {
int oid = prepared.paramType[i];
switch (oid) {
case PGUtil.PG_TYPE_UNSPECIFIED:
//TODO: should infer type from the parameter metadata from the parse message
break;
case PGUtil.PG_TYPE_BYTEA:
break;
case PGUtil.PG_TYPE_INT2:
param = (short)readLong((byte[])param, 2);
break;
case PGUtil.PG_TYPE_INT4:
param = (int)readLong((byte[])param, 4);
break;
case PGUtil.PG_TYPE_INT8:
param = readLong((byte[])param, 8);
break;
case PGUtil.PG_TYPE_FLOAT4:
param = Float.intBitsToFloat((int)readLong((byte[])param, 4));
break;
case PGUtil.PG_TYPE_FLOAT8:
param = Double.longBitsToDouble(readLong((byte[])param, 8));
break;
case PGUtil.PG_TYPE_DATE:
param = TimestampUtils.toDate(TimestampWithTimezone.getCalendar().getTimeZone(), (int)readLong((byte[])param, 4));
break;
default:
//start with the string conversion
param = new String((byte[])param, encoding);
break;
}
}
stmt.setObject(i+1, param);
}
this.portalMap.put(bindName, new Portal(bindName, prepared, resultColumnFormat, stmt));
this.client.bindComplete();
stmt = null;
} catch (SQLException e) {
errorOccurred(e);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
}
}
@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;
}
if (maxRows == 0) {
maxRows = -1;
}
// special case cursor execution through portal
final Cursor cursor = this.cursorMap.get(bindName);
if (cursor != null) {
sendCursorResults(cursor, maxRows);
return;
}
final Portal query = this.portalMap.get(bindName);
if (query == null) {
errorOccurred(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40078, bindName));
return;
}
if (query.prepared.sql.trim().isEmpty()) {
this.client.emptyQueryReceived();
return;
}
sendPortalResults(maxRows, query);
}
private void sendPortalResults(final int maxRows, final Portal query) {
if (query.rs != null) {
//this is a suspended portal
sendCursorResults(query, maxRows);
return;
}
final PreparedStatementImpl stmt = query.stmt;
try {
this.executionFuture = stmt.submitExecute(ResultsMode.EITHER, null);
executionFuture.addCompletionListener(new ResultsFuture.CompletionListener<Boolean>() {
@Override
public void onCompletion(ResultsFuture<Boolean> future) {
executionFuture = null;
try {
if (future.get()) {
query.rs = stmt.getResultSet();
sendCursorResults(query, maxRows);
} else {
client.sendUpdateCount(query.prepared.sql, stmt.getUpdateCount());
updateSessionProperties();
doneExecuting();
}
} catch (ExecutionException e) {
if (e.getCause() != null) {
errorOccurred(e.getCause());
} else {
errorOccurred(e);
}
} catch (Throwable e) {
errorOccurred(e);
}
}
});
} catch (SQLException e) {
errorOccurred(e);
}
}
private void sendCursorResults(final Portal cursor, final int fetchSize) {
ResultsFuture<Integer> result = new ResultsFuture<Integer>();
this.client.sendResults(null, cursor.rs, cursor.prepared.columnMetadata, result, CursorDirection.FORWARD, fetchSize, false, cursor.resultColumnFormat);
result.addCompletionListener(new ResultsFuture.CompletionListener<Integer>() {
public void onCompletion(ResultsFuture<Integer> future) {
try {
int rowsSent = future.get();
if (rowsSent < fetchSize || fetchSize <= 0) {
client.sendCommandComplete(cursor.prepared.sql, rowsSent);
}
else {
client.sendPortalSuspended();
}
doneExecuting();
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (ExecutionException e) {
errorOccurred(e.getCause());
}
};
});
}
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;
if (sql == null) {
return null;
}
Matcher m = 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$
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()){
String baseQuery = "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 "; //$NON-NLS-1$
if ("f".equals(m.group(14))) { //$NON-NLS-1$
//exported keys
return baseQuery + "PKTABLE_NAME = " + m.group(15)+" and PKTABLE_SCHEM = "+m.group(16);//$NON-NLS-1$ //$NON-NLS-2$
}
//imported keys
return baseQuery + "FKTABLE_NAME = " + m.group(15)+" and FKTABLE_SCHEM = "+m.group(16);//$NON-NLS-1$ //$NON-NLS-2$
}
else if (modified.equalsIgnoreCase("select version()")) { //$NON-NLS-1$
String version = POSTGRESQL_VERSION;
return "SELECT " + new Constant(version); //$NON-NLS-1$
}
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 (sql.equalsIgnoreCase("select current_schema()")) { //$NON-NLS-1$
// since teiid can work with multiple schemas at a given time
// this call resolution is ambiguous
return "SELECT ''"; //$NON-NLS-1$
}
else if (sql.equals("SELECT typinput='array_in'::regproc, typtype FROM pg_catalog.pg_type WHERE typname = $1")) { //$NON-NLS-1$
return "SELECT substring(typname,1,1) = '_', typtype FROM pg_catalog.pg_type WHERE typname = ?"; //$NON-NLS-1$
}
if ((m = columnMetadataPattern.matcher(modified)).matches()) {
return "select t1.schemaname as nspname, c.relname, t1.name as attname, t.oid as attypid, t.typname, convert(t1.Position, short) as attnum, t.typlen as attlen," //$NON-NLS-1$
+ PgCatalogMetadataStore.TYPMOD + " as atttypmod, " //$NON-NLS-1$
+ "CASE WHEN (t1.NullType = 'No Nulls') THEN true ELSE false END as attnotnull, c.relhasrules, c.relkind, c.oid, pg_get_expr(case when t1.IsAutoIncremented then 'nextval(' else t1.DefaultValue end, c.oid), " //$NON-NLS-1$
+ " case t.typtype when 'd' then t.typbasetype else 0 end, t.typtypmod, c.relhasoids from sys.columns as t1, pg_catalog.matpg_datatype as t, pg_catalog.pg_class c where c.relnspname=t1.schemaname and c.relname=t1.tablename and t1.DataType = t.Name and c.oid = " //$NON-NLS-1$
+ m.group(1)
+ " order by nspname, relname, attnum"; //$NON-NLS-1$
}
}
else if (sql.equalsIgnoreCase("show max_identifier_length")){ //$NON-NLS-1$
return "select 63"; //$NON-NLS-1$
}
else if ((m = setPattern.matcher(sql)).matches()) {
return "SET " + m.group(1) + " " + m.group(2); //$NON-NLS-1$ //$NON-NLS-2$
}
else if ((m = txnPattern.matcher(sql)).matches()) {
if (StringUtil.startsWithIgnoreCase(m.group(1), "BEGIN")) { //$NON-NLS-1$
return "START TRANSACTION"; //$NON-NLS-1$
}
return m.group(1);
}
else if ((m = rollbackPattern.matcher(modified)).matches()) {
return "set \"dummy-update-pg-odbc\" 0"; //$NON-NLS-1$
}
else if ((m = savepointPattern.matcher(sql)).matches()) {
return "set \"dummy-update-pg-odbc\" 0"; //$NON-NLS-1$
}
else if ((m = releasePattern.matcher(sql)).matches()) {
return "set \"dummy-update-pg-odbc\" 0"; //$NON-NLS-1$
}
for (int i = 0; i < modified.length(); i++) {
switch (modified.charAt(i)) {
case ':':
case '~':
ScriptReader reader = new ScriptReader(modified);
reader.setRewrite(true);
try {
return reader.readStatement();
} catch (IOException e) {
//can't happen
}
}
}
return modified;
}
@Override
public void executeQuery(String query) {
if (beginExecution()) {
errorOccurred("Awaiting asynch result"); //$NON-NLS-1$
ready();
return;
}
//46.2.3 Note that a simple Query message also destroys the unnamed portal.
Portal p = this.portalMap.remove(UNNAMED);
if (p != null) {
closePortal(p);
}
this.preparedMap.remove(UNNAMED);
query = query.trim();
if (query.length() == 0) {
client.emptyQueryReceived();
ready();
}
QueryWorkItem r = new QueryWorkItem(query);
r.run();
}
private boolean beginExecution() {
if (this.executionFuture != null) {
return true;
}
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.gs(RuntimePlugin.Event.TEIID40079, prepareName));
return;
}
// The response is a ParameterDescription message describing the parameters needed by the statement,
this.client.sendParameterDescription(query.paramType);
// followed by a RowDescription message describing the rows that will be returned when the statement
// is eventually executed (or a NoData message if the statement will not return rows).
this.client.sendResultSetDescription(query.columnMetadata, null);
}
private void errorOccurred(String error) {
this.client.errorOccurred(error);
synchronized (this) {
this.errorOccurred = true;
doneExecuting();
}
}
public 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.gs(RuntimePlugin.Event.TEIID40078, bindName));
}
else {
this.client.sendResultSetDescription(query.prepared.columnMetadata, query.resultColumnFormat);
}
}
@Override
public void sync() {
ready();
}
protected 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) {
closePortal(query);
}
this.client.statementClosed();
}
private void closePortal(Portal query) {
ResultSet rs = query.rs;
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
LogManager.logDetail(LogConstants.CTX_ODBC, e, "Did not successfully close portal", query.name); //$NON-NLS-1$
}
query.rs = null;
}
try {
query.stmt.close();
} catch (SQLException e) {
LogManager.logDetail(LogConstants.CTX_ODBC, e, "Did not successfully close portal", query.name); //$NON-NLS-1$
}
}
@Override
public void closePreparedStatement(String preparedName) {
if (preparedName == null || preparedName.length() == 0) {
preparedName = UNNAMED;
}
Prepared query = this.preparedMap.remove(preparedName);
if (query != null) {
synchronized (this.portalMap) {
for (Iterator<Portal> iter = this.portalMap.values().iterator(); iter.hasNext();) {
Portal p = iter.next();
if (p.prepared.name.equals(preparedName)) {
iter.remove();
}
closePortal(p);
}
}
}
this.client.statementClosed();
}
@Override
public void terminate() {
for (Portal p: this.portalMap.values()) {
closePortal(p);
}
this.portalMap.clear();
this.preparedMap.clear();
try {
if (this.connection != null) {
if (!this.connection.getAutoCommit()) {
this.connection.rollback(false);
}
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.gs(RuntimePlugin.Event.TEIID40081));
}
@Override
public void sslRequest() {
this.client.sendSslResponse();
}
private void updateSessionProperties() {
String encoding = getEncoding();
if (encoding != null) {
//this may be unnecessary
this.client.setEncoding(encoding, false);
}
String appName = this.connection.getExecutionProperty(PgBackendProtocol.APPLICATION_NAME);
if (appName != null) {
String existing = props.getProperty(PgBackendProtocol.APPLICATION_NAME);
if (!EquivalenceUtil.areEqual(appName, existing)) {
try {
SessionMetadata sm = ((LocalServerConnection)connection.getServerConnection()).getWorkContext().getSession();
sm.setApplicationName(appName);
} catch (SQLException e) {
//connection invalid
}
this.client.sendParameterStatus(PgBackendProtocol.APPLICATION_NAME, appName);
this.props.put(PgBackendProtocol.APPLICATION_NAME, appName);
}
}
}
public String getEncoding() {
return this.connection.getExecutionProperty(PgBackendProtocol.CLIENT_ENCODING);
}
static String normalizeName(String name) {
if (name.length() > 1 && name.startsWith("\"") && name.endsWith("\"")) {
return StringUtil.replaceAll(name.substring(1, name.length() - 1), "\"", "\"\"");
}
//--we are not consistently dealing with identifier naming/casing
//return name.toUpperCase();
return name;
}
private final class QueryWorkItem implements Runnable {
private final ScriptReader reader;
String sql;
private String next;
private QueryWorkItem(String query) {
this.reader = new ScriptReader(query);
}
private void done(Throwable error) {
if (error != null) {
errorOccurred(error);
} else {
doneExecuting();
}
ready();
}
@Override
public void run() {
try {
if (sql == null) {
sql = reader.readStatement();
}
while (sql != null) {
try {
ResultsFuture<Integer> results = new ResultsFuture<Integer>();
results.addCompletionListener(new ResultsFuture.CompletionListener<Integer>() {
public void onCompletion(ResultsFuture<Integer> future) {
try {
future.get();
if (next != null) {
sql = next;
next = null;
} else {
sql = reader.readStatement();
}
} catch (InterruptedException e) {
throw new AssertionError(e);
} catch (IOException e) {
done(e);
return;
} catch (ExecutionException e) {
Throwable cause = e;
while (cause instanceof ExecutionException && cause.getCause() != null && cause != cause.getCause()) {
cause = cause.getCause();
}
done(cause);
return;
}
QueryWorkItem.this.run(); //continue processing
};
});
if (isErrorOccurred()) {
if (!connection.getAutoCommit()) {
connection.rollback(false);
}
break;
}
if (!HONOR_DECLARE_FETCH_TXN && sql.equalsIgnoreCase("BEGIN") && connection.getAutoCommit()) { //$NON-NLS-1$
next = reader.readStatement();
if (next != null && (cursorSelectPattern.matcher(next)).matches()) {
sql = next;
next = null;
LogManager.logDetail(LogConstants.CTX_ODBC, "not honoring the transaction for declare/fetch"); //$NON-NLS-1$
}
}
Matcher m = null;
if ((m = cursorSelectPattern.matcher(sql)).matches()){
boolean scroll = false;
if (m.group(3) != null && m.group(4) == null ) {
scroll = true;
}
cursorExecute(normalizeName(m.group(1)), fixSQL(m.group(5)), results, scroll, m.group(2) != null);
}
else if ((m = fetchPattern.matcher(sql)).matches()){
int rowCount = 1;
String direction = m.group(1);
CursorDirection cursorDirection = CursorDirection.FORWARD;
if (direction != null) {
cursorDirection = CursorDirection.valueOf(direction.toUpperCase());
}
String rows = m.group(2);
if (rows != null) {
rowCount = Integer.parseInt(rows);
}
cursorFetch(normalizeName(m.group(3)), cursorDirection, rowCount, results);
}
else if ((m = fetchFirstLastPattern.matcher(sql)).matches()){
int rowCount = 1;
String direction = m.group(1);
CursorDirection cursorDirection = CursorDirection.valueOf(direction.toUpperCase());
cursorFetch(normalizeName(m.group(2)), cursorDirection, rowCount, results);
}
else if ((m = movePattern.matcher(sql)).matches()){
cursorMove(normalizeName(m.group(3)), m.group(1), Integer.parseInt(m.group(2)), results);
}
else if ((m = closePattern.matcher(sql)).matches()){
cursorClose(normalizeName(m.group(1)));
results.getResultsReceiver().receiveResults(1);
}
else if ((m = deallocatePattern.matcher(sql)).matches()) {
String plan_name = m.group(1);
plan_name = normalizeName(plan_name);
closePreparedStatement(plan_name);
client.sendCommandComplete("DEALLOCATE", null); //$NON-NLS-1$
results.getResultsReceiver().receiveResults(1);
}
else {
sqlExecute(sql, results);
}
return; //wait for the execution to finish
} catch (SQLException e) {
done(e);
return;
}
}
} catch (NumberFormatException e) {
//create a sqlexception so that the logic doesn't over log this
done(TeiidSQLException.create(e, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40147, e.getMessage())));
return;
} catch(Exception e) {
done(e);
return;
}
done(null);
}
}
/**
* @see PgCatalogMetadataStore add_pg_attribute for mod calculation
*/
private List<PgColInfo> getPgColInfo(ResultSetMetaData meta)
throws SQLException {
if (meta == null) {
return null;
}
int columns = meta.getColumnCount();
final ArrayList<PgColInfo> result = new ArrayList<PgColInfo>(columns);
for (int i = 1; i <= columns; i++) {
final PgColInfo info = new PgColInfo();
info.name = meta.getColumnLabel(i);
info.type = meta.getColumnType(i);
String typeName = meta.getColumnTypeName(i);
info.type = convertType(info.type, typeName);
info.precision = meta.getColumnDisplaySize(i);
if (info.type == PG_TYPE_NUMERIC) {
info.mod = 4+ 65536*Math.min(32767, meta.getPrecision(i))+Math.min(32767, meta.getScale(i));
} else if (info.type == PG_TYPE_BPCHAR || info.type == PG_TYPE_VARCHAR){
info.mod = (int) Math.min(Integer.MAX_VALUE, 4+(long)meta.getColumnDisplaySize(i));
} else {
info.mod = -1;
}
String name = meta.getColumnName(i);
String table = meta.getTableName(i);
String schema = meta.getSchemaName(i);
if (schema != null) {
final PreparedStatementImpl ps = this.connection.prepareStatement("select " //$NON-NLS-1$
+ "pg_catalog.getOid(SYS.Columns.TableUID), " //$NON-NLS-1$
+ "cast(SYS.Columns.Position as short), " //$NON-NLS-1$
+ "cast((select p.value from SYS.Properties p where p.name = 'pg_type:oid' and p.uid = SYS.Columns.uid) as integer) " //$NON-NLS-1$
+ "from SYS.Columns where Name = ? and TableName = ? and SchemaName = ?"); //$NON-NLS-1$
try {
ps.setString(1, name);
ps.setString(2, table);
ps.setString(3, schema);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
info.reloid = rs.getInt(1);
info.attnum = rs.getShort(2);
int specificType = rs.getInt(3);
if (!rs.wasNull()) {
info.type = specificType;
}
}
} finally {
ps.close();
}
}
result.add(info);
}
return result;
}
/**
* Represents a PostgreSQL Prepared object. The actual plan preparation is performed lazily.
*/
static class Prepared {
public Prepared (String name, String sql, String modifiedSql, int[] paramType, List<PgColInfo> columnMetadata) {
this.name = name;
this.sql = sql;
this.modifiedSql = modifiedSql;
this.paramType = paramType;
this.columnMetadata = columnMetadata;
}
/**
* The object name.
*/
final String name;
/**
* The original SQL statement.
*/
final String sql;
final String modifiedSql;
/**
* The list of pg parameter types (if set).
*/
final int[] paramType;
/**
* calculated column metadata
*/
final List<PgColInfo> columnMetadata;
}
/**
* Represents a PostgreSQL Portal object.
*/
static class Portal {
public Portal(String name, Prepared prepared,short[] resultColumnformat, PreparedStatementImpl stmt) {
this.name = name;
this.prepared = prepared;
this.resultColumnFormat = resultColumnformat;
this.stmt = stmt;
}
/**
* The portal name.
*/
final String name;
/**
* The format used in the result set columns (if set).
*/
final short[] resultColumnFormat;
final Prepared prepared;
volatile ResultSetImpl rs;
/**
* The prepared statement.
*/
final PreparedStatementImpl stmt;
}
static class Cursor extends Portal {
public Cursor (String name, String sql, PreparedStatementImpl stmt, ResultSetImpl rs, List<PgColInfo> colMetadata, boolean binary) {
super(name, new Prepared(UNNAMED, sql, sql, null, colMetadata), binary?new short[] {1}:null, stmt);
this.rs = rs;
}
}
}