/* * Copyright 2016 Hortonworks. * * 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.hortonworks.registries.storage.impl.jdbc.provider.postgresql.factory; /** * */ import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; import com.hortonworks.registries.storage.OrderByField; import com.hortonworks.registries.storage.Storable; import com.hortonworks.registries.storage.StorableKey; import com.hortonworks.registries.storage.impl.jdbc.config.ExecutionConfig; import com.hortonworks.registries.storage.impl.jdbc.connection.ConnectionBuilder; import com.hortonworks.registries.storage.impl.jdbc.connection.HikariCPConnectionBuilder; import com.hortonworks.registries.storage.impl.jdbc.provider.postgresql.query.PostgresqlDeleteQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.postgresql.query.PostgresqlInsertQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.postgresql.query.PostgresqlInsertUpdateDuplicate; import com.hortonworks.registries.storage.impl.jdbc.provider.postgresql.query.PostgresqlSelectQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.factory.AbstractQueryExecutor; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.query.SqlQuery; import com.hortonworks.registries.storage.impl.jdbc.provider.sql.statement.PreparedStatementBuilder; import com.hortonworks.registries.storage.impl.jdbc.util.Util; import com.zaxxer.hikari.HikariConfig; import java.sql.ResultSet; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; /** * SQL query executor for PostgreSQL * <p> * To issue the new ID to insert and get auto issued key in concurrent manner, PostgresExecutor utilizes Postgres's * SERIAL feature * <p> * If the value of id is null, we let Postgres issue new ID and get the new ID. If the value of id is not null, we just use that value. */ public class PostgresqlExecutor extends AbstractQueryExecutor { /** * @param config Object that contains arbitrary configuration that may be needed for any of the steps of the query execution process * @param connectionBuilder Object that establishes the connection to the database */ public PostgresqlExecutor(ExecutionConfig config, ConnectionBuilder connectionBuilder) { super(config, connectionBuilder); } /** * @param config Object that contains arbitrary configuration that may be needed for any of the steps of the query execution process * @param connectionBuilder Object that establishes the connection to the database * @param cacheBuilder Guava cache configuration. The maximum number of entries in cache (open connections) * must not exceed the maximum number of open database connections allowed */ public PostgresqlExecutor(ExecutionConfig config, ConnectionBuilder connectionBuilder, CacheBuilder<SqlQuery, PreparedStatementBuilder> cacheBuilder) { super(config, connectionBuilder, cacheBuilder); } // ============= Public API methods ============= @Override public void insert(Storable storable) { insertOrUpdateWithUniqueId(storable, new PostgresqlInsertQuery(storable)); } @Override public void insertOrUpdate(final Storable storable) { insertOrUpdateWithUniqueId(storable, new PostgresqlInsertUpdateDuplicate(storable)); } @Override public <T extends Storable> Collection<T> select(String namespace) { return executeQuery(namespace, new PostgresqlSelectQuery(namespace)); } @Override public <T extends Storable> Collection<T> select(String namespace, List<OrderByField> orderByFields) { return executeQuery(namespace, new PostgresqlSelectQuery(namespace, orderByFields)); } @Override public <T extends Storable> Collection<T> select(StorableKey storableKey) { return executeQuery(storableKey.getNameSpace(), new PostgresqlSelectQuery(storableKey)); } @Override public <T extends Storable> Collection<T> select(StorableKey storableKey, List<OrderByField> orderByFields) { return executeQuery(storableKey.getNameSpace(), new PostgresqlSelectQuery(storableKey, orderByFields)); } @Override public void delete(StorableKey storableKey) { executeUpdate(new PostgresqlDeleteQuery(storableKey)); } @Override public Long nextId(String namespace) { // We intentionally return null. Please refer the class javadoc for more details. return null; } public static PostgresqlExecutor createExecutor(Map<String, Object> jdbcProps) { Util.validateJDBCProperties(jdbcProps, Lists.newArrayList("dataSourceClassName", "dataSource.url")); String dataSourceClassName = (String) jdbcProps.get("dataSourceClassName"); log.info("data source class: [{}]", dataSourceClassName); String jdbcUrl = (String) jdbcProps.get("dataSource.url"); log.info("dataSource.url is: [{}] ", jdbcUrl); int queryTimeOutInSecs = -1; if (jdbcProps.containsKey("queryTimeoutInSecs")) { queryTimeOutInSecs = (Integer) jdbcProps.get("queryTimeoutInSecs"); if (queryTimeOutInSecs < 0) { throw new IllegalArgumentException("queryTimeoutInSecs property can not be negative"); } } Properties properties = new Properties(); properties.putAll(jdbcProps); HikariConfig hikariConfig = new HikariConfig(properties); HikariCPConnectionBuilder connectionBuilder = new HikariCPConnectionBuilder(hikariConfig); ExecutionConfig executionConfig = new ExecutionConfig(queryTimeOutInSecs); return new PostgresqlExecutor(executionConfig, connectionBuilder); } // this is required since the Id type in Storable is long and Postgres supports Int type for SERIAL (auto increment) field @Override protected QueryExecution getQueryExecution(SqlQuery sqlQuery) { return new QueryExecution(sqlQuery) { @Override protected List<Map<String, Object>> getMapsFromResultSet(ResultSet resultSet) { List<Map<String, Object>> res = super.getMapsFromResultSet(resultSet); if (res != null) { res.forEach(m -> { Object id = m.get("id"); if (id != null && id instanceof Integer) { m.put("id", Long.valueOf((Integer) id)); } }); } return res; } }; } private void insertOrUpdateWithUniqueId(final Storable storable, final SqlQuery sqlQuery) { try { Long id = storable.getId(); if (id == null) { id = executeUpdateWithReturningGeneratedKey(sqlQuery); log.debug("after executeUpdate, generated id {}", id); storable.setId(id); } else { executeUpdate(sqlQuery); } } catch (Exception e) { executeUpdate(sqlQuery); } } }