All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ehrbase.aql.sql.QueryProcessor Maven / Gradle / Ivy

There is a newer version: 2.12.0
Show newest version
/*
 * Copyright (c) 2019 vitasystems GmbH and Hannover Medical School.
 *
 * This file is part of project EHRbase
 *
 * 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
 *
 *     https://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 org.ehrbase.aql.sql;

import static org.ehrbase.jooq.pg.Tables.ENTRY;

import java.util.*;
import org.apache.commons.lang3.tuple.Pair;
import org.ehrbase.aql.compiler.Contains;
import org.ehrbase.aql.compiler.Statements;
import org.ehrbase.aql.compiler.TopAttributes;
import org.ehrbase.aql.definition.I_VariableDefinition;
import org.ehrbase.aql.definition.LateralJoinDefinition;
import org.ehrbase.aql.definition.Variables;
import org.ehrbase.aql.sql.binding.*;
import org.ehrbase.aql.sql.postprocessing.RawJsonTransform;
import org.ehrbase.aql.sql.queryimpl.*;
import org.ehrbase.aql.sql.queryimpl.attribute.JoinSetup;
import org.ehrbase.dao.access.interfaces.I_DomainAccess;
import org.ehrbase.service.IntrospectService;
import org.jooq.*;
import org.jooq.Record;
import org.jooq.conf.Settings;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Perform an assembled SQL query depending on its strategy
 * 

* The strategy depends on whether the query contains elements which path depends on the context * (e.g. a composition). *

    *
  • If a query contains path expression that need to be resolved, the query process consists in * evaluating the path for each composition (iteration) *
  • If the query contains only static fields (columns), a single query execution is done. *
*

*

* Created by christian on 4/28/2016. */ @SuppressWarnings({"java:S3776", "java:S3740"}) public class QueryProcessor extends TemplateMetaData { public static final String NIL_TEMPLATE = "*"; private final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * */ public static class AqlSelectQuery { private final SelectQuery selectQuery; private final Collection> querySteps; private boolean outputWithJson; AqlSelectQuery( SelectQuery selectQuery, Collection> querySteps, boolean outputWithJson) { this.selectQuery = selectQuery; this.querySteps = querySteps; this.outputWithJson = outputWithJson; } public SelectQuery getSelectQuery() { return selectQuery; } public boolean isOutputWithJson() { return outputWithJson; } Collection> getQuerySteps() { return querySteps; } } private final I_DomainAccess domainAccess; private final Contains contains; private Statements statements; private final String serverNodeId; private JoinSetup joinSetup = new JoinSetup(); public QueryProcessor( I_DomainAccess domainAccess, IntrospectService introspectCache, Contains contains, Statements statements, String serverNodeId) { super(introspectCache); this.domainAccess = domainAccess; this.contains = contains; this.statements = statements; this.serverNodeId = serverNodeId; } public AqlResult execute() { AqlSelectQuery aqlSelectQuery = buildAqlSelectQuery(); Result result = fetchResultSet(aqlSelectQuery.getSelectQuery(), null); // if any jsonb data field transform them into raw json RawJsonTransform.toRawJson(result); DurationFormatter.toISO8601(result); List> explainList = buildExplain(aqlSelectQuery.getSelectQuery()); return new AqlResult(result, explainList); } public AqlSelectQuery buildAqlSelectQuery() { Map> cacheQuery = new LinkedHashMap<>(); boolean containsJson = false; statements = new OrderByField(statements).merge(); if (contains.getTemplates().isEmpty()) { if (contains.hasContains() && contains.requiresTemplateWhereClause()) { try { cacheQuery.put(NIL_TEMPLATE, buildNullSelect()); } catch (UnknownVariableException e) { // do nothing } } else try { cacheQuery.put(NIL_TEMPLATE, buildQuerySteps(NIL_TEMPLATE)); } catch (UnknownVariableException e) { // do nothing } } else { for (String templateId : contains.getTemplates()) { try { cacheQuery.put(templateId, buildQuerySteps(templateId)); } catch (UnknownVariableException e) { // ignore if (logger.isDebugEnabled()) { logger.debug("Template {}: {}", templateId, e.getMessage()); } } } } // assemble the query from the cache SelectQuery unionSetQuery = domainAccess.getContext().selectQuery(); boolean first = true; for (List queryStepList : cacheQuery.values()) { for (QuerySteps queryStep : queryStepList) { SelectQuery select = queryStep.getSelectQuery(); if (!queryStep.getTemplateId().equalsIgnoreCase(NIL_TEMPLATE)) joinSetup.setUseEntry(true); JoinBinder joinBinder = new JoinBinder(domainAccess, joinSetup); select.addFrom(joinBinder.initialFrom()); select = joinBinder.addJoinClause(select); select = setLateralJoins(queryStep.getLateralJoins(), select); // this deals with 'contains c' which adds an implicit where on template id if (!queryStep.getTemplateId().equals(NIL_TEMPLATE)) { select.addConditions(ENTRY.TEMPLATE_ID.eq(queryStep.getTemplateId())); } Condition whereCondition = queryStep.getWhereCondition(); if (whereCondition != null) select.addConditions(Operator.AND, whereCondition); if (first) { unionSetQuery = select; first = false; } else { unionSetQuery.unionAll(select); } } } // Add function or Distinct SuperQuery superQuery = new SuperQuery(domainAccess, statements.getVariables(), unionSetQuery, containsJson); if (new Variables(statements.getVariables()).hasDefinedDistinct() || new Variables(statements.getVariables()).hasDefinedFunction()) { unionSetQuery = superQuery.select(); if (statements.getOrderAttributes() != null && !statements.getOrderAttributes().isEmpty()) { unionSetQuery = superQuery.setOrderBy(statements.getOrderAttributes(), unionSetQuery); } containsJson = superQuery.isOutputWithJson(); } else if (statements.getOrderAttributes() != null && !statements.getOrderAttributes().isEmpty()) { unionSetQuery = superQuery.selectOrderBy(statements.getOrderAttributes()); containsJson = superQuery.isOutputWithJson(); } // Add Top , Limit or Offset; Top and Limit can not be both present. LimitBinding limitBinding = new LimitBinding( Optional.ofNullable(statements.getTopAttributes()) .map(TopAttributes::getWindow) .orElse(statements.getLimitAttribute()), statements.getOffsetAttribute(), unionSetQuery); unionSetQuery = limitBinding.bind(); return new AqlSelectQuery(unionSetQuery, cacheQuery.values(), containsJson); } private List buildQuerySteps(String templateId) throws UnknownVariableException { List queryStepsList = new ArrayList<>(); // process WHERE clause first WhereMultiFields whereMultiFields = new WhereMultiFields( domainAccess, introspectCache, contains, statements.getWhereClause(), serverNodeId); MultiFieldsMap multiWhereFieldsMap = new MultiFieldsMap(whereMultiFields.bind(templateId)); joinSetup = joinSetup.merge(whereMultiFields.getJoinSetup()); // process SELECT and check to reconciliate where and select columns SelectBinder selectBinder = new SelectBinder(domainAccess, introspectCache, contains, statements, serverNodeId); MultiFieldsMultiMap multiSelectFieldsMap = new MultiFieldsMultiMap(selectBinder.bind(templateId)); joinSetup = joinSetup.merge(selectBinder.getCompositionAttributeQuery().getJoinSetup()); int selectCursor = 0; int whereCursor = 0; int selectCursorMax = multiSelectFieldsMap.upperPathBoundary(); int whereCursorMax = multiWhereFieldsMap.upperPathBoundary(); // build the actual sets of fields depending on the generated multi fields // ... SelectQuery select = domainAccess.getContext().selectQuery(); while (whereCursorMax == 0 || whereCursor < whereCursorMax) { // iterate on variable while (selectCursor < selectCursorMax) { // iterate on paths for the variable for (Iterator it = multiSelectFieldsMap.multiFieldsIterator(); it.hasNext(); ) { MultiFields multiSelectFields = it.next(); select.addSelect(multiSelectFields .getQualifiedFieldOrLast(selectCursor) .getSQLField()); } Condition condition = selectBinder.getWhereConditions( templateId, whereCursor, multiWhereFieldsMap, selectCursor, multiSelectFieldsMap); if (condition != null && condition.equals(DSL.falseCondition())) break; // do not add since it is always false List joins = new ArrayList<>(); joins.addAll(lateralJoinsSelectClause(NIL_TEMPLATE, 0)); // composition attributes if (!templateId.equals(NIL_TEMPLATE)) { joins.addAll(lateralJoinsSelectClause(templateId, selectCursor)); // select clause fields joins.addAll(lateralJoinsWhereClause(templateId, whereCursor)); // where clause fields } // check whether the *same* query step is already in the list QuerySteps querySteps = new QuerySteps(select, condition, joins, templateId); if (QuerySteps.isIncludedInList(querySteps, queryStepsList)) { // re-initialize select selectCursor++; select = domainAccess.getContext().selectQuery(); continue; } queryStepsList.add(querySteps); selectCursor++; // re-initialize select select = domainAccess.getContext().selectQuery(); } if (whereCursorMax == 0) // no where clause break; whereCursor++; selectCursor = 0; // re-initialize select select = domainAccess.getContext().selectQuery(); } return queryStepsList; } private SelectQuery setLateralJoins(List lateralJoins, SelectQuery selectQuery) { if (lateralJoins == null) { return selectQuery; } HashSet> usedLaterals = new HashSet<>(); for (LateralJoinDefinition lateralJoinDefinition : lateralJoins) { var key = Pair.of(lateralJoinDefinition.getTable().getName(), lateralJoinDefinition.getLateralVariable()); if (usedLaterals.contains(key)) { continue; } if (lateralJoinDefinition.getCondition() == null) { selectQuery.addJoin(lateralJoinDefinition.getTable(), lateralJoinDefinition.getJoinType()); } else { selectQuery.addJoin( lateralJoinDefinition.getTable(), lateralJoinDefinition.getJoinType(), lateralJoinDefinition.getCondition()); } usedLaterals.add(key); } return selectQuery; } private List lateralJoinsSelectClause(String templateId, int cursor) { List lateralJoinsList = new ArrayList<>(); // traverse the lateral joins derived from SELECT clause for (VariableDefinitions it = statements.getVariables(); it.hasNext(); ) { Object item = it.next(); if (item instanceof I_VariableDefinition && ((I_VariableDefinition) item).isLateralJoin(templateId)) { Set listOfLaterals = ((I_VariableDefinition) item).getLateralJoinDefinitions(templateId); int index = cursor < listOfLaterals.size() ? cursor : listOfLaterals.size() - 1; LateralJoinDefinition encapsulatedLateralJoinDefinition = ((I_VariableDefinition) item).getLateralJoinDefinition(templateId, index); LateralJoinDefinition lateralJoinDefinition = new LateralJoinDefinition( encapsulatedLateralJoinDefinition.getSqlExpression(), DSL.lateral(encapsulatedLateralJoinDefinition.getTable()), encapsulatedLateralJoinDefinition.getLateralVariable(), encapsulatedLateralJoinDefinition.getJoinType(), encapsulatedLateralJoinDefinition.getCondition(), encapsulatedLateralJoinDefinition.getClause()); lateralJoinsList.add(lateralJoinDefinition); } } return lateralJoinsList; } private List lateralJoinsWhereClause(String templateId, int cursor) { List lateralJoinsList = new ArrayList<>(); for (Object item : statements.getWhereClause()) { if (item instanceof I_VariableDefinition && ((I_VariableDefinition) item).isLateralJoin(templateId)) { LateralJoinDefinition lateralJoinDefinition = ((I_VariableDefinition) item).getLateralJoinDefinition(templateId, cursor); if (((I_VariableDefinition) item).getLateralJoinDefinitions(templateId) == null) { throw new IllegalStateException("unresolved lateral join for template: " + templateId + ", path:" + ((I_VariableDefinition) item).getPath()); } else if (cursor > ((I_VariableDefinition) item) .getLateralJoinDefinitions(templateId) .size() - 1) { continue; } // check if lateral join is borrowed from SELECT clause, if so, don't add else if (lateralJoinDefinition.getClause().equals(IQueryImpl.Clause.SELECT)) continue; lateralJoinsList.add(new LateralJoinDefinition( lateralJoinDefinition.getSqlExpression(), DSL.lateral(lateralJoinDefinition.getTable()), ((I_VariableDefinition) item).getSubstituteFieldVariable(), JoinType.JOIN, null, IQueryImpl.Clause.WHERE)); } } return lateralJoinsList; } private Result fetchResultSet(Select select, Result result) { Result intermediary; try { intermediary = (Result) select.fetch(); } catch (Exception e) { String reason = "Could not perform SQL query:" + e.getCause() + ", AQL expression:" + statements.getParsedExpression() + ", Translated SQL:" + select.getSQL(); throw new IllegalArgumentException(reason); } if (result != null) { result.addAll(intermediary); } else if (intermediary != null) { result = intermediary; } return result; } private List> buildExplain(Select select) { List> explainList = new ArrayList<>(); DSLContext pretty = DSL.using(domainAccess.getContext().dialect(), new Settings().withRenderFormatted(true)); String sql = pretty.render(select); List details = new ArrayList<>(); details.add(sql); for (Param parameter : select.getParams().values()) { details.add(parameter.getValue().toString()); } explainList.add(details); return explainList; } private List buildNullSelect() throws UnknownVariableException { List queryStepsList = buildQuerySteps(NIL_TEMPLATE); // force a null condition for these steps for (QuerySteps querySteps : queryStepsList) { querySteps.setWhereCondition(DSL.condition("1 = 0")); } return queryStepsList; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy