001: /*
002: * DdlCommand.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.sql.commands;
013:
014: import java.sql.SQLException;
015: import java.sql.Savepoint;
016: import java.util.ArrayList;
017: import java.util.Collections;
018: import java.util.List;
019: import java.util.regex.Matcher;
020: import java.util.regex.Pattern;
021: import workbench.db.DbSettings;
022: import workbench.db.WbConnection;
023: import workbench.sql.formatter.SQLLexer;
024: import workbench.sql.formatter.SQLToken;
025: import workbench.util.ExceptionUtil;
026: import workbench.log.LogMgr;
027: import workbench.resource.ResourceMgr;
028: import workbench.sql.SqlCommand;
029: import workbench.sql.StatementRunnerResult;
030: import workbench.util.SqlUtil;
031: import workbench.util.StringUtil;
032:
033: /**
034: *
035: * @author support@sql-workbench.net
036: */
037: public class DdlCommand extends SqlCommand {
038: public static final DdlCommand CREATE = new DdlCommand("CREATE");
039: public static final DdlCommand DROP = new DdlCommand("DROP");
040: public static final DdlCommand ALTER = new DdlCommand("ALTER");
041: public static final DdlCommand GRANT = new DdlCommand("GRANT");
042: public static final DdlCommand REVOKE = new DdlCommand("REVOKE");
043:
044: // Firebird RECREATE VIEW command
045: public static final SqlCommand RECREATE = new DdlCommand("RECREATE");
046:
047: public static final List<DdlCommand> DDL_COMMANDS;
048:
049: private Savepoint ddlSavepoint;
050:
051: static {
052: List<DdlCommand> l = new ArrayList<DdlCommand>(5);
053: l.add(DROP);
054: l.add(CREATE);
055: l.add(ALTER);
056: l.add(GRANT);
057: l.add(REVOKE);
058: DDL_COMMANDS = Collections.unmodifiableList(l);
059: }
060:
061: private String verb;
062:
063: private DdlCommand(String aVerb) {
064: this .verb = aVerb;
065: this .isUpdatingCommand = true;
066: }
067:
068: public StatementRunnerResult execute(String aSql)
069: throws SQLException {
070: StatementRunnerResult result = new StatementRunnerResult();
071:
072: DbSettings dbset = this .currentConnection.getMetadata()
073: .getDbSettings();
074: boolean useSavepoint = dbset.useSavePointForDDL()
075: && !this .currentConnection.getAutoCommit();
076:
077: if (useSavepoint
078: && !this .currentConnection.supportsSavepoints()) {
079: useSavepoint = false;
080: LogMgr
081: .logWarning(
082: "DdlCommand.execute()",
083: "A savepoint should be used for this DDL command, but the driver does not support savepoints!");
084: }
085:
086: try {
087: this .currentStatement = currentConnection.createStatement();
088:
089: aSql = currentConnection.getMetadata().filterDDL(aSql);
090:
091: String msg = null;
092: result.setSuccess();
093:
094: if (useSavepoint) {
095: this .ddlSavepoint = currentConnection.setSavepoint();
096: }
097:
098: if (isDropCommand(aSql)
099: && this .runner.getIgnoreDropErrors()) {
100: try {
101: this .currentStatement.executeUpdate(aSql);
102: result.addMessage(ResourceMgr
103: .getString("MsgDropSuccess"));
104: } catch (Exception th) {
105: this .currentConnection.rollback(ddlSavepoint);
106: this .ddlSavepoint = null;
107: result.addMessage(ResourceMgr
108: .getString("MsgDropWarning"));
109: result.addMessage(ExceptionUtil.getDisplay(th));
110: result.setSuccess();
111: }
112: } else {
113: boolean hasResult = this .currentStatement.execute(aSql);
114:
115: if ("DROP".equals(verb)) {
116: msg = ResourceMgr.getString("MsgDropSuccess");
117: } else if ("CREATE".equals(verb)
118: || "RECREATE".equals(verb)) {
119: msg = ResourceMgr.getString("MsgCreateSuccess");
120: } else {
121: msg = this .verb
122: + " "
123: + ResourceMgr
124: .getString("MsgKnownStatementOK");
125: }
126: result.addMessage(msg);
127:
128: // Using a generic execute and result processing ensures that DBMS that
129: // can process more than one statement with a single SQL are treated correctly.
130: // e.g. when sending a SELECT and other statements as a "batch" with SQL Server
131: processMoreResults(aSql, result, hasResult);
132:
133: // Process result will have added any warnings and set the warning flag
134: if (result.hasWarning()) {
135: if (this .addExtendErrorInfo(currentConnection,
136: aSql, result)) {
137: result.setFailure();
138: }
139: }
140: }
141: this .currentConnection.releaseSavepoint(ddlSavepoint);
142: } catch (Exception e) {
143: this .currentConnection.rollback(ddlSavepoint);
144: result.clear();
145:
146: StringBuilder msg = new StringBuilder(150);
147: msg.append(ResourceMgr.getString("MsgExecuteError") + "\n");
148: if (reportFullStatementOnError) {
149: msg.append(aSql);
150: } else {
151: int maxLen = 150;
152: msg.append(StringUtil.getMaxSubstring(aSql.trim(),
153: maxLen));
154: }
155: result.addMessage(msg);
156: result.addMessageNewLine();
157: result.addMessage(ExceptionUtil.getAllExceptions(e));
158:
159: addExtendErrorInfo(currentConnection, aSql, result);
160: result.setFailure();
161: LogMgr.logSqlError("DdlCommand.execute()", aSql, e);
162: } finally {
163: // we know that we don't need the statement any longer, so to make
164: // sure everything is cleaned up, we'll close it here
165: done();
166: }
167:
168: return result;
169: }
170:
171: public void done() {
172: super .done();
173: this .ddlSavepoint = null;
174: }
175:
176: public boolean isDropCommand(String sql) {
177: if ("DROP".equals(this .verb))
178: return true;
179: Pattern p = Pattern.compile(
180: "DROP\\s+(PRIMARY\\s+KEY|CONSTRAINT)\\s+",
181: Pattern.CASE_INSENSITIVE);
182: Matcher m = p.matcher(sql);
183: return m.find();
184: }
185:
186: /**
187: * Extract the name of the created object for Oracle stored procedures.
188: * @see #addExtendErrorInfo(workbench.db.WbConnection, String, workbench.sql.StatementRunnerResult)
189: */
190: protected String getObjectName(String sql) {
191: SQLLexer l = new SQLLexer(sql);
192: SQLToken t = l.getNextToken(false, false);
193: if (t == null)
194: return null;
195: String v = t.getContents();
196: if (!v.equals("CREATE") && !v.equals("CREATE OR REPLACE"))
197: return null;
198:
199: // next token must be the type
200: t = l.getNextToken(false, false);
201: if (t == null)
202: return null;
203:
204: // the token after the type must be the object's name
205: t = l.getNextToken(false, false);
206: return t.getContents();
207: }
208:
209: /**
210: * Retrieve extended error information if the DBMS supports this.
211: * Currently this is only implemented for Oracle to read errors
212: * after creating a stored procedure from the ALL_ERRORS view.
213: *
214: * @see #getObjectName(String)
215: * @see #getObjectType(String)
216: */
217: private boolean addExtendErrorInfo(WbConnection aConnection,
218: String sql, StatementRunnerResult result) {
219: String type = SqlUtil.getCreateType(sql);
220: if (type == null)
221: return false;
222:
223: String name = getObjectName(sql);
224: if (name == null)
225: return false;
226:
227: String msg = aConnection.getMetadata().getExtendedErrorInfo(
228: null, name, type);
229: if (msg != null && msg.length() > 0) {
230: result.addMessage(msg);
231: return true;
232: } else {
233: return false;
234: }
235: }
236:
237: public String getVerb() {
238: return verb;
239: }
240:
241: }
|