/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.presto;
import com.facebook.presto.connector.ConnectorId;
import com.facebook.presto.metadata.SessionPropertyManager;
import com.facebook.presto.security.AccessControl;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.spi.QueryId;
import com.facebook.presto.spi.security.Identity;
import com.facebook.presto.spi.type.TimeZoneKey;
import com.facebook.presto.sql.tree.Execute;
import com.facebook.presto.transaction.TransactionId;
import com.facebook.presto.transaction.TransactionManager;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.security.Principal;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TimeZone;
import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND;
import static com.facebook.presto.util.Failures.checkCondition;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
public final class Session
{
private final QueryId queryId;
private final Optional<TransactionId> transactionId;
private final boolean clientTransactionSupport;
private final Identity identity;
private final Optional<String> source;
private final Optional<String> catalog;
private final Optional<String> schema;
private final TimeZoneKey timeZoneKey;
private final Locale locale;
private final Optional<String> remoteUserAddress;
private final Optional<String> userAgent;
private final Optional<String> clientInfo;
private final long startTime;
private final Map<String, String> systemProperties;
private final Map<ConnectorId, Map<String, String>> connectorProperties;
private final Map<String, Map<String, String>> unprocessedCatalogProperties;
private final SessionPropertyManager sessionPropertyManager;
private final Map<String, String> preparedStatements;
public Session(
QueryId queryId,
Optional<TransactionId> transactionId,
boolean clientTransactionSupport,
Identity identity,
Optional<String> source,
Optional<String> catalog,
Optional<String> schema,
TimeZoneKey timeZoneKey,
Locale locale,
Optional<String> remoteUserAddress,
Optional<String> userAgent,
Optional<String> clientInfo,
long startTime,
Map<String, String> systemProperties,
Map<ConnectorId, Map<String, String>> connectorProperties,
Map<String, Map<String, String>> unprocessedCatalogProperties,
SessionPropertyManager sessionPropertyManager,
Map<String, String> preparedStatements)
{
this.queryId = requireNonNull(queryId, "queryId is null");
this.transactionId = requireNonNull(transactionId, "transactionId is null");
this.clientTransactionSupport = clientTransactionSupport;
this.identity = requireNonNull(identity, "identity is null");
this.source = requireNonNull(source, "source is null");
this.catalog = requireNonNull(catalog, "catalog is null");
this.schema = requireNonNull(schema, "schema is null");
this.timeZoneKey = requireNonNull(timeZoneKey, "timeZoneKey is null");
this.locale = requireNonNull(locale, "locale is null");
this.remoteUserAddress = requireNonNull(remoteUserAddress, "remoteUserAddress is null");
this.userAgent = requireNonNull(userAgent, "userAgent is null");
this.clientInfo = requireNonNull(clientInfo, "clientInfo is null");
this.startTime = startTime;
this.systemProperties = ImmutableMap.copyOf(requireNonNull(systemProperties, "systemProperties is null"));
this.sessionPropertyManager = requireNonNull(sessionPropertyManager, "sessionPropertyManager is null");
this.preparedStatements = requireNonNull(preparedStatements, "preparedStatements is null");
ImmutableMap.Builder<ConnectorId, Map<String, String>> catalogPropertiesBuilder = ImmutableMap.builder();
connectorProperties.entrySet().stream()
.map(entry -> Maps.immutableEntry(entry.getKey(), ImmutableMap.copyOf(entry.getValue())))
.forEach(catalogPropertiesBuilder::put);
this.connectorProperties = catalogPropertiesBuilder.build();
ImmutableMap.Builder<String, Map<String, String>> unprocessedCatalogPropertiesBuilder = ImmutableMap.builder();
unprocessedCatalogProperties.entrySet().stream()
.map(entry -> Maps.immutableEntry(entry.getKey(), ImmutableMap.copyOf(entry.getValue())))
.forEach(unprocessedCatalogPropertiesBuilder::put);
this.unprocessedCatalogProperties = unprocessedCatalogPropertiesBuilder.build();
checkArgument(!transactionId.isPresent() || unprocessedCatalogProperties.isEmpty(), "Catalog session properties cannot be set if there is an open transaction");
checkArgument(catalog.isPresent() || !schema.isPresent(), "schema is set but catalog is not");
}
public QueryId getQueryId()
{
return queryId;
}
public String getUser()
{
return identity.getUser();
}
public Identity getIdentity()
{
return identity;
}
public Optional<String> getSource()
{
return source;
}
public Optional<String> getCatalog()
{
return catalog;
}
public Optional<String> getSchema()
{
return schema;
}
public TimeZoneKey getTimeZoneKey()
{
return timeZoneKey;
}
public Locale getLocale()
{
return locale;
}
public Optional<String> getRemoteUserAddress()
{
return remoteUserAddress;
}
public Optional<String> getUserAgent()
{
return userAgent;
}
public Optional<String> getClientInfo()
{
return clientInfo;
}
public long getStartTime()
{
return startTime;
}
public Optional<TransactionId> getTransactionId()
{
return transactionId;
}
public TransactionId getRequiredTransactionId()
{
checkState(transactionId.isPresent(), "Not in a transaction");
return transactionId.get();
}
public boolean isClientTransactionSupport()
{
return clientTransactionSupport;
}
public <T> T getSystemProperty(String name, Class<T> type)
{
return sessionPropertyManager.decodeSystemPropertyValue(name, systemProperties.get(name), type);
}
public Map<ConnectorId, Map<String, String>> getConnectorProperties()
{
return connectorProperties;
}
public Map<String, String> getConnectorProperties(ConnectorId connectorId)
{
return connectorProperties.getOrDefault(connectorId, ImmutableMap.of());
}
public Map<String, Map<String, String>> getUnprocessedCatalogProperties()
{
return unprocessedCatalogProperties;
}
public Map<String, String> getSystemProperties()
{
return systemProperties;
}
public Map<String, String> getPreparedStatements()
{
return preparedStatements;
}
public String getPreparedStatementFromExecute(Execute execute)
{
return getPreparedStatement(execute.getName());
}
public String getPreparedStatement(String name)
{
String sql = preparedStatements.get(name);
checkCondition(sql != null, NOT_FOUND, "Prepared statement not found: " + name);
return sql;
}
public Session beginTransactionId(TransactionId transactionId, TransactionManager transactionManager, AccessControl accessControl)
{
requireNonNull(transactionId, "transactionId is null");
checkArgument(!this.transactionId.isPresent(), "Session already has an active transaction");
requireNonNull(transactionManager, "transactionManager is null");
requireNonNull(accessControl, "accessControl is null");
for (Entry<String, String> property : systemProperties.entrySet()) {
// verify permissions
accessControl.checkCanSetSystemSessionProperty(identity, property.getKey());
// validate session property value
sessionPropertyManager.validateSystemSessionProperty(property.getKey(), property.getValue());
}
// Now that there is a transaction, the catalog name can be resolved to a connector, and the catalog properties can be validated
ImmutableMap.Builder<ConnectorId, Map<String, String>> connectorProperties = ImmutableMap.builder();
for (Entry<String, Map<String, String>> catalogEntry : unprocessedCatalogProperties.entrySet()) {
String catalogName = catalogEntry.getKey();
Map<String, String> catalogProperties = catalogEntry.getValue();
if (catalogProperties.isEmpty()) {
continue;
}
ConnectorId connectorId = transactionManager.getOptionalCatalogMetadata(transactionId, catalogName)
.orElseThrow(() -> new PrestoException(NOT_FOUND, "Catalog does not exist: " + catalogName))
.getConnectorId();
for (Entry<String, String> property : catalogProperties.entrySet()) {
// verify permissions
accessControl.checkCanSetCatalogSessionProperty(transactionId, identity, catalogName, property.getKey());
// validate session property value
sessionPropertyManager.validateCatalogSessionProperty(connectorId, catalogName, property.getKey(), property.getValue());
}
connectorProperties.put(connectorId, catalogProperties);
}
return new Session(
queryId,
Optional.of(transactionId),
clientTransactionSupport,
identity,
source,
catalog,
schema,
timeZoneKey,
locale,
remoteUserAddress,
userAgent,
clientInfo,
startTime,
systemProperties,
connectorProperties.build(),
ImmutableMap.of(),
sessionPropertyManager,
preparedStatements);
}
public ConnectorSession toConnectorSession()
{
return new FullConnectorSession(queryId.toString(), identity, timeZoneKey, locale, startTime);
}
public ConnectorSession toConnectorSession(ConnectorId connectorId)
{
requireNonNull(connectorId, "connectorId is null");
return new FullConnectorSession(
queryId.toString(),
identity,
timeZoneKey,
locale,
startTime,
connectorProperties.getOrDefault(connectorId, ImmutableMap.of()),
connectorId,
connectorId.getCatalogName(),
sessionPropertyManager);
}
public SessionRepresentation toSessionRepresentation()
{
return new SessionRepresentation(
queryId.toString(),
transactionId,
clientTransactionSupport,
identity.getUser(),
identity.getPrincipal().map(Principal::toString),
source,
catalog,
schema,
timeZoneKey,
locale,
remoteUserAddress,
userAgent,
clientInfo,
startTime,
systemProperties,
connectorProperties,
preparedStatements);
}
@Override
public String toString()
{
return toStringHelper(this)
.add("queryId", queryId)
.add("transactionId", transactionId)
.add("user", getUser())
.add("principal", getIdentity().getPrincipal().orElse(null))
.add("source", source.orElse(null))
.add("catalog", catalog.orElse(null))
.add("schema", schema.orElse(null))
.add("timeZoneKey", timeZoneKey)
.add("locale", locale)
.add("remoteUserAddress", remoteUserAddress.orElse(null))
.add("userAgent", userAgent.orElse(null))
.add("clientInfo", clientInfo.orElse(null))
.add("startTime", startTime)
.omitNullValues()
.toString();
}
public static SessionBuilder builder(SessionPropertyManager sessionPropertyManager)
{
return new SessionBuilder(sessionPropertyManager);
}
@VisibleForTesting
public static SessionBuilder builder(Session session)
{
return new SessionBuilder(session);
}
public static class SessionBuilder
{
private QueryId queryId;
private TransactionId transactionId;
private boolean clientTransactionSupport;
private Identity identity;
private String source;
private String catalog;
private String schema;
private TimeZoneKey timeZoneKey = TimeZoneKey.getTimeZoneKey(TimeZone.getDefault().getID());
private Locale locale = Locale.getDefault();
private String remoteUserAddress;
private String userAgent;
private String clientInfo;
private long startTime = System.currentTimeMillis();
private final Map<String, String> systemProperties = new HashMap<>();
private final Map<String, Map<String, String>> catalogSessionProperties = new HashMap<>();
private final SessionPropertyManager sessionPropertyManager;
private final Map<String, String> preparedStatements = new HashMap<>();
private SessionBuilder(SessionPropertyManager sessionPropertyManager)
{
this.sessionPropertyManager = requireNonNull(sessionPropertyManager, "sessionPropertyManager is null");
}
private SessionBuilder(Session session)
{
requireNonNull(session, "session is null");
checkArgument(!session.getTransactionId().isPresent(), "Session builder cannot be created from a session in a transaction");
this.sessionPropertyManager = session.sessionPropertyManager;
this.queryId = session.queryId;
this.transactionId = session.transactionId.orElse(null);
this.clientTransactionSupport = session.clientTransactionSupport;
this.identity = session.identity;
this.source = session.source.orElse(null);
this.catalog = session.catalog.orElse(null);
this.schema = session.schema.orElse(null);
this.timeZoneKey = session.timeZoneKey;
this.locale = session.locale;
this.remoteUserAddress = session.remoteUserAddress.orElse(null);
this.userAgent = session.userAgent.orElse(null);
this.clientInfo = session.clientInfo.orElse(null);
this.startTime = session.startTime;
this.systemProperties.putAll(session.systemProperties);
this.catalogSessionProperties.putAll(session.unprocessedCatalogProperties);
this.preparedStatements.putAll(session.preparedStatements);
}
public SessionBuilder setQueryId(QueryId queryId)
{
this.queryId = requireNonNull(queryId, "queryId is null");
return this;
}
public SessionBuilder setTransactionId(TransactionId transactionId)
{
checkArgument(catalogSessionProperties.isEmpty(), "Catalog session properties cannot be set if there is an open transaction");
this.transactionId = transactionId;
return this;
}
public SessionBuilder setClientTransactionSupport()
{
this.clientTransactionSupport = true;
return this;
}
public SessionBuilder setCatalog(String catalog)
{
this.catalog = catalog;
return this;
}
public SessionBuilder setLocale(Locale locale)
{
this.locale = locale;
return this;
}
public SessionBuilder setRemoteUserAddress(String remoteUserAddress)
{
this.remoteUserAddress = remoteUserAddress;
return this;
}
public SessionBuilder setSchema(String schema)
{
this.schema = schema;
return this;
}
public SessionBuilder setSource(String source)
{
this.source = source;
return this;
}
public SessionBuilder setStartTime(long startTime)
{
this.startTime = startTime;
return this;
}
public SessionBuilder setTimeZoneKey(TimeZoneKey timeZoneKey)
{
this.timeZoneKey = timeZoneKey;
return this;
}
public SessionBuilder setIdentity(Identity identity)
{
this.identity = identity;
return this;
}
public SessionBuilder setUserAgent(String userAgent)
{
this.userAgent = userAgent;
return this;
}
public SessionBuilder setClientInfo(String clientInfo)
{
this.clientInfo = clientInfo;
return this;
}
/**
* Sets a system property for the session. The property name and value must
* only contain characters from US-ASCII and must not be for '='.
*/
public SessionBuilder setSystemProperty(String propertyName, String propertyValue)
{
systemProperties.put(propertyName, propertyValue);
return this;
}
/**
* Sets a catalog property for the session. The property name and value must
* only contain characters from US-ASCII and must not be for '='.
*/
public SessionBuilder setCatalogSessionProperty(String catalogName, String propertyName, String propertyValue)
{
checkArgument(transactionId == null, "Catalog session properties cannot be set if there is an open transaction");
catalogSessionProperties.computeIfAbsent(catalogName, id -> new HashMap<>()).put(propertyName, propertyValue);
return this;
}
public SessionBuilder addPreparedStatement(String statementName, String query)
{
this.preparedStatements.put(statementName, query);
return this;
}
public Session build()
{
return new Session(
queryId,
Optional.ofNullable(transactionId),
clientTransactionSupport,
identity,
Optional.ofNullable(source),
Optional.ofNullable(catalog),
Optional.ofNullable(schema),
timeZoneKey,
locale,
Optional.ofNullable(remoteUserAddress),
Optional.ofNullable(userAgent),
Optional.ofNullable(clientInfo),
startTime,
systemProperties,
ImmutableMap.of(),
catalogSessionProperties,
sessionPropertyManager,
preparedStatements);
}
}
}