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

com.redhat.lightblue.assoc.ResolvedFieldBinding 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.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.io.Serializable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.redhat.lightblue.query.FieldBinding;
import com.redhat.lightblue.query.ValueBinding;
import com.redhat.lightblue.query.ListBinding;
import com.redhat.lightblue.query.QueryInContext;
import com.redhat.lightblue.query.QueryExpression;
import com.redhat.lightblue.query.FieldComparisonExpression;
import com.redhat.lightblue.query.NaryFieldRelationalExpression;
import com.redhat.lightblue.query.RelativeRewriteIterator;
import com.redhat.lightblue.query.NaryLogicalExpression;
import com.redhat.lightblue.query.NaryLogicalOperator;
import com.redhat.lightblue.query.ArrayMatchExpression;
import com.redhat.lightblue.query.Value;
import com.redhat.lightblue.query.MapQueryFieldsIterator;
import com.redhat.lightblue.metadata.Type;
import com.redhat.lightblue.metadata.CompositeMetadata;
import com.redhat.lightblue.metadata.FieldTreeNode;
import com.redhat.lightblue.metadata.ArrayField;
import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.Error;
import com.redhat.lightblue.util.KeyValueCursor;

/**
 * This class represents a field binding interpreted based on the
 * metadata. It contains the original field binding information, the
 * field type information, and the field name relative to an entity in
 * a query plan that denotes the field name containing the value that
 * is bound in the field binding.
 *
 * For example:
 * 
 *    query={'field':'field1','op':'=','rfield':'$parent.referencedField'}
 * 
* * Assume referencedField is bound to a value in a * different entity. Then, binding contains the binding * information for referencedField, type * contains the type of referencedField, and valueField * contains referencedField, the field relative to the * entity it is contained in. If the binding is a list binding, then * type keeps the type of the list element. */ public class ResolvedFieldBinding implements Serializable { private static final long serialVersionUID=1l; private static final Logger LOGGER=LoggerFactory.getLogger(ResolvedFieldBinding.class); private final FieldBinding binding; /** * Value type of the bound field */ private final Type type; /** * The entity relative field name of the field */ private final Path valueField; /** * The entity of the bound field */ private final CompositeMetadata entity; /** * This class is used to return binding results. It contains the * bindings, and the query rewritten relative to the node the * query is bound, */ public static final class BindResult { private final List bindings; private final QueryExpression runExpression; private BindResult(List bindings, QueryExpression runExpression) { this.bindings=bindings; this.runExpression=runExpression; } public List getBindings() { return bindings; } public QueryExpression getRelativeQuery() { return runExpression; } } /** * Construct a resolved field binding using the given binding and the root composite metadata */ public ResolvedFieldBinding(Path fieldName, FieldBinding b, CompositeMetadata emd) { this.binding=b; FieldTreeNode field=emd.resolve(fieldName); if(field==null) throw Error.get(AssocConstants.ERR_CANNOT_FIND_FIELD,b.getField().toString()); if(b instanceof ListBinding) { if(field instanceof ArrayField) { this.type=((ArrayField)field).getElement().getType(); } else throw Error.get(AssocConstants.ERR_ARRAY_EXPECTED,b.getField().toString()); } else { this.type=field.getType(); } this.entity=emd.getEntityOfField(field); this.valueField=emd.getEntityRelativeFieldName(field); } public static class RelativeRewriter extends MapQueryFieldsIterator { private final Conjunct conjunct; private final CompositeMetadata root; private final CompositeMetadata thisMd; // Once passed through an elemMatch expression // this can be set to a nonzero value to only include a // certain suffix of field names private int relativeSuffixLength=0; /** * @param c The query to rewrite * @param root The root metadata * @param thisMd Ths metadata of the node relative to which the query will be rewritten * @param requestQuery if the query is a request query, this * is true. If the query is associated to a query plan node, * this is false. */ public RelativeRewriter(Conjunct c,CompositeMetadata root,CompositeMetadata thisMd) { this.conjunct=c; this.root=root; this.thisMd=thisMd; } /** * Array match expressions require special treatment when converting a query to relative. * Relative query may require removing the array clause to open up the elemMatch clause. * This is the case if, for instance, the clause is { array: a.b.c, elemMatch:{ } }, and * a.b.c is a reference to the current entity. */ @Override protected QueryExpression itrArrayMatchExpression(ArrayMatchExpression q, Path context) { // When this is called, any field in the query that belongs to a different // entity should already be bound. So, (array + field) always points to the current // entity for which this query is being rewritten. How the array field defined // determines the outcome. If array field points to an array in this entity, we // rewrite the query by getting a suffix of the array. If array field points to // an array in a parent entity, or if the array field points to the // reference field, this is no longer an elemMatch query. We return the // nested query instead. LOGGER.debug("Rewriting {}",q); Path arrayName=context.isEmpty()?q.getArray():new Path(context,q.getArray()); // Find a field with the array name as the prefix ResolvedFieldInfo arrayDescendant=null; for(ResolvedFieldInfo rfi:conjunct.getFieldInfo()) { Path p=rfi.getFieldName(); if(arrayName.matchingPrefix(p)) { arrayDescendant=rfi; break; } } if(arrayDescendant==null) throw new RuntimeException("Unknown field:"+arrayName); LOGGER.debug("Array descendant:{}",arrayDescendant); // arrayDescendant is a field in the query with the array being the prefix. From here, we can figure out which entity array belongs to CompositeMetadata fmd=arrayDescendant.getFieldEntityCompositeMetadata(); while(fmd!=null&&fmd.getEntityPath().numSegments()>arrayName.numSegments()) fmd=fmd.getParent(); CompositeMetadata arrayMd=fmd; LOGGER.debug("Array metadata:{}",arrayMd == null ? null : arrayMd.getName()); Path absoluteArray; if(conjunct.isRequestQuery()) { absoluteArray=arrayName; } else { absoluteArray=(arrayMd!=null&&arrayMd.getParent()!=null)?new Path(arrayMd.getEntityPath(),new Path(Path.ANYPATH,arrayName)):arrayName; } LOGGER.debug("Absolute array:{}",absoluteArray); if(arrayMd==thisMd&&!absoluteArray.equals(thisMd.getEntityPath())) { // Convert array to relative, and return an elem match query Path arrayPath; if(arrayMd.getParent()==null) { arrayPath=absoluteArray; } else { arrayPath=absoluteArray.suffix(-(arrayMd.getEntityPath().numSegments()+1)); } QueryExpression newq = iterate(q.getElemMatch(), new Path(new Path(context, arrayPath), Path.ANYPATH)); return new ArrayMatchExpression(arrayPath,newq); } else { // This is no longer an array match expression. // We need to find out where the entity boundary is crossed // So if the fields are like this, where c is a reference: // a . b . c | d . e . f // and if the query is something like: // { array: a.b, elemMatch{ field: c.d.e } } // then, the relative query is // {field: d.e } // So: first find the length of the portion remaining in the other entity int delta=thisMd.getEntityPath().numSegments() - absoluteArray.numSegments(); // We need to cut a prefix of 'delta' length from all fields relativeSuffixLength+=delta; QueryExpression ret=iterate(q.getElemMatch(),context); relativeSuffixLength-=delta; return ret; } } @Override protected Path map(Path p) { // Get a relative suffix Path suffix=relativeSuffixLength>0?p.suffix(-relativeSuffixLength):p; Path x=conjunct.mapOriginalFieldName(suffix); // If path is unchanged, return null, so the clause is kept unmodified if(x==p) return null; else return x; } } /** * Binds a list of clauses for execution at a particular query plan node * * @param clauses The query conjuncts that will be bound * @param atNode The query plan node at which the query will be run * * @return Returns the binding result containing the bindings, and * the query expression rewritten to be run at the given query * plan node. Returns null if there are no bindings or query. */ public static BindResult bind(List clauses, QueryPlanNode atNode, CompositeMetadata root) { BindResult ret = null; if(clauses!=null&&!clauses.isEmpty()) { List bindings = new ArrayList<>(); List boundQueries=new ArrayList<>(); for(Conjunct conjunct:clauses) { LOGGER.debug("Resolving bindings for {}",conjunct); QueryExpression query=conjunct.getClause(); List bindable=query.getBindableClauses(); LOGGER.debug("Bindable clauses:{}",bindable); // default the bound query to input query QueryExpression boundQuery = query; if(!bindable.isEmpty()) { LOGGER.debug("Building bind request"); Set bindRequest=new HashSet<>(); for(QueryInContext qic:bindable) { if(qic.getQuery() instanceof FieldComparisonExpression) { FieldComparisonExpression fce=(FieldComparisonExpression)qic.getQuery(); Path lfield=new Path(qic.getContext(),fce.getField()); Path rfield=new Path(qic.getContext(),fce.getRfield()); ResolvedFieldInfo f=getFieldToBind(lfield,rfield,conjunct,atNode); if (f != null) { bindRequest.add(f.getFieldName()); } } else if(qic.getQuery() instanceof NaryFieldRelationalExpression) { NaryFieldRelationalExpression nfr=(NaryFieldRelationalExpression)qic.getQuery(); Path lfield=new Path(qic.getContext(),nfr.getField()); Path rfield=new Path(qic.getContext(),nfr.getRfield()); ResolvedFieldInfo f=getFieldToBind(lfield,rfield,conjunct,atNode); if (f != null) { bindRequest.add(f.getFieldName()); } } } LOGGER.debug("Bind fields:{}",bindRequest); List fb=new ArrayList<>(); // get the bound query boundQuery=query.bind(fb,bindRequest); // collect bindings for(FieldBinding b:fb) { ResolvedFieldInfo rfi=conjunct.getFieldInfoByOriginalFieldName(b.getField()); if (rfi != null) { bindings.add(new ResolvedFieldBinding(rfi.getEntityRelativeFieldName(), b, rfi.getFieldQueryPlanNode().getMetadata())); } } } RelativeRewriter rw=new RelativeRewriter(conjunct,root,atNode.getMetadata()); boundQueries.add(rw.iterate(boundQuery)); } QueryExpression query; if(boundQueries.size()==1) { query=boundQueries.get(0); } else { query=new NaryLogicalExpression(NaryLogicalOperator._and,boundQueries); } LOGGER.debug("Bound query:{}",query); ret=new BindResult(bindings,query); } return ret; } /** * Returns the field to bind. One of the fields belong to the * current query plan node (atNode), and the other one belongs to * an ancestor of the current node, so we bind the field that * belongs to an ancestor. */ private static ResolvedFieldInfo getFieldToBind(Path lfield,Path rfield,Conjunct clause,QueryPlanNode atNode) { ResolvedFieldInfo lInfo=clause.getFieldInfoByOriginalFieldName(lfield); ResolvedFieldInfo rInfo=clause.getFieldInfoByOriginalFieldName(rfield); // If lfieldNode points to the destination node, // rfieldNode points to an ancestor, or vice versa if(lInfo != null && lInfo.getFieldQueryPlanNode().getName().equals(atNode.getName())) { return rInfo; } else { return lInfo; } } /** * Attempts to refresh this binding starting at the given parent document, and ascending to its parents */ public boolean refresh(ChildDocReference ref) { ResultDoc doc=ref.getDocument(); if(doc.getMetadata()==entity) { refresh(doc,ref.getReferenceField()); return true; } else { for(Map.Entry entry: doc.getParentDocs().entrySet()) { if(refresh(entry.getValue())) return true; } return false; } } public List getParentDocReferences(ResultDoc doc) { List list=new ArrayList<>(); KeyValueCursor nodeCursor=doc.getDoc().getAllNodes(valueField); while(nodeCursor.hasNext()) { nodeCursor.next(); Path p=nodeCursor.getCurrentKey(); list.add(new ParentDocReference(doc,p,this)); } return list; } public void refresh(ParentDocReference ref) { ResultDoc doc=ref.getDocument(); if(doc.getMetadata()==entity) { refresh(doc.getDoc().get(ref.getField())); } else throw new RuntimeException("Unsupported binding"); } public void refresh(ResultDoc doc,Path referenceField) { // Reinterpret the value field using the indexes in refField Path field=valueField.mutableCopy().rewriteIndexes(referenceField).immutableCopy(); LOGGER.debug("Refreshing bindings using {}",field); refresh(doc.getDoc().get(field)); } public void refresh(JsonNode valueNode) { if(binding instanceof ValueBinding) { if(valueNode==null) ((ValueBinding)binding).getValue().setValue(null); else ((ValueBinding)binding).getValue().setValue(type.fromJson(valueNode)); } else if(binding instanceof ListBinding) { if(valueNode==null) ((ListBinding)binding).getList().setList(new ArrayList()); else { List l=new ArrayList<>(((ArrayNode) valueNode).size()); for(Iterator itr=((ArrayNode)valueNode).elements();itr.hasNext();) { l.add(new Value(type.fromJson(itr.next()))); } ((ListBinding)binding).getList().setList(l); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy