/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.eql.parser.generic;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.jxb.entity.Efield;
import com.queplix.core.jxb.entity.Entity;
import com.queplix.core.jxb.entity.Listref;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLDateObject;
import com.queplix.core.modules.eql.EQLNullObject;
import com.queplix.core.modules.eql.EQLNumberObject;
import com.queplix.core.modules.eql.EQLObject;
import com.queplix.core.modules.eql.EQLReq;
import com.queplix.core.modules.eql.EQLReqAggFunc;
import com.queplix.core.modules.eql.EQLReqEntity;
import com.queplix.core.modules.eql.EQLReqField;
import com.queplix.core.modules.eql.EQLReqFrom;
import com.queplix.core.modules.eql.EQLReqJoin;
import com.queplix.core.modules.eql.EQLReqOp;
import com.queplix.core.modules.eql.EQLReqOrder;
import com.queplix.core.modules.eql.EQLReqSelect;
import com.queplix.core.modules.eql.EQLReqSelectAttr;
import com.queplix.core.modules.eql.EQLReqSubOp;
import com.queplix.core.modules.eql.EQLReqSubOpMember;
import com.queplix.core.modules.eql.EQLReqSubOpMemberEnum;
import com.queplix.core.modules.eql.EQLReqSubOpMemberField;
import com.queplix.core.modules.eql.EQLReqSubOpMemberUnknown;
import com.queplix.core.modules.eql.EQLReqSubOrder;
import com.queplix.core.modules.eql.EQLReqSubWhere;
import com.queplix.core.modules.eql.EQLReqWhere;
import com.queplix.core.modules.eql.EQLStringObject;
import com.queplix.core.modules.eql.EQLTimeObject;
import com.queplix.core.modules.eql.aggs.CountAggFunc;
import com.queplix.core.modules.eql.aggs.MaxAggFunc;
import com.queplix.core.modules.eql.aggs.MinAggFunc;
import com.queplix.core.modules.eql.conds.EqCond;
import com.queplix.core.modules.eql.conds.GtCond;
import com.queplix.core.modules.eql.conds.GtEqCond;
import com.queplix.core.modules.eql.conds.InCond;
import com.queplix.core.modules.eql.conds.IsNotNullCond;
import com.queplix.core.modules.eql.conds.IsNullCond;
import com.queplix.core.modules.eql.conds.LikeCond;
import com.queplix.core.modules.eql.conds.LtCond;
import com.queplix.core.modules.eql.conds.LtEqCond;
import com.queplix.core.modules.eql.conds.NoneCond;
import com.queplix.core.modules.eql.conds.NotEqCond;
import com.queplix.core.modules.eql.conds.NotInCond;
import com.queplix.core.modules.eql.conds.NotLikeCond;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.funcs.DateDiffFunc;
import com.queplix.core.modules.eql.funcs.IsNullFunc;
import com.queplix.core.modules.eql.funcs.LowerFunc;
import com.queplix.core.modules.eql.funcs.SoundexFunc;
import com.queplix.core.modules.eql.funcs.UpperFunc;
import com.queplix.core.modules.eql.ops.DivOp;
import com.queplix.core.modules.eql.ops.MinusOp;
import com.queplix.core.modules.eql.ops.MultOp;
import com.queplix.core.modules.eql.ops.NoneOp;
import com.queplix.core.modules.eql.ops.PlusOp;
import com.queplix.core.modules.eql.parser.SQLSelectBuilder;
import com.queplix.core.utils.StringHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>Select SQL builder generic implementation</p>
 *
 * @author Baranov Andrey [ALB]
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:31 $
 */

public abstract class SQLSelectBuilderGenericImpl
        extends SQLSelectBuilder {

// -------------------- VARIABLES --------------------------

    public final static String DISTINCT_PARAM = "distinct";

    // cache for list EQLReqField object
    private static Map listReqFieldMap = Collections.synchronizedMap(new HashMap());

// ------------ PROTECTED OVERRIDED METHODS ----------------

    /*
     * (No javadoc)
     * @see SQLBuilder#addSelectHint
     */

    protected void addSelectHint()
            throws EQLException {

        // Add DISTINCT
        // 1. Try to find in Meta
        boolean distinct = false;
        Boolean val = (Boolean) req.getMetaData().getParam(DISTINCT_PARAM);
        if(val != null) {
            distinct = val.booleanValue();
        }
        // 2. If no, try to call EQLReqSelect#isDistinct
        if(distinct || req.getSelect().isDistinct()) {
            hint.append(" DISTINCT ");
        }
    }

    /*
     * (No javadoc)
     * @see SQLBuilder#addSelectClause
     */
    protected int addSelectClause()
            throws EQLException {

        EQLReqSelect reqSelect = req.getSelect();
        int size = reqSelect.size();
        int added = 0;
        for(int i = 0; i < size; i++) {
            if(added > 0) {
                selectClause.append(",\n");
            }
            String selectSql = getSQLFieldSelect(req, reqSelect.getAttr(i), i);
            if(selectSql != null) {
                selectClause.append(selectSql);
                added++;
            }
        }

        return added;
    }

    /*
     * (No javadoc)
     * @see SQLBuilder#addFromClause
     */
    protected void addFromClause()
            throws EQLException {

        EQLReqFrom reqFrom = req.getFrom();
        int fromSize = reqFrom.fromSize();
        int joinSize = reqFrom.joinSize();

        if(fromSize > 0) {

            // build set of "should be joined" entities
            Set shouldBeJoinedReqEntities = new HashSet();
            for(int i = 0; i < joinSize; i++) {
                EQLReqJoin reqJoin = reqFrom.getJoinEntity(i);
                shouldBeJoinedReqEntities.add(reqJoin.getRightEntity());
            }

            // build FROM clause in back order
            // because all entities joined only to first (main) entity
            // and it must be the last in FROM clause
            int addedInFrom = 0;
            for(int i = 0; i < fromSize; i++) {

                // current entity
                EQLReqEntity reqFromEntity = reqFrom.getFromEntity(i);
                if(shouldBeJoinedReqEntities.contains(reqFromEntity)) {
                    // this entity is added or will be added in join list
                    continue;
                }

                // add current entity in FROM list
                if(addedInFrom > 0) {
                    fromClause.append(",\n");
                }
                fromClause.append(getSQLTableName(reqFromEntity));

                // add all join entities for current entity
                getJoinClause(req, reqFromEntity, fromClause);

                addedInFrom++;
            }
        }
    }

    /*
     * (No javadoc)
     * @see SQLBuilder#addWhereClause
     */
    protected void addWhereClause()
            throws EQLException {

        // get WHERE clause
        EQLReqWhere reqWhere = req.getWhere();
        int size = reqWhere.size();
        if(size > 0) {
            whereClause.append(getSQLWhere(reqWhere));
        }
    }

    /*
     * (No javadoc)
     * @see SQLBuilder#addGroupByClause
     */
    protected void addGroupByClause()
            throws EQLException {
        
        ArrayList attrs = req.getSelect().getAttrs();

        List<EQLReqSelectAttr> groupByCols = new ArrayList<EQLReqSelectAttr>(attrs);

        boolean hasGroup = false;
        for (Iterator it = attrs.iterator(); it.hasNext();) {
            EQLReqSelectAttr attr = (EQLReqSelectAttr) it.next();

            EQLReqOp reqOp = attr.getReqOp();
            if (attr.isConstant() ||
                    (reqOp.size() == 1 && reqOp.getSubOp(0).getMember() instanceof EQLReqAggFunc))
            {
                groupByCols.remove(attr);
                if(!attr.isConstant())
                    hasGroup = true;
            }
        }
        
        if (hasGroup && !groupByCols.isEmpty()) {
            List<String> sqlList = new ArrayList<String>();

            for (int i = 0; i < groupByCols.size(); i++) {
                EQLReqSelectAttr groupByCol = groupByCols.get(i);

                String groupBySql = getSQLOperand(groupByCol.getReqOp());
                if(groupBySql != null)
                    sqlList.add(groupBySql);

                // Add listRef field to "group by" clause to avoid
                // potential issues with "order by" clause
                Efield masterEfield = groupByCol.getReqField().getField();
                Listref listRef = masterEfield.getListref();
                if (listRef != null) {
                    EQLReqField listRefReqField = getListReqField(masterEfield, listRef);
                    String listRefSql = getSQLOperand(new EQLReqOp(listRefReqField));
                    if (listRefSql != null)
                        sqlList.add(listRefSql);
                }
            }

            if(!sqlList.isEmpty()){
                groupByClause.append(
                        StringHelper.join(
                                (String[]) sqlList.toArray(new String[0]), ",\n"));
            }
        }
    }

    //
    // Get EQLReqField for list field
    //
    protected EQLReqField getListReqField(Efield masterField, Listref listref) {

        String key = masterField.getId();
        EQLReqField reqField = (EQLReqField) listReqFieldMap.get(key);
        if(reqField == null) {
            DEBUG("Create new EQLReqField object for: " + key);
            Entity lrefEntity = getSession().findEntity(listref.getEntity());
            Efield lrefField = EntityHelper.getEfield(
                    listref.getEfield(), lrefEntity);
            reqField = new EQLReqField(lrefEntity, lrefField);
            listReqFieldMap.put(key, reqField);
        }
        return reqField;
    }


    /*
     * (No javadoc)
     * @see SQLBuilder#addOrderClause
     */
    protected void addOrderClause()
            throws EQLException {

        EQLReqOrder reqOrder = req.getOrder();
        int size = reqOrder.size();
        if(size > 0) {
            boolean hasMandatorySubOrders = reqOrder.hasMandatory();

            int added = 0;
            for(int i = 0; i < size; i++) {
                EQLReqSubOrder reqSubOrder = reqOrder.getSubOrder(i);
                boolean isMandatory = reqSubOrder.isMandatory();

                if(hasMandatorySubOrders && !isMandatory) {
                    // don't add none mandatory order clause
                    continue;
                }

                String orderSql = getSQLOrder(reqSubOrder);
                if(added > 0) {
                    orderClause.append(",\n ");
                }
                orderClause.append(orderSql);

                added++;
            }
        }
    }

    /*
     * (No javadoc)
     * @see SQLBuilder#getCountSql
     */
    protected String getCountSql(String mainSql)
            throws EQLException {
        return "SELECT COUNT(*) FROM (" + mainSql + ") REQ";
    }

// --------------- PROTECTED METHODS -----------------------

    /**
     * Build select field clause
     *
     * @param req           EQLReq object
     * @param reqSelectAttr current EQLreq select attribute
     * @param i             current position
     * @return select clause or NULL if nothing was added
     * @throws EQLException
     */
    protected String getSQLFieldSelect(EQLReq req,
                                       EQLReqSelectAttr reqSelectAttr,
                                       int i)
            throws EQLException {

        StringBuffer sql = null;

        if(reqSelectAttr.isConstant()) {
            EQLObject constObject = reqSelectAttr.getConstant();
            sql = new StringBuffer();
            sql.append(_getSQLValue(constObject)).append(" AS FIELD").append(i);
        } else {
            // add field to SQL query
            String sqlColumnName = getSQLColumnSelect(req, reqSelectAttr);
            sql = new StringBuffer();
            sql.append(sqlColumnName).append(" AS FIELD").append(i);
        }

        if(!req.isLazy(reqSelectAttr.getReqField().getField())) {
            // field is not for lazy loading - try to add listfield
            EQLReqSelectAttr listFieldReqSelectAttr = reqSelectAttr
                    .getListFieldSelectAttr();
            if(listFieldReqSelectAttr != null) {
                // add list field to SQL query
                String listFieldSqlColumnName = getSQLColumnSelect(req,
                        listFieldReqSelectAttr);
                if(sql == null) {
                    sql = new StringBuffer();
                } else {
                    sql.append(",");
                }
                sql.append(listFieldSqlColumnName).append(" AS LIST").append(i);
            }
        }

        return (sql == null) ? null:sql.toString();
    }

    /**
     * Build SQL column select field clause
     *
     * @param req           EQLReq object
     * @param reqSelectAttr current EQLreq select attribute
     * @return SQL column
     * @throws EQLException
     */
    protected String getSQLColumnSelect(EQLReq req,
                                        EQLReqSelectAttr reqSelectAttr)
            throws EQLException {
        // Build SQL column
        return getSQLOperand(reqSelectAttr.getReqOp());
    }

    /**
     * EQL to SQL join clause transformation
     *
     * @param reqJoin EQLReqJoin object
     * @return sql substring
     * @throws EQLException
     */
    protected String getSQLJoin(EQLReqJoin reqJoin)
            throws EQLException {

        int size = reqJoin.size();
        EQLReqWhere reqWhere = reqJoin.getReqWhere();
        EQLReqEntity reqEntity2 = reqJoin.getRightField(0).getReqEntity();

        StringBuffer sb = new StringBuffer();
        switch(reqJoin.getType()) {
            case EQLReqJoin.INNER_JOIN:
                sb.append("INNER JOIN ");
                break;

            case EQLReqJoin.OUTER_JOIN:
                sb.append("LEFT OUTER JOIN ");
                break;

            default:
                throw new GenericSystemException(
                        "Unsupported Join Type: " + reqJoin.getType());
        }
        sb.append(getSQLTableName(reqEntity2)).append(" ON ");

        // add foreign keys
        for(int i = 0; i < size; i++) {
            if(i > 0) {
                sb.append(" AND ");
            }

            EQLReqField leftReqField = reqJoin.getLeftField(i);
            EQLReqField rightReqField = reqJoin.getRightField(i);

            sb.append(getSQLColumnName(leftReqField));
            sb.append(" = ");
            sb.append(getSQLColumnName(rightReqField));
        }

        // add special foreign condition
        if(reqWhere != null && reqWhere.size() > 0) {
            sb.append(" AND ( ");
            sb.append(getSQLWhere(reqWhere));
            sb.append(" ) ");
        }

        return sb.toString();
    }

    /**
     * EQL to SQL order by clause transformation
     *
     * @param reqSubOrder EQLReqSubOrder object
     * @return sql substring
     * @throws EQLException
     */
    protected String getSQLOrder(EQLReqSubOrder reqSubOrder)
            throws EQLException {

        StringBuffer orderClause = new StringBuffer();

        orderClause.append(getSQLOperand(reqSubOrder.getReqOp()));

        int op = reqSubOrder.getOperation();
        switch(op) {
            case EQLReqSubOrder.ASC_OP:
                orderClause.append(" ASC");
                break;

            case EQLReqSubOrder.DESC_OP:
                orderClause.append(" DESC");
                break;

            default:
                throw new GenericSystemException(
                        "Unsupported order operation '" + op + "'");
        }

        return orderClause.toString();
    }

    /**
     * EQL to SQL where clause transformation
     *
     * @param reqWhere EQLReqWhere object
     * @return sql substring
     * @throws EQLException
     */
    protected String getSQLWhere(EQLReqWhere reqWhere)
            throws EQLException {

        StringBuffer whereClause = new StringBuffer();

        int size = reqWhere.size();

        for(int i = 0; i < size; i++) {
            EQLReqSubWhere reqSubWhere = reqWhere.getSubWhere(i);

            int op = reqSubWhere.getOperation();
            switch(op) {
                case EQLReqSubWhere.NONE_OP:
                    // default where conditions could be before...
                    if(i > 0) {
                        whereClause.append(" AND\n");
                    }
                    break;

                case EQLReqSubWhere.AND_OP:
                    whereClause.append(" AND\n");
                    break;

                case EQLReqSubWhere.OR_OP:
                    whereClause.append(" OR\n");
                    break;

                default:
                    throw new GenericSystemException(
                            "Unsupported where operation '" + op + "'");
            }

            java.io.Serializable o = reqSubWhere.getCond();
            whereClause.append(_getSQLCond(o));
        }

        return whereClause.toString();
    }

    //
    // Operand
    //

    protected String getSQLOperand(EQLReqOp reqOp)
            throws EQLException {

        StringBuffer sb = new StringBuffer();

        int size = reqOp.size();
        for(int i = 0; i < size; i++) {
            EQLReqSubOp subOp = reqOp.getSubOp(i);
            sb.append(_getSQLSubOperand(subOp));
        }

        return sb.toString();
    }

    //
    // Conditions
    //

    protected String getSQLCond(EQLReqWhere reqWhere)
            throws EQLException {

        return "(\n" + getSQLWhere(reqWhere) + "\n)";
    }

    protected String getSQLCond(EqCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " = " + rightOp;
    }

    protected String getSQLCond(GtCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " > " + rightOp;
    }

    protected String getSQLCond(LtCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " < " + rightOp;
    }

    protected String getSQLCond(GtEqCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " >= " + rightOp;
    }

    protected String getSQLCond(LtEqCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " <= " + rightOp;
    }

    protected String getSQLCond(NotEqCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " != " + rightOp;
    }

    protected String getSQLCond(IsNullCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        return leftOp + " IS NULL";
    }

    protected String getSQLCond(IsNotNullCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        return leftOp + " IS NOT NULL";
    }

    protected String getSQLCond(InCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " IN " + rightOp;
    }

    protected String getSQLCond(NotInCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " NOT IN " + rightOp;
    }

    protected String getSQLCond(LikeCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " LIKE " + rightOp + " ESCAPE '" + EQL_ESCAPE_CHARACTER
                + "'";
    }

    protected String getSQLCond(NotLikeCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        String rightOp = getSQLOperand(cond.getRightMember());
        return leftOp + " NOT LIKE " + rightOp + " ESCAPE '"
                + EQL_ESCAPE_CHARACTER + "'";
    }

    protected String getSQLCond(NoneCond cond)
            throws EQLException {

        String leftOp = getSQLOperand(cond.getLeftMember());
        return leftOp;
    }

    //
    // Sub Operands
    //

    protected String getSQLSubOperand(NoneOp subOp)
            throws EQLException {

        return _getSQLOperandMember(subOp.getMember());
    }

    protected String getSQLSubOperand(PlusOp subOp)
            throws EQLException {

        return "+" + _getSQLOperandMember(subOp.getMember());
    }

    protected String getSQLSubOperand(MinusOp subOp)
            throws EQLException {

        return "-" + _getSQLOperandMember(subOp.getMember());
    }

    protected String getSQLSubOperand(MultOp subOp)
            throws EQLException {

        return "*" + _getSQLOperandMember(subOp.getMember());
    }

    protected String getSQLSubOperand(DivOp subOp)
            throws EQLException {

        return "/" + _getSQLOperandMember(subOp.getMember());
    }

    //
    // Operand Members
    //

    protected String getSQLOperandMember(EQLReqOp memberField)
            throws EQLException {

        return "(" + getSQLOperand(memberField) + ")";
    }

    protected String getSQLOperandMember(EQLReqSubOpMemberField memberField)
            throws EQLException {

        if(memberField.getReqField() == null) {
            return "NULL";
        } else {
            return getSQLColumnName(memberField.getReqField());
        }
    }

    protected String getSQLOperandMember(EQLReqSubOpMemberUnknown memberField) {
        return "?";
    }

    protected String getSQLOperandMember(EQLReqSubOpMemberEnum memberField)
            throws EQLException {

        StringBuffer ret = new StringBuffer("(");
        int size = memberField.getSize();
        for(int i = 0; i < size; i++) {
            if(i > 0) {
                ret.append(",");
            }
            ret.append(_getSQLOperandMember(memberField.getParameter(i)));
        }
        ret.append(")");

        return ret.toString();
    }

    protected String getSQLOperandMember(EQLReq memberField)
            throws EQLException {

        SQLSelectBuilder child = (SQLSelectBuilder) createChildBuilder();
        String childSQL = child.buildSelectSql(memberField).getMainSql();
        if(childSQL == null) {
            /** @todo is it right to insert NULL here? */
            return "( NULL )";
        } else {
            return "(" + childSQL + ")";
        }
    }

    protected String getSQLOperandMember(SoundexFunc memberField)
            throws EQLException {

        return "soundex(" + getSQLOperand(memberField.getParameter(0)) + ")";
    }

    protected String getSQLOperandMember(LowerFunc memberField)
            throws EQLException {

        return "lower(" + getSQLOperand(memberField.getParameter(0)) + ")";
    }

    protected String getSQLOperandMember(UpperFunc memberField)
            throws EQLException {

        return "upper(" + getSQLOperand(memberField.getParameter(0)) + ")";
    }

    protected String getSQLOperandMember(DateDiffFunc memberField)
            throws EQLException {

        return "datediff(" + getSQLOperand(memberField.getParameter(0)) + ","
                + getSQLOperand(memberField.getParameter(1)) + ")";
    }

    protected String getSQLOperandMember(IsNullFunc memberField)
            throws EQLException {

        return "isnull(" + getSQLOperand(memberField.getParameter(0)) + ","
                + getSQLOperand(memberField.getParameter(1)) + ")";
    }

    protected String getSQLOperandMember(CountAggFunc memberField)
            throws EQLException {

        return "count(*)";
    }

    protected String getSQLOperandMember(MinAggFunc memberField)
            throws EQLException {

        return "min(" + getSQLOperand(memberField.getParameter(0)) + ")";
    }

    protected String getSQLOperandMember(MaxAggFunc memberField)
            throws EQLException {

        return "max(" + getSQLOperand(memberField.getParameter(0)) + ")";
    }

    //
    // Values
    //

    protected String getSQLValue(EQLStringObject o)
            throws EQLException {
        return StringHelper.java2sql(o.getValue());
    }

    protected String getSQLValue(EQLNumberObject o)
            throws EQLException {
        return o.getValue().toString();
    }

    protected String getSQLValue(EQLDateObject o)
            throws EQLException {
        return sqlWrapper.getTimestampParser().sqlDateFunction(o.getValue());
    }

    protected String getSQLValue(EQLTimeObject o)
            throws EQLException {
        return sqlWrapper.getTimeParser().sqlTimeFunction(o.getValue());
    }

    protected String getSQLValue(EQLNullObject o)
            throws EQLException {
        return "NULL";
    }

// ---------------- PRIVATE METHODS ------------------------

    //
    // Build JOIN clause
    //

    private void getJoinClause(EQLReq req,
                               EQLReqEntity reqLeftEntity,
                               StringBuffer sql)
            throws EQLException {

        EQLReqFrom reqFrom = req.getFrom();
        int joinSize = reqFrom.joinSize();

        // add all join entities for left entity
        for(int j = 0; j < joinSize; j++) {
            EQLReqJoin reqJoin = reqFrom.getJoinEntity(j);

            if(reqLeftEntity.equals(reqJoin.getLeftEntity())) {
                sql.append("\n");
                sql.append(getSQLJoin(reqJoin));
                getJoinClause(req, reqJoin.getRightEntity(), sql);
            }
        }
    }

}
