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

io.inversion.query.Where Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015-2019 Rocket Partners, LLC
 * https://github.com/inversion-api
 *
 * 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 io.inversion.query;

import io.inversion.*;
import io.inversion.rql.Term;
import io.inversion.utils.Utils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class Where extends Builder {

    final Set         existsFunctions    = Utils.asSet("eq", "nn", "gt", "ge", "lt", "le", "like", "sw", "ew", "in", "w");
    final Set         notExistsFunctions = Utils.asSet("ne", "n", "out", "wo", "emp");
    final Map notExistsMap       = Utils.asMap("ne", "eq", "n", "nn", "out", "in", "wo", "w", "emp", "nemp");

    public Where(P query) {
        super(query);
        withFunctions("_key", "_exists", "_notexists", "and", "or", "not", "eq", "ne", "n", "nn", "like", "sw", "ew", "lt", "le", "gt", "ge", "in", "out", "if", "w", "wo", "emp", "nemp");
    }

    protected boolean addTerm(String token, Term term) {

        String function = term.getToken();
        if (!term.isLeaf() && functions.contains(function)) {

            term = transform(term);

            List unknownCols = term.stream().filter(this::isInvalidColumn).collect(Collectors.toList());
            if (unknownCols.size() > 0) {
                //Chain.debug("Ignoring query terms with unknown columns: " + unknownCols);
                //System.err.println("Ignoring query terms with unknown columns: " + unknownCols);
                return true;
            }

            if (term.getParent() == null && term.hasToken("and"))//"unwrap" root and terms as redundant
            {
                for (Term t : term.getTerms()) {
                    t.withParent(null);
                    super.addTerm(t.getToken(), t);
                }
            } else {
                super.addTerm(term.getToken(), term);
            }
            return true;
        } else {
            return super.addTerm(token, term);
        }

    }

    /**
     * Checks to see if a column referenced by a function call is valid.
     * 

* This function only validates the first leaf child of the supplied token in (FUNCTIN(COLUMN,...) format) and does not recurse. *

* To be considered valid the column name must match a Collection Properties' column name * OR the Collection must be null and the column name must only contain alphanumeric characters and underscores and can not start with an underscore. *

* For example: *

    *
  • in(COLUMN_NAME,1,2,3,4,5) - returns false if the collection has a property with the column name or if the collection is null *
  • in(COLUMN-NAME,1,2,3,4,5) - returns false only if the collection has a property with the column name *
  • in(_COLUMN_NAME,1,2,3,4,5) - returns false only if the collection has a property with the column name *
  • 'like' - any single token will return false as it is not in the FUNCTION(COLUMN,...) format *
  • or(eq(COLUMN_NAME,5), eq(COLUMN_NAME, 10)) - will return false as it si not on FUNCTION(COLUMN,...) format *
*

* IMPLEMENTATION NOTE: You may want to override this if you are using a document store (such as Azure Cosmos) that does not require * each column to be defined. *

* IMPLEMENTATION NOTE: Terms that are passed into this function presumably have already been filtered by the Db object for known restricted columns. * * @param t the term to check for valid column references * @return false if the first child is a leaf with an invalid column name */ protected boolean isInvalidColumn(Term t) { if (t.isLeaf() || !t.getTerm(0).isLeaf()) return false; Collection collection = getParent().getCollection(); String function = t.getToken(); String column = t.getToken(0); //-- support for special case functions that don't take a column arg if (function.equalsIgnoreCase("_key")) return false; //-- end special cases //-- columns with "." are join filters if (column.indexOf(".") > 0 && collection != null) { String relName = Utils.substringBefore(column, "."); Relationship relationship = collection.getRelationship(relName); if (relationship != null) { collection = relationship.getRelated(); column = Utils.substringAfter(column, "."); } else { return false; } } return isInvalidColumn(collection, column); } protected boolean isInvalidColumn(Collection collection, String column) { if (collection == null) return column.startsWith("_") || !column.matches("^[a-zA-Z0-9_]+$"); else return collection.getPropertyByColumnName(column) == null; } protected Term transform(Term parent) { Term transformed = parent; for (Term child : parent.getTerms()) { if (!child.isLeaf()) { if (!functions.contains(child.getToken())) throw ApiException.new400BadRequest("Invalid where function token '{}' : {}", child.getToken(), parent); transform(child); } } if (!parent.isLeaf()) { //check the first child expecting that to be the column name //if it is in the form "relationship.column then wrap this //in an "exists" or "notExists" function if (parent.getTerm(0).isLeaf() && parent.getToken(0).indexOf(".") > 0 && !parent.hasToken("_key")) { String rel = parent.getToken(0); rel = rel.substring(0, rel.indexOf(".")); if (getParent().getCollection().getRelationship(rel) != null) { Term relCol = parent.getTerm(0); relCol.withToken("~~relTbl_" + relCol.getToken()); String token = parent.getToken().toLowerCase(); if (existsFunctions.contains(token)) { transformed = Term.term(parent.getParent(), "_exists", parent); } else if (notExistsFunctions.contains(token)) { parent.withToken(notExistsMap.get(token)); transformed = Term.term(parent.getParent(), "_notexists", parent); } return transformed; } } } if (parent.hasToken("_key")) { String indexName = parent.getToken(0); Index index = getParent().getCollection().getIndex(indexName); if (index == null) throw ApiException.new400BadRequest("You can't use the _key() function unless your table has a unique index"); if (index.size() == 1) { Term t = Term.term(null, "in", index.getProperty(0).getColumnName()); List children = parent.getTerms(); for (int i = 1; i < children.size(); i++) { Term child = children.get(i); t.withTerm(child); } if (t.getNumTerms() == 2) t.withToken("eq"); transformed = t; } else { //collection/valCol1~valCol2,valCol1~valCol2,valCol1~valCol2 //keys(valCol1~valCol2,valCol1~valCol2,valCol1~valCol2) //or( and(eq(col1,val),eq(col2,val)), and(eq(col1,val),eq(col2,val)), and(eq(col1val), eq(col2,val)) Term or = Term.term(null, "or"); List children = parent.getTerms(); transformed = or; for (int i = 1; i < children.size(); i++) { Term child = children.get(i); if (!child.isLeaf()) throw ApiException.new400BadRequest("Resource key value is not a leaf node: {}", child); Map keyParts = getParent().getCollection().decodeKeyToColumnNames(index, child.getToken()); Term and = Term.term(or, "and"); for (String key : keyParts.keySet()) { and.withTerm(Term.term(and, "eq", key, keyParts.get(key).toString())); } } if (or.getNumTerms() == 1) { transformed = or.getTerm(0); transformed.withParent(null); } } } if (parent.getParent() != null && transformed != parent) parent.getParent().replaceTerm(parent, transformed); return transformed; } public List getFilters() { return getTerms(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy