package javaxt.sql;
//******************************************************************************
//** SQL Parser
//******************************************************************************
/**
* Used to parse and modify SQL Select Statements ("Select * from MyTable").
* Other SQL commands are not supported (insert, update, create, etc.).
*
* Note that this implementation is incomplete and needs a lot more testing.
* For example, the parser does not yet handle unions, subselects, and
* having clauses. Other potential problems include arithmetic operators in
* the where clause ("where posting_time + 60 > sysdate").
*
******************************************************************************/
public class Parser {
private java.util.HashMap sql = new java.util.HashMap();
private static String[] sqlOperators =
new String[]{"IS NULL","IS NOT NULL","BETWEEN","CONTAINS","LIKE","<>","<=",">=","=","<",">","IN","MATCHES","SOME","NOT EXISTS","EXISTS"};
private static String[] sqlKeywords =
new String[]{"SELECT","FROM","WHERE","ORDER BY","GROUP BY","HAVING","LIMIT","OFFSET"};
private static String[] joinTypes = new String[]{
//Order is important!
"RIGHT OUTER JOIN",
"RIGHT INNER JOIN",
"FULL OUTER JOIN",
"LEFT OUTER JOIN",
"LEFT INNER JOIN",
"NATURAL JOIN",
"RIGHT JOIN",
"INNER JOIN",
"OUTER JOIN",
"CROSS JOIN",
"LEFT JOIN",
"JOIN"
};
private SelectStatement[] selectStatements = null;
private WhereStatement[] whereStatements = null;
private OrderByStatement[] orderByStatements = null;
private GroupByStatement[] groupByStatements = null;
private FromStatement fromStatement = null;
public String getSelectString() { return (String)sql.get("SELECT"); }
public String getFromString() { return (String)sql.get("FROM"); }
public String getWhereString() { return (String)sql.get("WHERE"); }
public String getOrderByString(){ return (String)sql.get("ORDER BY"); }
public String getGroupByString() { return (String)sql.get("GROUP BY"); }
public String getHavingString() { return (String)sql.get("HAVING"); }
public String getOffsetString() { return (String)sql.get("OFFSET"); }
public String getLimitString() { return (String)sql.get("LIMIT"); }
//**************************************************************************
//** clone
//**************************************************************************
public Parser clone(){
Parser parser = new Parser();
java.util.HashMap sql = new java.util.HashMap();
java.util.Iterator it = this.sql.keySet().iterator();
while (it.hasNext()){
Object key = it.next();
Object val = this.sql.get(key);
sql.put(key, val);
}
parser.sql = sql;
parser.fromStatement = fromStatement;
if (selectStatements!=null){
SelectStatement[] arr = new SelectStatement[selectStatements.length];
for (int i=0; i0) a = s.substring(i-1,i);
if (i+keyword.length()+10){
Object prevStatement = this.sql.get(previousKeyword);
if (prevStatement==null) {
this.sql.put(previousKeyword, entry);
}
this.sql.put(currentKeyword, null);
}
phrase = new StringBuffer();
i = i + (keyword.length()-1);
previousKeyword = currentKeyword;
keywords.remove(j);
break;
}
}
}
}
}
if (i==(s.length()-1)){
if (phrase.toString().trim().equals(s)){
}
else{
this.sql.put(previousKeyword, phrase.toString().trim());
}
}
}//end parsing text
}
// //**************************************************************************
// //** addWhere
// //**************************************************************************
// /** Used to update the where clause in the SQL String. Preserves existing
// * where clause, if one exists, by adding an "AND" statement.
// */
// public String addWhereStatement(String whereClause){
//
// if (whereClause!=null){
// whereClause = whereClause.trim();
// if (whereClause.length()>0){
// String orgWhere = this.getWhereString();
// if (orgWhere==null || orgWhere.trim().length()==0){
// setWhere(whereClause);
// }
// else{
// setWhere("(" + orgWhere.trim() + ") AND (" + whereClause + ")");
// }
// }
// }
//
// return this.toString();
// }
//**************************************************************************
//** setSelect
//**************************************************************************
/** Used to update the select clause. Returns an updated SQL statement.
*/
public String setSelect(String selectClause){
if (selectClause==null) selectClause = "*";
else{
selectClause = selectClause.trim();
if (selectClause.length()==0) selectClause = "*";
}
sql.put("SELECT", selectClause);
selectStatements = null;
getSelectStatements();
return this.toString();
}
//**************************************************************************
//** setFrom
//**************************************************************************
/** Used to update the from clause in the SQL String. The entire from
* clause will be replaced with the given string. Returns an updated SQL
* statement.
*/
public String setFrom(String fromClause){
if (fromClause!=null){
fromClause = fromClause.trim();
if (fromClause.length()==0) fromClause = null;
}
sql.put("FROM", fromClause);
this.fromStatement = null;
getFromStatement();
return this.toString();
}
//**************************************************************************
//** setWhere
//**************************************************************************
/** Used to update the where clause in the SQL String. The entire where
* clause will be replaced with the given string. Returns an updated SQL
* statement.
*/
public String setWhere(String whereClause){
if (whereClause!=null){
whereClause = whereClause.trim();
if (whereClause.length()==0) whereClause = null;
}
sql.put("WHERE", whereClause);
this.whereStatements = null;
getWhereStatements();
return this.toString();
}
//**************************************************************************
//** setOrderBy
//**************************************************************************
/** Used to update the order by clause in the SQL String. The entire order
* by clause will be replaced with the given string. Returns an updated SQL
* statement.
*/
public String setOrderBy(String orderByClause){
if (orderByClause!=null){
orderByClause = orderByClause.trim();
if (orderByClause.length()==0) orderByClause = null;
}
sql.put("ORDER BY", orderByClause);
this.orderByStatements = null;
getOrderByStatements();
return this.toString();
}
//**************************************************************************
//** setGroupBy
//**************************************************************************
/** Used to update the group by clause in the SQL String. The entire group
* by clause will be replaced with the given string. Returns an updated SQL
* statement.
*/
public String setGroupBy(String groupByClause){
if (groupByClause!=null){
groupByClause = groupByClause.trim();
if (groupByClause.length()==0) groupByClause = null;
}
sql.put("GROUP BY", groupByClause);
this.groupByStatements = null;
getGroupByStatements();
return this.toString();
}
//**************************************************************************
//** setOffset
//**************************************************************************
/** Used to update the offset clause in the SQL String. Returns an updated
* SQL statement.
*/
public String setOffset(Integer offset){
if (offset==null || offset<0){
sql.remove("OFFSET");
}
else{
sql.put("OFFSET", offset);
}
return this.toString();
}
//**************************************************************************
//** setLimit
//**************************************************************************
/** Used to update the limit clause in the SQL String. Returns an updated
* SQL statement.
*/
public String setLimit(Integer limit){
if (limit==null || limit<0){
sql.remove("LIMIT");
}
else{
sql.put("LIMIT", limit);
}
return this.toString();
}
//**************************************************************************
//** toString
//**************************************************************************
/** Returns an sql String, including any updates
*/
public String toString() {
String selectClause = this.getSelectString();
String fromClause = this.getFromString();
if (selectClause==null || fromClause==null){
return null;
}
else if(selectClause.length()<=0 || fromClause.length()<=0){
return null;
}
else{
StringBuffer sql = new StringBuffer();
sql.append("SELECT "); sql.append(selectClause);
sql.append(" FROM "); sql.append(fromClause);
String whereClause = this.getWhereString();
if (whereClause!=null) sql.append(" WHERE " + whereClause);
String orderByClause = this.getOrderByString();
if (orderByClause!=null) sql.append(" ORDER BY " + orderByClause);
String groupByClause = this.getGroupByString();
if (groupByClause!=null) sql.append(" GROUP BY " + groupByClause);
String havingClause = this.getHavingString();
if (havingClause!=null) sql.append(" HAVING " + havingClause);
String offsetClause = this.getOffsetString();
if (offsetClause!=null) sql.append(" OFFSET " + offsetClause);
String limitClause = this.getLimitString();
if (limitClause!=null) sql.append(" LIMIT " + limitClause);
return sql.toString();
}
}
//**************************************************************************
//** getSelectStatements
//**************************************************************************
/** Used to break down the select clause into individual elements. For
* example, "Select FirstName, LastName from Contacts" would return an
* array with 2 entries: "FirstName" and "LastName".
*/
public SelectStatement[] getSelectStatements(){
if (selectStatements!=null) return selectStatements;
else{
String[] array = this.split(getSelectString());
selectStatements = new SelectStatement[array.length];
for (int i=0; i=0 && i+3<=s.length()){
prevChar = s.substring(i-1,i);
nextChar = s.substring(i+2,i+3);
}
if ((prevChar.equals(" ") || prevChar.equals(")") || prevChar.equals("]") || prevChar.equals("\"")) &&
(nextChar.equals(" ") || nextChar.equals("(") || nextChar.equals("[") || nextChar.equals("\"")))
{
this.alias = removeParentheses(as);
this.field = s.substring(0,i-1).trim();
this.columnName = stripFunctions(field);
}
}
}
}
}//end parsing text
}//end if
//Check whether the field equals the column name. If not, then there
//is a function present in the expression
if (!field.equals(columnName)) isFunction = true;
//Iterate throught the list of operands and identify any exposed columns (columns that are not wrapped in quotes)
String[] elements = new String[]{columnName, alias};
for (int i=0; i0){
phrase = new StringBuffer();
i = i + (keyword.length()-1);
fromStatement.addEntry(entry);
//System.out.println("(+) " + entry + " (" + keyword + ")");
}
break;
}
}
}
}
if (i==(s.length()-1)){
if (phrase.toString().trim().equals(s)){
}
else{
String entry = phrase.toString().trim();
fromStatement.addEntry(entry);
}
}
}//end parsing text
}
else{
String[] tables = fromClause.split(",");
for (int i=0; i " + tableName + " [" + joinCondition + "]");
}
public java.util.HashSet getExposedElements(){ return exposedElements; }
public String[] getTables(){
String[] array = new String[tables.size()];
java.util.Iterator it = tables.iterator();
int i=0;
while (it.hasNext()){
String tableName = (String)it.next();
array[i] = tableName;
i++;
}
return array;
}
public String toString(){ return statement; }
}
//**************************************************************************
//** getTables
//**************************************************************************
/** Returns an array of Tables Found in the SQL String */
public String[] getTables(){
return getFromStatement().getTables();
}
//**************************************************************************
//** getWhereStatements
//**************************************************************************
/** Used to retrieve a list of where statements found in the where clause.
* Returns an empty array if no where statements are found.
*/
public WhereStatement[] getWhereStatements(){
if (this.whereStatements!=null) return this.whereStatements;
String whereClause = this.getWhereString();
if (whereClause==null || whereClause.trim().length()<=0) {
return new WhereStatement[0];
}
else{
//Create a list of sql fragments to parse
java.util.List list = new java.util.LinkedList();
//Add Where Clause to the list of sql fragments to parse
list.add(whereClause);
//Iterate through all the sql fragments and extract individual statements
for (int x=0; x0){
list.add(entry);
phrase = new StringBuffer();
i = i + (keyword.length()-1);
//System.out.println("(+) " + entry);
}
}
}
if (i==(s.length()-1)){
if (phrase.toString().trim().equals(s)){ //<--might need to check the size of the list as well
}
else{
String entry = phrase.toString().trim();
//entry = phrase.substring(0, phrase.length()-1).trim();
entry = removeParentheses(entry);
list.add(entry);
list.remove(x);
//System.out.println("(+) " + entry + " *");
//System.out.println("(-) " + s);
}
}
}//end parsing text
}
//Create a list of possible SQL Operators found in the Where clause
java.util.List operators = new java.util.LinkedList();
for (int i=0; i0) a = s.substring(i-1,i);
if (i+keyword.length()+1") || keyword.equals("<=") || keyword.equals(">=") ||
keyword.equals("=") || keyword.equals("<") || keyword.equals(">")
)
){
String entry = phrase.substring(0, phrase.length()-1).trim();
i = i + (keyword.length()-1);
phrase = new StringBuffer();
if (entry.length()>0){
list.add(entry);
this.operator = keyword;
this.leftOperand = entry;
}
else{
//Things like "NOT EXISTS" statements
this.operator = keyword;
this.leftOperand = null;
}
break;
}
}
}
}
}
if (i==(s.length()-1)){
if (phrase.toString().trim().equals(s)){
}
else{
String entry = phrase.toString().trim();
if (entry.length()>0){
if (!entry.equalsIgnoreCase("null")){
list.add(entry);
}
//System.out.println("(+) " + entry + " **");
//System.out.println("(-) " + s);
this.rightOperand = entry;
}
}
}
}//end parsing text
//Iterate throught the list of operands and identify any exposed columns (columns that are not wrapped in quotes)
exposedColumns = new java.util.LinkedList();
for (int i=0; i0){
for (int i=0; i0){
for (int i=0; i0){
for (int i=0; i0){
exposedElements.clear();
for (int i=0; i0){
exposedElements.clear();
for (int i=0; i0){
exposedElements.clear();
for (int i=0; i0){
if (c.equalsIgnoreCase(exposedElement.substring(0,1)) && (i+exposedElement.length())<=s.length()){
String str = s.substring(i,i+exposedElement.length());
if (str.equalsIgnoreCase(exposedElement)){
str = phrase.substring(0, phrase.length()-1);
String prevChar = "";
String nextChar = "";
if (i-1>=0 && i+exposedElement.length()+1<=s.length()){
prevChar = s.substring(i-1,i);
nextChar = s.substring(i+exposedElement.length(),i+exposedElement.length()+1);
}
//System.out.println("[" + prevChar + "]" + exposedElement + "[" + nextChar + "]");
if (wrapElement(prevChar, nextChar)){
newSQL.append(str + "\"" + exposedElement + "\"");
phrase = new StringBuffer();
i = i + (exposedElement.length()-1);
//For efficiency, remove any unused elements from the exposedElements HashSet
if (i+exposedElement.length()<=s.length()){
if (s.substring(i+exposedElement.length()).toUpperCase().contains(exposedElement.toUpperCase())==false){
exposedElements.remove(exposedElement);
it = exposedElements.iterator();
//System.out.println("(-) " + exposedElement);
}
}
break;
}
}
}
}
}
}
if (i==(s.length()-1)){
if (phrase.toString().trim().equals(s)){
}
else{
newSQL.append(phrase.toString());
}
}
}//end parsing text
return newSQL.toString();
}
//**************************************************************************
//** wrapElement
//**************************************************************************
/** Used to help determine whether to add quotes around a block of text. */
private boolean wrapElement(String prevChar, String nextChar){
if ((prevChar.equals("") || prevChar.equals(" ") || prevChar.equals(")") || prevChar.equals("(") || prevChar.equals(",") || prevChar.equals("=") || prevChar.equals(">") || prevChar.equals("<")) &&
(nextChar.equals("") || nextChar.equals(" ") || nextChar.equals(")") || nextChar.equals("(") || nextChar.equals(",") || nextChar.equals("=") || nextChar.equals(">") || nextChar.equals("<")))
{
return true;
}
else{
return false;
}
}
//**************************************************************************
//** stripFunctions (getColumnName)
//**************************************************************************
/** Used to strip functions from an SQL fragment and return the column name.
* For example, "SUM(Sales.SaleAmount)" would return "Sales.SaleAmount". In
* cases where the function uses multiple parameters, the first parameter
* is assumed to be the column name (or alias). For example,
* "SUBSTR(LastName,0,1)" would return "LastName".
*/
private String stripFunctions(String str){
boolean FunctionsPresent = true;
if (
(InStr(str, "(") > 0 && InStr(str, ")") > 0) &&
(InStr(str, "(") < InStr(str, ")"))
)
{
while (FunctionsPresent){
str = Right(str, Len(str) - InStrRev(str, "("));
str = Left(str, InStrRev(str, ")") - 1);
str = str.trim();
/*
'Special Case and one BIG assumption:
'if the resultant str has a comma, then the
'function has multiple parameters. The assumption
'is that the first parameter is the column name
*/
if (InStr(str, ",") > 0) {
str = Left(str, InStr(str, ",") - 1);
str = str.trim();
}
if (InStr(str, "(") == 0 && InStr(str, ")") == 0) {
FunctionsPresent = false;
}
}
}
return str;
}
//**************************************************************************
//** Legacy VB String Functions
//**************************************************************************
private int InStr(String str, String ch){return str.indexOf(ch)+1;}
private int InStrRev(String str, String ch){return str.lastIndexOf(ch)+1;}
private int Len(String str){return str.length();}
private String Left(String str, int n){return str.substring(0,n);}
private String Right(String str, int n){ int iLen = str.length(); return str.substring(iLen - n, iLen);}
private boolean isNumeric(String str){
try{
Double.valueOf(str).doubleValue();
return true;
}
catch(Exception e){
return false;
}
}
//**************************************************************************
//** removeParentheses
//**************************************************************************
/** Used to remove the outer paratheses surrounding a block of text. */
private String removeParentheses(String text){
text = text.trim();
if (text.startsWith("(") && text.endsWith(")")){
boolean insideSingleQuotes = false;
boolean insideDoubleQuotes = false;
boolean insideParenthesis = false;
int parenthesis = 0;
int gaps = 0;
String s = text;
String c = "";
for (int i = 0; i < s.length(); i++){
c = s.substring(i,i+1);
if (c.equals("\"")){
if (!insideDoubleQuotes) insideDoubleQuotes = true;
else insideDoubleQuotes = false;
}
if (c.equals("'")){
if (!insideSingleQuotes) insideSingleQuotes = true;
else insideSingleQuotes = false;
}
if ((c.equals("(") && !insideParenthesis) && (!insideDoubleQuotes && !insideSingleQuotes)) {
insideParenthesis = true;
parenthesis = 0;
}
if ((c.equals("(") ) && (!insideDoubleQuotes && !insideSingleQuotes)) {
parenthesis += 1;
}
if ((c.equals(")") && insideParenthesis) && (!insideDoubleQuotes && !insideSingleQuotes)){
parenthesis = parenthesis - 1;
if (parenthesis==0) {
insideParenthesis = false;
if (i+1 < s.length()){
String nextString = s.substring(i+1).trim();
if (!nextString.startsWith("(")) gaps++;
}
}
}
}
if (gaps==0) text = text.substring(1, text.length()-1).trim();
}
return text;
}
//**************************************************************************
//** Split
//**************************************************************************
/** Used to break down the select clause into individual elements. For
* example, "Select FirstName, LastName from Contacts" would return an
* array with 2 entries: "FirstName" and "LastName".
*/
private String[] split(String statement){
if (statement==null || statement.length()<=0) return new String[0];
else statement = statement.trim();
if (statement.contains(",")==false){
return new String[]{statement};
}
else{
boolean insideSingleQuotes = false;
boolean insideDoubleQuotes = false;
boolean insideParenthesis = false;
int parenthesis = 0;
String s = statement + " "; //<-- something's not quite right b/c we need this extra space at the end...
String c = "";
StringBuffer str = new StringBuffer();
java.util.List list = new java.util.LinkedList();
for (int i = 0; i < s.length(); i++){
c = s.substring(i,i+1);
if (c.equals("\"")){
if (!insideDoubleQuotes) insideDoubleQuotes = true;
else insideDoubleQuotes = false;
}
if (c.equals("'")){
if (!insideSingleQuotes) insideSingleQuotes = true;
else insideSingleQuotes = false;
}
if ((c.equals("(") && !insideParenthesis) && (!insideDoubleQuotes && !insideSingleQuotes)) {
insideParenthesis = true;
parenthesis = 0;
}
if ((c.equals("(") ) && (!insideDoubleQuotes && !insideSingleQuotes)) {
parenthesis += 1;
}
if ((c.equals(")") && insideParenthesis) && (!insideDoubleQuotes && !insideSingleQuotes)){
parenthesis = parenthesis - 1;
if (parenthesis==0) insideParenthesis = false;
}
str.append(c);
if (c.equals(",") || i==(s.length()-1)){
if (!insideDoubleQuotes && !insideSingleQuotes && !insideParenthesis){
String element = str.substring(0,str.length()-1).toString().trim();
list.add(element);
str = new StringBuffer();
}
}
}//end for loop
//Convert list into an array of statements
String[] statements = new String[list.size()];
for (int i=0; i0){
System.out.println();
System.out.println("-----------------------------------------");
System.out.println("WHERE Statements");
System.out.println("-----------------------------------------");
for (int i=0; i0){
System.out.println();
System.out.println("-----------------------------------------");
System.out.println("ORDER BY Statements");
System.out.println("-----------------------------------------");
for (int i=0; i0){
System.out.println();
System.out.println("-----------------------------------------");
System.out.println("GROUP BY Statements");
System.out.println("-----------------------------------------");
for (int i=0; i