/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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.seasar.extension.sql.parser;

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

import junit.framework.TestCase;

import org.seasar.extension.sql.EndCommentNotFoundRuntimeException;
import org.seasar.extension.sql.Node;
import org.seasar.extension.sql.SemicolonNotAllowedRuntimeException;
import org.seasar.extension.sql.SqlContext;
import org.seasar.extension.sql.SqlParser;
import org.seasar.extension.sql.TokenNotClosedRuntimeException;
import org.seasar.extension.sql.VariableSqlNotAllowedRuntimeException;
import org.seasar.extension.sql.context.SqlContextImpl;
import org.seasar.extension.sql.node.BindVariableNode;
import org.seasar.extension.sql.node.IfNode;
import org.seasar.extension.sql.node.SqlNode;

/**
 * @author higa
 * 
 */
public class SqlParserImplTest extends TestCase {

    /**
     * @throws Exception
     */
    public void testParse() throws Exception {
        String sql = "SELECT * FROM emp";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node node = parser.parse();
        node.accept(ctx);
        assertEquals("1", sql, ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseEndSemicolon() throws Exception {
        testParseEndSemicolon(";");
        testParseEndSemicolon(";\t");
        testParseEndSemicolon("; ");
    }

    /**
     * @param endChar
     */
    public void testParseEndSemicolon(String endChar) {
        String sql = "SELECT * FROM emp";
        SqlParser parser = new SqlParserImpl(sql + endChar);
        SqlContext ctx = new SqlContextImpl();
        Node node = parser.parse();
        node.accept(ctx);
        assertEquals("1", sql, ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testCommentEndNotFound() throws Exception {
        String sql = "SELECT * FROM emp/*hoge";
        SqlParser parser = new SqlParserImpl(sql);
        try {
            parser.parse();
            fail("1");
        } catch (TokenNotClosedRuntimeException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testParseBindVariable() throws Exception {
        String sql = "SELECT * FROM emp WHERE job = /*job*/'CLERK' AND deptno = /*deptno*/20";
        String sql2 = "SELECT * FROM emp WHERE job = ? AND deptno = ?";
        String sql3 = "SELECT * FROM emp WHERE job = ";
        String sql4 = " AND deptno = ";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "CLERK";
        Integer deptno = new Integer(20);
        ctx.addArg("job", job, job.getClass());
        ctx.addArg("deptno", deptno, deptno.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 2, vars.length);
        assertEquals("3", job, vars[0]);
        assertEquals("4", deptno, vars[1]);
        assertEquals("5", 4, root.getChildSize());
        SqlNode sqlNode = (SqlNode) root.getChild(0);
        assertEquals("6", sql3, sqlNode.getSql());
        BindVariableNode varNode = (BindVariableNode) root.getChild(1);
        assertEquals("7", "job", varNode.getExpression());
        SqlNode sqlNode2 = (SqlNode) root.getChild(2);
        assertEquals("8", sql4, sqlNode2.getSql());
        BindVariableNode varNode2 = (BindVariableNode) root.getChild(3);
        assertEquals("9", "deptno", varNode2.getExpression());
    }

    /**
     * @throws Exception
     */
    public void testParseBindVariable2() throws Exception {
        String sql = "SELECT * FROM emp WHERE job = /* job*/'CLERK'";
        String sql2 = "SELECT * FROM emp WHERE job = 'CLERK'";
        String sql3 = "SELECT * FROM emp WHERE job = ";
        String sql4 = "'CLERK'";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        assertEquals("2", 2, root.getChildSize());
        SqlNode sqlNode = (SqlNode) root.getChild(0);
        assertEquals("3", sql3, sqlNode.getSql());
        SqlNode sqlNode2 = (SqlNode) root.getChild(1);
        assertEquals("4", sql4, sqlNode2.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseBindVariable3() throws Exception {
        String sql = "BETWEEN sal ? AND ?";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("$1", new Integer(0), Integer.class);
        ctx.addArg("$2", new Integer(1000), Integer.class);
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("BETWEEN sal ? AND ?", ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 2, vars.length);
        assertEquals("3", new Integer(0), vars[0]);
        assertEquals("4", new Integer(1000), vars[1]);
    }

    /**
     * @throws Exception
     */
    public void testParseBindVariable4() throws Exception {
        String sql = "SELECT * FROM emp WHERE job = #*job*#'CLERK' AND deptno = #*deptno*#20";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "CLERK";
        Integer deptno = new Integer(20);
        ctx.addArg("job", job, job.getClass());
        ctx.addArg("deptno", deptno, deptno.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println(ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseBindVariable5() throws Exception {
        String sql = "SELECT * FROM emp WHERE job = /*job*/'CLERK'";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "HOGE";
        ctx.addArg("job", job, job.getClass());
        Node root = parser.parse();
        root.accept(ctx);
    }

    /**
     * @throws Exception
     */
    public void testParseWhiteSpace() throws Exception {
        String sql = "SELECT * FROM emp WHERE empno = /*empno*/1 AND 1 = 1";
        String sql2 = "SELECT * FROM emp WHERE empno = ? AND 1 = 1";
        String sql3 = " AND 1 = 1";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Integer empno = new Integer(7788);
        ctx.addArg("empno", empno, empno.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        SqlNode sqlNode = (SqlNode) root.getChild(2);
        assertEquals("2", sql3, sqlNode.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf() throws Exception {
        String sql = "SELECT * FROM emp/*IF job != null*/ WHERE job = /*job*/'CLERK'/*END*/";
        String sql2 = "SELECT * FROM emp WHERE job = ?";
        String sql3 = "SELECT * FROM emp";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "CLERK";
        ctx.addArg("job", job, job.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 1, vars.length);
        assertEquals("3", job, vars[0]);
        assertEquals("4", 2, root.getChildSize());
        SqlNode sqlNode = (SqlNode) root.getChild(0);
        assertEquals("5", sql3, sqlNode.getSql());
        IfNode ifNode = (IfNode) root.getChild(1);
        assertEquals("6", "job != null", ifNode.getExpression());
        assertEquals("7", 2, ifNode.getChildSize());
        SqlNode sqlNode2 = (SqlNode) ifNode.getChild(0);
        assertEquals("8", " WHERE job = ", sqlNode2.getSql());
        BindVariableNode varNode = (BindVariableNode) ifNode.getChild(1);
        assertEquals("9", "job", varNode.getExpression());
        SqlContext ctx2 = new SqlContextImpl();
        root.accept(ctx2);
        System.out.println(ctx2.getSql());
        assertEquals("10", sql3, ctx2.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf2() throws Exception {
        String sql = "/*IF aaa != null*/aaa/*IF bbb != null*/bbb/*END*//*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("1", "", ctx.getSql());
        ctx.addArg("aaa", null, String.class);
        ctx.addArg("bbb", "hoge", String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("2", "", ctx.getSql());
        ctx.addArg("aaa", "hoge", String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("3", "aaabbb", ctx.getSql());
        SqlContext ctx2 = new SqlContextImpl();
        ctx2.addArg("aaa", "hoge", String.class);
        ctx2.addArg("bbb", null, String.class);
        root.accept(ctx2);
        System.out.println("[" + ctx2.getSql() + "]");
        assertEquals("4", "aaa", ctx2.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf3() throws Exception {
        String sql = "/*IF aaa != null*/aaa,/*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("", ctx.getSql());
        ctx.addArg("aaa", "hoge", String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("aaa,", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf4() throws Exception {
        String sql = "/*IF has_aaa*/aaa,/*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("", ctx.getSql());
        ctx.addArg("aaa", null, String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("aaa,", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf5() throws Exception {
        String sql = "/*BEGIN*//*IF has_aaa*/,aaa/*END*//*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("", ctx.getSql());
        ctx.addArg("aaa", null, String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("aaa", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseIf_NotAllowed() throws Exception {
        String sql = "SELECT * FROM emp/*IF job != null*/ WHERE job = /*job*/'CLERK'/*END*/";
        SqlParser parser = new SqlParserImpl(sql, false);
        try {
            parser.parse();
            fail();
        } catch (VariableSqlNotAllowedRuntimeException expected) {
        }
    }

    /**
     * @throws Exception
     */
    public void testParseElse() throws Exception {
        String sql = "SELECT * FROM emp WHERE /*IF job != null*/job = /*job*/'CLERK'-- ELSE job is null/*END*/";
        String sql2 = "SELECT * FROM emp WHERE job = ?";
        String sql3 = "SELECT * FROM emp WHERE job is null";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "CLERK";
        ctx.addArg("job", job, job.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 1, vars.length);
        assertEquals("3", job, vars[0]);
        SqlContext ctx2 = new SqlContextImpl();
        root.accept(ctx2);
        System.out.println("[" + ctx2.getSql() + "]");
        assertEquals("4", sql3, ctx2.getSql());
    }

    /**
     * @throws Exception
     */
    public void testParseElse2() throws Exception {
        String sql = "/*IF false*/aaa--ELSE bbb = /*bbb*/123/*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Integer bbb = new Integer(123);
        ctx.addArg("bbb", bbb, bbb.getClass());
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("1", "bbb = ?", ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 1, vars.length);
        assertEquals("3", bbb, vars[0]);
    }

    /**
     * @throws Exception
     */
    public void testParseElse3() throws Exception {
        String sql = "/*IF false*/aaa--ELSE bbb/*IF false*/ccc--ELSE ddd/*END*//*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        Node root = parser.parse();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("1", "bbbddd", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testElse4() throws Exception {
        String sql = "SELECT * FROM emp/*BEGIN*/ WHERE /*IF false*/aaa-- ELSE AND deptno = 10/*END*//*END*/";
        String sql2 = "SELECT * FROM emp WHERE deptno = 10";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testBegin() throws Exception {
        String sql = "SELECT * FROM emp/*BEGIN*/ WHERE /*IF job != null*/job = /*job*/'CLERK'/*END*//*IF deptno != null*/ AND deptno = /*deptno*/20/*END*//*END*/";
        String sql2 = "SELECT * FROM emp";
        String sql3 = "SELECT * FROM emp WHERE job = ?";
        String sql4 = "SELECT * FROM emp WHERE job = ? AND deptno = ?";
        String sql5 = "SELECT * FROM emp WHERE deptno = ?";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());

        SqlContext ctx2 = new SqlContextImpl();
        ctx2.addArg("job", "CLERK", String.class);
        ctx2.addArg("deptno", null, Integer.class);
        root.accept(ctx2);
        System.out.println(ctx2.getSql());
        assertEquals("2", sql3, ctx2.getSql());

        SqlContext ctx3 = new SqlContextImpl();
        ctx3.addArg("job", "CLERK", String.class);
        ctx3.addArg("deptno", new Integer(20), Integer.class);
        root.accept(ctx3);
        System.out.println(ctx3.getSql());
        assertEquals("3", sql4, ctx3.getSql());

        SqlContext ctx4 = new SqlContextImpl();
        ctx4.addArg("deptno", new Integer(20), Integer.class);
        ctx4.addArg("job", null, String.class);
        root.accept(ctx4);
        System.out.println(ctx4.getSql());
        assertEquals("4", sql5, ctx4.getSql());
    }

    /**
     * @throws Exception
     */
    public void testBegin2() throws Exception {
        String sql = "SELECT * FROM emp /*BEGIN*/WHERE /*IF job != null*/job = /*job*/'CLERK'/*END*//*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        SqlContext ctx = new SqlContextImpl();
        String job = "HOGE";
        ctx.addArg("job", job, job.getClass());
        Node root = parser.parse();
        root.accept(ctx);
    }

    /**
     * @throws Exception
     */
    public void testBeginAnd() throws Exception {
        String sql = "/*BEGIN*/WHERE /*IF true*/aaa BETWEEN /*bbb*/111 AND /*ccc*/123/*END*//*END*/";
        String sql2 = "WHERE aaa BETWEEN ? AND ?";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("bbb", "111", String.class);
        ctx.addArg("ccc", "222", String.class);
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals(sql2, ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testPrefixSql_comma() throws Exception {
        String sql = "/*BEGIN*//*IF false*/aaa/*END*//*IF true*/,bbb/*END*//*END*/";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        root.accept(ctx);
        System.out.println("[" + ctx.getSql() + "]");
        assertEquals("bbb", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testIn() throws Exception {
        String sql = "SELECT * FROM emp WHERE deptno IN /*deptnoList*/(10, 20) ORDER BY ename";
        String sql2 = "SELECT * FROM emp WHERE deptno IN (?, ?) ORDER BY ename";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        List deptnoList = new ArrayList();
        deptnoList.add(new Integer(10));
        deptnoList.add(new Integer(20));
        ctx.addArg("deptnoList", deptnoList, List.class);
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 2, vars.length);
        assertEquals("3", new Integer(10), vars[0]);
        assertEquals("4", new Integer(20), vars[1]);
    }

    /**
     * @throws Exception
     */
    public void testIn2() throws Exception {
        String sql = "SELECT * FROM emp WHERE deptno IN /*deptnoList*/(10, 20) ORDER BY ename";
        String sql2 = "SELECT * FROM emp WHERE deptno IN (?, ?) ORDER BY ename";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        int[] deptnoArray = { 10, 20 };
        ctx.addArg("deptnoList", deptnoArray, deptnoArray.getClass());
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 2, vars.length);
        assertEquals("3", new Integer(10), vars[0]);
        assertEquals("4", new Integer(20), vars[1]);
    }

    /**
     * @throws Exception
     */
    public void testIn3() throws Exception {
        String sql = "SELECT * FROM emp WHERE ename IN /*enames*/('SCOTT','MARY') AND job IN /*jobs*/('ANALYST', 'FREE')";
        String sql2 = "SELECT * FROM emp WHERE ename IN (?, ?) AND job IN (?, ?)";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        String[] enames = { "SCOTT", "MARY" };
        String[] jobs = { "ANALYST", "FREE" };
        ctx.addArg("enames", enames, enames.getClass());
        ctx.addArg("jobs", jobs, jobs.getClass());
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", sql2, ctx.getSql());
        Object[] vars = ctx.getBindVariables();
        assertEquals("2", 4, vars.length);
        assertEquals("3", "SCOTT", vars[0]);
        assertEquals("4", "MARY", vars[1]);
        assertEquals("5", "ANALYST", vars[2]);
        assertEquals("6", "FREE", vars[3]);
    }

    /**
     * @throws Exception
     */
    public void testEndNotFound() throws Exception {
        String sql = "/*BEGIN*/";
        SqlParser parser = new SqlParserImpl(sql);
        try {
            parser.parse();
            fail("1");
        } catch (EndCommentNotFoundRuntimeException ex) {
            System.out.println(ex);
        }
    }

    /**
     * @throws Exception
     */
    public void testEndParent() throws Exception {
        String sql = "INSERT INTO ITEM (ID, NUM) VALUES (/*id*/1, /*num*/20)";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("id", new Integer(0), Integer.class);
        ctx.addArg("num", new Integer(1), Integer.class);
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", true, ctx.getSql().endsWith(")"));
    }

    /**
     * @throws Exception
     */
    public void testEmbeddedValue() throws Exception {
        String sql = "/*$aaa*/";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("aaa", new Integer(0), Integer.class);
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("1", "0", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testEmbeddedValue_semicolon() throws Exception {
        String sql = "/*$aaa*/";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("aaa", ";update", String.class);
        try {
            root.accept(ctx);
            fail();
        } catch (SemicolonNotAllowedRuntimeException e) {
            System.out.println(e);
        }
    }

    /**
     * @throws Exception
     */
    public void testEmbeddedValue_orderBy() throws Exception {
        String sql = "/*orderBy*/";
        SqlParser parser = new SqlParserImpl(sql);
        Node root = parser.parse();
        SqlContext ctx = new SqlContextImpl();
        ctx.addArg("orderBy", "id desc", String.class);
        root.accept(ctx);
        System.out.println(ctx.getSql());
        assertEquals("id desc", ctx.getSql());
    }

    /**
     * @throws Exception
     */
    public void testEmbeddedValue_NotAllowed() throws Exception {
        String sql = "/*$aaa*/";
        SqlParser parser = new SqlParserImpl(sql, false);
        try {
            parser.parse();
            fail();
        } catch (VariableSqlNotAllowedRuntimeException expected) {
        }
    }

}