001: /**
002: * com.mckoi.database.interpret.Statement 09 Sep 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.jdbc.SQLQuery;
026: import com.mckoi.database.*;
027: import com.mckoi.debug.DebugLogger;
028: import java.util.*;
029:
030: /**
031: * Provides a set of useful utility functions to use by all the
032: * interpretted statements.
033: *
034: * @author Tobias Downer
035: */
036:
037: public abstract class Statement {
038:
039: /**
040: * The Database context.
041: */
042: protected DatabaseConnection database;
043:
044: /**
045: * The user context.
046: */
047: protected User user;
048:
049: /**
050: * The StatementTree object that is the container for the query.
051: */
052: protected StatementTree cmd;
053:
054: /**
055: * The SQLQuery object that was used to produce this statement.
056: */
057: protected SQLQuery query;
058:
059: /**
060: * The list of all FromTableInterface objects of resources referenced in
061: * this query. (FromTableInterface)
062: */
063: protected Vector table_list = new Vector();
064:
065: /**
066: * Returns a DebugLogger object used to log debug commands.
067: */
068: public final DebugLogger Debug() {
069: return database.Debug();
070: }
071:
072: /**
073: * Resets this statement so it may be re-prepared and evaluated again.
074: * Useful for repeating a query multiple times.
075: */
076: void reset() {
077: database = null;
078: user = null;
079: table_list = new Vector();
080: }
081:
082: /**
083: * Performs initial preparation on the contents of the StatementTree by
084: * resolving all sub queries and mapping functions to their executable
085: * forms.
086: * <p>
087: * Given a StatementTree and a Database context, this method will convert
088: * all sub-queries found in the StatementTree to a Queriable object. In
089: * other words, all StatementTree are converted to Select objects. The given
090: * 'database' object is used as the session to prepare the sub-queries
091: * against.
092: * <p>
093: * This is called after 'init' and before 'prepare'.
094: */
095: public final void resolveTree() throws DatabaseException {
096:
097: // For every expression in this select we must go through and resolve
098: // any sub-queries we find to the correct Select object.
099: // This method will prepare the sub-query substitute the StatementTree
100: // object for a Select object in the expression.
101: ExpressionPreparer preparer = new ExpressionPreparer() {
102: public boolean canPrepare(Object element) {
103: return element instanceof StatementTree;
104: }
105:
106: public Object prepare(Object element)
107: throws DatabaseException {
108: StatementTree stmt_tree = (StatementTree) element;
109: Select stmt = new Select();
110: stmt.init(database, stmt_tree, null);
111: stmt.resolveTree();
112: stmt.prepare();
113: return stmt;
114: }
115: };
116: cmd.prepareAllExpressions(preparer);
117:
118: }
119:
120: /**
121: * Given a fully resolved table name ( eg. Part.id ) this method will
122: * attempt to find the Table object that the column is in.
123: */
124: FromTableInterface findTableWithColumn(Variable column_name) {
125: for (int i = 0; i < table_list.size(); ++i) {
126: FromTableInterface table = (FromTableInterface) table_list
127: .elementAt(i);
128: TableName tname = column_name.getTableName();
129: String sch_name = null;
130: String tab_name = null;
131: String col_name = column_name.getName();
132: if (tname != null) {
133: sch_name = tname.getSchema();
134: tab_name = tname.getName();
135: }
136: int rcc = table.resolveColumnCount(null, sch_name,
137: tab_name, col_name);
138: if (rcc > 0) {
139: return table;
140: }
141: }
142: return null;
143: }
144:
145: /**
146: * Given a fully resolved table name ( eg. Part.id ) this returns true if
147: * there is a table with the given column name, otherwise false.
148: * <p>
149: * NOTE: Intended to be overwritten...
150: */
151: boolean existsTableWithColumn(Variable column_name) {
152: return findTableWithColumn(column_name) != null;
153: }
154:
155: /**
156: * Overwrite this method if your statement has some sort of column aliasing
157: * capability (such as a select statement). Returns a list of all fully
158: * qualified Variables that match the alias name, or an empty list if no
159: * matches found.
160: * <p>
161: * By default, returns an empty list.
162: */
163: ArrayList resolveAgainstAliases(Variable alias_name) {
164: return new ArrayList(0);
165: }
166:
167: /**
168: * Resolves a TableName string (eg. 'Customer' 'APP.Customer' ) to a
169: * TableName object. If the schema part of the table name is not present
170: * then it is set to the current schema of the database connection. If the
171: * database is ignoring the case then this will correctly resolve the table
172: * to the cased version of the table name.
173: */
174: TableName resolveTableName(String name, DatabaseConnection db) {
175: return db.resolveTableName(name);
176: }
177:
178: /**
179: * Returns the first FromTableInterface object that matches the given schema,
180: * table reference. Returns null if no objects with the given schema/name
181: * reference match.
182: */
183: FromTableInterface findTableInQuery(String schema, String name) {
184: for (int p = 0; p < table_list.size(); ++p) {
185: FromTableInterface table = (FromTableInterface) table_list
186: .get(p);
187: if (table.matchesReference(null, schema, name)) {
188: return table;
189: }
190: }
191: return null;
192: }
193:
194: /**
195: * Attempts to resolve an ambiguous column name such as 'id' into a
196: * Variable from the tables in this statement.
197: */
198: Variable resolveColumn(Variable v) {
199: // Try and resolve against alias names first,
200: ArrayList list = new ArrayList();
201: list.addAll(resolveAgainstAliases(v));
202:
203: TableName tname = v.getTableName();
204: String sch_name = null;
205: String tab_name = null;
206: String col_name = v.getName();
207: if (tname != null) {
208: sch_name = tname.getSchema();
209: tab_name = tname.getName();
210: }
211:
212: int matches_found = 0;
213: // Find matches in our list of tables sources,
214: for (int i = 0; i < table_list.size(); ++i) {
215: FromTableInterface table = (FromTableInterface) table_list
216: .elementAt(i);
217: int rcc = table.resolveColumnCount(null, sch_name,
218: tab_name, col_name);
219: if (rcc == 1) {
220: Variable matched = table.resolveColumn(null, sch_name,
221: tab_name, col_name);
222: list.add(matched);
223: } else if (rcc > 1) {
224: throw new StatementException("Ambiguous column name ("
225: + v + ")");
226: }
227: }
228:
229: int total_matches = list.size();
230: if (total_matches == 0) {
231: throw new StatementException("Can't find column: " + v);
232: } else if (total_matches == 1) {
233: return (Variable) list.get(0);
234: } else if (total_matches > 1) {
235: // if there more than one match, check if they all match the identical
236: // resource,
237: throw new StatementException("Ambiguous column name (" + v
238: + ")");
239: } else {
240: // Should never reach here but we include this exception to keep the
241: // compiler happy.
242: throw new Error("Negative total matches?");
243: }
244:
245: }
246:
247: /**
248: * Given a Variable object, this will resolve the name into a column name
249: * the database understands (substitutes aliases, etc).
250: */
251: public Variable resolveVariableName(Variable v) {
252: return resolveColumn(v);
253: }
254:
255: /**
256: * Given an Expression, this will run through the expression and resolve
257: * any variable names via the 'resolveVariableName' method here.
258: */
259: void resolveExpression(Expression exp) {
260: // NOTE: This gets variables in all function parameters.
261: List vars = exp.allVariables();
262: for (int i = 0; i < vars.size(); ++i) {
263: Variable v = (Variable) vars.get(i);
264: Variable to_set = resolveVariableName(v);
265: v.set(to_set);
266: }
267: }
268:
269: /**
270: * Add an FromTableInterface that is used within this query. These tables
271: * are used when we try to resolve a column name.
272: */
273: protected void addTable(FromTableInterface table) {
274: table_list.addElement(table);
275: }
276:
277: /**
278: * Sets up internal variables for this statement for derived classes to use.
279: * This is called before 'prepare' and 'isExclusive' is called.
280: * <p>
281: * It is assumed that any ? style parameters in the StatementTree will have
282: * been resolved previous to a call to this method.
283: *
284: * @param db the DatabaseConnection that will execute this statement.
285: * @param stree the StatementTree that contains the parsed content of the
286: * statement being executed.
287: */
288: public final void init(DatabaseConnection db, StatementTree stree,
289: SQLQuery query) {
290: this .database = db;
291: this .user = db.getUser();
292: this .cmd = stree;
293: this .query = query;
294: }
295:
296: /**
297: * Prepares the statement with the given Database object. This is called
298: * before the statement is evaluated. The prepare statement queries the
299: * database and resolves information about the statement (for example, it
300: * resolves column names and aliases and determines the tables that are
301: * touched by this statement so we can lock the appropriate tables before
302: * we evaluate).
303: * <p>
304: * NOTE: Care must be taken to ensure that all methods called here are safe
305: * in as far as modifications to the data occuring. The rules for
306: * safety should be as follows. If the database is in EXCLUSIVE mode,
307: * then we need to wait until it's switched back to SHARED mode before
308: * this method is called.
309: * All collection of information done here should not involve any table
310: * state info. except for column count, column names, column types, etc.
311: * Queries such as obtaining the row count, selectable scheme information,
312: * and certainly 'getCellContents' must never be called during prepare.
313: * When prepare finishes, the affected tables are locked and the query is
314: * safe to 'evaluate' at which time table state is safe to inspect.
315: */
316: public abstract void prepare() throws DatabaseException;
317:
318: /**
319: * Evaluates the statement and returns a table that represents the result
320: * set. This is called after 'prepare'.
321: */
322: public abstract Table evaluate() throws DatabaseException,
323: TransactionException;
324:
325: }
|