/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.pg;
import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.sql.server.ServerServiceRequirements;
import com.foundationdb.sql.server.ServerSessionBase;
import com.foundationdb.sql.server.ServerSessionMonitor;
import com.foundationdb.sql.server.ServerStatement;
import com.foundationdb.sql.server.ServerStatementCache;
import com.foundationdb.sql.server.ServerValueDecoder;
import com.foundationdb.sql.server.ServerValueEncoder;
import com.foundationdb.sql.StandardException;
import com.foundationdb.sql.parser.ParameterNode;
import com.foundationdb.sql.parser.SQLParserException;
import com.foundationdb.sql.parser.StatementNode;
import com.foundationdb.qp.operator.QueryBindings;
import com.foundationdb.qp.operator.QueryContext;
import com.foundationdb.server.api.DDLFunctions;
import com.foundationdb.server.error.*;
import com.foundationdb.server.service.metrics.LongMetric;
import com.foundationdb.server.service.monitor.CursorMonitor;
import com.foundationdb.server.service.monitor.MonitorStage;
import com.foundationdb.server.service.monitor.PreparedStatementMonitor;
import com.foundationdb.server.service.monitor.SessionMonitor.StatementTypes;
import com.foundationdb.util.MultipleCauseException;
import com.foundationdb.util.tap.InOutTap;
import com.foundationdb.util.tap.Tap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ietf.jgss.*;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import java.net.*;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.util.*;
import static com.foundationdb.sql.pg.PostgresStatement.PostgresStatementResult;
/**
* Connection to a Postgres server client.
* Runs in its own thread; has its own Main Session.
*
*/
public class PostgresServerConnection extends ServerSessionBase
implements PostgresServerSession, Runnable
{
private static final Logger logger = LoggerFactory.getLogger(PostgresServerConnection.class);
private static final InOutTap READ_MESSAGE = Tap.createTimer("PostgresServerConnection: read message");
private static final InOutTap PROCESS_MESSAGE = Tap.createTimer("PostgresServerConnection: process message");
private static final String THREAD_NAME_PREFIX = "PostgresServer_Session-"; // Session ID appended
private static final String MD5_SALT = "MD5_SALT";
private final PostgresServer server;
private boolean running = false, ignoreUntilSync = false;
private Socket socket;
private PostgresMessenger messenger;
private ServerValueEncoder valueEncoder;
private ServerValueDecoder valueDecoder;
private OutputFormat outputFormat = OutputFormat.TABLE;
private final int sessionId, secret;
private int version;
private Map<String,PostgresPreparedStatement> preparedStatements =
new HashMap<>();
private Map<String,PostgresBoundQueryContext> boundPortals =
new HashMap<>();
private ServerStatementCache<PostgresStatement> statementCache;
private PostgresStatementParser[] unparsedGenerators;
private PostgresStatementGenerator[] parsedGenerators;
private Thread thread;
private final LongMetric bytesInMetric, bytesOutMetric;
private volatile String cancelForKillReason, cancelByUser;
public PostgresServerConnection(PostgresServer server, Socket socket,
int sessionId, int secret,
LongMetric bytesInMetric, LongMetric bytesOutMetric,
ServerServiceRequirements reqs) {
super(reqs);
this.server = server;
this.socket = socket;
this.sessionId = sessionId;
this.secret = secret;
this.bytesInMetric = bytesInMetric;
this.bytesOutMetric = bytesOutMetric;
this.sessionMonitor = new ServerSessionMonitor(PostgresServer.SERVER_TYPE,
sessionId) {
@Override
public List<PreparedStatementMonitor> getPreparedStatements() {
List<PreparedStatementMonitor> result =
new ArrayList<>(preparedStatements.size());
synchronized (preparedStatements) {
result.addAll(preparedStatements.values());
}
return result;
}
@Override
public List<CursorMonitor> getCursors() {
List<CursorMonitor> result =
new ArrayList<>(boundPortals.size());
synchronized (boundPortals) {
result.addAll(boundPortals.values());
}
return result;
}
};
sessionMonitor.setRemoteAddress(socket.getInetAddress().getHostAddress());
session = reqs.sessionService().createSession();
reqs.monitor().registerSessionMonitor(sessionMonitor, session);
}
public void start() {
running = true;
thread = new Thread(this, THREAD_NAME_PREFIX + sessionId);
thread.start();
}
public void stop() {
running = false;
// Can only wake up stream read by closing down socket.
try {
socket.close();
}
catch (IOException ex) {
}
if ((thread != null) && (thread != Thread.currentThread())) {
try {
// Wait a bit, but don't hang up shutdown if thread is wedged.
thread.join(500);
if (thread.isAlive())
logger.warn("Connection {} still running", sessionId);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
thread = null;
}
}
public void run() {
try {
createMessenger();
topLevel();
}
catch (Exception ex) {
if (running)
logger.warn("Error in server", ex);
} catch (Throwable ex) {
logger.error("Error in server {}", ex);
}
finally {
try {
socket.close();
}
catch (IOException ex) {
}
}
}
protected void createMessenger() throws IOException {
messenger = new PostgresMessenger(socket) {
@Override
public void beforeIdle() throws IOException {
super.beforeIdle();
sessionMonitor.enterStage(MonitorStage.IDLE);
}
@Override
public void afterIdle() throws IOException {
sessionMonitor.leaveStage();
super.afterIdle();
}
@Override
public void idle() {
if (cancelForKillReason != null) {
String msg = cancelForKillReason;
cancelForKillReason = null;
if (cancelByUser != null) {
msg += " by " + cancelByUser;
cancelByUser = null;
}
throw new ConnectionTerminatedException(msg);
}
}
@Override
public void bytesRead(int count) {
bytesInMetric.increment(count);
}
@Override
public void bytesWritten(int count) {
bytesOutMetric.increment(count);
}
};
}
protected void topLevel() throws IOException, Exception {
logger.debug("Connect from {}" + socket.getRemoteSocketAddress());
boolean startupComplete = false;
try {
while (running) {
READ_MESSAGE.in();
PostgresMessages type;
try {
type = messenger.readMessage(startupComplete);
} catch (ConnectionTerminatedException ex) {
logger.debug("About to terminate", ex);
notifyClient(QueryContext.NotificationLevel.WARNING,
ex.getCode(), ex.getShortMessage());
stop();
continue;
} finally {
READ_MESSAGE.out();
}
PROCESS_MESSAGE.in();
if (ignoreUntilSync) {
if ((type != PostgresMessages.EOF_TYPE) && (type != PostgresMessages.SYNC_TYPE))
continue;
ignoreUntilSync = false;
}
long startNsec = System.nanoTime();
try {
switch (type) {
case EOF_TYPE: // EOF
stop();
break;
case SYNC_TYPE:
readyForQuery();
break;
case STARTUP_MESSAGE_TYPE:
startupComplete = processStartupMessage();
break;
case PASSWORD_MESSAGE_TYPE:
processPasswordMessage();
break;
case QUERY_TYPE:
processQuery();
break;
case PARSE_TYPE:
processParse();
break;
case BIND_TYPE:
processBind();
break;
case DESCRIBE_TYPE:
processDescribe();
break;
case EXECUTE_TYPE:
processExecute();
break;
case FLUSH_TYPE:
processFlush();
break;
case CLOSE_TYPE:
processClose();
break;
case TERMINATE_TYPE:
processTerminate();
break;
}
} catch (QueryCanceledException ex) {
InvalidOperationException nex = ex;
boolean forKill = false;
if (cancelForKillReason != null) {
nex = new ConnectionTerminatedException(cancelForKillReason);
nex.initCause(ex);
cancelForKillReason = null;
forKill = true;
}
logError(ErrorLogLevel.INFO, "Query {} canceled", nex);
String msg = nex.getShortMessage();
if (cancelByUser != null) {
if (!forKill) msg = "Query canceled";
msg += " by " + cancelByUser;
cancelByUser = null;
}
sendErrorResponse(type, nex, nex.getCode(), msg);
if (forKill) stop();
} catch (ConnectionTerminatedException ex) {
logError(ErrorLogLevel.DEBUG, "Query {} terminated self", ex);
sendErrorResponse(type, ex, ex.getCode(), ex.getShortMessage());
stop();
} catch (InvalidOperationException ex) {
// Most likely a user error, not a system error.
String fmt = logger.isDebugEnabled() ?
"Error in query {}" : // Include stack trace
"Error in query {} => {}"; // Just summarize error
logError(ErrorLogLevel.WARN, fmt, ex);
sendErrorResponse(type, ex, ex.getCode(), ex.getShortMessage());
} catch (MultipleCauseException ex) {
int count = 1;
int length = ex.getCauses().size();
for(Throwable throwable : ex.getCauses()) {
if (throwable instanceof InvalidOperationException){
if(count == length){
logError(ErrorLogLevel.WARN, "Error in query {}", ex);
sendErrorResponse(type,
((InvalidOperationException) throwable),
((InvalidOperationException) throwable).getCode(),
((InvalidOperationException) throwable).getShortMessage());
} else {
notifyClient(QueryContext.NotificationLevel.WARNING,
((InvalidOperationException) throwable).getCode(),
((InvalidOperationException) throwable).getShortMessage());
}
} else {
if(count == length){
logError(ErrorLogLevel.WARN, "Unexpected runtime exception in query {}", ex);
sendErrorResponse(type,
(RuntimeException)throwable,
ErrorCode.UNEXPECTED_EXCEPTION,
ex.getMessage());
} else {
notifyClient(QueryContext.NotificationLevel.WARNING,
ErrorCode.UNEXPECTED_EXCEPTION,
ex.getMessage());
}
}
count++;
}
} catch (Exception ex) {
logError(ErrorLogLevel.WARN, "Unexpected error in query {}", ex);
String message = (ex.getMessage() == null ? ex.getClass().toString() : ex.getMessage());
sendErrorResponse(type, ex, ErrorCode.UNEXPECTED_EXCEPTION, message);
} catch (AssertionError ex) {
logError(ErrorLogLevel.WARN, "Assertion in query {}", ex);
throw ex;
}
finally {
long stopNsec = System.nanoTime();
if (logger.isTraceEnabled()) {
logger.trace("Executed {}: {} usec", type, (stopNsec - startNsec) / 1000);
}
}
PROCESS_MESSAGE.out();
}
}
finally {
if (transaction != null) {
transaction.abort();
transaction = null;
}
server.removeConnection(sessionId);
reqs.monitor().deregisterSessionMonitor(sessionMonitor, session);
logger.debug("Disconnect");
}
}
private enum ErrorLogLevel { WARN, INFO, DEBUG };
private void logError(ErrorLogLevel level, String msg, Throwable ex) {
String sql = null;
if (sessionMonitor.getCurrentStatementEndTimeMillis() < 0) {
// Current statement did not complete, include in error message.
sql = sessionMonitor.getCurrentStatement();
if (sql != null) {
sessionMonitor.failStatement(ex); // For system tables and for next time.
if (reqs.monitor().isQueryLogEnabled())
reqs.monitor().logQuery(sessionMonitor, ex);
}
}
if (sql == null)
sql = "";
if (reqs.config().testing()) {
level = ErrorLogLevel.DEBUG;
}
switch (level) {
case DEBUG:
logger.debug(msg, sql, ex);
break;
case INFO:
logger.info(msg, sql, ex);
break;
case WARN:
default:
logger.warn(msg, sql, ex);
break;
}
}
protected void sendErrorResponse(PostgresMessages type, Exception exception, ErrorCode errorCode, String message) throws Exception {
PostgresMessages.ErrorMode errorMode = type.errorMode();
if (errorMode == PostgresMessages.ErrorMode.NONE) {
throw exception;
}
else if (version < 3<<16) {
// V2 error message has no length field. We do not support
// that version, except enough to tell the client that we
// do not.
OutputStream raw = messenger.getOutputStream();
raw.write(PostgresMessages.ERROR_RESPONSE_TYPE.code());
raw.write(message.getBytes(messenger.getEncoding()));
raw.write(0);
raw.flush();
}
else {
messenger.beginMessage(PostgresMessages.ERROR_RESPONSE_TYPE.code());
messenger.write('S');
messenger.writeString((errorMode == PostgresMessages.ErrorMode.FATAL)
? "FATAL" : "ERROR");
messenger.write('C');
messenger.writeString(errorCode.getFormattedValue());
messenger.write('M');
messenger.writeString(message);
if (exception instanceof BaseSQLException) {
int pos = ((BaseSQLException)exception).getErrorPosition();
if (pos > 0) {
messenger.write('P');
messenger.writeString(Integer.toString(pos));
}
}
messenger.write(0);
messenger.sendMessage(true);
}
switch (errorMode) {
case FATAL:
stop();
break;
case EXTENDED:
ignoreUntilSync = true;
break;
default:
readyForQuery();
}
}
protected void readyForQuery() throws IOException {
messenger.beginMessage(PostgresMessages.READY_FOR_QUERY_TYPE.code());
char mode = 'I'; // Idle
if (isTransactionActive())
mode = isTransactionRollbackPending() ? 'E' : 'T';
messenger.writeByte(mode);
messenger.sendMessage(true);
}
protected boolean processStartupMessage() throws IOException {
int version = messenger.readInt();
switch (version) {
case PostgresMessenger.VERSION_CANCEL:
processCancelRequest();
return false;
case PostgresMessenger.VERSION_SSL:
processSSLMessage();
return false;
default:
this.version = version;
if (version < 3<<16) {
throw new UnsupportedProtocolException("protocol version " + (version >> 16));
}
logger.debug("Version {}.{}", (version >> 16), (version & 0xFFFF));
}
Properties clientProperties = new Properties(server.getProperties());
while (true) {
String param = messenger.readString();
if (param.length() == 0) break;
String value = messenger.readString();
clientProperties.put(param, value);
}
logger.debug("Properties: {}", clientProperties);
setProperties(clientProperties);
// TODO: Not needed right now and not a convenient time to
// encounter schema lock from long-running DDL.
// But see comment in initParser(): what if we wanted to warn
// or error when schema does not exist?
//updateAIS(null);
switch (server.getAuthenticationType()) {
case NONE:
{
String user = properties.getProperty("user");
logger.debug("Login {}", user);
authenticationOkay();
}
break;
case CLEAR_TEXT:
case JAAS:
{
messenger.beginMessage(PostgresMessages.AUTHENTICATION_TYPE.code());
messenger.writeInt(PostgresMessenger.AUTHENTICATION_CLEAR_TEXT);
messenger.sendMessage(true);
}
break;
case MD5:
{
byte[] salt = new byte[4];
new SecureRandom().nextBytes(salt);
setAttribute(MD5_SALT, salt);
messenger.beginMessage(PostgresMessages.AUTHENTICATION_TYPE.code());
messenger.writeInt(PostgresMessenger.AUTHENTICATION_MD5);
messenger.write(salt);
messenger.sendMessage(true);
}
break;
case GSS:
authenticationGSS();
break;
}
return true;
}
protected void processCancelRequest() throws IOException {
int sessionId = messenger.readInt();
int secret = messenger.readInt();
PostgresServerConnection connection = server.getConnection(sessionId);
if ((connection != null) && (secret == connection.secret)) {
connection.cancelQuery(null, null);
}
stop(); // That's all for this connection.
}
protected void processSSLMessage() throws IOException {
OutputStream raw = messenger.getOutputStream();
if (System.getProperty("javax.net.ssl.keyStore") == null) {
// JSSE doesn't have a keystore; TLSv1 handshake is gonna fail. Deny support.
raw.write('N');
raw.flush();
}
else {
// Someone seems to have configured for SSL. Wrap the
// socket and start server mode negotiation. Client should
// then use SSL socket to start regular server protocol.
raw.write('S');
raw.flush();
SSLSocketFactory sslFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket)sslFactory.createSocket(socket, socket.getLocalAddress().toString(), socket.getLocalPort(), true);
socket = sslSocket;
createMessenger();
sslSocket.setUseClientMode(false);
sslSocket.startHandshake();
}
}
protected void processPasswordMessage() throws IOException {
String user = properties.getProperty("user");
String pass = messenger.readString();
Principal principal = null;
switch (server.getAuthenticationType()) {
case NONE:
break;
case CLEAR_TEXT:
principal = reqs.securityService()
.authenticateLocal(session, user, pass);
break;
case MD5:
principal = reqs.securityService()
.authenticateLocal(session, user, pass,
(byte[])attributes.remove(MD5_SALT));
break;
case JAAS:
principal = reqs.securityService()
.authenticateJaas(session, user, pass,
server.getJaasConfigName(), server.getJaasUserClass(), server.getJaasRoleClasses());
break;
}
logger.debug("Login {}", (principal != null) ? principal : user);
authenticationOkay();
sessionMonitor.setUserMonitor(reqs.monitor().getUserMonitor(user));
}
// Currently supported subset given by Postgres 9.1.
protected static final String[] INITIAL_STATUS_SETTINGS = {
"client_encoding", "server_encoding", "server_version", "session_authorization",
"DateStyle", "integer_datetimes", "standard_conforming_strings",
"foundationdb_server"
};
protected void authenticationOkay() throws IOException {
{
messenger.beginMessage(PostgresMessages.AUTHENTICATION_TYPE.code());
messenger.writeInt(PostgresMessenger.AUTHENTICATION_OK);
messenger.sendMessage();
}
for (String prop : INITIAL_STATUS_SETTINGS) {
messenger.beginMessage(PostgresMessages.PARAMETER_STATUS_TYPE.code());
messenger.writeString(prop);
messenger.writeString(getSessionSetting(prop));
messenger.sendMessage();
}
{
messenger.beginMessage(PostgresMessages.BACKEND_KEY_DATA_TYPE.code());
messenger.writeInt(sessionId);
messenger.writeInt(secret);
messenger.sendMessage();
}
readyForQuery();
}
protected void authenticationGSS() throws IOException {
messenger.beginMessage(PostgresMessages.AUTHENTICATION_TYPE.code());
messenger.writeInt(PostgresMessenger.AUTHENTICATION_GSS);
messenger.sendMessage(true);
final Subject gssLogin;
try {
gssLogin = server.getGSSLogin();
}
catch (LoginException ex) {
throw new AuthenticationFailedException(ex); // or is this internal?
}
GSSName authenticated = Subject.doAs(gssLogin, new PrivilegedAction<GSSName>() {
@Override
public GSSName run() {
return gssNegotation(gssLogin);
}
});
logger.debug("Login {}", authenticated);
properties.setProperty("user", authenticated.toString());
authenticationOkay();
}
protected GSSName gssNegotation(Subject gssLogin) {
String serverName = null;
Iterator<Principal> iter = gssLogin.getPrincipals().iterator();
if (iter.hasNext())
serverName = iter.next().getName();
try {
GSSManager manager = GSSManager.getInstance();
GSSCredential serverCreds =
manager.createCredential(manager.createName(serverName, null),
GSSCredential.INDEFINITE_LIFETIME,
new Oid("1.2.840.113554.1.2.2"), // krb5
GSSCredential.ACCEPT_ONLY);
GSSContext serverContext = manager.createContext(serverCreds);
do {
switch (messenger.readMessage()) {
case PASSWORD_MESSAGE_TYPE:
break;
default:
throw new AuthenticationFailedException("Protocol error: not password message");
}
byte[] token = messenger.getRawMessage(); // Note: not a String.
token = serverContext.acceptSecContext(token, 0, token.length);
if (token != null) {
messenger.beginMessage(PostgresMessages.AUTHENTICATION_TYPE.code());
messenger.writeInt(PostgresMessenger.AUTHENTICATION_GSS_CONTINUE);
messenger.write(token); // Again, no wrapping.
messenger.sendMessage(true);
}
} while (!serverContext.isEstablished());
return serverContext.getSrcName();
}
catch (GSSException ex) {
throw new AuthenticationFailedException(ex);
}
catch (IOException ex) {
throw new AkibanInternalException("Error reading message", ex);
}
}
protected void processQuery() throws IOException {
long startTime = System.currentTimeMillis();
String sql = messenger.readString();
logger.debug("Query: {}", sql);
if (sql.length() == 0) {
emptyQuery();
return;
}
sessionMonitor.startStatement(sql, startTime);
PostgresQueryContext context = new PostgresQueryContext(this);
QueryBindings bindings = context.createBindings(); // Empty of parameters.
updateAIS(context);
PostgresStatement pstmt = null;
if (statementCache != null)
pstmt = statementCache.get(sql);
if (pstmt != null) {
sessionMonitor.countEvent(StatementTypes.FROM_CACHE);
} else {
for (PostgresStatementParser parser : unparsedGenerators) {
// Try special recognition first; only allowed to turn
// into one statement.
pstmt = parser.parse(this, sql, null);
if (pstmt != null) {
pstmt.setAISGeneration(ais.getGeneration());
break;
}
}
}
int rowsProcessed = 0;
if (pstmt != null) {
pstmt.sendDescription(context, false, false);
rowsProcessed = executeStatementWithAutoTxn(pstmt, context, bindings, -1);
}
else {
// Parse as a _list_ of statements and process each in turn.
List<StatementNode> stmts;
try {
sessionMonitor.enterStage(MonitorStage.PARSE);
stmts = parser.parseStatements(sql);
}
catch (SQLParserException ex) {
throw new SQLParseException(ex);
}
catch (StandardException ex) {
throw new SQLParserInternalException(ex);
}
finally {
sessionMonitor.leaveStage();
}
boolean singleStmt = (stmts.size() == 1);
for (StatementNode stmt : stmts) {
String stmtSQL;
if (singleStmt)
stmtSQL = sql;
else
stmtSQL = sql.substring(stmt.getBeginOffset(),
stmt.getEndOffset() + 1);
pstmt = generateStatementStub(stmtSQL, stmt, null, null);
boolean local = beforeExecute(pstmt);
PostgresStatementResult result;
boolean success = false;
try {
pstmt = finishGenerating(context, stmtSQL, stmt, null, null);
if ((statementCache != null) && singleStmt && pstmt.putInCache())
statementCache.put(stmtSQL, pstmt);
pstmt.sendDescription(context, false, false);
result = executeStatement(pstmt, context, bindings, -1);
success = true;
} finally {
afterExecute(pstmt, local, success, true);
}
result.sendCommandComplete(messenger);
rowsProcessed = result.getRowsProcessed();
}
}
readyForQuery();
sessionMonitor.endStatement(rowsProcessed);
logger.debug("Query complete: {} rows", rowsProcessed);
if (reqs.monitor().isQueryLogEnabled()) {
reqs.monitor().logQuery(sessionMonitor, null);
}
}
protected void processParse() throws IOException {
String stmtName = messenger.readString();
String sql = messenger.readString();
short nparams = messenger.readShort();
int[] paramTypes = new int[nparams];
for (int i = 0; i < nparams; i++)
paramTypes[i] = messenger.readInt();
sessionMonitor.startStatement(sql, stmtName);
logger.debug("Parse: {} = {} with types {}", stmtName, sql, paramTypes);
PostgresQueryContext context = new PostgresQueryContext(this);
updateAIS(context);
PostgresStatement pstmt = null;
if (statementCache != null)
pstmt = statementCache.get(sql);
// Verify the parameter types from the parse request match the
// parameter requests from our potential cached statement
// if they don't match, assume the statement isn't a match
if (pstmt != null) {
if (pstmt.getParameterTypes() != null && pstmt.getParameterTypes().length >= nparams){
for (int i = 0; i < nparams; i++ ) {
if (pstmt.getParameterTypes()[i].getOid() != paramTypes[i]) {
pstmt = null;
break;
}
}
}
}
if (pstmt == null) {
for (PostgresStatementParser parser : unparsedGenerators) {
pstmt = parser.parse(this, sql, null);
if (pstmt != null) {
pstmt.setAISGeneration(ais.getGeneration());
break;
}
}
}
if (pstmt == null) {
StatementNode stmt;
List<ParameterNode> params;
try {
sessionMonitor.enterStage(MonitorStage.PARSE);
stmt = parser.parseStatement(sql);
params = parser.getParameterList();
}
catch (SQLParserException ex) {
throw new SQLParseException(ex);
}
catch (StandardException ex) {
throw new SQLParserInternalException(ex);
}
finally {
sessionMonitor.leaveStage();
}
pstmt = generateStatementStub(sql, stmt, params, paramTypes);
boolean local = beforeExecute(pstmt);
boolean success = false;
try {
pstmt = finishGenerating(context, sql, stmt, params, paramTypes);
success = true;
} finally {
afterExecute(pstmt, local, success, false);
}
if ((statementCache != null) && pstmt.putInCache()) {
statementCache.put(sql, pstmt);
}
}
PostgresPreparedStatement ppstmt =
new PostgresPreparedStatement(this, stmtName, sql, pstmt,
sessionMonitor.getCurrentStatementStartTimeMillis());
synchronized (preparedStatements) {
preparedStatements.put(stmtName, ppstmt);
}
messenger.beginMessage(PostgresMessages.PARSE_COMPLETE_TYPE.code());
messenger.sendMessage();
}
protected void processBind() throws IOException {
String portalName = messenger.readString();
String stmtName = messenger.readString();
byte[][] params = null;
boolean[] paramsBinary = null;
{
short nformats = messenger.readShort();
if (nformats > 0) {
paramsBinary = new boolean[nformats];
for (int i = 0; i < nformats; i++)
paramsBinary[i] = (messenger.readShort() == 1);
}
short nparams = messenger.readShort();
if (nparams > 0) {
params = new byte[nparams][];
for (int i = 0; i < nparams; i++) {
int len = messenger.readInt();
if (len < 0) continue; // Null
byte[] param = new byte[len];
messenger.readFully(param, 0, len);
params[i] = param;
}
}
}
boolean[] resultsBinary = null;
boolean defaultResultsBinary = false;
{
short nresults = messenger.readShort();
if (nresults == 1)
defaultResultsBinary = (messenger.readShort() == 1);
else if (nresults > 0) {
resultsBinary = new boolean[nresults];
for (int i = 0; i < nresults; i++) {
resultsBinary[i] = (messenger.readShort() == 1);
}
defaultResultsBinary = resultsBinary[nresults-1];
}
}
logger.debug("Bind: {} = {}", stmtName, portalName);
PostgresPreparedStatement pstmt = preparedStatements.get(stmtName);
if (pstmt == null)
throw new NoSuchPreparedStatementException(stmtName);
PostgresStatement stmt = pstmt.getStatement();
boolean canSuspend = ((stmt instanceof PostgresCursorGenerator) &&
((PostgresCursorGenerator<?>)stmt).canSuspend(this));
PostgresBoundQueryContext bound =
new PostgresBoundQueryContext(this, pstmt, portalName, canSuspend, true);
QueryBindings bindings = bound.createBindings();
if (params != null) {
if (valueDecoder == null)
valueDecoder = new ServerValueDecoder(typesTranslator(),
messenger.getEncoding());
PostgresType[] parameterTypes = null;
if (stmt instanceof PostgresBaseStatement) {
PostgresDMLStatement dml = (PostgresDMLStatement)stmt;
parameterTypes = dml.getParameterTypes();
}
for (int i = 0; i < params.length; i++) {
PostgresType pgType = null;
if (parameterTypes != null)
pgType = parameterTypes[i];
boolean binary = false;
if ((paramsBinary != null) && (i < paramsBinary.length))
binary = paramsBinary[i];
valueDecoder.decodeValue(params[i], pgType, binary, bindings, i, bound, typesRegistryService());
}
logger.debug("Bound params: {}", bindings);
}
bound.setBindings(bindings);
bound.setColumnBinary(resultsBinary, defaultResultsBinary);
PostgresBoundQueryContext prev;
synchronized (boundPortals) {
prev = boundPortals.put(portalName, bound);
}
if (prev != null)
prev.close();
messenger.beginMessage(PostgresMessages.BIND_COMPLETE_TYPE.code());
messenger.sendMessage();
}
protected void processDescribe() throws IOException{
byte source = messenger.readByte();
String name = messenger.readString();
logger.debug("Describe: {}", name);
PostgresStatement pstmt;
PostgresQueryContext context;
boolean params;
switch (source) {
case (byte)'S':
pstmt = preparedStatements.get(name).getStatement();
if (pstmt == null)
throw new NoSuchPreparedStatementException(name);
context = new PostgresQueryContext(this);
params = true;
break;
case (byte)'P':
{
PostgresBoundQueryContext bound = boundPortals.get(name);
if (bound == null)
throw new NoSuchCursorException(name);
pstmt = bound.getStatement().getStatement();
context = bound;
}
params = false;
break;
default:
throw new IOException("Unknown describe source: " + (char)source);
}
pstmt.sendDescription(context, true, params);
}
protected void processExecute() throws IOException {
long startTime = System.currentTimeMillis();
String portalName = messenger.readString();
int maxrows = messenger.readInt();
logger.debug("Execute: {}", portalName);
PostgresBoundQueryContext context = boundPortals.get(portalName);
if (context == null)
throw new NoSuchCursorException(portalName);
QueryBindings bindings = context.getBindings();
PostgresPreparedStatement pstmt = context.getStatement();
sessionMonitor.startStatement(pstmt.getSQL(), pstmt.getName(), startTime);
int rowsProcessed = executeStatementWithAutoTxn(pstmt.getStatement(), context, bindings, maxrows);
sessionMonitor.endStatement(rowsProcessed);
logger.debug("Execute complete: {} rows", rowsProcessed);
if (reqs.monitor().isQueryLogEnabled()) {
reqs.monitor().logQuery(sessionMonitor, null);
}
}
protected void processFlush() throws IOException {
messenger.flush();
}
protected void processClose() throws IOException {
byte source = messenger.readByte();
String name = messenger.readString();
switch (source) {
case (byte)'S':
deallocatePreparedStatement(name);
break;
case (byte)'P':
closeBoundPortal(name);
break;
default:
throw new IOException("Unknown describe source: " + (char)source);
}
messenger.beginMessage(PostgresMessages.CLOSE_COMPLETE_TYPE.code());
messenger.sendMessage();
}
protected void processTerminate() throws IOException {
stop();
}
public void cancelQuery(String forKillReason, String byUser) {
this.cancelForKillReason = forKillReason;
this.cancelByUser = byUser;
// A running query checks session state for query cancelation during Cursor.next() calls. If the
// query is stuck in a blocking operation, then thread interruption should unstick it. Either way,
// the query should eventually throw QueryCanceledException which will be caught by topLevel().
if (session != null) {
session.cancelCurrentQuery(true);
}
if (thread != null) {
thread.interrupt();
}
}
public void waitAndStop() {
// Wait a little bit for the connection to stop itself.
for (int i = 0; i < 5; i++) {
if (!running) return;
try {
Thread.sleep(50);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
// Force stop.
stop();
}
// When the AIS changes, throw everything away, since it might
// point to obsolete objects.
protected void updateAIS(PostgresQueryContext context) {
DDLFunctions ddl = reqs.dxl().ddlFunctions();
AkibanInformationSchema newAIS = ddl.getAIS(session);
if ((ais != null) && (ais.getGeneration() == newAIS.getGeneration()))
return; // Unchanged.
ais = newAIS;
rebuildCompiler();
}
protected void rebuildCompiler() {
Object parserKeys = initParser();
PostgresOperatorCompiler compiler;
String format = getProperty("OutputFormat", "table");
if (format.equals("table"))
outputFormat = OutputFormat.TABLE;
else if (format.equals("json"))
outputFormat = OutputFormat.JSON;
else if (format.equals("json_with_meta_data"))
outputFormat = OutputFormat.JSON_WITH_META_DATA;
else
throw new InvalidParameterValueException(format);
switch (outputFormat) {
case TABLE:
default:
compiler = PostgresOperatorCompiler.create(this, reqs.store());
break;
case JSON:
case JSON_WITH_META_DATA:
compiler = PostgresJsonCompiler.create(this, reqs.store());
break;
}
initAdapters(compiler);
unparsedGenerators = new PostgresStatementParser[] {
new PostgresEmulatedMetaDataStatementParser(this),
new PostgresEmulatedSessionStatementParser(this)
};
parsedGenerators = new PostgresStatementGenerator[] {
// Can be ordered by frequency so long as there is no overlap.
compiler,
new PostgresDDLStatementGenerator(this, compiler),
new PostgresSessionStatementGenerator(this),
new PostgresCallStatementGenerator(this),
new PostgresExplainStatementGenerator(this),
new PostgresServerStatementGenerator(this),
new PostgresCursorStatementGenerator(this),
new PostgresCopyStatementGenerator(this)
};
statementCache = getStatementCache();
}
public int getStatementCacheCapacity() {
return server.getStatementCacheCapacity();
}
protected ServerStatementCache<PostgresStatement> getStatementCache() {
// Statement cache depends on some connection settings.
return server.getStatementCache(Arrays.asList(parser.getFeatures(),
defaultSchemaName,
getProperty("OutputFormat", "table"),
getProperty("optimizerDummySetting")),
ais.getGeneration());
}
@Override
protected void sessionChanged() {
if (parsedGenerators == null) return; // setAttribute() from generator's ctor.
for (PostgresStatementParser parser : unparsedGenerators) {
parser.sessionChanged(this);
}
for (PostgresStatementGenerator generator : parsedGenerators) {
generator.sessionChanged(this);
}
statementCache = getStatementCache();
}
protected PostgresStatement generateStatementStub(String sql, StatementNode stmt,
List<ParameterNode> params,
int[] paramTypes) {
try {
sessionMonitor.enterStage(MonitorStage.OPTIMIZE);
for (PostgresStatementGenerator generator : parsedGenerators) {
PostgresStatement pstmt = generator.generateStub(this, sql, stmt,
params, paramTypes);
if (pstmt != null)
return pstmt;
}
}
finally {
sessionMonitor.leaveStage();
}
throw new UnsupportedSQLException ("", stmt);
}
protected PostgresStatement finishGenerating(PostgresQueryContext context,
String sql, StatementNode stmt,
List<ParameterNode> params,
int[] paramTypes) {
try {
sessionMonitor.enterStage(MonitorStage.OPTIMIZE);
updateAIS(context);
PostgresStatement pstmt = generateStatementStub(sql, stmt, params, paramTypes);
PostgresStatement newpstmt = pstmt.finishGenerating(this, sql, stmt, params, paramTypes);
if (!newpstmt.hasAISGeneration())
newpstmt.setAISGeneration(ais.getGeneration());
return newpstmt;
}
finally {
sessionMonitor.leaveStage();
}
}
protected int executeStatementWithAutoTxn(PostgresStatement pstmt, PostgresQueryContext context, QueryBindings bindings, int maxrows)
throws IOException {
boolean localTransaction = beforeExecute(pstmt);
PostgresStatementResult result;
boolean success = false;
try {
result = executeStatement(pstmt, context, bindings, maxrows);
success = true;
}
finally {
afterExecute(pstmt, localTransaction, success, true);
sessionMonitor.leaveStage();
}
result.sendCommandComplete(messenger);
return result.getRowsProcessed();
}
protected PostgresStatementResult executeStatement(PostgresStatement pstmt, PostgresQueryContext context, QueryBindings bindings, int maxrows)
throws IOException {
try {
if (pstmt.getAISGenerationMode() == ServerStatement.AISGenerationMode.NOT_ALLOWED) {
updateAIS(context);
if (pstmt.getAISGeneration() != ais.getGeneration())
throw new StaleStatementException();
}
session.setTimeoutAfterMillis(getQueryTimeoutMilli());
sessionMonitor.enterStage(MonitorStage.EXECUTE);
return pstmt.execute(context, bindings, maxrows);
}
finally {
sessionMonitor.leaveStage();
}
}
protected void emptyQuery() throws IOException {
messenger.beginMessage(PostgresMessages.EMPTY_QUERY_RESPONSE_TYPE.code());
messenger.sendMessage();
readyForQuery();
}
@Override
public void prepareStatement(String name,
String sql, StatementNode stmt,
List<ParameterNode> params, int[] paramTypes) {
long prepareTime = System.currentTimeMillis();
PostgresQueryContext context = new PostgresQueryContext(this);
PostgresStatement pstmt = generateStatementStub(sql, stmt, params, paramTypes);
boolean local = beforeExecute(pstmt);
boolean success = false;
try {
pstmt = finishGenerating(context, sql, stmt, params, paramTypes);
success = true;
}
finally {
afterExecute(pstmt, local, success, false);
}
PostgresPreparedStatement ppstmt = new PostgresPreparedStatement(this, name,
sql, pstmt,
prepareTime);
synchronized (preparedStatements) {
preparedStatements.put(name, ppstmt);
}
}
@Override
public PostgresStatementResult executePreparedStatement(PostgresExecuteStatement estmt, int maxrows)
throws IOException {
PostgresPreparedStatement pstmt = preparedStatements.get(estmt.getName());
if (pstmt == null)
throw new NoSuchPreparedStatementException(estmt.getName());
PostgresQueryContext context = new PostgresQueryContext(this);
QueryBindings bindings = context.createBindings();
estmt.setParameters(bindings);
sessionMonitor.startStatement(pstmt.getSQL(), pstmt.getName());
pstmt.getStatement().sendDescription(context, false, false);
int nrows = executeStatementWithAutoTxn(pstmt.getStatement(), context, bindings, maxrows);
sessionMonitor.endStatement(nrows);
return PostgresStatementResults.noResult(nrows); // Already sent.
}
@Override
public void deallocatePreparedStatement(String name) {
PostgresPreparedStatement pstmt;
synchronized (preparedStatements) {
pstmt = preparedStatements.remove(name);
}
}
@Override
public void declareStatement(String name,
String sql, StatementNode stmt) {
PostgresQueryContext context = new PostgresQueryContext(this);
PostgresStatement pstmt = generateStatementStub(sql, stmt, null, null);
boolean local = beforeExecute(pstmt);
boolean success = false;
try {
pstmt = finishGenerating(context, sql, stmt, null, null);
success = true;
}
finally {
afterExecute(pstmt, local, success, false);
}
PostgresPreparedStatement ppstmt;
PostgresExecuteStatement estmt = null;
if (pstmt instanceof PostgresExecuteStatement) {
// DECLARE ... EXECUTE ... gets spliced out rather than
// making a second prepared statement.
estmt = (PostgresExecuteStatement)pstmt;
ppstmt = preparedStatements.get(estmt.getName());
if (ppstmt == null)
throw new NoSuchPreparedStatementException(estmt.getName());
pstmt = ppstmt.getStatement();
}
else {
ppstmt = new PostgresPreparedStatement(this, null, sql, pstmt,
System.currentTimeMillis());
}
if (!(pstmt instanceof PostgresCursorGenerator)) {
throw new UnsupportedSQLException("DECLARE can only be used with a result-generating statement", stmt);
}
if (!((PostgresCursorGenerator<?>)pstmt).canSuspend(this)) {
throw new UnsupportedSQLException("DECLARE can only be used within a transaction", stmt);
}
PostgresBoundQueryContext bound =
new PostgresBoundQueryContext(this, ppstmt, name, true, false);
QueryBindings bindings = bound.createBindings();
if (estmt != null) {
estmt.setParameters(bindings);
}
bound.setBindings(bindings);
PostgresBoundQueryContext prev;
synchronized (boundPortals) {
prev = boundPortals.put(name, bound);
}
if (prev != null)
prev.close();
}
@Override
public PostgresStatementResult fetchStatement(String name, int count) throws IOException {
PostgresBoundQueryContext bound = boundPortals.get(name);
if (bound == null)
throw new NoSuchCursorException(name);
sessionMonitor.countEvent(StatementTypes.FROM_CACHE);
QueryBindings bindings = bound.getBindings();
PostgresPreparedStatement pstmt = bound.getStatement();
sessionMonitor.startStatement(pstmt.getSQL(), pstmt.getName());
pstmt.getStatement().sendDescription(bound, false, false);
int nrows = executeStatementWithAutoTxn(pstmt.getStatement(), bound, bindings, count);
sessionMonitor.endStatement(nrows);
return PostgresStatementResults.noResult(nrows); // Already sent.
}
@Override
public void closeBoundPortal(String name) {
PostgresBoundQueryContext bound;
synchronized (boundPortals) {
bound = boundPortals.remove(name);
}
if (bound != null)
bound.close();
}
@Override
public Date currentTime() {
Date override = server.getOverrideCurrentTime();
if (override != null)
return override;
else
return super.currentTime();
}
@Override
public void notifyClient(QueryContext.NotificationLevel level, ErrorCode errorCode, String message)
throws IOException {
if (shouldNotify(level)) {
Object state = messenger.suspendMessage();
messenger.beginMessage(PostgresMessages.NOTICE_RESPONSE_TYPE.code());
messenger.write('S');
switch (level) {
case WARNING:
messenger.writeString("WARN");
break;
case INFO:
messenger.writeString("INFO");
break;
case DEBUG:
messenger.writeString("DEBUG");
break;
// Other possibilities are "NOTICE" and "LOG".
}
if (errorCode != null) {
messenger.write('C');
messenger.writeString(errorCode.getFormattedValue());
}
messenger.write('M');
messenger.writeString(message);
messenger.write(0);
messenger.sendMessage(true);
messenger.resumeMessage(state);
}
}
/* PostgresServerSession */
@Override
public int getVersion() {
return version;
}
@Override
public PostgresMessenger getMessenger() {
return messenger;
}
@Override
public OutputFormat getOutputFormat() {
return outputFormat;
}
@Override
public ServerValueEncoder getValueEncoder() {
if (valueEncoder == null)
valueEncoder = new ServerValueEncoder(typesTranslator(),
messenger.getEncoding(),
getZeroDateTimeBehavior(),
getFormatOptions());
return valueEncoder;
}
/* ServerSession */
@Override
protected boolean propertySet(String key, String value) {
if ("client_encoding".equals(key)) {
messenger.setEncoding(value);
valueEncoder = null; // These depend on the encoding.
valueDecoder = null;
return true;
}
if ("OutputFormat".equals(key) ||
"parserInfixBit".equals(key) ||
"parserInfixLogical".equals(key) ||
"parserDoubleQuoted".equals(key) ||
"columnAsFunc".equals(key) ||
"optimizerDummySetting".equals(key)) {
if (parsedGenerators != null)
rebuildCompiler();
return true;
}
if ("zeroDateTimeBehavior".equals(key)) {
valueEncoder = null; // Also depends on this.
}
if ("binary_output".equals(key) || "jsonbinary_output".equals(key)){
valueEncoder = null;
}
return super.propertySet(key, value);
}
@Override
public String getSessionSetting(String key) {
String prop = super.getSessionSetting(key);
if (prop != null) return prop;
if ("client_encoding".equals(key))
return "UTF8";
else if ("server_encoding".equals(key))
return messenger.getEncoding();
else if ("server_version".equals(key))
return "8.4.7"; // Latest of the 8.x series used to flag client(s) for supported functionality
else if ("session_authorization".equals(key))
return properties.getProperty("user");
else if ("DateStyle".equals(key))
return "ISO, MDY";
else if ("transaction_isolation".equals(key))
return getTransactionIsolationLevel().getSyntax().toLowerCase();
else if ("integer_datetimes".equals(key))
return "on";
else if ("standard_conforming_strings".equals(key))
return "on";
else if ("foundationdb_server".equals(key))
return reqs.layerInfo().getVersionInfo().versionShort;
else
return null;
}
public PostgresServer getServer() {
return server;
}
}