001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.table;
007:
008: import java.sql.SQLException;
009: import org.h2.command.Prepared;
010: import org.h2.command.dml.Query;
011: import org.h2.constant.ErrorCode;
012: import org.h2.engine.Constants;
013: import org.h2.engine.Session;
014: import org.h2.engine.User;
015: import org.h2.expression.Expression;
016: import org.h2.index.Index;
017: import org.h2.index.IndexType;
018: import org.h2.index.ViewIndex;
019: import org.h2.message.Message;
020: import org.h2.result.Row;
021: import org.h2.schema.Schema;
022: import org.h2.util.IntArray;
023: import org.h2.util.ObjectArray;
024: import org.h2.util.SmallLRUCache;
025: import org.h2.util.StringUtils;
026: import org.h2.value.Value;
027:
028: /**
029: * A view is a virtual table that is defined by a query.
030: */
031: public class TableView extends Table {
032:
033: private String querySQL;
034: private ObjectArray tables;
035: private final String[] columnNames;
036: private Query viewQuery;
037: private ViewIndex index;
038: private boolean recursive;
039: private SQLException createException;
040: private SmallLRUCache indexCache = new SmallLRUCache(
041: Constants.VIEW_INDEX_CACHE_SIZE);
042: private long lastModificationCheck;
043: private long maxDataModificationId;
044: private User owner;
045:
046: public TableView(Schema schema, int id, String name,
047: String querySQL, ObjectArray params, String[] columnNames,
048: Session session, boolean recursive) throws SQLException {
049: super (schema, id, name, false);
050: this .querySQL = querySQL;
051: this .columnNames = columnNames;
052: this .recursive = recursive;
053: index = new ViewIndex(this , querySQL, params, recursive);
054: initColumnsAndTables(session);
055: }
056:
057: public Query recompileQuery(Session session) throws SQLException {
058: Prepared p = session.prepare(querySQL);
059: if (!(p instanceof Query)) {
060: throw Message.getSyntaxError(querySQL, 0);
061: }
062: Query query = (Query) p;
063: querySQL = query.getPlanSQL();
064: return query;
065: }
066:
067: private void initColumnsAndTables(Session session)
068: throws SQLException {
069: Column[] cols;
070: removeViewFromTables();
071: try {
072: Query query = recompileQuery(session);
073: tables = new ObjectArray(query.getTables());
074: ObjectArray expressions = query.getExpressions();
075: ObjectArray list = new ObjectArray();
076: for (int i = 0; i < query.getColumnCount(); i++) {
077: Expression expr = (Expression) expressions.get(i);
078: String name = null;
079: if (columnNames != null && columnNames.length > i) {
080: name = columnNames[i];
081: }
082: if (name == null) {
083: name = expr.getAlias();
084: }
085: int type = expr.getType();
086: long precision = expr.getPrecision();
087: int scale = expr.getScale();
088: int displaySize = expr.getDisplaySize();
089: Column col = new Column(name, type, precision, scale,
090: displaySize);
091: col.setTable(this , i);
092: list.add(col);
093: }
094: cols = new Column[list.size()];
095: list.toArray(cols);
096: createException = null;
097: if (getId() != 0) {
098: addViewToTables();
099: }
100: viewQuery = query;
101: } catch (SQLException e) {
102: createException = e;
103: // if it can't be compiled, then it's a 'zero column table'
104: // this avoids problems when creating the view when opening the
105: // database
106: tables = new ObjectArray();
107: cols = new Column[0];
108: if (recursive && columnNames != null) {
109: cols = new Column[columnNames.length];
110: for (int i = 0; i < columnNames.length; i++) {
111: cols[i] = new Column(columnNames[i], Value.STRING);
112: }
113: index.setRecursive(true);
114: recursive = true;
115: createException = null;
116: }
117:
118: }
119: setColumns(cols);
120: }
121:
122: public boolean getInvalid() {
123: return createException != null;
124: }
125:
126: public PlanItem getBestPlanItem(Session session, int[] masks)
127: throws SQLException {
128: PlanItem item = new PlanItem();
129: item.cost = index.getCost(session, masks);
130: IntArray masksArray = new IntArray(masks == null ? new int[0]
131: : masks);
132: ViewIndex i2 = (ViewIndex) indexCache.get(masksArray);
133: if (i2 == null || i2.getSession() != session) {
134: i2 = new ViewIndex(this , index, session, masks);
135: indexCache.put(masksArray, i2);
136: }
137: item.setIndex(i2);
138: return item;
139: }
140:
141: public String getDropSQL() {
142: return "DROP VIEW IF EXISTS " + getSQL();
143: }
144:
145: public String getCreateSQL() {
146: StringBuffer buff = new StringBuffer();
147: buff.append("CREATE FORCE VIEW ");
148: buff.append(getSQL());
149: if (comment != null) {
150: buff.append(" COMMENT ");
151: buff.append(StringUtils.quoteStringSQL(comment));
152: }
153: if (columns.length > 0) {
154: buff.append('(');
155: for (int i = 0; i < columns.length; i++) {
156: if (i > 0) {
157: buff.append(", ");
158: }
159: buff.append(columns[i].getSQL());
160: }
161: buff.append(")");
162: }
163: buff.append(" AS\n");
164: buff.append(querySQL);
165: return buff.toString();
166: }
167:
168: public void checkRename() throws SQLException {
169: }
170:
171: public void lock(Session session, boolean exclusive, boolean force)
172: throws SQLException {
173: // exclusive lock means: the view will be dropped
174: }
175:
176: public void close(Session session) throws SQLException {
177: }
178:
179: public void unlock(Session s) {
180: }
181:
182: public boolean isLockedExclusively() {
183: return false;
184: }
185:
186: public void removeIndex(String indexName) throws SQLException {
187: throw Message.getUnsupportedException();
188: }
189:
190: public Index addIndex(Session session, String indexName,
191: int indexId, IndexColumn[] cols, IndexType indexType,
192: int headPos, String comment) throws SQLException {
193: throw Message.getUnsupportedException();
194: }
195:
196: public void removeRow(Session session, Row row) throws SQLException {
197: throw Message.getUnsupportedException();
198: }
199:
200: public void addRow(Session session, Row row) throws SQLException {
201: throw Message.getUnsupportedException();
202: }
203:
204: public void checkSupportAlter() throws SQLException {
205: // TODO view: alter what? rename is ok
206: throw Message.getUnsupportedException();
207: }
208:
209: public void truncate(Session session) throws SQLException {
210: throw Message.getUnsupportedException();
211: }
212:
213: public long getRowCount(Session session) {
214: throw Message.getInternalError();
215: }
216:
217: public boolean canGetRowCount() {
218: // TODO view: could get the row count, but not that easy
219: return false;
220: }
221:
222: public boolean canDrop() {
223: return true;
224: }
225:
226: public String getTableType() {
227: return Table.VIEW;
228: }
229:
230: public void removeChildrenAndResources(Session session)
231: throws SQLException {
232: removeViewFromTables();
233: super .removeChildrenAndResources(session);
234: database.removeMeta(session, getId());
235: querySQL = null;
236: index = null;
237: invalidate();
238: }
239:
240: public String getSQL() {
241: if (getTemporary()) {
242: StringBuffer buff = new StringBuffer(querySQL.length());
243: buff.append("(");
244: buff.append(querySQL);
245: buff.append(")");
246: return buff.toString();
247: }
248: return super .getSQL();
249: }
250:
251: public Index getScanIndex(Session session) throws SQLException {
252: if (createException != null) {
253: String msg = createException.getMessage();
254: throw Message.getSQLException(ErrorCode.VIEW_IS_INVALID_2,
255: new String[] { getSQL(), msg }, createException);
256: }
257: PlanItem item = getBestPlanItem(session, null);
258: return item.getIndex();
259: }
260:
261: public ObjectArray getIndexes() {
262: return null;
263: }
264:
265: public ObjectArray getTables() {
266: return tables;
267: }
268:
269: public void recompile(Session session) throws SQLException {
270: for (int i = 0; i < tables.size(); i++) {
271: Table t = (Table) tables.get(i);
272: t.removeView(this );
273: }
274: tables.clear();
275: initColumnsAndTables(session);
276: }
277:
278: public long getMaxDataModificationId() {
279: if (createException != null) {
280: throw Message.getInternalError();
281: }
282: if (viewQuery == null) {
283: return Long.MAX_VALUE;
284: }
285: // if nothing was modified in the database since the last check, and the
286: // last is known, then we don't need to check again
287: // this speeds up nested views
288: long dbMod = database.getModificationDataId();
289: if (dbMod > lastModificationCheck
290: && maxDataModificationId <= dbMod) {
291: maxDataModificationId = viewQuery
292: .getMaxDataModificationId();
293: lastModificationCheck = dbMod;
294: }
295: return maxDataModificationId;
296: }
297:
298: public Index getUniqueIndex() {
299: return null;
300: }
301:
302: private void removeViewFromTables() {
303: if (tables != null) {
304: for (int i = 0; i < tables.size(); i++) {
305: Table t = (Table) tables.get(i);
306: t.removeView(this );
307: }
308: tables.clear();
309: }
310: }
311:
312: private void addViewToTables() {
313: for (int i = 0; i < tables.size(); i++) {
314: Table t = (Table) tables.get(i);
315: t.addView(this );
316: }
317: }
318:
319: public void setOwner(User owner) {
320: this .owner = owner;
321: }
322:
323: public User getOwner() {
324: return owner;
325: }
326:
327: public static TableView createTempView(Session s, User owner,
328: Query query) throws SQLException {
329: String tempViewName = s.getNextTempViewName();
330: Schema mainSchema = s.getDatabase().getSchema(
331: Constants.SCHEMA_MAIN);
332: String querySQL = query.getPlanSQL();
333: TableView v = new TableView(mainSchema, 0, tempViewName,
334: querySQL, query.getParameters(), null, s, false);
335: v.setOwner(owner);
336: v.setTemporary(true);
337: return v;
338: }
339:
340: }
|