001: /*
002: * Copyright (C) 1999-2004 <a href="mailto:mandarax@jbdietrich.com">Jens Dietrich</a>
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package org.mandarax.jdbc.server.sql;
020:
021: import java.util.*;
022: import java.util.regex.Matcher;
023: import java.util.regex.Pattern;
024:
025: import org.mandarax.kernel.ConstantTerm;
026: import org.mandarax.kernel.InferenceException;
027: import org.mandarax.kernel.VariableTerm;
028: import org.mandarax.util.regex.WildcardMatcher;
029:
030: /**
031: * Represents a simple condition used in a WHERE clause, e.g. a condition
032: * that compares two column terms.
033: * @author <A HREF="mailto:mandarax@jbdietrich.com">Jens Dietrich</A>
034: * @version 3.3.2 <29 December 2004>
035: * @since 3.0
036: */
037:
038: public class SimpleCondition extends Condition implements
039: ColumnTermContext {
040: public static final int EQUALS = 0;
041: public static final int LESS_THAN = 1;
042: public static final int GREATER_THAN = 2;
043: public static final int LESS_THAN_OR_EQUALS = 3;
044: public static final int GREATER_THAN_OR_EQUALS = 4;
045: public static final int LIKE = 5;
046: public static final int NOT_EQUALS = 6;
047: public static final Null NULL = Null.NULL;
048: private int connective = -1;
049: private List terms = new ArrayList();
050: // cached pattern for wildcard matching
051: private Pattern pattern = null;
052: private Matcher matcher = null;
053: private Map variablesByName = null;
054:
055: /**
056: * Constructor.
057: * @param connective the connective (see the respective class constants)
058: * @param condition1 the first sub condition
059: * @param condition2 the second subcondition
060: */
061: public SimpleCondition(int connective, ColumnTerm col1,
062: ColumnTerm col2) {
063: super ();
064: this .connective = connective;
065: add(col1);
066: add(col2);
067: }
068:
069: /**
070: * Constructor.
071: * @param connective the connective (see the respective class constants)
072: * @param colName a column name
073: * @param value a column value
074: */
075: public SimpleCondition(int connective, String colName, String value) {
076: super ();
077: this .connective = connective;
078: add(new ColumnName(colName));
079: add(new Value(value));
080: }
081:
082: /**
083: * Constructor.
084: * @param connective the connective (see the respective class constants)
085: */
086: public SimpleCondition(int connective) {
087: super ();
088: this .connective = connective;
089: }
090:
091: /**
092: * Constructor.
093: */
094: public SimpleCondition() {
095: super ();
096: }
097:
098: /**
099: * Compares objects.
100: * @param obj another object.
101: * @return a boolean
102: */
103: public boolean sameAs(Object obj) {
104: if (obj != null && obj instanceof SimpleCondition) {
105: SimpleCondition s = (SimpleCondition) obj;
106: boolean result = connective == s.connective;
107: result = result && terms.size() == s.terms.size();
108: if (result) {
109: for (int i = 0; i < terms.size(); i++) {
110: result = result
111: && (((ColumnTerm) terms.get(i))
112: .sameAs(s.terms.get(i)));
113: }
114: }
115: return result;
116: }
117:
118: return false;
119: }
120:
121: /**
122: * Gather the host variables.
123: * @param variables the list used to collect the variables
124: */
125: public void prepare(java.util.List variables) {
126: for (int i = 0; i < terms.size(); i++)
127: ((ColumnTerm) terms.get(i)).prepare(variables);
128: }
129:
130: /**
131: * Add a column term to this context.
132: * @param term a column term
133: */
134: public void add(ColumnTerm term) {
135: terms.add(term);
136: term.setOwner(this );
137: }
138:
139: /**
140: * Get the connective.
141: * @return the connective
142: */
143: public int getConnective() {
144: return connective;
145: }
146:
147: /**
148: * Sets the connective.
149: * @param connective The connective to set
150: */
151: public void setConnective(int connective) {
152: this .connective = connective;
153: }
154:
155: /**
156: * Normalize the object.
157: * @param typesByColumn associations between column names and (SQL) types
158: */
159: public void normalize(Map typesByColumn) {
160: if (mustSynchronizeTypes()) {
161: // look for column, get the types of this column and try to
162: // set it to constants in order to "cast" their values
163: Integer type = null;
164: Object next = null;
165: int i = 0;
166: while (type == null && i < terms.size()) {
167: next = terms.get(i);
168: if (next instanceof ColumnName) {
169: ColumnName col = (ColumnName) next;
170: type = (Integer) typesByColumn.get(col.getName());
171: }
172: i = i + 1;
173: }
174: // set types
175: if (type != null) {
176: for (i = 0; i < terms.size(); i++) {
177: next = terms.get(i);
178: if (next instanceof Value) {
179: Value val = (Value) next;
180: val.setType(type.intValue());
181: }
182: }
183: }
184: }
185:
186: }
187:
188: /**
189: * Indicates whether the types of all terms must be the same.
190: * This is not necessarily the case, e.g. it makes sense to have statements
191: * like 3.4.5 (comparing decimals and integers).
192: * TODO: an implementation that takes this into account
193: */
194: private boolean mustSynchronizeTypes() {
195: return true;
196: }
197:
198: /**
199: * Evaluate the condition.
200: * @param values an association variable terms -> values
201: * @return a boolean
202: */
203: private boolean evaluate(Map values) throws InferenceException {
204: Object v1 = bindColumnTerm(values, 0);
205: Object v2 = bindColumnTerm(values, 1);
206: if (connective == EQUALS) {
207: return v1 == NULL ? v2 == NULL : v1.equals(v2);
208: }
209: if (connective == NOT_EQUALS) {
210: return v1 == NULL ? v2 != NULL : !(v1.equals(v2));
211: }
212: if (connective == LESS_THAN) {
213: if (v1 instanceof Comparable && v2 instanceof Comparable) {
214: Comparable c1 = (Comparable) v1;
215: Comparable c2 = (Comparable) v2;
216: return c1.compareTo(c2) < 0;
217: } else
218: throw new InferenceException(
219: "Only objects that are comparable can be compared with <");
220: }
221: if (connective == LESS_THAN_OR_EQUALS) {
222: if (v1 instanceof Comparable && v2 instanceof Comparable) {
223: Comparable c1 = (Comparable) v1;
224: Comparable c2 = (Comparable) v2;
225: return c1.compareTo(c2) <= 0;
226: } else
227: throw new InferenceException(
228: "Only objects that are comparable can be compared with <=");
229: }
230: if (connective == GREATER_THAN) {
231: if (v1 instanceof Comparable && v2 instanceof Comparable) {
232: Comparable c1 = (Comparable) v1;
233: Comparable c2 = (Comparable) v2;
234: return c1.compareTo(c2) > 0;
235: } else
236: throw new InferenceException(
237: "Only objects that are comparable can be compared with >");
238: }
239: if (connective == GREATER_THAN_OR_EQUALS) {
240: if (v1 instanceof Comparable && v2 instanceof Comparable) {
241: Comparable c1 = (Comparable) v1;
242: Comparable c2 = (Comparable) v2;
243: return c1.compareTo(c2) >= 0;
244: } else
245: throw new InferenceException(
246: "Only objects that are comparable can be compared with >=");
247: }
248: if (connective == LIKE) {
249: if (pattern == null) {
250: pattern = WildcardMatcher.DB_INSTANCE
251: .getPattern((String) v2);
252: }
253: matcher = pattern.matcher((String) v1);
254: return matcher.matches();
255: }
256: throw new InferenceException(
257: "Evaluating this condition with connective #"
258: + connective + " is not yet supported");
259: }
260:
261: /**
262: * Bind the column terms. Functions (like the SQL UPPER function) are not yet supported.
263: * @param values an association column names -> values
264: * @param termNumber the term number
265: * @return a value
266: */
267: Object bindColumnTerm(Map values, int termNumber)
268: throws InferenceException {
269:
270: // bind values
271: Object t = terms.get(termNumber);
272: if (t instanceof Value)
273: return ((Value) t).getConvertedValue();
274: else if (t instanceof HostVariable) {
275: if (!((HostVariable) t).isBound())
276: throw new InferenceException("Unbound host variable");
277: else
278: return ((HostVariable) t).getValue();
279: } else if (t instanceof ColumnName) {
280: String colName = ((ColumnName) t).getName();
281: VariableTerm var = (VariableTerm) variablesByName
282: .get(colName);
283: Object value = values.get(var);
284: if (value == null)
285: throw new InferenceException(
286: "null value in Value encountered");
287: if (value instanceof ConstantTerm)
288: return ((ConstantTerm) value).getObject();
289: else
290: throw new InferenceException(
291: "Result set contains non constant term - cannot evaluate this in SQL condition");
292: } else if (t instanceof ComplexTerm1) {
293: // this is in particular the case when evaluating conditions in the HAVING clause
294: String colName = ((ComplexTerm1) t).toString();
295: VariableTerm var = (VariableTerm) variablesByName
296: .get(colName);
297: Object value = values.get(var);
298: if (value == null)
299: throw new InferenceException(
300: "null value in Value encountered");
301: if (value instanceof ConstantTerm)
302: return ((ConstantTerm) value).getObject();
303: else
304: throw new InferenceException(
305: "Result set contains non constant term - cannot evaluate this in SQL condition");
306: } else if (t == Null.NULL)
307: return NULL;
308: else
309: throw new InferenceException(
310: "Only column names, complex terms with 1 argument and values are supported column terms.");
311: }
312:
313: /**
314: * Indicates whether this is a condition for a column with a given name.
315: * @return a boolean
316: * @param name a column name
317: */
318: boolean isCondition4Column(String name) {
319: for (int i = 0; i < terms.size(); i++) {
320: if (terms.get(i) instanceof ColumnName
321: && name.equals(((ColumnName) terms.get(i))
322: .getName()))
323: return true;
324: }
325: return false;
326: }
327:
328: /**
329: * Get a column term for a certain index.
330: * @return a column term
331: * @param position an index
332: */
333: ColumnTerm getColumnTerm(int position) {
334: return (ColumnTerm) terms.get(position);
335: }
336:
337: /**
338: * Indicates whether the condition is true for an mandarax result set.
339: * @param rs a mandarax result set
340: * @return a boolean
341: */
342: public boolean isSatisfiedBy(org.mandarax.kernel.ResultSet rs)
343: throws InferenceException {
344: return isNegated() ? (!evaluate(rs.getResults())) : evaluate(rs
345: .getResults());
346: }
347:
348: /**
349: * Set variable names / variable associations.
350: * @param variablesByNames a map containing variable name -> variable term associations
351: */
352: void setVariablesByName(Map variablesByName) {
353: this .variablesByName = variablesByName;
354: }
355:
356: /**
357: * Print the object on a buffer in order to display the parsed SQL.
358: * @param out a string bufer to print on
359: */
360: public void print(StringBuffer out) {
361: ColumnTerm t1 = (ColumnTerm) terms.get(0);
362: ColumnTerm t2 = (ColumnTerm) terms.get(1);
363: if (this .isNegated())
364: out.append("NOT ");
365: t1.print(out);
366: if (connective == EQUALS)
367: out.append('=');
368: else if (connective == NOT_EQUALS)
369: out.append("!=");
370: else if (connective == LESS_THAN)
371: out.append('<');
372: else if (connective == LESS_THAN_OR_EQUALS)
373: out.append("<=");
374: else if (connective == GREATER_THAN)
375: out.append('>');
376: else if (connective == GREATER_THAN_OR_EQUALS)
377: out.append(">=");
378: else if (connective == LIKE)
379: out.append(" like ");
380: else
381: out.append(" <an unknown operator> ");
382: t2.print(out);
383: }
384: }
|