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

org.hpccsystems.commons.ecl.FileFilter Maven / Gradle / Ivy

/*******************************************************************************
 * HPCC SYSTEMS software Copyright (C) 2018 HPCC Systems®.
 *
 * 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 org.hpccsystems.commons.ecl;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hpccsystems.commons.filter.SQLExpression;
import org.hpccsystems.commons.filter.SQLFilter;
import org.hpccsystems.commons.filter.SQLFragment.FragmentType;
import org.hpccsystems.commons.filter.SQLOperator;
import org.hpccsystems.commons.filter.SQLOperator.OperatorType;
import org.json.JSONArray;

/**
 * A filter to select records from a file or key. The filter is conjunction of
 * field filters with each field having a list of one or more value ranges.
 *
 * filter : setfilter | wildfilter ;
 * setfilter : field equals set
 * wildfilter : field wildcard
 * set : setfragment (, setfragment)*
 * setfragment : ( upperinclusive | upperexclusive) setvalue ( lowerinclusive | lowerexclusive) //ignore the single paren notation for now
 * setvalue : INTEGER_NUM | QUOTEDUTF8STR
 * QUOTEDUTF8STR : squote utf8string squote;
 * upperinclusive : '[' ;
 * lowerinclusive : ']' ;
 * upperexclusive : '(' ;
 * lowerexclusive : ')' ;
 * wildcard : '*' ;
 * equals : '=' ;
 * range : ':' ;
 * squote : '\'' ;
 * field : ( 'A'..'Z' | 'a'..'z') (( 'A'..'Z' | 'a'..'z') | UNDERSCORE | ETC | INTEGER_NUM )* INTEGER_NUM)?;
 * INTEGER_NUM : ( '1'..'9')( '0'..'9')*;
 *
 */
public class FileFilter implements Serializable
{
    private static final Logger log              = LogManager.getLogger(FileFilter.class);

    public static final long    serialVersionUID = 2L;

    private List   fieldfilters     = new ArrayList();
    private List    andFileFilters   = new ArrayList();

    /**
     * A file filter expression to select records, using a string.
     * An expression can be comprised of multiple comma delimited clauses
     *
     * An empty filter, selects all records
     */
    public FileFilter()
    {
        // expressions initialized but yet to be populated
    }

    /**
     * Creates a FileFilter based on an SQL filter
     * for example
     * "(table.field2 <= 'a' AND table.field2 != 4) OR table.field2 != 5 AND field3 In ('Germany', 'France', 'UK')"
     *
     * @param sqlfilter
     *            the sqlfilter
     * @throws Exception
     *             the exception
     */
    public FileFilter(String sqlfilter) throws Exception
    {
        if (sqlfilter != null && !sqlfilter.isEmpty())
            ConvertToHPCCFileFilter(sqlfilter);
        else
            throw new Exception("Could not create FileFilter, empty sqlfilter encountered");
    }

    /**
     * Creates a FileFilter based on an SQL filter object.
     *
     * @param sqlfilter
     *            the sqlfilter
     * @throws Exception
     *             the exception
     */
    public FileFilter(SQLFilter sqlfilter) throws Exception
    {
        if (sqlfilter != null)
            ConvertToHPCCFileFilter(sqlfilter);
        else
            throw new Exception("Could not create FileFilter, empty sqlfilter encountered");
    }

    /**
     * Creates a FileFilter based on the provided fieldfilter.
     *
     * @param filter
     *            the filter
     */
    public FileFilter(FieldFilter filter)
    {
        fieldfilters.add(filter);
    }

    /**
     * Creates a FileFilter based on the provided filefilter's fieldfilter(s).
     *
     * @param filter
     *            the filter
     */
    public FileFilter(FileFilter filter)
    {
        try
        {
            orFilter(filter);
        }
        catch (Exception e)
        {}
    }

    /**
     * A null filter which selects all records.
     *
     * @return an empty filter
     */
    static public FileFilter nullFilter()
    {
        return new FileFilter();
    }

    /**
     * Is this filter empty?.
     *
     * @return true when empty
     */
    public boolean isEmpty()
    {
        return fieldfilters.isEmpty();
    }

    /**
     * The filefilter expression is ANDed to this filefilter expression.
     *
     * @param filefilter
     *            the filefilter
     * @return the file filter
     * @throws Exception
     *             the exception
     */
    public FileFilter andFilter(FileFilter filefilter) throws Exception
    {
        if (filefilter != null)
        {
            if (fieldfilters.isEmpty())
            {
                for (int fieldfilterindex = 0; fieldfilterindex < filefilter.getFieldFiltersCount(); fieldfilterindex++)
                {
                    orFilter(filefilter.getFieldFilter(fieldfilterindex));
                }
            }
            else
                andFileFilters.add(filefilter);
        }
        return this;
    }

    /**
     * The fieldfilter(s) expression is ANDed to this filefilter expression.
     *
     * @param fieldfilter
     *            the fieldfilter
     * @return the file filter
     * @throws Exception
     *             the exception
     */
    public FileFilter andFilter(FieldFilter fieldfilter) throws Exception
    {
        if (fieldfilter != null)
        {
            if (fieldfilters.isEmpty())
                fieldfilters.add(fieldfilter);
            else
                andFileFilters.add(new FileFilter(fieldfilter));
        }
        return this;
    }

    /**
     * The filefilter expression is ORed to this filefilter expression.
     *
     * @param filefilter
     *            the filefilter
     * @return the file filter
     * @throws Exception
     *             the exception
     */
    public FileFilter orFilter(FileFilter filefilter) throws Exception
    {
        if (filefilter != null)
        {
            for (int fieldfilterindex = 0; fieldfilterindex < filefilter.getFieldFiltersCount(); fieldfilterindex++)
            {
                orFilter(filefilter.getFieldFilter(fieldfilterindex));
            }
        }
        return this;
    }

    /**
     * The fieldfilter expression is ORed to this filefilter expression
     * (must target the same field).
     *
     * @param fieldfilter
     *            the fieldfilter
     * @return the file filter
     * @throws Exception
     *             the exception
     */
    public FileFilter orFilter(FieldFilter fieldfilter) throws Exception
    {
        if (fieldfilter != null)
        {
            if (andFileFilters.isEmpty())
            {
                if (fieldfilters.isEmpty() || fieldfilter.getFieldName().equals(fieldfilters.get(0).getFieldName()))
                    fieldfilters.add(fieldfilter);
                else
                    throw new Exception("FileFilter expressions must target the same field! Cannot append filter '" + fieldfilter.toString()
                            + "' to FileFilter targeting field: '" + fieldfilters.get(0).getFieldName() + "'");
            }
            else
                andFileFilters.get(andFileFilters.size() - 1).orFilter(fieldfilter);
        }
        return this;
    }

    /**
     * private used for toString formatting.
     *
     * @return the string
     */
    private String outputOrFilter()
    {
        StringBuilder sb = new StringBuilder((fieldfilters.size() * 40) + 30);
        for (int index = 0; index < fieldfilters.size(); index++)
        {
            if (index != 0)
                sb.append(',');
            else
                sb.append(fieldfilters.get(0).getFieldName()).append("=");

            sb.append(fieldfilters.get(index).toString(false));
        }

        return sb.toString();
    }

    /**
     * Reports the number of filter clauses in this filefilter.
     *
     * @return the field filters count
     */
    public int getFieldFiltersCount()
    {
        return fieldfilters.size();
    }

    /**
     * Fetches the ith filter clause in this filefilter.
     *
     * @param i
     *            the i
     * @return the field filter
     */
    public FieldFilter getFieldFilter(int i)
    {
        if (i < 0 || i >= fieldfilters.size())
            throw new IndexOutOfBoundsException("getFieldFilter(" + i + ") index out of bounds - fielfilters size: " + fieldfilters.size());

        return fieldfilters.get(i);
    }

    public List getAndFileFilters()
    {
        return andFileFilters;
    }

    /**
     * JSon string representation of an array of filefilters.
     *
     * @return the string
     */
    public String toJson()
    {
        JSONArray keyfilters = new JSONArray();

        keyfilters.put(outputOrFilter());

        for (int index = 0; index < andFileFilters.size(); index++)
        {
            FileFilter currentfilter = andFileFilters.get(index);
            String filef = currentfilter.outputOrFilter();
            keyfilters.put(filef);
        }

        return "\"keyFilter\": " + keyfilters.toString();
    }

    /**
     * Convert to HPCC file filter.
     *
     * @param sqlfilterstr
     *            the sqlfilterstr
     * @throws Exception
     *             the exception
     */
    private void ConvertToHPCCFileFilter(String sqlfilterstr) throws Exception
    {
        if (sqlfilterstr == null || sqlfilterstr.isEmpty())
            throw new Exception("Cannot convert empty SQL Filter");

        try
        {
            SQLFilter sqlfilter = new SQLFilter();
            sqlfilter.parseWhereClause(sqlfilterstr);

            ConvertToHPCCFileFilter(sqlfilter);
        }
        catch (SQLException e)
        {
            log.error(e.getMessage());
            throw e;
        }
    }

    /**
     * Convert to HPCC file filter.
     *
     * @param sqlfilter
     *            the sqlfilter
     * @throws Exception
     *             the exception
     */
    private void ConvertToHPCCFileFilter(SQLFilter sqlfilter) throws Exception
    {
        if (sqlfilter == null) throw new Exception("Cannot convert empty SQL Filter");

        Iterator expressions = sqlfilter.getExpressions();
        while (expressions.hasNext())
        {
            SQLExpression sqlExpression = (SQLExpression) expressions.next();

            SQLOperator prevunifier = sqlExpression.getPreviousExpUnifier();
            if (prevunifier == null || prevunifier.getValue().equals(SQLOperator.or))
                orFilter(ConvertToHPCCFileFilter(sqlExpression));
            else if (prevunifier.getValue().equals(SQLOperator.and))
                andFilter(ConvertToHPCCFileFilter(sqlExpression));
            else
                throw new Exception("Invalid filter expression unifier operator detected: '" + prevunifier.getValue() + "' in filter: '"
                        + sqlExpression.toString() + "'");
        }
    }

    /**
     * Checks if is valid filter.
     *
     * @param sqlfilter
     *            the sqlfilter
     * @return true, if is valid filter
     * @throws Exception
     *             the exception
     */
    private static boolean isValidFilter(SQLExpression sqlfilter) throws Exception
    {
        SQLOperator op = sqlfilter.getOperator();
        if (op.getType() == OperatorType.PRE_UNARY)
        {
            throw new Exception("Pre Unary operator not supported: " + op.getValue());
        }
        else if (op.getType() == OperatorType.POST_UNARY)
        {
            if (op.equals(SQLOperator.isNull))
                throw new Exception("Cannot convert filter based on 'isNull' operator!");
            else if (op.equals(SQLOperator.isNotNull))
                return true;
            else
                throw new Exception("Post Unary operator not supported: " + op.getValue());
        }
        else if (op.getType() == OperatorType.BINARY)
        {
            FragmentType prefixtype = sqlfilter.getPrefixType();
            FragmentType postfixtype = sqlfilter.getPostfixType();
            if (prefixtype == FragmentType.FIELD && postfixtype == FragmentType.FIELD)
                throw new Exception("Cannot convert filter comparing 2 fields -- '" + sqlfilter.toString() + "'");
            else if (prefixtype != FragmentType.FIELD && postfixtype != FragmentType.FIELD)
                throw new Exception("Cannot convert filter comparing 0 fields -- '" + sqlfilter.toString() + "'");
            return true;
        }

        return false;
    }

    /**
     * Convert to HPCC file filter.
     *
     * @param sqlfilter
     *            the sqlfilter
     * @return the file filter
     * @throws Exception
     *             the exception
     */
    public static FileFilter ConvertToHPCCFileFilter(SQLExpression sqlfilter) throws Exception
    {
        FileFilter hpccfilter = null;
        if (isValidFilter(sqlfilter)) // either pre or post type == field
        {
            boolean prefixcontainsfield = (sqlfilter.getPrefixType() == FragmentType.FIELD);
            SQLOperator op = sqlfilter.getOperator();
            if (op.getValue().equals(SQLOperator.eq))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeEq(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeEq(sqlfilter.getPrefixValue())));
            }
            else if (op.getValue().equals(SQLOperator.gt))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeGT(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeGT(sqlfilter.getPrefixValue())));
            }
            else if (op.getValue().equals(SQLOperator.gte))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeGE(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeGE(sqlfilter.getPrefixValue())));
            }
            else if (op.getValue().equals(SQLOperator.in))
            {
                String list = sqlfilter.getPostfixValue().replaceAll("^\\[|\\]$", ""); // List string representation is encapsulated in square
                                                                                       // brackets
                hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeIn(list.split("\\s*,\\s*"), false))); // each
                                                                                                                                                   // entry
                                                                                                                                                   // is
                                                                                                                                                   // comma+space
                                                                                                                                                   // delimited
            }
            else if (op.getValue().equals(SQLOperator.isNotNull))
            {
                log.info("Filter 'IsNotNull' ignored -- No concept of NULL in HPCC/ECL");
            }
            else if (op.getValue().equals(SQLOperator.lt))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeLT(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeLT(sqlfilter.getPrefixValue())));
            }
            else if (op.getValue().equals(SQLOperator.lte))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeLE(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeLE(sqlfilter.getPrefixValue())));
            }
            else if (op.getValue().equals(SQLOperator.neq) || op.getValue().equals(SQLOperator.neq2))
            {
                if (prefixcontainsfield)
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPrefixValue(), FieldFilterRange.makeNE(sqlfilter.getPostfixValue())));
                else
                    hpccfilter = new FileFilter(new FieldFilter(sqlfilter.getPostfixValue(), FieldFilterRange.makeNE(sqlfilter.getPrefixValue())));
            }
            else
                throw new Exception("Invalid filter operator detected: '" + op.getValue() + "' In filter: '" + sqlfilter.toString() + "'");
        }
        else
            throw new Exception("Invalid filter clause: " + sqlfilter.toString());

        log.debug("Converted SQL filter: '" + sqlfilter.toString() + "' to HPCC filter: '" + hpccfilter);
        return hpccfilter;
    }

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(String[] args)
    {
        try
        {
            FileFilter filter = new FileFilter(
                    "(table.field2 <= 'a' AND table.field2 != 4) OR table.field2 != 5 AND field3 In ('Germany', 'France', 'UK')");
            System.out.println(filter.toJson());
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy