001: /**
002: * com.mckoi.database.ViewManager 20 Mar 2003
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;
024:
025: import java.util.HashMap;
026: import com.mckoi.database.global.BlobAccessor;
027: import com.mckoi.database.jdbc.SQLQuery;
028: import com.mckoi.util.IntegerVector;
029:
030: /**
031: * A DatabaseConnection view manager. This controls adding, updating, deleting,
032: * and processing views inside the system view table.
033: *
034: * @author Tobias Downer
035: */
036:
037: public class ViewManager {
038:
039: /**
040: * The DatabaseConnection.
041: */
042: private DatabaseConnection connection;
043:
044: /**
045: * The context.
046: */
047: private DatabaseQueryContext context;
048:
049: /**
050: * Set to true when the connection makes changes to the view table through
051: * this manager.
052: */
053: private boolean view_table_changed;
054:
055: /**
056: * A local cache of ViewDef objects mapped by row id in the system view
057: * table. This cache is invalidated when changes are committed to the system
058: * view table.
059: */
060: private HashMap local_cache;
061:
062: /**
063: * Constructs the ViewManager for a DatabaseConnection.
064: */
065: ViewManager(DatabaseConnection connection) {
066: this .connection = connection;
067: this .context = new DatabaseQueryContext(connection);
068: this .local_cache = new HashMap();
069: this .view_table_changed = false;
070:
071: // Attach a cache backed on the VIEW table which will invalidate the
072: // connection cache whenever the view table is modified.
073: connection.attachTableBackedCache(new TableBackedCache(
074: Database.SYS_VIEW) {
075: public void purgeCacheOfInvalidatedEntries(
076: IntegerVector added_rows, IntegerVector removed_rows) {
077: // If there were changed then invalidate the cache
078: if (view_table_changed) {
079: invalidateViewCache();
080: view_table_changed = false;
081: }
082: // Otherwise, if there were committed added or removed changes also
083: // invalidate the cache,
084: else if ((added_rows != null && added_rows.size() > 0)
085: || (removed_rows != null && removed_rows.size() > 0)) {
086: invalidateViewCache();
087: }
088: }
089: });
090:
091: }
092:
093: /**
094: * Returns the local cache of ViewDef objects. This cache is mapped from
095: * row_id to view object. The cache is invalidated when changes are
096: * committed to the system view table.
097: */
098: private HashMap getViewCache() {
099: return local_cache;
100: }
101:
102: /**
103: * Invalidates the view cache.
104: */
105: private void invalidateViewCache() {
106: local_cache.clear();
107: }
108:
109: /**
110: * Given the SYS_VIEW table, this returns a new table that contains the
111: * entry with the given view name, or an empty result if the view is not
112: * found.
113: * Generates an error if more than 1 entry found.
114: */
115: private Table findViewEntry(DataTable table, TableName view_name) {
116:
117: Operator EQUALS = Operator.get("=");
118:
119: Variable schemav = table.getResolvedVariable(0);
120: Variable namev = table.getResolvedVariable(1);
121:
122: Table t = table.simpleSelect(context, namev, EQUALS,
123: new Expression(TObject.stringVal(view_name.getName())));
124: t = t.exhaustiveSelect(context, Expression.simple(schemav,
125: EQUALS, TObject.stringVal(view_name.getSchema())));
126:
127: // This should be at most 1 row in size
128: if (t.getRowCount() > 1) {
129: throw new RuntimeException(
130: "Assert failed: multiple view entries for "
131: + view_name);
132: }
133:
134: // Return the entries found.
135: return t;
136:
137: }
138:
139: /**
140: * Returns true if the view with the given name exists.
141: */
142: public boolean viewExists(TableName view_name) {
143:
144: DataTable table = connection.getTable(Database.SYS_VIEW);
145: return findViewEntry(table, view_name).getRowCount() == 1;
146:
147: }
148:
149: /**
150: * Defines a view. If the view with the name has not been defined it is
151: * defined. If the view has been defined then it is overwritten with this
152: * information.
153: *
154: * @param view information that defines the view.
155: * @param query the query that forms the view.
156: * @param user the user that owns this view being defined.
157: */
158: public void defineView(ViewDef view, SQLQuery query, User user)
159: throws DatabaseException {
160:
161: DataTableDef data_table_def = view.getDataTableDef();
162: DataTable view_table = connection.getTable(Database.SYS_VIEW);
163:
164: TableName view_name = data_table_def.getTableName();
165:
166: // Create the view record
167: RowData rdat = new RowData(view_table);
168: rdat.setColumnDataFromObject(0, data_table_def.getSchema());
169: rdat.setColumnDataFromObject(1, data_table_def.getName());
170: rdat.setColumnDataFromObject(2, query.serializeToBlob());
171: rdat.setColumnDataFromObject(3, view.serializeToBlob());
172: rdat.setColumnDataFromObject(4, user.getUserName());
173:
174: // Find the entry from the view that equals this name
175: Table t = findViewEntry(view_table, view_name);
176:
177: // Delete the entry if it already exists.
178: if (t.getRowCount() == 1) {
179: view_table.delete(t);
180: }
181:
182: // Insert the new view entry in the system view table
183: view_table.add(rdat);
184:
185: // Notify that this database object has been successfully created.
186: connection.databaseObjectCreated(view_name);
187:
188: // Change to the view table
189: view_table_changed = true;
190:
191: }
192:
193: /**
194: * Deletes the view with the given name, or returns false if no entries were
195: * deleted from the view table.
196: */
197: public boolean deleteView(TableName view_name)
198: throws DatabaseException {
199:
200: DataTable table = connection.getTable(Database.SYS_VIEW);
201:
202: // Find the entry from the view table that equal this name
203: Table t = findViewEntry(table, view_name);
204:
205: // No entries so return false
206: if (t.getRowCount() == 0) {
207: return false;
208: }
209:
210: table.delete(t);
211:
212: // Notify that this database object has been successfully dropped.
213: connection.databaseObjectDropped(view_name);
214:
215: // Change to the view table
216: view_table_changed = true;
217:
218: // Return that 1 or more entries were dropped.
219: return true;
220: }
221:
222: /**
223: * Creates a ViewDef object for the given view name in the table. The
224: * access is cached through the given HashMap object.
225: * <p>
226: * We assume the access to the cache is limited to the current thread
227: * calling this method. We don't synchronize over the cache at any time.
228: */
229: private static ViewDef getViewDef(HashMap cache,
230: TableDataSource view_table, TableName view_name) {
231:
232: RowEnumeration e = view_table.rowEnumeration();
233: while (e.hasMoreRows()) {
234: int row = e.nextRowIndex();
235:
236: String c_schema = view_table.getCellContents(0, row)
237: .getObject().toString();
238: String c_name = view_table.getCellContents(1, row)
239: .getObject().toString();
240:
241: if (view_name.getSchema().equals(c_schema)
242: && view_name.getName().equals(c_name)) {
243:
244: Object cache_key = new Long(row);
245: ViewDef view_def = (ViewDef) cache.get(cache_key);
246:
247: if (view_def == null) {
248: // Not in the cache, so deserialize it and put it in the cache.
249: BlobAccessor blob = (BlobAccessor) view_table
250: .getCellContents(3, row).getObject();
251: // Derserialize the blob
252: view_def = ViewDef.deserializeFromBlob(blob);
253: // Put this in the cache....
254: cache.put(cache_key, view_def);
255:
256: }
257: return view_def;
258: }
259:
260: }
261:
262: throw new StatementException("View '" + view_name
263: + "' not found.");
264:
265: }
266:
267: /**
268: * Creates a ViewDef object for the given index value in the table. The
269: * access is cached through the given HashMap object.
270: * <p>
271: * We assume the access to the cache is limited to the current thread
272: * calling this method. We don't synchronize over the cache at any time.
273: */
274: private static ViewDef getViewDef(HashMap cache,
275: TableDataSource view_table, int index) {
276:
277: RowEnumeration e = view_table.rowEnumeration();
278: int i = 0;
279: while (e.hasMoreRows()) {
280: int row = e.nextRowIndex();
281:
282: if (i == index) {
283: Object cache_key = new Long(row);
284: ViewDef view_def = (ViewDef) cache.get(cache_key);
285:
286: if (view_def == null) {
287: // Not in the cache, so deserialize it and put it in the cache.
288: BlobAccessor blob = (BlobAccessor) view_table
289: .getCellContents(3, row).getObject();
290: // Derserialize the blob
291: view_def = ViewDef.deserializeFromBlob(blob);
292: // Put this in the cache....
293: cache.put(cache_key, view_def);
294:
295: }
296: return view_def;
297: }
298:
299: ++i;
300: }
301: throw new Error("Index out of range.");
302: }
303:
304: /**
305: * Returns a freshly deserialized QueryPlanNode object for the given view
306: * object.
307: */
308: public QueryPlanNode createViewQueryPlanNode(TableName view_name) {
309: DataTable table = connection.getTable(Database.SYS_VIEW);
310: return getViewDef(local_cache, table, view_name)
311: .getQueryPlanNode();
312: }
313:
314: /**
315: * Returns an InternalTableInfo object used to model the list of views
316: * that are accessible within the given Transaction object. This is used to
317: * model all views as regular tables accessible within a transaction.
318: * <p>
319: * Note that the 'ViewManager' parameter can be null if there is no backing
320: * view manager. The view manager is intended as a cache to improve the
321: * access speed of the manager.
322: */
323: static InternalTableInfo createInternalTableInfo(
324: ViewManager manager, Transaction transaction) {
325: return new ViewInternalTableInfo(manager, transaction);
326: }
327:
328: // ---------- Inner classes ----------
329:
330: /**
331: * An object that models the list of views as table objects in a
332: * transaction.
333: */
334: private static class ViewInternalTableInfo extends
335: AbstractInternalTableInfo2 {
336:
337: ViewManager view_manager;
338: HashMap view_cache;
339:
340: ViewInternalTableInfo(ViewManager manager,
341: Transaction transaction) {
342: super (transaction, Database.SYS_VIEW);
343: this .view_manager = manager;
344: if (view_manager == null) {
345: view_cache = new HashMap();
346: } else {
347: view_cache = view_manager.getViewCache();
348: }
349: }
350:
351: public String getTableType(int i) {
352: return "VIEW";
353: }
354:
355: public DataTableDef getDataTableDef(int i) {
356: return getViewDef(view_cache,
357: transaction.getTable(Database.SYS_VIEW), i)
358: .getDataTableDef();
359: }
360:
361: public MutableTableDataSource createInternalTable(int i) {
362: throw new RuntimeException("Not supported for views.");
363: }
364:
365: }
366:
367: }
|