001: /**
002: * com.mckoi.database.interpret.TableExpressionFromSet 01 Nov 2001
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database.interpret;
024:
025: import com.mckoi.database.Variable;
026: import com.mckoi.database.TableName;
027: import com.mckoi.database.Expression;
028: import com.mckoi.database.DatabaseSystem;
029: import com.mckoi.database.DatabaseException;
030: import com.mckoi.database.StatementException;
031: import com.mckoi.database.ExpressionPreparer;
032: import com.mckoi.database.CorrelatedVariable;
033: import com.mckoi.database.DatabaseConnection;
034: import java.util.ArrayList;
035:
036: /**
037: * A set of tables and function references that make up the resources made
038: * available by a table expression. When a SelectQueriable is prepared this
039: * object is created and is used to dereference names to sources. It also
040: * has the ability to chain to another TableExpressionFromSet and resolve
041: * references over a complex sub-query hierarchy.
042: *
043: * @author Tobias Downer
044: */
045:
046: class TableExpressionFromSet {
047:
048: /**
049: * The list of table resources in this set.
050: * (FromTableInterface).
051: */
052: private ArrayList table_resources;
053:
054: /**
055: * The list of function expression resources. For example, one table
056: * expression may expose a function as 'SELECT (a + b) AS c, ....' in which
057: * case we have a virtual assignment of c = (a + b) in this set.
058: */
059: private ArrayList function_resources;
060:
061: /**
062: * The list of Variable references in this set that are exposed to the
063: * outside, including function aliases. For example,
064: * SELECT a, b, c, (a + 1) d FROM ABCTable
065: * Would be exposing variables 'a', 'b', 'c' and 'd'.
066: */
067: private ArrayList exposed_variables;
068:
069: /**
070: * Set to true if this should do case insensitive resolutions.
071: */
072: private boolean case_insensitive = false;
073:
074: /**
075: * The parent TableExpressionFromSet if one exists. This is used for
076: * chaining a set of table sets together. When chained the
077: * 'globalResolveVariable' method can be used to resolve a reference in the
078: * chain.
079: */
080: private TableExpressionFromSet parent;
081:
082: /**
083: * Constructs the object.
084: */
085: public TableExpressionFromSet(DatabaseConnection connection) {
086: table_resources = new ArrayList();
087: function_resources = new ArrayList();
088: exposed_variables = new ArrayList();
089: // Is the database case insensitive?
090: this .case_insensitive = connection.isInCaseInsensitiveMode();
091: }
092:
093: /**
094: * Sets the parent of this expression. parent can be set to null.
095: */
096: public void setParent(TableExpressionFromSet parent) {
097: this .parent = parent;
098: }
099:
100: /**
101: * Returns the parent of this set. If it has no parent it returns null.
102: */
103: public TableExpressionFromSet getParent() {
104: return parent;
105: }
106:
107: /**
108: * Toggle the case sensitivity flag.
109: */
110: public void setCaseInsensitive(boolean status) {
111: case_insensitive = status;
112: }
113:
114: boolean stringCompare(String str1, String str2) {
115: if (!case_insensitive) {
116: return str1.equals(str2);
117: }
118: return str1.equalsIgnoreCase(str2);
119: }
120:
121: /**
122: * Adds a table resource to the set.
123: */
124: public void addTable(FromTableInterface table_resource) {
125: table_resources.add(table_resource);
126: }
127:
128: /**
129: * Adds a function resource to the set. Note that is possible for there to
130: * be references in the 'expression' that do not reference resources in this
131: * set. For example, a correlated reference.
132: */
133: public void addFunctionRef(String name, Expression expression) {
134: // System.out.println("addFunctionRef: " + name + ", " + expression);
135: function_resources.add(name);
136: function_resources.add(expression);
137: }
138:
139: /**
140: * Adds a variable in this from set that is exposed to the outside. This
141: * list should contain all references from the SELECT ... part of the
142: * query. For example, SELECT a, b, (a + 1) d exposes variables
143: * a, b and d.
144: */
145: public void exposeVariable(Variable v) {
146: // System.out.println("exposeVariable: " + v);
147: // new Error().printStackTrace();
148: exposed_variables.add(v);
149: }
150:
151: /**
152: * Exposes all the columns from the given FromTableInterface.
153: */
154: public void exposeAllColumnsFromSource(FromTableInterface table) {
155: Variable[] v = table.allColumns();
156: for (int p = 0; p < v.length; ++p) {
157: exposeVariable(v[p]);
158: }
159: }
160:
161: /**
162: * Exposes all the columns in all the child tables.
163: */
164: public void exposeAllColumns() {
165: for (int i = 0; i < setCount(); ++i) {
166: exposeAllColumnsFromSource(getTable(i));
167: }
168: }
169:
170: /**
171: * Exposes all the columns from the given table name.
172: */
173: public void exposeAllColumnsFromSource(TableName tn) {
174: FromTableInterface table_interface = findTable(tn.getSchema(),
175: tn.getName());
176: if (table_interface == null) {
177: throw new StatementException("Table name found: " + tn);
178: }
179: exposeAllColumnsFromSource(table_interface);
180: }
181:
182: /**
183: * Returns a Variable[] array for each variable that is exposed in this
184: * from set. This is a list of fully qualified variables that are
185: * referencable from the final result of the table expression.
186: */
187: public Variable[] generateResolvedVariableList() {
188: int sz = exposed_variables.size();
189: Variable[] list = new Variable[sz];
190: for (int i = 0; i < sz; ++i) {
191: list[i] = new Variable((Variable) exposed_variables.get(i));
192: }
193: return list;
194: }
195:
196: /**
197: * Returns the first FromTableInterface object that matches the given schema,
198: * table reference. Returns null if no objects with the given schema/name
199: * reference match.
200: */
201: FromTableInterface findTable(String schema, String name) {
202: for (int p = 0; p < setCount(); ++p) {
203: FromTableInterface table = getTable(p);
204: if (table.matchesReference(null, schema, name)) {
205: return table;
206: }
207: }
208: return null;
209: }
210:
211: /**
212: * Returns the number of FromTableInterface objects in this set.
213: */
214: int setCount() {
215: return table_resources.size();
216: }
217:
218: /**
219: * Returns the FromTableInterface object at the given index position in this
220: * set.
221: */
222: FromTableInterface getTable(int i) {
223: return (FromTableInterface) table_resources.get(i);
224: }
225:
226: /**
227: * Dereferences a fully qualified reference that is within this set. For
228: * example, SELECT ( a + b ) AS z given 'z' would return the expression
229: * (a + b).
230: * <p>
231: * Returns null if unable to dereference assignment because it does not
232: * exist.
233: */
234: Expression dereferenceAssignment(Variable v) {
235: TableName tname = v.getTableName();
236: String var_name = v.getName();
237: // We are guarenteed not to match with a function if the table name part
238: // of a Variable is present.
239: if (tname != null) {
240: return null;
241: }
242:
243: // Search for the function with this name
244: Expression last_found = null;
245: int matches_found = 0;
246: for (int i = 0; i < function_resources.size(); i += 2) {
247: String fun_name = (String) function_resources.get(i);
248: if (stringCompare(fun_name, var_name)) {
249: if (matches_found > 0) {
250: throw new StatementException(
251: "Ambiguous reference '" + v + "'");
252: }
253: last_found = (Expression) function_resources.get(i + 1);
254: ++matches_found;
255: }
256: }
257:
258: return last_found;
259: }
260:
261: /**
262: * Resolves the given Variable object to an assignment if it's possible to do
263: * so within the context of this set. If the variable can not be
264: * unambiguously resolved to a function or aliased column, a
265: * StatementException is thrown. If the variable isn't assigned to any
266: * function or aliased column, 'null' is returned.
267: */
268: private Variable resolveAssignmentReference(Variable v) {
269: TableName tname = v.getTableName();
270: String var_name = v.getName();
271: // We are guarenteed not to match with a function if the table name part
272: // of a Variable is present.
273: if (tname != null) {
274: return null;
275: }
276:
277: // Search for the function with this name
278: Variable last_found = null;
279: int matches_found = 0;
280: for (int i = 0; i < function_resources.size(); i += 2) {
281: String fun_name = (String) function_resources.get(i);
282: if (stringCompare(fun_name, var_name)) {
283: if (matches_found > 0) {
284: throw new StatementException(
285: "Ambiguous reference '" + v + "'");
286: }
287: last_found = new Variable(fun_name);
288: ++matches_found;
289: }
290: }
291:
292: return last_found;
293: }
294:
295: /**
296: * Resolves the given Variable against the table columns in this from set.
297: * If the variable does not resolve to anything 'null' is returned. If the
298: * variable is ambiguous, a StatementException is thrown.
299: * <p>
300: * Note that the given variable does not have to be fully qualified but the
301: * returned expressions are fully qualified.
302: */
303: Variable resolveTableColumnReference(Variable v) {
304: TableName tname = v.getTableName();
305: String sch_name = null;
306: String tab_name = null;
307: String col_name = v.getName();
308: if (tname != null) {
309: sch_name = tname.getSchema();
310: tab_name = tname.getName();
311: }
312:
313: // Find matches in our list of tables sources,
314: Variable matched_var = null;
315:
316: for (int i = 0; i < table_resources.size(); ++i) {
317: FromTableInterface table = (FromTableInterface) table_resources
318: .get(i);
319: int rcc = table.resolveColumnCount(null, sch_name,
320: tab_name, col_name);
321: if (rcc == 0) {
322: // do nothing if no matches
323: } else if (rcc == 1 && matched_var == null) {
324: // If 1 match and matched_var = null
325: matched_var = table.resolveColumn(null, sch_name,
326: tab_name, col_name);
327: } else { // if (rcc >= 1 and matched_var != null)
328: System.out.println(matched_var);
329: System.out.println(rcc);
330: throw new StatementException("Ambiguous reference '"
331: + v + "'");
332: }
333: }
334:
335: return matched_var;
336: }
337:
338: /**
339: * Resolves the given Variable object to a fully resolved Variable
340: * within the context of this table expression. If the variable does not
341: * resolve to anything 'null' is returned. If the variable is ambiguous, a
342: * StatementException is thrown.
343: * <p>
344: * If the variable name references a table column, an expression with a
345: * single Variable element is returned. If the variable name references a
346: * function, an expression of the function is returned.
347: * <p>
348: * Note that the given variable does not have to be fully qualified but the
349: * returned expressions are fully qualified.
350: */
351: Variable resolveReference(Variable v) {
352: // Try and resolve against alias names first,
353: ArrayList list = new ArrayList();
354:
355: // Expression exp = dereferenceAssignment(v);
356: // // If this is an alias like 'a AS b' then add 'a' to the list instead of
357: // // adding 'b'. This allows us to handle a number of ambiguous conditions.
358: // if (exp != null) {
359: // Variable v2 = exp.getVariable();
360: // if (v2 != null) {
361: // list.add(resolveTableColumnReference(v2));
362: // }
363: // else {
364: // list.add(resolveAssignmentReference(v));
365: // }
366: // }
367:
368: Variable function_var = resolveAssignmentReference(v);
369: if (function_var != null) {
370: list.add(function_var);
371: }
372:
373: Variable tc_var = resolveTableColumnReference(v);
374: if (tc_var != null) {
375: list.add(tc_var);
376: }
377:
378: // TableName tname = v.getTableName();
379: // String sch_name = null;
380: // String tab_name = null;
381: // String col_name = v.getName();
382: // if (tname != null) {
383: // sch_name = tname.getSchema();
384: // tab_name = tname.getName();
385: // }
386: //
387: // // Find matches in our list of tables sources,
388: // for (int i = 0; i < table_resources.size(); ++i) {
389: // FromTableInterface table = (FromTableInterface) table_resources.get(i);
390: // int rcc = table.resolveColumnCount(null, sch_name, tab_name, col_name);
391: // if (rcc == 1) {
392: // Variable matched =
393: // table.resolveColumn(null, sch_name, tab_name, col_name);
394: // list.add(matched);
395: // }
396: // else if (rcc > 1) {
397: // throw new StatementException("Ambiguous reference '" + v + "'");
398: // }
399: // }
400:
401: // Return the variable if we found one unambiguously.
402: int list_size = list.size();
403: if (list_size == 0) {
404: return null;
405: } else if (list_size == 1) {
406: return (Variable) list.get(0);
407: } else {
408: // // Check if the variables are the same?
409: // Variable cv = (Variable) list.get(0);
410: // for (int i = 1; i < list.size(); ++i) {
411: // if (!cv.equals(list.get(i))) {
412: throw new StatementException("Ambiguous reference '" + v
413: + "'");
414: // }
415: // }
416: // // If they are all the same return the variable.
417: // return v;
418: }
419:
420: }
421:
422: /**
423: * Resolves the given Variable reference within the chained list of
424: * TableExpressionFromSet objects to a CorrelatedVariable. If the reference
425: * is not found in this set the method recurses to the parent set. The first
426: * unambiguous reference is returned.
427: * <p>
428: * If resolution is ambiguous within a set, a StatementException is thrown.
429: * <p>
430: * Returns null if the reference could not be resolved.
431: */
432: CorrelatedVariable globalResolveReference(int level, Variable v) {
433: Variable nv = resolveReference(v);
434: if (nv == null && getParent() != null) {
435: // If we need to descend to the parent, increment the level.
436: return getParent().globalResolveReference(level + 1, v);
437: } else if (nv != null) {
438: return new CorrelatedVariable(nv, level);
439: }
440: return null;
441: }
442:
443: /**
444: * Attempts to qualify the given Variable object to a value found either
445: * in this from set, or a value in the parent from set. A variable that
446: * is qualified by the parent is called a correlated variable. Any
447: * correlated variables that are successfully qualified are returned as
448: * CorrelatedVariable objects.
449: */
450: Object qualifyVariable(Variable v_in) {
451: Variable v = resolveReference(v_in);
452: if (v == null) {
453: // If not found, try and resolve in parent set (correlated)
454: if (getParent() != null) {
455: CorrelatedVariable cv = getParent()
456: .globalResolveReference(1, v_in);
457: if (cv == null) {
458: throw new StatementException("Reference '" + v_in
459: + "' not found.");
460: }
461: return cv;
462: }
463: if (v == null) {
464: throw new StatementException("Reference '" + v_in
465: + "' not found.");
466: }
467: }
468: return v;
469: }
470:
471: /**
472: * Returns an ExpressionPreparer that qualifies all variables in an
473: * expression to either a qualified Variable or a CorrelatedVariable object.
474: */
475: ExpressionPreparer expressionQualifier() {
476: return new ExpressionPreparer() {
477: public boolean canPrepare(Object element) {
478: return element instanceof Variable;
479: }
480:
481: public Object prepare(Object element)
482: throws DatabaseException {
483: return qualifyVariable((Variable) element);
484: }
485: };
486: }
487:
488: }
|