//============================================================================== // QueryExpr.java //============================================================================== package tribble.parse.sql; // System imports import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.lang.Character; import java.lang.Cloneable; import java.lang.CloneNotSupportedException; import java.lang.Integer; import java.lang.Exception; import java.lang.String; import java.lang.StringBuffer; import java.util.HashMap; // Local imports import tribble.io.ParseTreeI; /******************************************************************************* * Query expression tree. * *
* An object of this type is produced by a {@link QueryParser} object, which * parses an SQL-like expression (i.e., an expression similar to an SQL * 'SELECT WHERE' clause) and produces an expression tree * in the form of a {@link QueryExpr} object. * * *
* Some example query expressions and the expression trees resulting from parsing * them: * *
*
Query | *Expression Tree | *
---|---|
* * exists ** |
*
* * val * "exists" ** |
*
* * not directory ** |
*
* * not * val * "directory" ** |
*
* * not directory or writable ** |
*
* * or * not * val * "directory" * val * "writable" ** |
*
* * name like "Sarah%Conner" and * ( birth_date >= '1996-06-14' ) ** |
*
* * and * like * "name" * "\"Sarah%Conner\"" * ge * "birth_date" * "'1996-06-14'" ** |
*
* * interest >= 5.000 and * interest < 10.000 ** |
*
* * and * ge * "interest" * "5.000" * lt * "interest" * "10.000" ** |
*
* * rec.id between 'A000' and 'B999' ** |
*
* * between * "rec.id" * and * "'A000'" * "'B999'" ** |
*
* * pay-type in ( 'WK' 'HR' 'YR' ) ** |
*
* * in * "pay-type" * list * "'WK'" * list * "'HR'" * list * "'YR'" ** |
*
* * addr is not null ** |
*
* * not * is * "addr" * "null" ** |
*
* * rec.pay[mon] ** |
*
* * subscr * member * "rec" * "pay" * "mon" ** |
*
* Copyright ©2001 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by
* by the United States Department of State as a terrorist, or terrorist
* government or agency, to use and distribute this source code provided
* that the original copyright notice remains present and unaltered.
*
* @see QueryParser
*/
public class QueryExpr
///+TEMP: implements tribble.io.ParseTreeI, java.lang.Cloneable
implements java.lang.Cloneable
{
// Identification
/** Revision information. */
static final String REV =
"@(#)tribble/parse/sql/QueryExpr.java $Revision: 1.10 $ $Date: 2007/08/01 03:14:02 $\n";
public static final int SERIES = 200;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public constants
//----------------------------------
// Operator types
public static final String OP_NOP = "nop"; // Ext
public static final String OP_VALUE = "val";
public static final String OP_NOT = "not";
public static final String OP_AND = "and";
public static final String OP_OR = "or";
public static final String OP_LIST = "list";
public static final String OP_IS = "is";
public static final String OP_EQ = "eq";
public static final String OP_NE = "ne";
public static final String OP_LT = "lt";
public static final String OP_LE = "le";
public static final String OP_GT = "gt";
public static final String OP_GE = "ge";
public static final String OP_EXPO = "exp";
public static final String OP_MUL = "mul";
public static final String OP_DIV = "div";
public static final String OP_MOD = "mod";
public static final String OP_ADD = "add";
public static final String OP_SUB = "sub";
public static final String OP_CONCAT = "concat";
public static final String OP_POS = "pos";
public static final String OP_NEG = "neg";
public static final String OP_MEMBER = "member";
public static final String OP_SUBSCR = "subscr";
public static final String OP_CONTAINS = "contains";
public static final String OP_LIKE = "like";
public static final String OP_LIKEFILE = "likefile"; // Ext
public static final String OP_IN = "in";
public static final String OP_BETWEEN = "between";
public static final String OP_NULL = "null";
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Package private constants
static final String OP_LP = "(";
static final String OP_RP = ")";
static final String OP_SUBSCR2 = "]";
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Public static methods
/***************************************************************************
* Determine if a string matches a pattern string.
*
* @param pat
* An SQL pattern string.
* A pattern is composed of regular characters and
* special pattern matching characters, which are:
*
* For example, given the query expression: *
* len > 80 and rec.type not = 'T'* * the following XML is generated for it: *
* <query> * <and> * <gt> * <arg>len</arg> * <arg>80</arg> * </gt> * <not> * <eq> * <member> * <arg>rec</arg> * <arg>type</arg> * </member> * <arg>'T'</arg> * </eq> * </not> * </and> * </query>* * * @param out * Output stream to write to. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @since 1.6, 2001-04-02 */ public void writeAsXml(Writer out) throws IOException { BufferedWriter os; if (out instanceof BufferedWriter) os = (BufferedWriter) out; else os = new BufferedWriter(out); // Write this expression tree as XML os.write("
* Replaces the following query subexpressions with equivalent, but simpler, * subexpressions: *
* "X between A and B" replaced with "X >= A and X <= B" * "X in ( A, B, C )" replaced with "X = A or X = B or X = C" * "not X = A" replaced with "X <> A" * "not not X = A" replaced with "X = A"* *
* In other words, the following subexpression trees are replaced with * simpler equivalent subtrees: * *
*
Before | *After | *
---|---|
* * between * "rec_id" * and * "'A000'" * "'B999'" ** |
*
*
* * and * ge * "rec_id" * "'A000'" * le * "rec_id" * "'B999'" ** |
*
* * in * "pay-type" * list * "'WK'" * list * "'HR'" * list * "'YR'" ** |
*
*
* * or * eq * "pay-type" * "'WK'" * or * eq * "pay-type" * "'HR'" * eq * "pay-type" * "'YR'" ** |
*
* * not * gt * "Amt" * "25.00" ** |
*
*
* * le * "Amt" * "25.00" ** |
*
* * not * not * eq * "Amt" * "50.00" ** |
*
*
* * eq * "Amt" * "50.00" ** |
*
* This method is called by methods {@link #dump(Writer)} and * {@link #dump(OutputStream)}. * *
* Example *
* Parsing the expression: *
* name like "sarah%conner" and ( birth.date >= '1996-06-14' )* results in an expression tree that is printed like this: *
* and * like * name * "sarah%conner" * ge * birth.date * '1996-06-14'* * * @param out * An output stream to write to. * * @param indent * Leading indentation. * * @see #dump(Writer) * @see #dump(OutputStream) * * @since 1.1, 2001-03-12 */ protected void dumpTree(PrintWriter out, String indent) { // Print the node operator out.println(indent + (m_op == null ? "op:
* This method is called by method {@link #toString}. * * @param text * A string buffer to append the text string representation of this * subexpression to. * * @see #toString * * @since 1.1, 2001-03-26 */ protected void buildString(StringBuffer text) { // Stringize and append this expression subtree to the text string if (m_op == OP_VALUE && m_arg2 == null) { buildString(m_arg1, text); } else if (m_op == OP_NOT) { text.append("not ("); if (m_arg1 != null) buildString(m_arg1, text); else text.append("null?"); if (m_arg2 != null) { text.append("("); buildString(m_arg2, text); text.append(")?"); } text.append(")"); } else if (m_op == OP_AND) { if (m_arg1 != null) { if (m_arg1 instanceof QueryExpr && ((QueryExpr) m_arg1).m_op == OP_OR) { text.append('('); buildString(m_arg1, text); text.append(')'); } else buildString(m_arg1, text); } else text.append("null?"); text.append(" and "); if (m_arg2 != null) { if (m_arg2 instanceof QueryExpr && ((QueryExpr) m_arg2).m_op == OP_OR) { text.append('('); buildString(m_arg2, text); text.append(')'); } else buildString(m_arg2, text); } } else if (m_op == OP_POS || m_op == OP_NEG) { text.append(m_op == OP_POS ? "+" : "-"); if (m_arg1 != null) buildString(m_arg1, text); else text.append("null?"); } else { String op; String op2 = null; if (m_op == null) op = " nullop? "; else if (m_op == OP_EQ) op = " = "; else if (m_op == OP_NE) op = " <> "; else if (m_op == OP_LT) op = " < "; else if (m_op == OP_LE) op = " <= "; else if (m_op == OP_GT) op = " > "; else if (m_op == OP_GE) op = " >= "; else if (m_op == OP_ADD) op = " + "; else if (m_op == OP_SUB) op = " - "; else if (m_op == OP_CONCAT) op = " || "; else if (m_op == OP_MUL) op = " * "; else if (m_op == OP_DIV) op = " / "; else if (m_op == OP_MOD) op = " mod "; else if (m_op == OP_EXPO) op = " ** "; else if (m_op == OP_MEMBER) op = "."; else if (m_op == OP_SUBSCR) { op = "["; op2 = "]"; } else if (m_op == OP_IN) { op = " in ("; op2 = ")"; } else if (m_op == OP_LIST) { op = ""; if (m_arg2 != null) op = ", "; } else op = ' ' + m_op + ' '; if (m_arg1 != null) buildString(m_arg1, text); else text.append("null?"); text.append(op); if (m_arg2 != null) buildString(m_arg2, text); if (op2 != null) text.append(op2); } } /*************************************************************************** * Format an operand, appending it to a string buffer. * *
* This method is called by method {@link #toString}. * * @param arg * An operand to append, which is either a String or an * {@link QueryExpr} subtree. if it is a String, it will be * delimited with quotes as necessary. * * @param text * A string buffer to append the text string representation of this * subexpression to. * * @see #toString * @see #buildString(StringBuffer) * * @since 1.3, 2001-03-30 */ protected void buildString(Object arg, StringBuffer text) { if (arg instanceof String) { String s; // Append the string literal operand to the text string s = (String) arg; text.append(asQuotedOperand(s)); } else ((QueryExpr) arg).buildString(text); } /*************************************************************************** * Print this query expression tree as XML. * *
* This method is called by method {@link #writeAsXml(Writer) writeAsXml()}. * * @param out * Output stream to write to. * * @param indent * Indentation prefix for each line of XML text output. * * @throws IOException * Thrown if an I/O (write) error occurs. * * @see #writeAsXml(Writer) writeAsXml * * @since 1.6, 2001-04-02 */ protected void writeAsXml(BufferedWriter out, String indent) throws IOException { // Write this expression tree as XML out.write(indent); out.write('<'); out.write(m_op); out.write('>'); out.newLine(); if (m_arg1 != null || m_arg2 != null) { String indent2; indent2 = indent + " "; if (m_arg1 != null) { if (m_arg1 instanceof String) writeStringAsXml(out, indent2, (String) m_arg1); else if (m_arg1 instanceof QueryExpr) ((QueryExpr) m_arg1).writeAsXml(out, indent2); } if (m_arg2 != null) { if (m_arg2 instanceof String) writeStringAsXml(out, indent2, (String) m_arg2); else if (m_arg2 instanceof QueryExpr) ((QueryExpr) m_arg2).writeAsXml(out, indent2); } } out.write(indent); out.write(""); out.write(m_op); out.write('>'); out.newLine(); } /*************************************************************************** * Print a query expression string argument tree as XML. * *
* This method is called by method {@link #writeAsXml(Writer) writeAsXml()}.
*
* @param out
* Output stream to write to.
*
* @param indent
* Indentation prefix for each line of XML text output.
*
* @param arg
* An expression operand from a query expression tree.
*
* @throws IOException
* Thrown if an I/O (write) error occurs.
*
* @see #writeAsXml(Writer) writeAsXml
*
* @since 1.6, 2001-04-02
*/
protected void writeStringAsXml(BufferedWriter out, String indent,
String arg)
throws IOException
{
int len;
char quote = '"';
// Write a single expression operand as XML text
out.write(indent);
out.write("