Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.hazelcast.sql.impl.SqlServiceImpl Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2021, Hazelcast, Inc. All Rights Reserved.
*
* 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.hazelcast.sql.impl;
import com.hazelcast.config.SqlConfig;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.internal.nio.Packet;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.internal.util.Preconditions;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.exception.ServiceNotFoundException;
import com.hazelcast.spi.impl.NodeEngine;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.sql.SqlExpectedResultType;
import com.hazelcast.sql.SqlResult;
import com.hazelcast.sql.SqlService;
import com.hazelcast.sql.SqlStatement;
import com.hazelcast.sql.impl.optimizer.DisabledSqlOptimizer;
import com.hazelcast.sql.impl.optimizer.OptimizationTask;
import com.hazelcast.sql.impl.optimizer.SqlOptimizer;
import com.hazelcast.sql.impl.optimizer.SqlPlan;
import com.hazelcast.sql.impl.plan.Plan;
import com.hazelcast.sql.impl.plan.cache.CacheablePlan;
import com.hazelcast.sql.impl.plan.cache.PlanCache;
import com.hazelcast.sql.impl.plan.cache.PlanCacheChecker;
import com.hazelcast.sql.impl.plan.cache.PlanCacheKey;
import com.hazelcast.sql.impl.schema.SqlCatalog;
import com.hazelcast.sql.impl.schema.TableResolver;
import com.hazelcast.sql.impl.schema.map.JetMapMetadataResolver;
import com.hazelcast.sql.impl.schema.map.PartitionedMapTableResolver;
import com.hazelcast.sql.impl.security.NoOpSqlSecurityContext;
import com.hazelcast.sql.impl.security.SqlSecurityContext;
import com.hazelcast.sql.impl.state.QueryState;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.logging.Level;
import static com.hazelcast.sql.SqlExpectedResultType.ANY;
import static com.hazelcast.sql.SqlExpectedResultType.ROWS;
import static com.hazelcast.sql.SqlExpectedResultType.UPDATE_COUNT;
/**
* Base SQL service implementation that bridges optimizer implementation, public and private APIs.
*/
public class SqlServiceImpl implements SqlService, Consumer {
static final String OPTIMIZER_CLASS_PROPERTY_NAME = "hazelcast.sql.optimizerClass";
private static final String SQL_MODULE_OPTIMIZER_CLASS = "com.hazelcast.sql.impl.calcite.CalciteSqlOptimizer";
/** Outbox batch size in bytes. */
private static final int OUTBOX_BATCH_SIZE = 512 * 1024;
/** Default state check frequency. */
private static final long STATE_CHECK_FREQUENCY = 1_000L;
private static final int PLAN_CACHE_SIZE = 10_000;
private final ILogger logger;
private final NodeEngineImpl nodeEngine;
private final NodeServiceProviderImpl nodeServiceProvider;
private final PlanCache planCache = new PlanCache(PLAN_CACHE_SIZE);
private final int poolSize;
private final long queryTimeout;
private JetSqlCoreBackend jetSqlCoreBackend;
private List tableResolvers;
private SqlOptimizer optimizer;
private volatile SqlInternalService internalService;
public SqlServiceImpl(NodeEngineImpl nodeEngine) {
this.logger = nodeEngine.getLogger(getClass());
this.nodeEngine = nodeEngine;
this.nodeServiceProvider = new NodeServiceProviderImpl(nodeEngine);
SqlConfig config = nodeEngine.getConfig().getSqlConfig();
int poolSize = config.getExecutorPoolSize();
long queryTimeout = config.getStatementTimeoutMillis();
if (poolSize == SqlConfig.DEFAULT_EXECUTOR_POOL_SIZE) {
poolSize = Runtime.getRuntime().availableProcessors();
}
assert poolSize > 0;
assert queryTimeout >= 0L;
this.poolSize = poolSize;
this.queryTimeout = queryTimeout;
}
public void start() {
try {
jetSqlCoreBackend = nodeEngine.getService(JetSqlCoreBackend.SERVICE_NAME);
} catch (HazelcastException e) {
if (!(e.getCause() instanceof ServiceNotFoundException)) {
throw e;
}
}
tableResolvers = createTableResolvers(nodeEngine, jetSqlCoreBackend);
optimizer = createOptimizer(nodeEngine, jetSqlCoreBackend);
String instanceName = nodeEngine.getHazelcastInstance().getName();
InternalSerializationService serializationService = (InternalSerializationService) nodeEngine.getSerializationService();
PlanCacheChecker planCacheChecker = new PlanCacheChecker(
nodeEngine,
planCache,
tableResolvers
);
internalService = new SqlInternalService(
instanceName,
nodeServiceProvider,
serializationService,
poolSize,
OUTBOX_BATCH_SIZE,
STATE_CHECK_FREQUENCY,
planCacheChecker
);
internalService.start();
}
public void reset() {
planCache.clear();
if (jetSqlCoreBackend != null) {
jetSqlCoreBackend.reset();
}
}
public void shutdown() {
planCache.clear();
if (jetSqlCoreBackend != null) {
jetSqlCoreBackend.shutdown(true);
}
if (internalService != null) {
internalService.shutdown();
}
}
public SqlInternalService getInternalService() {
return internalService;
}
/**
* For testing only.
*/
public void setInternalService(SqlInternalService internalService) {
this.internalService = internalService;
}
/**
* For testing only.
*/
public SqlOptimizer getOptimizer() {
return optimizer;
}
/**
* For testing only.
*/
public void setOptimizer(SqlOptimizer optimizer) {
this.optimizer = optimizer;
}
public PlanCache getPlanCache() {
return planCache;
}
@Nonnull
@Override
public SqlResult execute(@Nonnull SqlStatement statement) {
return execute(statement, NoOpSqlSecurityContext.INSTANCE);
}
public SqlResult execute(@Nonnull SqlStatement statement, SqlSecurityContext securityContext) {
return execute(statement, securityContext, null);
}
public SqlResult execute(@Nonnull SqlStatement statement, SqlSecurityContext securityContext, QueryId queryId) {
Preconditions.checkNotNull(statement, "Query cannot be null");
try {
if (nodeEngine.getLocalMember().isLiteMember()) {
throw QueryException.error("SQL queries cannot be executed on lite members");
}
long timeout = statement.getTimeoutMillis();
if (timeout == SqlStatement.TIMEOUT_NOT_SET) {
timeout = queryTimeout;
}
if (queryId == null) {
queryId = QueryId.create(nodeServiceProvider.getLocalMemberId());
}
return query0(
queryId,
statement.getSchema(),
statement.getSql(),
statement.getParameters(),
timeout,
statement.getCursorBufferSize(),
statement.getExpectedResultType(),
securityContext
);
} catch (AccessControlException e) {
throw e;
} catch (Exception e) {
throw QueryUtils.toPublicException(e, nodeServiceProvider.getLocalMemberId());
}
}
@Override
public void accept(Packet packet) {
internalService.onPacket(packet);
}
private SqlResult query0(
QueryId queryId,
String schema,
String sql,
List params,
long timeout,
int pageSize,
SqlExpectedResultType expectedResultType,
SqlSecurityContext securityContext
) {
// Validate and normalize
if (sql == null || sql.isEmpty()) {
throw QueryException.error("SQL statement cannot be empty.");
}
List params0 = new ArrayList<>(params);
if (timeout < 0) {
throw QueryException.error("Timeout cannot be negative: " + timeout);
}
if (pageSize <= 0) {
throw QueryException.error("Page size must be positive: " + pageSize);
}
// Prepare and execute
SqlPlan plan = prepare(schema, sql, expectedResultType);
if (securityContext.isSecurityEnabled()) {
plan.checkPermissions(securityContext);
}
return execute(queryId, plan, params0, timeout, pageSize);
}
private SqlPlan prepare(String schema, String sql, SqlExpectedResultType expectedResultType) {
List> searchPaths = prepareSearchPaths(schema);
PlanCacheKey planKey = new PlanCacheKey(searchPaths, sql);
SqlPlan plan = planCache.get(planKey);
if (plan == null) {
SqlCatalog catalog = new SqlCatalog(tableResolvers);
plan = optimizer.prepare(new OptimizationTask(sql, searchPaths, catalog));
if (plan instanceof CacheablePlan) {
CacheablePlan plan0 = (CacheablePlan) plan;
planCache.put(planKey, plan0);
}
}
checkReturnType(plan, expectedResultType);
return plan;
}
private void checkReturnType(SqlPlan plan, SqlExpectedResultType expectedResultType) {
if (expectedResultType == ANY) {
return;
}
boolean producesRows = plan.producesRows();
if (producesRows && expectedResultType == UPDATE_COUNT) {
throw QueryException.error("The statement doesn't produce update count");
}
if (!producesRows && expectedResultType == ROWS) {
throw QueryException.error("The statement doesn't produce rows");
}
}
private List> prepareSearchPaths(String schema) {
List> currentSearchPaths;
if (schema == null || schema.isEmpty()) {
currentSearchPaths = Collections.emptyList();
} else {
currentSearchPaths = Collections.singletonList(Collections.singletonList(schema));
}
return QueryUtils.prepareSearchPaths(currentSearchPaths, tableResolvers);
}
private SqlResult execute(QueryId queryId, SqlPlan plan, List params, long timeout, int pageSize) {
if (plan instanceof Plan) {
return executeImdg(queryId, (Plan) plan, params, timeout, pageSize);
} else {
return executeJet(queryId, plan, params, timeout, pageSize);
}
}
private SqlResult executeImdg(QueryId queryId, Plan plan, List params, long timeout, int pageSize) {
QueryState state = internalService.execute(queryId, plan, params, timeout, pageSize, planCache);
return SqlResultImpl.createRowsResult(state, (InternalSerializationService) nodeEngine.getSerializationService());
}
private SqlResult executeJet(QueryId queryId, SqlPlan plan, List params, long timeout, int pageSize) {
return jetSqlCoreBackend.execute(queryId, plan, params, timeout, pageSize);
}
/**
* Create either normal or not-implemented optimizer instance.
*
* @param nodeEngine Node engine.
* @return Optimizer.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private SqlOptimizer createOptimizer(NodeEngine nodeEngine, JetSqlCoreBackend jetSqlCoreBackend) {
// 1. Resolve class name.
String className = System.getProperty(OPTIMIZER_CLASS_PROPERTY_NAME, SQL_MODULE_OPTIMIZER_CLASS);
// 2. Get the class.
Class clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
logger.log(SQL_MODULE_OPTIMIZER_CLASS.equals(className) ? Level.FINE : Level.WARNING,
"Optimizer class \"" + className + "\" not found, falling back to " + DisabledSqlOptimizer.class.getName());
return new DisabledSqlOptimizer();
} catch (Exception e) {
throw new HazelcastException("Failed to resolve optimizer class " + className + ": " + e.getMessage(), e);
}
// 3. Get required constructor.
Constructor constructor;
try {
constructor = clazz.getConstructor(
NodeEngine.class,
JetSqlCoreBackend.class
);
} catch (ReflectiveOperationException e) {
throw new HazelcastException("Failed to get the constructor for the optimizer class "
+ className + ": " + e.getMessage(), e);
}
// 4. Finally, get the instance.
try {
return constructor.newInstance(nodeEngine, jetSqlCoreBackend);
} catch (ReflectiveOperationException e) {
throw new HazelcastException("Failed to instantiate the optimizer class " + className + ": " + e.getMessage(), e);
}
}
private static List createTableResolvers(
NodeEngine nodeEngine,
@Nullable JetSqlCoreBackend jetSqlCoreBackend
) {
List res = new ArrayList<>();
JetMapMetadataResolver jetMetadataResolver;
if (jetSqlCoreBackend != null) {
res.addAll(jetSqlCoreBackend.tableResolvers());
jetMetadataResolver = jetSqlCoreBackend.mapMetadataResolver();
} else {
jetMetadataResolver = JetMapMetadataResolver.NO_OP;
}
res.add(new PartitionedMapTableResolver(nodeEngine, jetMetadataResolver));
return res;
}
}