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

com.redhat.lightblue.assoc.AnalyzeQuery Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.assoc;

import java.util.ArrayList;
import java.util.List;

import com.redhat.lightblue.metadata.CompositeMetadata;
import com.redhat.lightblue.metadata.ResolvedReferenceField;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.ArrayElement;

import com.redhat.lightblue.query.*;

import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.MutablePath;
import com.redhat.lightblue.util.Error;

/**
 * Analyzes a query to collect field information. For each field in a query,
 * these are collected:
 * 
    *
  • fieldNameInClause: Name of the field as it appears in the smallest clause * containing it.
  • *
  • fullFieldName: Full name of the field as known to the query. For fields * in an array elemMatch query, this is the name of the field containing the * array name and field name under the array.
  • *
  • fieldMd: Metadata node for the field
  • *
  • fieldEntity: The metadata for the entity containing the field
  • *
  • entityRelativeFieldName: The relative field name of the field in the * entity containing it
  • *
  • clause: The query clause
  • *
*/ public class AnalyzeQuery extends QueryIterator { /** * The root composite metadata */ private final CompositeMetadata root; /** * The reference field containing the query. Null if the query is a request * query */ private final ResolvedReferenceField ref; private List fieldInfo; private int nested = 0; /** * Ctor * * @param root The root entity * @param referenceField The resolved reference for the reference field * containing the query. null if the query is a request query */ public AnalyzeQuery(CompositeMetadata root, ResolvedReferenceField referenceField) { this.root = root; this.ref = referenceField; } public List getFieldInfo() { return fieldInfo; } @Override public QueryExpression iterate(QueryExpression q, Path context) { if (nested == 0) { fieldInfo = new ArrayList<>(); } nested++; try { return super.iterate(q, context); } finally { nested--; } } /** * Resolve the given field, find its metadata. Throw exception if it cannot * be resolved. */ private FieldTreeNode resolve(Path field) { FieldTreeNode fieldNode = ref == null ? root.resolve(field) : ref.getElement().resolve(field); if (fieldNode == null) { throw Error.get(AssocConstants.ERR_CANNOT_FIND_FIELD, field.toString()); } return fieldNode; } private CompositeMetadata getFieldMd(FieldTreeNode fieldNode) { // rr: The first resolved reference ancestor of the field ResolvedReferenceField rr = root.getResolvedReferenceOfField(fieldNode instanceof ResolvedReferenceField ? fieldNode.getParent() : fieldNode); // fieldMd: The composite metadata containing the field return rr == null ? root : rr.getReferencedMetadata(); } private Path computeEntityRelativeFieldName(Path normalizedFieldName, FieldTreeNode fieldNode) { // Now compute the relative field name within its entity // start from fieldNode, go backwards until the entity boundary // copy the indexes from the normalizedFieldName FieldTreeNode trc = fieldNode; int n = normalizedFieldName.numSegments() - 1; ArrayList list = new ArrayList<>(n); while (!((trc instanceof ArrayElement && ((ArrayElement) trc).getParent() instanceof ResolvedReferenceField) || trc.getParent() == null)) { String name = trc.getName(); if (Path.ANY.equals(name)) { if (n >= 0) { String head = normalizedFieldName.head(n); if (!head.equals("$parent")) { list.add(head); } else { list.add(Path.ANY); } } else { list.add(Path.ANY); } } else { list.add(name); } n--; trc = trc.getParent(); } MutablePath p = new MutablePath(); n = list.size(); for (int i = n - 1; i >= 0; i--) { p.push(list.get(i)); } return p.immutableCopy(); } /** * Returns true if 'ancestor' is an ancestor of 'descendant'. Does not cross * entity boundaries. */ private boolean isAnAncestor(FieldTreeNode ancestor, FieldTreeNode descendant) { FieldTreeNode trc = descendant; do { if (trc == ancestor) { return true; } else { trc = trc.getParent(); } } while (trc.getParent() != null && !(trc instanceof ArrayElement && ((ArrayElement) trc).getParent() instanceof ResolvedReferenceField)); return false; } private static class NearestCommonNode { final FieldTreeNode node; // The nearest node, or null of root final int numSegments; // Number of segment we have to back in context public NearestCommonNode(FieldTreeNode node, int n) { this.node = node; this.numSegments = n; } } /** * If contextNode and fieldNode are both in the same entity, returns the * nearest common node in the context's ancestors, or null if there is none. * Also returns the number of nodes passed to get there. */ private NearestCommonNode findNearestCommonNode(FieldTreeNode contextNode, FieldTreeNode fieldNode) { int n = 0; FieldTreeNode trc = contextNode; do { if (isAnAncestor(trc, fieldNode)) { return new NearestCommonNode(trc, n); } else { trc = trc.getParent(); n++; } } while (trc.getParent() != null && !(trc instanceof ArrayElement && ((ArrayElement) trc).getParent() instanceof ResolvedReferenceField)); return new NearestCommonNode(null, n); } /** * This is the function that builds the QueryFieldInfo. Given the clause * field name (the field name as it appears in the clause), the context (any * array references if this is an elem-match query), and the closest query * clause, this function determines the field metadata, the entity the field * is in, and the entity relative field name of the field, and returns this * information in QueryFieldInfo */ private QueryFieldInfo resolveField(Path clauseFieldName, Path context, QueryExpression clause, boolean leaf) { // fullFieldName: The name of the field, including any enclosing elemMatch queries Path fullFieldName = context.isEmpty() ? clauseFieldName : new Path(context, clauseFieldName); // The field node in metadata. Resolved relative to the // reference field if the query is for a reference, or // resolved relative to the root if the query is a request // query FieldTreeNode fieldNode = resolve(fullFieldName); CompositeMetadata fieldMd = getFieldMd(fieldNode); Path entityRelativeContext = Path.EMPTY; FieldTreeNode contextNode = null; // Is context in the same entity as the field? If so, then a // suffix of the context should be removed from // entityRelativeFieldNameWithContext Otherwise, there is no // need to remove the context if (!context.isEmpty()) { contextNode = resolve(context); CompositeMetadata contextMd = getFieldMd(contextNode); if (contextMd == fieldMd) { entityRelativeContext = computeEntityRelativeFieldName(context.normalize(), contextNode); } } // normalizedFieldName: the field name where $parent can only appear at the beginning // No $this can appear Path normalizedFieldName = fullFieldName.normalize(); Path entityRelativeFieldNameWithContext = computeEntityRelativeFieldName(normalizedFieldName, fieldNode); // If context is nonempty, and it is in the same entity as the // field, we have to compute entity relative field name // without context Path entityRelativeFieldName; if (!entityRelativeContext.isEmpty()) { NearestCommonNode ncn = findNearestCommonNode(contextNode, fieldNode); // A prefix of entityRelativeFieldNameWithContext must resolve to the nearest common node // That prefix needs to be removed // then, numSegments $parents should be prepended int n = entityRelativeFieldNameWithContext.numSegments(); Path inContextSuffix = entityRelativeFieldNameWithContext; for (int i = 0; i < n; i++) { if (i == 0) { // No prefix, entity root. if (ncn.node == null) { // We found the common prefix-root inContextSuffix = entityRelativeFieldNameWithContext; break; } } else { FieldTreeNode node = fieldMd.resolve(entityRelativeFieldNameWithContext.prefix(i)); if (node == ncn.node) { // We found the common prefix // cut it inContextSuffix = entityRelativeFieldNameWithContext.suffix(-i); break; } } } if (ncn.numSegments > 0) { MutablePath p = new MutablePath(); for (int i = 0; i < ncn.numSegments; i++) { p.push("$parent"); } entityRelativeFieldName = new Path(p, inContextSuffix); } else { entityRelativeFieldName = inContextSuffix; } } else { entityRelativeFieldName = entityRelativeFieldNameWithContext; } return new QueryFieldInfo(clauseFieldName, fullFieldName, fieldNode, fieldMd, entityRelativeFieldName, entityRelativeFieldNameWithContext, clause, leaf); } @Override protected QueryExpression itrValueComparisonExpression(ValueComparisonExpression q, Path context) { fieldInfo.add(resolveField(q.getField(), context, q, true)); return q; } @Override protected QueryExpression itrFieldComparisonExpression(FieldComparisonExpression q, Path context) { QueryFieldInfo lField = resolveField(q.getField(), context, q, true); QueryFieldInfo rField = resolveField(q.getRfield(), context, q, true); fieldInfo.add(lField); fieldInfo.add(rField); return q; } @Override protected QueryExpression itrRegexMatchExpression(RegexMatchExpression q, Path context) { fieldInfo.add(resolveField(q.getField(), context, q, true)); return q; } @Override protected QueryExpression itrNaryValueRelationalExpression(NaryValueRelationalExpression q, Path context) { fieldInfo.add(resolveField(q.getField(), context, q, true)); return q; } @Override protected QueryExpression itrNaryFieldRelationalExpression(NaryFieldRelationalExpression q, Path context) { QueryFieldInfo lField = resolveField(q.getField(), context, q, true); QueryFieldInfo rField = resolveField(q.getRfield(), context, q, true); fieldInfo.add(lField); fieldInfo.add(rField); return q; } @Override protected QueryExpression itrArrayContainsExpression(ArrayContainsExpression q, Path context) { fieldInfo.add(resolveField(q.getArray(), context, q, true)); return q; } @Override protected QueryExpression itrUnaryLogicalExpression(UnaryLogicalExpression q, Path context) { return super.itrUnaryLogicalExpression(q, context); } @Override protected QueryExpression itrNaryLogicalExpression(NaryLogicalExpression q, Path context) { return super.itrNaryLogicalExpression(q, context); } @Override protected QueryExpression itrArrayMatchExpression(ArrayMatchExpression q, Path context) { fieldInfo.add(resolveField(q.getArray(), context, q, false)); return super.itrArrayMatchExpression(q, context); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy