Skip to content
Snippets Groups Projects
Commit df7bac57 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Add support for stored procedures in persistence

parent 06b4a6a5
No related branches found
No related tags found
No related merge requests found
Pipeline #11571 passed
......@@ -13,9 +13,13 @@ package org.eclipsefoundation.persistence.dao.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.ParameterMode;
import javax.persistence.Query;
import javax.persistence.StoredProcedureQuery;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
......@@ -26,6 +30,7 @@ import org.eclipsefoundation.core.exception.MaintenanceException;
import org.eclipsefoundation.core.response.PaginatedResultsFilter;
import org.eclipsefoundation.persistence.dao.PersistenceDao;
import org.eclipsefoundation.persistence.dto.BareNode;
import org.eclipsefoundation.persistence.model.ParameterizedCallStatement;
import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement.Clause;
import org.eclipsefoundation.persistence.model.RDBMSQuery;
import org.slf4j.Logger;
......@@ -47,31 +52,41 @@ public abstract class BaseHibernateDao implements PersistenceDao {
if (isMaintenanceMode()) {
throw new MaintenanceException();
}
// handle root request parameters (setting headers to track limit and max count)
if (q.isRoot()) {
handleRoot(q);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Querying DB using the following query: {}", q.getFilter().getSelectSql());
// set up the query, either for stored procedures or standard SQL
Query query;
if (q.getFilter() instanceof ParameterizedCallStatement) {
query = getSecondaryEntityManager()
.createStoredProcedureQuery(((ParameterizedCallStatement) q.getFilter()).getCallStatement(), q.getDocType());
} else {
query = getSecondaryEntityManager().createQuery(q.getFilter().getSelectSql(), q.getDocType());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Querying DB using the following query: {}", q.getFilter().getSelectSql());
}
// handle root request parameters (setting headers to track limit and max count)
if (q.isRoot()) {
handleRoot(q);
}
// check if result set should be limited
if (q.getDTOFilter().useLimit() && q.useLimit()) {
LOGGER.debug("Querying DB using offset of {} and limit of {}", getOffset(q), getLimit(q));
query = query.setFirstResult(getOffset(q)).setMaxResults(getLimit(q));
}
}
// build base query
TypedQuery<T> query = getSecondaryEntityManager().createQuery(q.getFilter().getSelectSql(), q.getDocType());
// add ordinal parameters
int ord = 1;
for (Clause c : q.getFilter().getClauses()) {
for (Object param : c.getParams()) {
// for each of params, if processing call statement, add params as registered params
if (q.getFilter() instanceof ParameterizedCallStatement) {
((StoredProcedureQuery) query).registerStoredProcedureParameter(ord, param.getClass(), ParameterMode.IN);
}
query.setParameter(ord++, param);
}
}
// check if result set should be limited
if (q.getDTOFilter().useLimit() && q.useLimit()) {
LOGGER.debug("Querying DB using offset of {} and limit of {}", getOffset(q), getLimit(q));
query = query.setFirstResult(getOffset(q)).setMaxResults(getLimit(q));
}
// run the query
return query.getResultList();
return castResultsToType(query.getResultList(), q.getDocType());
}
@Transactional
......@@ -138,10 +153,7 @@ public abstract class BaseHibernateDao implements PersistenceDao {
EntityManager em = getSecondaryEntityManager();
// build base query
TypedQuery<Long> query = em
.createQuery(
q.getFilter()
.getCountSql(), Long.class);
TypedQuery<Long> query = em.createQuery(q.getFilter().getCountSql(), Long.class);
// add ordinal parameters
int ord = 1;
for (Clause c : q.getFilter().getClauses()) {
......@@ -161,8 +173,8 @@ public abstract class BaseHibernateDao implements PersistenceDao {
}
/**
* Handles operations that should happen on a "root" or main DB request for a fulfilled request. This is done to
* ensure that values such as DB max are abided.
* Handles operations that should happen on a "root" or main DB request for a fulfilled request. This is done to ensure
* that values such as DB max are abided.
*/
private void handleRoot(RDBMSQuery<?> q) {
// check if count has been performed, if not do so and set it to the response
......@@ -171,12 +183,10 @@ public abstract class BaseHibernateDao implements PersistenceDao {
}
private int getLimit(RDBMSQuery<?> q) {
return q.getLimit() > 0
? Math.min(q.getLimit(),
ConfigProvider.getConfig().getOptionalValue("eclipse.db.default.limit", Integer.class)
.orElseGet(() -> 1000))
: ConfigProvider.getConfig().getOptionalValue("eclipse.db.default.limit.max", Integer.class)
.orElseGet(() -> 1000);
return q.getLimit() > 0 ? Math
.min(q.getLimit(),
ConfigProvider.getConfig().getOptionalValue("eclipse.db.default.limit", Integer.class).orElseGet(() -> 1000))
: ConfigProvider.getConfig().getOptionalValue("eclipse.db.default.limit.max", Integer.class).orElseGet(() -> 1000);
}
private int getOffset(RDBMSQuery<?> q) {
......@@ -193,6 +203,19 @@ public abstract class BaseHibernateDao implements PersistenceDao {
return (limit * q.getPage()) - limit;
}
/**
* Checked casting of results to properly typed list.
*
* @param <T> the type to cast the list to
* @param l the list that needs to be cast to a type
* @param clazz the literal type to cast to
* @return the original list cast to the passed type
* @throws ClassCastException if the internal types do not match the passed type.
*/
private <T> List<T> castResultsToType(List<?> l, Class<T> clazz) {
return l.stream().map(clazz::cast).collect(Collectors.toList());
}
@Override
public HealthCheckResponse call() {
HealthCheckResponseBuilder b = HealthCheckResponse.named("DB readiness");
......@@ -203,8 +226,7 @@ public abstract class BaseHibernateDao implements PersistenceDao {
}
protected boolean isMaintenanceMode() {
return ConfigProvider.getConfig().getOptionalValue("eclipse.db.maintenance", Boolean.class)
.orElseGet(() -> false);
return ConfigProvider.getConfig().getOptionalValue("eclipse.db.maintenance", Boolean.class).orElseGet(() -> false);
}
/**
......@@ -218,9 +240,9 @@ public abstract class BaseHibernateDao implements PersistenceDao {
}
/**
* Allow for multiple connections to be associated with a datasource for separate read/write strategies. The
* secondary connection would be a read-only interface to reduce traffic on primary write enable database
* replicants. Default behaviour returns the the primary entity manager but this can be overriden.
* Allow for multiple connections to be associated with a datasource for separate read/write strategies. The secondary
* connection would be a read-only interface to reduce traffic on primary write enable database replicants. Default
* behaviour returns the the primary entity manager but this can be overriden.
*
* @return the secondary entity manager, defaults to the primary entity manager.
*/
......
......@@ -22,7 +22,7 @@ import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement.GroupBy
import org.eclipsefoundation.persistence.model.ParameterizedSQLStatement.IJoin;
public class HQLGenerator implements SQLGenerator {
private static final Pattern ORDINAL_PARAMETER_PATTERN = Pattern.compile("\\?(?!\\d)");
public static final Pattern ORDINAL_PARAMETER_PATTERN = Pattern.compile("\\?(?!\\d)");
@Override
public String getSelectSQL(ParameterizedSQLStatement src) {
......
/*********************************************************************
* Copyright (c) 2022 Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipsefoundation.persistence.model;
/**
* Special use case SQL statement that allows for direct execution of SQL with minimal changes/modifications. Ordinals
* are still substituted, but SQL is directly set by callStatement variable.
*/
public class ParameterizedCallStatement extends ParameterizedSQLStatement {
private String callStatement;
ParameterizedCallStatement(DtoTable base, SQLGenerator gen) {
super(base, gen);
}
@Override
public String getSelectSql() {
throw new IllegalStateException("Cannot use select when referencing stored procedures");
}
@Override
public String getCountSql() {
throw new IllegalStateException("Cannot use count when referencing stored procedures");
}
/**
* Set thet call statement to be executed.
*
* @param callStatement
*/
public void setCallStatement(String callStatement) {
this.callStatement = callStatement;
}
public String getCallStatement() {
return this.callStatement;
}
}
......@@ -23,4 +23,7 @@ public class ParameterizedSQLStatementBuilder {
return new ParameterizedSQLStatement(base, generator);
}
public ParameterizedCallStatement buildCallStatement(DtoTable base) {
return new ParameterizedCallStatement(base, generator);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment