001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Julie Marguerite.
021: * Contributor(s): Mathieu Peltier, Emmanuel Cecchet.
022: */package org.continuent.sequoia.controller.requests;
023:
024: import java.io.Serializable;
025: import java.sql.SQLException;
026: import java.util.ArrayList;
027: import java.util.Collection;
028: import java.util.StringTokenizer;
029: import java.util.regex.Matcher;
030: import java.util.regex.Pattern;
031:
032: import org.continuent.sequoia.common.i18n.Translate;
033: import org.continuent.sequoia.common.sql.schema.DatabaseColumn;
034: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
035: import org.continuent.sequoia.common.sql.schema.DatabaseTable;
036: import org.continuent.sequoia.common.sql.schema.TableColumn;
037:
038: /**
039: * A <code>CreateRequest</code> is a SQL request of the following syntax:
040: *
041: * <pre>
042: * CREATE [TEMPORARY] TABLE table-name [(column-name column-type [,column-name colum-type]* [,table-constraint-definition]*)]
043: * </pre>
044: *
045: * We now also support SELECT INTO statements.
046: *
047: * @author <a href="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
048: * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
049: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
050: * @version 1.0
051: */
052: public class CreateRequest extends AbstractWriteRequest implements
053: Serializable {
054: private static final long serialVersionUID = -8810498358284503952L;
055:
056: /** The table to create. */
057: protected transient DatabaseTable table = null;
058:
059: /**
060: * List of tables used to fill the created table in case of create query
061: * containing a select.
062: */
063: protected transient Collection fromTables = null;
064:
065: // Be conservative, if we cannot parse the query, assumes it invalidates
066: // everything
067: protected boolean altersDatabaseCatalog = true;
068: protected boolean altersDatabaseSchema = true;
069: protected boolean altersSomething = true;
070: protected boolean altersStoredProcedureList = true;
071: protected boolean altersUserDefinedTypes = true;
072: protected boolean altersUsers = true;
073:
074: protected boolean altersAggregateList = false;
075: protected boolean altersMetadataCache = false;
076: protected boolean altersQueryResultCache = false;
077:
078: protected boolean createsTemporaryTable = false;
079:
080: /**
081: * Creates a new <code>CreateRequest</code> instance. The caller must give
082: * an SQL request, without any leading or trailing spaces and beginning with
083: * 'create table ' (it will not be checked).
084: * <p>
085: * The request is not parsed but it can be done later by a call to
086: * {@link #parse(DatabaseSchema, int, boolean)}.
087: *
088: * @param sqlQuery the SQL request
089: * @param escapeProcessing should the driver to escape processing before
090: * sending to the database ?
091: * @param timeout an <code>int</code> value
092: * @param lineSeparator the line separator used in the query
093: * @see #parse
094: */
095: public CreateRequest(String sqlQuery, boolean escapeProcessing,
096: int timeout, String lineSeparator) {
097: super (sqlQuery, escapeProcessing, timeout, lineSeparator,
098: RequestType.CREATE);
099: setMacrosAreProcessed(true); // no macro processing needed
100: }
101:
102: /**
103: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersAggregateList()
104: */
105: public boolean altersAggregateList() {
106: return altersAggregateList;
107: }
108:
109: /**
110: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersDatabaseCatalog()
111: */
112: public boolean altersDatabaseCatalog() {
113: return altersDatabaseCatalog;
114: }
115:
116: /**
117: * Returns true if this create request alters the current database schema
118: * (using create table, create schema, create view) and false otherwise
119: * (create database, create index, create function, create method, create
120: * procedure, create role, create trigger, create type).
121: *
122: * @return Returns true if this query alters the database schema.
123: */
124: public boolean altersDatabaseSchema() {
125: return altersDatabaseSchema;
126: }
127:
128: /**
129: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersMetadataCache()
130: */
131: public boolean altersMetadataCache() {
132: return altersMetadataCache;
133: }
134:
135: /**
136: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersQueryResultCache()
137: */
138: public boolean altersQueryResultCache() {
139: return altersQueryResultCache;
140: }
141:
142: /**
143: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersSomething()
144: */
145: public boolean altersSomething() {
146: return altersSomething;
147: }
148:
149: /**
150: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersStoredProcedureList()
151: */
152: public boolean altersStoredProcedureList() {
153: return altersStoredProcedureList;
154: }
155:
156: /**
157: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUserDefinedTypes()
158: */
159: public boolean altersUserDefinedTypes() {
160: return altersUserDefinedTypes;
161: }
162:
163: /**
164: * @see org.continuent.sequoia.controller.requests.AbstractRequest#altersUsers()
165: */
166: public boolean altersUsers() {
167: return altersUsers;
168: }
169:
170: /**
171: * @see AbstractRequest#cloneParsing(AbstractRequest)
172: */
173: public void cloneParsing(AbstractRequest request) {
174: if (!request.isParsed())
175: return;
176: CreateRequest createRequest = (CreateRequest) request;
177: cloneTableNameAndColumns((AbstractWriteRequest) request);
178: table = createRequest.getDatabaseTable();
179: fromTables = createRequest.getFromTables();
180: isParsed = true;
181: }
182:
183: /**
184: * Gets the database table created by this statement (in case of a CREATE
185: * TABLE statement).
186: *
187: * @return a <code>DatabaseTable</code> value
188: */
189: public DatabaseTable getDatabaseTable() {
190: return table;
191: }
192:
193: /**
194: * Returns the list of tables used to fill the created table in case of create
195: * query containing a select.
196: *
197: * @return <code>Collection</code> of tables
198: */
199: public Collection getFromTables() {
200: return fromTables;
201: }
202:
203: /**
204: * @see org.continuent.sequoia.controller.requests.AbstractRequest#needsMacroProcessing()
205: */
206: public boolean needsMacroProcessing() {
207: return false;
208: }
209:
210: /**
211: * Pattern for SELECT INTO statement. This permits to extract from the
212: * statement the name of the table.
213: */
214: private static final String CREATE_TABLE_SELECT_INTO = "^select.*into\\s+((temporary|temp)\\s+)?(table\\s+)?([^(\\s|\\()]+)(.*)?";
215: private static final Pattern CREATE_TABLE_SELECT_INTO_STATEMENT_PATTERN = Pattern
216: .compile(CREATE_TABLE_SELECT_INTO, Pattern.CASE_INSENSITIVE
217: | Pattern.DOTALL);
218:
219: /**
220: * @see org.continuent.sequoia.controller.requests.AbstractRequest#parse(org.continuent.sequoia.common.sql.schema.DatabaseSchema,
221: * int, boolean)
222: */
223: public void parse(DatabaseSchema schema, int granularity,
224: boolean isCaseSensitive) throws SQLException {
225: if (granularity == ParsingGranularities.NO_PARSING) {
226: isParsed = true;
227: return;
228: }
229:
230: String originalSQL = this .trimCarriageReturnAndTabs();
231: String sql = originalSQL.toLowerCase();
232:
233: Matcher matcher = CREATE_TABLE_SELECT_INTO_STATEMENT_PATTERN
234: .matcher(sql);
235: if (matcher.matches()) {
236: // INSERT... INTO
237: table = new DatabaseTable(matcher.group(4));
238:
239: altersDatabaseCatalog = false;
240: altersDatabaseSchema = true;
241: altersStoredProcedureList = false;
242: altersUserDefinedTypes = false;
243: altersUsers = false;
244: isParsed = true;
245: return;
246: }
247:
248: // Strip create
249: sql = sql.substring("create".length()).trim();
250:
251: // Check what kind of create we are facing
252: if (sql.startsWith("database")) { // CREATE DATABASE alters only the database catalog
253: altersDatabaseCatalog = true;
254: altersDatabaseSchema = false;
255: altersStoredProcedureList = false;
256: altersUserDefinedTypes = false;
257: altersUsers = false;
258: return;
259: }
260: if (sql.startsWith("index") || sql.startsWith("unique")
261: || sql.startsWith("role") || sql.startsWith("sequence")) { // Does not alter anything :
262: // CREATE [UNIQUE] INDEX
263: // CREATE ROLE
264: // CREATE SEQUENCE
265: altersSomething = false;
266: return;
267: }
268: if (sql.startsWith("function") || sql.startsWith("method")
269: || sql.startsWith("procedure")
270: || sql.startsWith("trigger") || sql.startsWith("type")) { // CREATE FUNCTION, CREATE METHOD, CREATE PROCEDURE, CREATE TRIGGER and
271: // CREATE TYPE only alters definitions
272: altersDatabaseCatalog = false;
273: altersDatabaseSchema = false;
274: altersStoredProcedureList = true;
275: altersUserDefinedTypes = true;
276: altersUsers = false;
277: return;
278: }
279:
280: // From this point on, only the schema is affected
281: altersDatabaseCatalog = false;
282: altersDatabaseSchema = true;
283: altersStoredProcedureList = false;
284: altersUserDefinedTypes = false;
285: altersUsers = false;
286: if (sql.startsWith("schema") || sql.startsWith("view"))
287: // No parsing to do
288: return;
289:
290: // Let's try to check if we have a 'create [temporary] table '
291: int tableIdx = sql.indexOf("table");
292: if (tableIdx < 0)
293: throw new SQLException("Unsupported CREATE statement: '"
294: + sqlQueryOrTemplate + "'");
295:
296: //
297: // Starting from here, everything is just to handle CREATE TABLE statements
298: //
299:
300: // Strip up to 'table'
301: sql = sql.substring(tableIdx + 5).trim();
302:
303: // Does the query contain a select?
304: int selectIdx = sql.indexOf("select");
305: if (selectIdx != -1 && sql.charAt(selectIdx + 6) != ' ')
306: selectIdx = -1;
307:
308: if (isCaseSensitive) // Reverse to the original case
309: sql = originalSQL.substring(originalSQL.length()
310: - sql.length());
311:
312: if (selectIdx != -1) {
313: // Get the table on which CREATE occurs
314: int nextSpaceIdx = sql.indexOf(" ");
315: tableName = sql.substring(0, nextSpaceIdx).trim();
316: table = new DatabaseTable(tableName);
317: // Parse the select
318: sql = sql.substring(selectIdx).trim();
319: SelectRequest select = new SelectRequest(sql, false, 60,
320: getLineSeparator());
321: select.parse(schema, granularity, isCaseSensitive);
322: fromTables = select.getFrom();
323: if (granularity > ParsingGranularities.TABLE) { // Update the columns and add them to the table
324: columns = select.getSelect();
325: int size = columns.size();
326: for (int i = 0; i < size; i++) {
327: TableColumn tc = (TableColumn) columns.get(i);
328: table.addColumn(new DatabaseColumn(tc
329: .getColumnName(), false));
330: }
331: }
332: } else {
333: // Get the table on which CREATE occurs
334: // Look for the parenthesis
335: int openParenthesisIdx = sql.indexOf("(");
336: int closeParenthesisIdx = sql.lastIndexOf(")");
337: if ((openParenthesisIdx == -1)
338: && (closeParenthesisIdx == -1)) {
339: // no parenthesis found
340: table = new DatabaseTable(sql.trim());
341: if (granularity > ParsingGranularities.TABLE)
342: columns = new ArrayList();
343: isParsed = true;
344: return;
345: } else if ((openParenthesisIdx == -1)
346: || (closeParenthesisIdx == -1)
347: || (openParenthesisIdx > closeParenthesisIdx)) {
348: throw new SQLException(
349: "Syntax error in this CREATE statement: '"
350: + sqlQueryOrTemplate + "'");
351: } else {
352: tableName = sql.substring(0, openParenthesisIdx).trim();
353: }
354: table = new DatabaseTable(tableName);
355:
356: // Get the column names
357: if (granularity > ParsingGranularities.TABLE) {
358: columns = new ArrayList();
359: sql = sql.substring(openParenthesisIdx + 1,
360: closeParenthesisIdx).trim();
361: StringTokenizer columnTokens = new StringTokenizer(sql,
362: ",");
363: String word;
364: String lowercaseWord;
365: StringTokenizer wordTokens = null;
366: String token;
367: DatabaseColumn col = null;
368:
369: while (columnTokens.hasMoreTokens()) {
370: token = columnTokens.nextToken().trim();
371:
372: // work around to prevent bug: if the request contains for example:
373: // INDEX foo (col1,col2)
374: // we have to merge the 2 tokens: 'INDEX foo (col1' and 'col2)'
375: if ((token.indexOf("(") != -1)
376: && (token.indexOf(")") == -1)) {
377: if (columnTokens.hasMoreTokens())
378: token = token + ","
379: + columnTokens.nextToken().trim();
380: else {
381: tableName = null;
382: columns = null;
383: throw new SQLException(
384: "Syntax error in this CREATE statement: '"
385: + sqlQueryOrTemplate + "'");
386: }
387: }
388:
389: // First word of the line: either a column name or
390: // a table constraint definition
391: wordTokens = new StringTokenizer(token, " ");
392: word = wordTokens.nextToken().trim();
393: lowercaseWord = word.toLowerCase();
394:
395: // If it's a constraint, index or check keyword do not do anything
396: // else parse the line
397: if (!lowercaseWord.equals("constraint")
398: && !lowercaseWord.equals("index")
399: && !lowercaseWord.equals("check")) {
400: String columnName;
401: boolean isUnique = false;
402: // Check for primary key or unique constraint
403: if (lowercaseWord.equals("primary")
404: || lowercaseWord.startsWith("unique")) {
405:
406: // Get the name of the column
407: openParenthesisIdx = token.indexOf("(");
408: closeParenthesisIdx = token.indexOf(")");
409: if ((openParenthesisIdx == -1)
410: || (closeParenthesisIdx == -1)
411: || (openParenthesisIdx > closeParenthesisIdx)) {
412: tableName = null;
413: columns = null;
414: throw new SQLException(
415: "Syntax error in this CREATE statement: '"
416: + sqlQueryOrTemplate
417: + "'");
418: }
419:
420: columnName = token.substring(
421: openParenthesisIdx + 1,
422: closeParenthesisIdx).trim();
423:
424: int comma;
425: while ((comma = columnName.indexOf(',')) != -1) {
426: String col1 = columnName.substring(0,
427: comma).trim();
428: col = table.getColumn(col1);
429: if (col == null) {
430: tableName = null;
431: columns = null;
432: throw new SQLException(
433: "Syntax error in this CREATE statement: '"
434: + sqlQueryOrTemplate
435: + "'");
436: } else
437: col.setIsUnique(true);
438: columnName = columnName
439: .substring(comma + 1);
440: }
441:
442: // Set this column to unique
443: col = table.getColumn(columnName);
444:
445: // Test first if dbTable contains this column. This can fail with
446: // some invalid request, for example:
447: // CREATE TABLE categories(id INT4, name TEXT, PRIMARY KEY((id))
448: if (col == null) {
449: tableName = null;
450: columns = null;
451: throw new SQLException(
452: "Syntax error in this CREATE statement: '"
453: + sqlQueryOrTemplate
454: + "'");
455: } else
456: col.setIsUnique(true);
457: } else {
458: // It's a column name
459: columnName = word;
460:
461: if (!wordTokens.hasMoreTokens()) {
462: // at least type declaration is required
463: tableName = null;
464: columns = null;
465: throw new SQLException(
466: "Syntax error in this CREATE statement: '"
467: + sqlQueryOrTemplate
468: + "'");
469: }
470:
471: // Check for primary key or unique constraints
472: do {
473: word = wordTokens.nextToken().trim()
474: .toLowerCase();
475: if (word.equals("primary")
476: || word.startsWith("unique")) {
477: // Create the column as unique
478: isUnique = true;
479: break;
480: }
481: } while (wordTokens.hasMoreTokens());
482:
483: // Add the column to the parsed columns list and
484: // to the create DatabaseTable
485: columns.add(new TableColumn(tableName,
486: columnName));
487: table.addColumn(new DatabaseColumn(
488: columnName, isUnique));
489: }
490: }
491: }
492: }
493: }
494: isParsed = true;
495: }
496:
497: /**
498: * Does this request returns a ResultSet?
499: *
500: * @return false
501: */
502: public boolean returnsResultSet() {
503: return false;
504: }
505:
506: /**
507: * @see org.continuent.sequoia.controller.requests.AbstractRequest#getParsingResultsAsString()
508: */
509: public String getParsingResultsAsString() {
510: StringBuffer sb = new StringBuffer(super
511: .getParsingResultsAsString());
512: sb.append(Translate.get("request.create.table", table));
513: if (fromTables != null) {
514: sb.append(Translate.get("request.create.from.tables"));
515: for (int i = 0; i < fromTables.size(); i++)
516: sb.append(Translate.get("request.create.from.table",
517: fromTables.toArray()[i]));
518: }
519: sb.append(Translate.get("request.alters", new String[] {
520: String.valueOf(altersAggregateList()),
521: String.valueOf(altersDatabaseCatalog()),
522: String.valueOf(altersDatabaseSchema()),
523: String.valueOf(altersMetadataCache()),
524: String.valueOf(altersQueryResultCache()),
525: String.valueOf(altersSomething()),
526: String.valueOf(altersStoredProcedureList()),
527: String.valueOf(altersUserDefinedTypes()),
528: String.valueOf(altersUsers()) }));
529: return sb.toString();
530: }
531:
532: /**
533: * Displays some debugging information about this request.
534: */
535: public void debug() {
536: super .debug();
537: if (tableName != null)
538: System.out.println("Created table: " + tableName);
539: else
540: System.out.println("No information about created table");
541:
542: if (columns != null) {
543: System.out.println("Created columns:");
544: for (int i = 0; i < columns.size(); i++)
545: System.out.println(" "
546: + ((TableColumn) columns.get(i))
547: .getColumnName());
548: } else
549: System.out.println("No information about created columns");
550:
551: System.out.println();
552: }
553:
554: public boolean createsTemporaryTable() {
555: return createsTemporaryTable;
556: }
557:
558: }
|