/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 Adempiere, 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. *
*****************************************************************************/
package org.compiere.dbPort;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import org.compiere.util.CLogger;
import org.compiere.util.Util;
/***
* Convert from oracle syntax to sql 92 standard
* @author Low Heng Sin
*/
public abstract class Convert_SQL92 extends Convert {
/** Logger */
private static final CLogger log = CLogger.getCLogger (Convert_SQL92.class);
/**
* Convert Outer Join.
* Converting joins can be very complex when multiple tables/keys are involved.
* The main scenarios supported are two tables with multiple key columns
* and multiple tables with single key columns.
*
{@code
* SELECT a.Col1, b.Col2 FROM tableA a, tableB b WHERE a.ID=b.ID(+)
* => SELECT a.Col1, b.Col2 FROM tableA a LEFT OUTER JOIN tableB b ON (a.ID=b.ID)
*
* SELECT a.Col1, b.Col2 FROM tableA a, tableB b WHERE a.ID(+)=b.ID
* => SELECT a.Col1, b.Col2 FROM tableA a RIGHT OUTER JOIN tableB b ON (a.ID=b.ID)
* Assumptions:
* - No outer joins in sub queries (ignores sub-queries)
* - OR condition ignored (not sure what to do, should not happen)
* Limitations:
* - Parameters for outer joins must be first - as sequence of parameters changes
* }
* @param sqlStatement
* @return converted statement
*/
protected String convertOuterJoin (String sqlStatement)
{
boolean trace = false;
//
int fromIndex = Util.findIndexOf (sqlStatement.toUpperCase(), " FROM ");
int whereIndex = Util.findIndexOf(sqlStatement.toUpperCase(), " WHERE ");
int endWhereIndex = Util.findIndexOf(sqlStatement.toUpperCase(), " GROUP BY ");
if (endWhereIndex == -1)
endWhereIndex = Util.findIndexOf(sqlStatement.toUpperCase(), " ORDER BY ");
if (endWhereIndex == -1)
endWhereIndex = sqlStatement.length();
//
if (trace)
{
if (log.isLoggable(Level.INFO)) log.info("OuterJoin<== " + sqlStatement);
}
//
String selectPart = sqlStatement.substring(0, fromIndex);
String fromPart = sqlStatement.substring(fromIndex, whereIndex);
String wherePart = sqlStatement.substring(whereIndex, endWhereIndex);
String rest = sqlStatement.substring(endWhereIndex);
// find/remove all (+) from WHERE clase ------------------------------
String newWherePart = wherePart;
ArrayList joins = new ArrayList();
int pos = newWherePart.indexOf("(+)");
while (pos != -1)
{
// find starting point
int start = newWherePart.lastIndexOf(" AND ", pos);
int startOffset = 5;
if (start == -1)
{
start = newWherePart.lastIndexOf(" OR ", pos);
startOffset = 4;
}
if (start == -1)
{
start = newWherePart.lastIndexOf("WHERE ", pos);
startOffset = 6;
}
if (start == -1)
{
String error = "Start point not found in clause " + wherePart;
log.severe(error);
m_conversionError = error;
return sqlStatement;
}
// find end point
int end = newWherePart.indexOf(" AND ", pos);
if (end == -1)
end = newWherePart.indexOf(" OR ", pos);
if (end == -1)
end = newWherePart.length();
// extract condition
String condition = newWherePart.substring(start+startOffset, end);
joins.add(condition);
if (trace)
if (log.isLoggable(Level.INFO)) log.info("->" + condition);
// new WHERE clause
newWherePart = newWherePart.substring(0, start) + newWherePart.substring(end);
//
pos = newWherePart.indexOf("(+)");
}
// correct beginning
newWherePart = newWherePart.trim();
if (newWherePart.startsWith("AND "))
newWherePart = "WHERE" + newWherePart.substring(3);
else if (newWherePart.startsWith("OR "))
newWherePart = "WHERE" + newWherePart.substring(2);
if (trace)
if (log.isLoggable(Level.INFO)) log.info("=> " + newWherePart);
// Correct FROM clause -----------------------------------------------
// Disassemble FROM
String[] fromParts = fromPart.trim().substring(4).split(",");
HashMap fromAlias = new HashMap(); // tables to be processed
HashMap fromLookup = new HashMap(); // used tabled
for (int i = 0; i < fromParts.length; i++)
{
String entry = fromParts[i].trim();
String alias = entry; // no alias
String table = entry;
int aPos = entry.lastIndexOf(' ');
if (aPos != -1)
{
alias = entry.substring(aPos+1);
table = entry.substring(0, entry.indexOf(' ')); // may have AS
}
fromAlias.put(alias, table);
fromLookup.put(alias, table);
if (trace)
if (log.isLoggable(Level.INFO)) log.info("Alias=" + alias + ", Table=" + table);
}
/** Single column
SELECT t.TableName, w.Name FROM AD_Table t, AD_Window w
WHERE t.AD_Window_ID=w.AD_Window_ID(+)
-- 275 rows
SELECT t.TableName, w.Name FROM AD_Table t
LEFT OUTER JOIN AD_Window w ON (t.AD_Window_ID=w.AD_Window_ID)
SELECT t.TableName, w.Name FROM AD_Table t, AD_Window w
WHERE t.AD_Window_ID(+)=w.AD_Window_ID
-- 239 rows
SELECT t.TableName, w.Name FROM AD_Table t
RIGHT OUTER JOIN AD_Window w ON (t.AD_Window_ID=w.AD_Window_ID)
** Multiple columns
SELECT tn.Node_ID,tn.Parent_ID,tn.SeqNo,tb.IsActive
FROM AD_TreeNode tn, AD_TreeBar tb
WHERE tn.AD_Tree_ID=tb.AD_Tree_ID(+) AND tn.Node_ID=tb.Node_ID(+)
AND tn.AD_Tree_ID=10
-- 235 rows
SELECT tn.Node_ID,tn.Parent_ID,tn.SeqNo,tb.IsActive
FROM AD_TreeNode tn LEFT OUTER JOIN AD_TreeBar tb
ON (tn.Node_ID=tb.Node_ID AND tn.AD_Tree_ID=tb.AD_Tree_ID AND tb.AD_User_ID=0)
WHERE tn.AD_Tree_ID=10
SELECT tn.Node_ID,tn.Parent_ID,tn.SeqNo,tb.IsActive
FROM AD_TreeNode tn, AD_TreeBar tb
WHERE tn.AD_Tree_ID=tb.AD_Tree_ID(+) AND tn.Node_ID=tb.Node_ID(+)
AND tn.AD_Tree_ID=10 AND tb.AD_User_ID(+)=0
-- 214 rows
SELECT tn.Node_ID,tn.Parent_ID,tn.SeqNo,tb.IsActive
FROM AD_TreeNode tn LEFT OUTER JOIN AD_TreeBar tb
ON (tn.Node_ID=tb.Node_ID AND tn.AD_Tree_ID=tb.AD_Tree_ID AND tb.AD_User_ID=0)
WHERE tn.AD_Tree_ID=10
*/
StringBuilder newFrom = new StringBuilder ();
for (int i = 0; i < joins.size(); i++)
{
Join first = new Join ((String)joins.get(i));
first.setMainTable((String)fromLookup.get(first.getMainAlias()));
fromAlias.remove(first.getMainAlias()); // remove from list
first.setJoinTable((String)fromLookup.get(first.getJoinAlias()));
fromAlias.remove(first.getJoinAlias()); // remove from list
if (trace)
if (log.isLoggable(Level.INFO)) log.info("-First: " + first);
//
if (newFrom.length() == 0)
newFrom.append(" FROM ");
else
newFrom.append(", ");
newFrom.append(first.getMainTable()).append(" ").append(first.getMainAlias())
.append(first.isLeft() ? " LEFT" : " RIGHT").append(" OUTER JOIN ")
.append(first.getJoinTable()).append(" ").append(first.getJoinAlias())
.append(" ON (").append(first.getCondition());
// keep it open - check for other key comparisons
for (int j = i+1; j < joins.size(); j++)
{
Join second = new Join ((String)joins.get(j));
second.setMainTable((String)fromLookup.get(second.getMainAlias()));
second.setJoinTable((String)fromLookup.get(second.getJoinAlias()));
if ((first.getMainTable().equals(second.getMainTable())
&& first.getJoinTable().equals(second.getJoinTable()))
|| second.isConditionOf(first) )
{
if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Second/key: " + second);
newFrom.append(" AND ").append(second.getCondition());
joins.remove(j); // remove from join list
fromAlias.remove(first.getJoinAlias()); // remove from table list
//----
for (int k = i+1; k < joins.size(); k++)
{
Join third = new Join ((String)joins.get(k));
third.setMainTable((String)fromLookup.get(third.getMainAlias()));
third.setJoinTable((String)fromLookup.get(third.getJoinAlias()));
if (third.isConditionOf(second))
{
if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Third/key: " + third);
newFrom.append(" AND ").append(third.getCondition());
joins.remove(k); // remove from join list
fromAlias.remove(third.getJoinAlias()); // remove from table list
}
else if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Third/key-skip: " + third);
}
}
else if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Second/key-skip: " + second);
}
newFrom.append(")"); // close ON
// check dependency on first table
for (int j = i+1; j < joins.size(); j++)
{
Join second = new Join ((String)joins.get(j));
second.setMainTable((String)fromLookup.get(second.getMainAlias()));
second.setJoinTable((String)fromLookup.get(second.getJoinAlias()));
if (first.getMainTable().equals(second.getMainTable()))
{
if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Second/dep: " + second);
// FROM (AD_Field f LEFT OUTER JOIN AD_Column c ON (f.AD_Column_ID = c.AD_Column_ID))
// LEFT OUTER JOIN AD_FieldGroup fg ON (f.AD_FieldGroup_ID = fg.AD_FieldGroup_ID),
newFrom.insert(6, '('); // _FROM ...
newFrom.append(')'); // add parantesis on previous relation
//
newFrom.append(second.isLeft() ? " LEFT" : " RIGHT").append(" OUTER JOIN ")
.append(second.getJoinTable()).append(" ").append(second.getJoinAlias())
.append(" ON (").append(second.getCondition());
joins.remove(j); // remove from join list
fromAlias.remove(second.getJoinAlias()); // remove from table list
// additional join columns would come here
newFrom.append(")"); // close ON
//----
for (int k = i+1; k < joins.size(); k++)
{
Join third = new Join ((String)joins.get(k));
third.setMainTable((String)fromLookup.get(third.getMainAlias()));
third.setJoinTable((String)fromLookup.get(third.getJoinAlias()));
if (second.getJoinTable().equals(third.getMainTable()))
{
if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Third-dep: " + third);
// FROM ((C_BPartner p LEFT OUTER JOIN AD_User c ON (p.C_BPartner_ID=c.C_BPartner_ID))
// LEFT OUTER JOIN C_BPartner_Location l ON (p.C_BPartner_ID=l.C_BPartner_ID))
// LEFT OUTER JOIN C_Location a ON (l.C_Location_ID=a.C_Location_ID)
newFrom.insert(6, '('); // _FROM ...
newFrom.append(')'); // add parantesis on previous relation
//
newFrom.append(third.isLeft() ? " LEFT" : " RIGHT").append(" OUTER JOIN ")
.append(third.getJoinTable()).append(" ").append(third.getJoinAlias())
.append(" ON (").append(third.getCondition());
joins.remove(k); // remove from join list
fromAlias.remove(third.getJoinAlias()); // remove from table list
// additional join columns would come here
newFrom.append(")"); // close ON
}
else if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Third-skip: " + third);
}
}
else if (trace)
if (log.isLoggable(Level.INFO)) log.info("-Second/dep-skip: " + second);
} // dependency on first table
}
// remaining Tables
Iterator it = fromAlias.keySet().iterator();
while (it.hasNext())
{
Object alias = it.next();
Object table = fromAlias.get(alias);
newFrom.append(", ").append(table);
if (!table.equals(alias))
newFrom.append(" ").append(alias);
}
if (trace)
if (log.isLoggable(Level.INFO)) log.info(newFrom.toString());
//
StringBuilder retValue = new StringBuilder (sqlStatement.length()+20);
retValue.append(selectPart)
.append(newFrom).append(" ")
.append(newWherePart).append(rest);
//
if (trace)
if (log.isLoggable(Level.INFO)) log.info("OuterJoin==> " + retValue.toString());
return retValue.toString();
} // convertOuterJoin
/**
* Converts Decode.
* {@code
* DECODE (a, 1, 'one', 2, 'two', 'none')
* => CASE WHEN a = 1 THEN 'one' WHEN a = 2 THEN 'two' ELSE 'none' END
* }
* @param sqlStatement
* @return converted statement
*/
protected String convertDecode(String sqlStatement, int fromIndex)
{
String statement = sqlStatement;
StringBuilder sb = new StringBuilder("CASE");
int index = statement.toUpperCase().indexOf("DECODE", fromIndex);
if (index <= 0) return sqlStatement;
char previousChar = statement.charAt(index - 1);
if (!(Character.isWhitespace(previousChar) || isOperator(previousChar)))
return sqlStatement;
String firstPart = statement.substring(0,index);
// find the opening (
index = index + 6;
while (index < statement.length()) {
char c = statement.charAt(index);
if (Character.isWhitespace(c)) {
index++;
continue;
}
if (c == '(') break;
return sqlStatement;
}
statement = statement.substring(index+1);
// find the expression "a" - find first , ignoring ()
index = Util.findIndexOf (statement, ',');
String expression = statement.substring(0, index).trim();
// Pairs "1, 'one',"
statement = statement.substring(index+1);
index = Util.findIndexOf (statement, ',');
while (index != -1)
{
String first = statement.substring(0, index);
char cc = statement.charAt(index);
statement = statement.substring(index+1);
//
boolean error = false;
if (cc == ',')
{
index = Util.findIndexOf (statement, ',',')');
if (index == -1)
error = true;
else
{
String second = statement.substring(0, index);
sb.append(" WHEN ").append(expression).append("=").append(first.trim())
.append(" THEN ").append(second.trim());
statement = statement.substring(index+1);
index = Util.findIndexOf (statement, ',',')');
}
}
else if (cc == ')')
{
sb.append(" ELSE ").append(first.trim()).append(" END");
index = -1;
}
else
error = true;
if (error)
{
log.log(Level.SEVERE, "SQL=(" + sqlStatement
+ ")\n====Result=(" + sb.toString()
+ ")\n====Statement=(" + statement
+ ")\n====First=(" + first
+ ")\n====Index=" + index);
m_conversionError = "Decode conversion error";
}
}
sb.append(statement);
sb.insert(0, firstPart);
return sb.toString();
} // convertDecode
/**
* Converts Delete.
*
*
* DELETE C_Order i WHERE
* => DELETE FROM C_Order WHERE
*
*
* @param sqlStatement
* @return converted statement
*/
protected String convertDelete(String sqlStatement) {
int index = sqlStatement.toUpperCase().indexOf("DELETE ");
if (index < 7) {
return "DELETE FROM " + sqlStatement.substring(index + 7);
}
return sqlStatement;
} // convertDelete
/**
* Is character a valid SQL operator
* @param c
* @return true if c is SQL operator
*/
protected boolean isOperator(char c)
{
if ('=' == c)
return true;
else if ('<' == c)
return true;
else if ('>' == c)
return true;
else if ('|' == c)
return true;
else if ('(' == c)
return true;
else if (')' == c)
return true;
else if ('+' == c)
return true;
else if ('-' == c)
return true;
else if ('*' == c)
return true;
else if ('/' == c)
return true;
else if ('!' == c)
return true;
else if (',' == c)
return true;
else if ('?' == c)
return true;
else if ('#' == c)
return true;
else if ('@' == c)
return true;
else if ('~' == c)
return true;
else if ('&' == c)
return true;
else if ('^' == c)
return true;
else if ('!' == c)
return true;
else
return false;
}
}