/** * 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.ForeignKey; import com.foundationdb.ais.model.Schema; import com.foundationdb.ais.model.Table; import com.foundationdb.qp.operator.QueryBindings; import com.foundationdb.server.error.ForeignKeyNotDeferrableException; import com.foundationdb.server.error.IsolationLevelChangedException; import com.foundationdb.server.error.NoSuchConstraintException; import com.foundationdb.server.error.NoSuchSchemaException; import com.foundationdb.server.error.UnsupportedConfigurationException; import com.foundationdb.server.error.UnsupportedSQLException; import com.foundationdb.server.service.monitor.SessionMonitor.StatementTypes; import com.foundationdb.sql.optimizer.plan.CostEstimate; import com.foundationdb.sql.parser.AccessMode; import com.foundationdb.sql.parser.IsolationLevel; import com.foundationdb.sql.parser.ParameterNode; import com.foundationdb.sql.parser.SetConfigurationNode; import com.foundationdb.sql.parser.SetConstraintsNode; import com.foundationdb.sql.parser.SetSchemaNode; import com.foundationdb.sql.parser.SetTransactionAccessNode; import com.foundationdb.sql.parser.SetTransactionIsolationNode; import com.foundationdb.sql.parser.ShowConfigurationNode; import com.foundationdb.sql.parser.StatementNode; import com.foundationdb.sql.parser.StatementType; import com.foundationdb.sql.parser.TableName; import com.foundationdb.sql.parser.TableNameList; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.TreeMap; /** SQL statements that affect session / environment state. */ public class PostgresSessionStatement extends PostgresStatementResults implements PostgresStatement { enum Operation { USE, SET_CONFIGURATION, SHOW_CONFIGURATION, BEGIN_TRANSACTION, COMMIT_TRANSACTION, ROLLBACK_TRANSACTION, TRANSACTION_ISOLATION, TRANSACTION_ACCESS, SET_CONSTRAINTS; public PostgresSessionStatement getStatement(StatementNode statement) { return new PostgresSessionStatement (this, statement); } }; public static final Map<String,String> ALLOWED_CONFIGURATION = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); static { for (String key : new String[] { // Output. "OutputFormat", "maxNotificationLevel", "zeroDateTimeBehavior", "binary_output", "jsonbinary_output", // Optimization. (Dummy for testing of statement cache.) "optimizerDummySetting", "statementCacheCapacity", "resetStatementCache", // Execution. "constraintCheckTime", "queryTimeoutSec", "transactionPeriodicallyCommit", // Compatible and translated. "statement_timeout", // Compatible that actually does something. "client_encoding", // Compatible but ignored. "application_name", "DateStyle", "extra_float_digits", "geqo", "ksqo", "lc_monetary", "ssl_renegotiation_limit" }) { ALLOWED_CONFIGURATION.put(key, key); } }; private Operation operation; private StatementNode statement; private long aisGeneration; protected PostgresSessionStatement(Operation operation, StatementNode statement) { this.operation = operation; this.statement = statement; } public StatementNode getStatement() { return statement; } @Override public PostgresType[] getParameterTypes() { return null; } static final PostgresEmulatedMetaDataStatement.ColumnType SHOW_PG_TYPE = PostgresEmulatedMetaDataStatement.ColumnType.DEFVAL; @Override public void sendDescription(PostgresQueryContext context, boolean always, boolean params) throws IOException { if (always || (operation == Operation.SHOW_CONFIGURATION)) { PostgresServerSession server = context.getServer(); PostgresMessenger messenger = server.getMessenger(); if (params) { messenger.beginMessage(PostgresMessages.PARAMETER_DESCRIPTION_TYPE.code()); messenger.writeShort(0); messenger.sendMessage(); } switch (operation) { case SHOW_CONFIGURATION: PostgresType columnType = PostgresEmulatedMetaDataStatement.getColumnTypes(context.getTypesTranslator()).get(SHOW_PG_TYPE); messenger.beginMessage(PostgresMessages.ROW_DESCRIPTION_TYPE.code()); messenger.writeShort(1); // single column messenger.writeString(((ShowConfigurationNode)statement).getVariable()); // attname messenger.writeInt(0); // attrelid messenger.writeShort(0); // attnum messenger.writeInt(columnType.getOid()); // atttypid messenger.writeShort(columnType.getLength()); // attlen messenger.writeInt(columnType.getModifier()); // atttypmod messenger.writeShort(0); break; default: messenger.beginMessage(PostgresMessages.NO_DATA_TYPE.code()); break; } messenger.sendMessage(); } } @Override public TransactionMode getTransactionMode() { switch (operation) { case SET_CONSTRAINTS: return TransactionMode.REQUIRED; default: return TransactionMode.ALLOWED; } } @Override public TransactionAbortedMode getTransactionAbortedMode() { switch (operation) { case USE: case ROLLBACK_TRANSACTION: case SET_CONFIGURATION: case SHOW_CONFIGURATION: return TransactionAbortedMode.ALLOWED; default: return TransactionAbortedMode.NOT_ALLOWED; } } @Override public AISGenerationMode getAISGenerationMode() { return AISGenerationMode.ALLOWED; } @Override public PostgresStatementResult execute(PostgresQueryContext context, QueryBindings bindings, int maxrows) throws IOException { PostgresServerSession server = context.getServer(); server.getSessionMonitor().countEvent(StatementTypes.OTHER_STMT); doOperation(context, server); return statementComplete(statement, (operation == Operation.SHOW_CONFIGURATION) ? 1 : 0); } @Override public boolean hasAISGeneration() { return aisGeneration != 0; } @Override public void setAISGeneration(long aisGeneration) { this.aisGeneration = aisGeneration; } @Override public long getAISGeneration() { return aisGeneration; } @Override public PostgresStatement finishGenerating(PostgresServerSession server, String sql, StatementNode stmt, List<ParameterNode> params, int[] paramTypes) { return this; } @Override public boolean putInCache() { return false; } @Override public CostEstimate getCostEstimate() { return null; } public static final String ISOLATION_LEVEL_WARNED = "ISOLATION_LEVEL_WARNED"; protected void doOperation(PostgresQueryContext context, PostgresServerSession server) throws IOException { switch (operation) { case USE: { SetSchemaNode node = (SetSchemaNode)statement; String schemaName = (node.statementType() == StatementType.SET_SCHEMA_USER ? server.getProperty("user") : node.getSchemaName()); if (server.getAIS().getSchema(schemaName) != null) { server.setDefaultSchemaName(schemaName); } else { throw new NoSuchSchemaException(schemaName); } } break; case BEGIN_TRANSACTION: server.beginTransaction(); break; case COMMIT_TRANSACTION: server.commitTransaction(); break; case ROLLBACK_TRANSACTION: server.rollbackTransaction(); break; case TRANSACTION_ISOLATION: { SetTransactionIsolationNode node = (SetTransactionIsolationNode)statement; boolean current = node.isCurrent(); IsolationLevel requestedLevel = node.getIsolationLevel(), actualLevel; if (current) actualLevel = server.setTransactionIsolationLevel(requestedLevel); else actualLevel = server.setTransactionDefaultIsolationLevel(requestedLevel); if ((requestedLevel != IsolationLevel.UNSPECIFIED_ISOLATION_LEVEL) && (requestedLevel != actualLevel) && (server.getAttribute(ISOLATION_LEVEL_WARNED) == null)) { context.warnClient(new IsolationLevelChangedException(requestedLevel.getSyntax(), actualLevel.getSyntax())); server.setAttribute(ISOLATION_LEVEL_WARNED, Boolean.TRUE); } } break; case TRANSACTION_ACCESS: { SetTransactionAccessNode node = (SetTransactionAccessNode)statement; boolean current = node.isCurrent(); boolean readOnly = (node.getAccessMode() == AccessMode.READ_ONLY_ACCESS_MODE); if (current) server.setTransactionReadOnly(readOnly); else server.setTransactionDefaultReadOnly(readOnly); } break; case SET_CONFIGURATION: { SetConfigurationNode node = (SetConfigurationNode)statement; setVariable (server, node.getVariable(), node.getValue()); } break; case SHOW_CONFIGURATION: { ShowConfigurationNode node = (ShowConfigurationNode)statement; showVariable (context, server, node.getVariable()); } break; case SET_CONSTRAINTS: { SetConstraintsNode node = (SetConstraintsNode)statement; deferConstraints(server, node.isAll(), node.getConstraints(), node.isDeferred()); } break; default: throw new UnsupportedSQLException("session control", statement); } } protected void setVariable(PostgresServerSession server, String variable, String value) { String cased = allowedConfiguration(variable); if (cased == null) throw new UnsupportedConfigurationException (variable); server.setProperty(cased, value); } protected void showVariable(PostgresQueryContext context, PostgresServerSession server, String variable) throws IOException { String cased = allowedConfiguration(variable); if (cased != null) variable = cased; String value; if (variable == "statementCacheCapacity") { value = Integer.toString(server.getStatementCacheCapacity()); } else { value = server.getSessionSetting(variable); if (value == null) throw new UnsupportedConfigurationException (variable); } PostgresType columnType = PostgresEmulatedMetaDataStatement.getColumnTypes(context.getTypesTranslator()).get(SHOW_PG_TYPE); PostgresMessenger messenger = server.getMessenger(); messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code()); messenger.writeShort(1); // single column PostgresEmulatedMetaDataStatement.writeColumn(context, server, messenger, 0, value, columnType); messenger.sendMessage(); } protected void deferConstraints(PostgresServerSession server, boolean all, TableNameList constraints, boolean deferred) { if (all) { server.setDeferredForeignKey(null, deferred); } else { for (TableName constraintName : constraints) { String schemaName = constraintName.getSchemaName(); if (schemaName == null) schemaName = server.getDefaultSchemaName(); Schema schema = server.getAIS().getSchema(schemaName); if (schema == null) throw new NoSuchSchemaException(schemaName); ForeignKey foreignKey = null; for (Table table : schema.getTables().values()) { ForeignKey tfk = table.getReferencingForeignKey(constraintName.getTableName()); if (tfk != null) { if (foreignKey == null) foreignKey = tfk; } } if (foreignKey == null) throw new NoSuchConstraintException(schemaName, constraintName.getTableName()); if (!foreignKey.isDeferrable()) throw new ForeignKeyNotDeferrableException(constraintName.getTableName(), schemaName, foreignKey.getReferencingTable().getName().getTableName()); server.setDeferredForeignKey(foreignKey, deferred); } } } /** Check for known variables <em>and</em> standardize their case. */ public static String allowedConfiguration(String key) { return ALLOWED_CONFIGURATION.get(key); } }