/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
*****************************************************************************/
package org.compiere.model;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.logging.Level;
import org.compiere.util.CLogger;
/**
* Parse FROM in SQL WHERE clause
*
* @author Jorg Janke
* @version $Id: AccessSqlParser.java,v 1.3 2006/07/30 00:58:36 jjanke Exp $
*
* @author Teo Sarca, SC ARHIPAC SERVICE SRL
*
BF [ 1652623 ] AccessSqlParser.getTableInfo(String) - tablename parsing bug
* BF [ 1964496 ] AccessSqlParser is not parsing well JOIN CLAUSE
* BF [ 2840157 ] AccessSqlParser is not parsing well ON keyword
* https://sourceforge.net/p/adempiere/bugs/2042/
*/
public class AccessSqlParser
{
/**
* Base Constructor.
* You need to set the SQL and start the parsing manually.
*/
public AccessSqlParser ()
{
} // AccessSqlParser
/**
* Full Constructor
* @param sql sql command
*/
public AccessSqlParser (String sql)
{
setSql(sql);
} // AccessSqlParser
/** FROM String */
private static final String FROM = " FROM ";
private static final int FROM_LENGTH = FROM.length();
private static final String WHERE = " WHERE ";
private static final String ON = " ON ";
/** Logger */
private static final CLogger log = CLogger.getCLogger(AccessSqlParser.class);
/** Original SQL */
private String m_sqlOriginal;
/** SQL Selects */
private String[] m_sql;
/** List of Arrays */
private ArrayList m_tableInfo = new ArrayList();
/**
* Set Sql and parse it
* @param sql sql
*/
public void setSql (String sql)
{
if (sql == null)
throw new IllegalArgumentException("No SQL");
m_sqlOriginal = sql;
int index = m_sqlOriginal.indexOf("\nFROM ");
if (index != -1)
m_sqlOriginal = m_sqlOriginal.replace("\nFROM ", FROM);
index = m_sqlOriginal.indexOf("\nWHERE ");
if (index != -1)
m_sqlOriginal = m_sqlOriginal.replace("\nWHERE ", WHERE);
//
parse();
} // setSQL
/**
* Get (original) Sql
* @return sql
*/
public String getSql()
{
return m_sqlOriginal;
} // getSql
/**
* Parse Original SQL.
* Called from setSql or Constructor.
* @return true if parsed successfully
*/
public boolean parse()
{
if (m_sqlOriginal == null || m_sqlOriginal.length() == 0)
throw new IllegalArgumentException("No SQL");
getSelectStatements();
// analyse each select
for (int i = 0; i < m_sql.length; i++)
{
TableInfo[] info = getTableInfo(m_sql[i].trim());
m_tableInfo.add(info);
}
//
if (log.isLoggable(Level.FINE)) log.fine(toString());
return m_tableInfo.size() > 0;
} // parse
/**
* Parses {@link #m_sqlOriginal} and creates Array of {@link #m_sql} statements
*/
private void getSelectStatements()
{
String[] sqlIn = new String[] {m_sqlOriginal};
String[] sqlOut = null;
try
{
sqlOut = getSubSQL (sqlIn);
}
catch (Exception e)
{
log.log(Level.SEVERE, m_sqlOriginal, e);
throw new IllegalArgumentException(m_sqlOriginal);
}
// a sub-query was found
while (sqlIn.length != sqlOut.length)
{
sqlIn = sqlOut;
try
{
sqlOut = getSubSQL (sqlIn);
}
catch (Exception e)
{
log.log(Level.SEVERE, m_sqlOriginal, e);
throw new IllegalArgumentException(sqlOut.length + ": "+ m_sqlOriginal);
}
}
m_sql = sqlOut;
} // getSelectStatements
/**
* Get Sub SQL of sql statements
* @param sqlIn array of input sql
* @return array of resulting sql
*/
private String[] getSubSQL (String[] sqlIn)
{
ArrayList list = new ArrayList();
for (int sqlIndex = 0; sqlIndex < sqlIn.length; sqlIndex++)
{
String sql = sqlIn[sqlIndex];
int index = sql.indexOf("(SELECT ", 7);
while (index != -1)
{
int endIndex = index+1;
int parenthesisLevel = 0;
// search for the end of the sql
while (endIndex++ < sql.length())
{
char c = sql.charAt(endIndex);
if (c == ')')
{
if (parenthesisLevel == 0)
break;
else
parenthesisLevel--;
}
else if (c == '(')
parenthesisLevel++;
}
String subSQL = sql.substring(index, endIndex+1);
list.add(subSQL);
// remove inner SQL (##)
sql = sql.substring(0,index+1) + "##"
+ sql.substring(endIndex);
index = sql.indexOf("(SELECT ", 7);
}
list.add(sql); // last SQL
}
String[] retValue = new String[list.size()];
list.toArray(retValue);
return retValue;
} // getSubSQL
/**
* Get Table Info for SQL
* @param sql sql
* @return array of table info for sql
*/
private TableInfo[] getTableInfo (String sql)
{
ArrayList list = new ArrayList();
// remove ()
if (sql.startsWith("(") && sql.endsWith(")"))
sql = sql.substring(1,sql.length()-1);
int fromIndex = sql.indexOf(FROM);
if (fromIndex != sql.lastIndexOf(FROM))
log.log(Level.WARNING, "More than one FROM clause - " + sql);
while (fromIndex != -1)
{
String from = sql.substring(fromIndex+FROM_LENGTH);
int index = from.lastIndexOf(WHERE); // end at where
if (index != -1)
from = from.substring(0, index);
from = from.replaceAll("[\r\n\t ]+AS[\r\n\t ]+", " ");
from = from.replaceAll("[\r\n\t ]+as[\r\n\t ]+", " ");
from = from.replaceAll("[\r\n\t ]+INNER[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+LEFT[\r\n\t ]+OUTER[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+RIGHT[\r\n\t ]+OUTER[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+FULL[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+LEFT[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+RIGHT[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+JOIN[\r\n\t ]+", ", ");
from = from.replaceAll("[\r\n\t ]+[Oo][Nn][\r\n\t ]+", ON); // teo_sarca, BF [ 2840157 ]
// Remove ON clause - assumes that there is no IN () in the clause
index = from.indexOf(ON);
while (index != -1)
{
int indexClose = getIndexClose(from); // does not catch "IN (1,2)" in ON
int indexNextOn = from.indexOf(ON, index+4);
if (indexNextOn != -1)
indexClose = from.lastIndexOf(')', indexNextOn);
if (indexClose != -1)
{
if (index > indexClose)
{
throw new IllegalStateException("Could not remove (index="+index+" > indexClose="+indexClose+") - "+from);
}
from = from.substring(0, index) + from.substring(indexClose+1);
}
else
{
log.log(Level.SEVERE, "Could not remove ON " + from);
break;
}
index = from.indexOf(ON);
}
StringTokenizer tableST = new StringTokenizer (from, ",");
while (tableST.hasMoreTokens())
{
String tableString = tableST.nextToken().trim();
StringTokenizer synST = new StringTokenizer (tableString, " \r\n\t"); // teo_sarca [ 1652623 ] AccessSqlParser.getTableInfo(String) - tablename parsing bug
TableInfo tableInfo = null;
if (synST.countTokens() > 1)
tableInfo = new TableInfo(synST.nextToken(), synST.nextToken());
else
tableInfo = new TableInfo(tableString);
list.add(tableInfo);
}
//
sql = sql.substring(0, fromIndex);
fromIndex = sql.lastIndexOf(FROM);
}
TableInfo[] retValue = new TableInfo[list.size()];
list.toArray(retValue);
return retValue;
} // getTableInfo
/**
* String Representation
* @return info
*/
public String toString()
{
StringBuilder sb = new StringBuilder("AccessSqlParser[");
if (m_tableInfo == null)
sb.append(m_sqlOriginal);
else
{
for (int i = 0; i < m_tableInfo.size(); i++)
{
if (i > 0)
sb.append("|");
TableInfo[] info = (TableInfo[])m_tableInfo.get(i);
for (int ii = 0; ii < info.length; ii++)
{
if (ii > 0)
sb.append(",");
sb.append(info[ii].toString());
}
}
}
sb.append("|").append(getMainSqlIndex());
sb.append("]");
return sb.toString();
} // toString
/**
* Get Table Info.
* @param index record index
* @return table info
*/
public TableInfo[] getTableInfo (int index)
{
if (index < 0 || index > m_tableInfo.size())
return null;
TableInfo[] retValue = (TableInfo[])m_tableInfo.get(index);
return retValue;
} // getTableInfo
/**
* Get Sql Statements
* @param index record index
* @return index index of query
*/
public String getSqlStatement (int index)
{
if (index < 0 || index > m_sql.length)
return null;
return m_sql[index];
} // getSqlStatement
/**
* Get No of SQL Statements
* @return FROM clause count
*/
public int getNoSqlStatments()
{
if (m_sql == null)
return 0;
return m_sql.length;
} // getNoSqlStatments
/**
* Get index of main Statements
* @return index of main statement or -1 if not found
*/
public int getMainSqlIndex()
{
if (m_sql == null)
return -1;
else if (m_sql.length == 1)
return 0;
for (int i = m_sql.length-1; i >= 0; i--)
{
if (m_sql[i].charAt(0) != '(')
return i;
}
return -1;
} // getMainSqlIndex
/**
* Get main sql Statement
* @return main statement
*/
public String getMainSql()
{
if (m_sql == null)
return m_sqlOriginal;
if (m_sql.length == 1)
return m_sql[0];
for (int i = m_sql.length-1; i >= 0; i--)
{
if (m_sql[i].charAt(0) != '(')
return m_sql[i];
}
return "";
} // getMainSql
/**
* Get index of ')'
* @return index of ')'
*/
public int getIndexClose(String from)
{
int parenthesisLevel = 0;
int indexOpen=from.indexOf('(');
int index=-1;
while(indexOpen!=-1)
{
parenthesisLevel++;
int indexNextOpen = from.indexOf('(', indexOpen+1);
int indexClose = from.indexOf(')',indexOpen+1);
if (indexNextOpen==-1 || indexClose0)
{
index = from.indexOf(')',index+1);
parenthesisLevel--;
}
return index;
}
/**
* Table Info VO
*/
public static class TableInfo
{
/**
* Constructor
* @param tableName table
* @param synonym synonym
*/
public TableInfo (String tableName, String synonym)
{
m_tableName = tableName;
m_synonym = synonym;
} // TableInfo
/**
* Short Constructor - no synonym
* @param tableName table
*/
public TableInfo (String tableName)
{
this (tableName, null);
} // TableInfo
private String m_tableName;
private String m_synonym;
/**
* Get Table Synonym
* @return synonym
*/
public String getSynonym()
{
if (m_synonym == null)
return "";
return m_synonym;
} // getSynonym
/**
* Get TableName
* @return table name
*/
public String getTableName()
{
return m_tableName;
} // getTableName
/**
* String Representation
* @return info
*/
public String toString()
{
StringBuilder sb = new StringBuilder(m_tableName);
if (getSynonym().length() > 0)
sb.append("=").append(m_synonym);
return sb.toString();
} // toString
} // TableInfo
} // AccessSqlParser